Hi,
I'm stuck! This code works under limited cases. And I understand why it doesn't work but don't understand PIDLs well enough to know how to work around DX's ShellTreeView implementation. Here's the code:
Delphiprocedure TfmSelectFolder.actFolderCreateExecute(Sender: TObject);
var
sNewPath, sNewFolder : string;
node : TTreeNode;
pidl : PItemIDList;
begin
sNewFolder := 'New Folder';
if not InputQuery('Create New Folder', 'New Folder Name', sNewFolder) then
exit;
FolderTreeView.SetFocus;
pidl := FolderTreeView.GetNodeAbsolutePIDL(FolderTreeView.InnerTreeView.Selected);
msginfo(GetPIDLName(pidl));
sNewPath := backslashPath(FolderTreeView.Path) + sNewFolder;
if not CreateDir(sNewPath) then
raise exception.createFmt('New Folder "%s" could not be created.', [sNewFolder]); FolderTreeView.UpdateContent; application.ProcessMessages;
FolderTreeView.Path := sNewPath; <-- neds to be relative path (relative to root)
FolderTreeView.Path := 'MyDir\Tenders\' + sNewFolder; // open the new node
pidl := PathToAbsolutePIDL(sNewPath, FolderTreeView.Root, THackShellTreeView(FolderTreeView).GetViewOptions);
node := THackcxCustomInnerShellTreeView(FolderTreeView.InnerTreeView).GetNodeByPIDL(pidl);
if node <> nil then
node.Expand(false);
end;
Drat! One line again - this HTML editor not so great.
Here's the code again…
Delphiprocedure TfmSelectFolder.actFolderCreateExecute(Sender: TObject);
var sNewPath, sNewFolder : string;
node : TTreeNode;
pidl : PItemIDList;
begin
sNewFolder := 'New Folder';
if not InputQuery('Create New Folder', 'New Folder Name', sNewFolder) then
exit;
FolderTreeView.SetFocus;
pidl := FolderTreeView.GetNodeAbsolutePIDL(FolderTreeView.InnerTreeView.Selected);
msginfo(GetPIDLName(pidl));
sNewPath := backslashPath(FolderTreeView.Path) + sNewFolder;
if not CreateDir(sNewPath) then
raise exception.createFmt('New Folder "%s" could not be created.', [sNewFolder]);
FolderTreeView.UpdateContent;
application.ProcessMessages;
FolderTreeView.Path := sNewPath; // Works off ROOT only
// FolderTreeView.Path := 'MyDir\Tenders\' + sNewFolder; // Works!
// open the new node
pidl := PathToAbsolutePIDL(sNewPath, FolderTreeView.Root, THackShellTreeView(FolderTreeView).GetViewOptions);
node := THackcxCustomInnerShellTreeView(FolderTreeView.InnerTreeView).GetNodeByPIDL(pidl);
if node <> nil then
node.Expand(false);
end;
The above code will work if you are in the root directory eg "My Docs". If you are NOT on the root, it will fail. but I hard coded a test (see MyDirs) above and it works.
The reason it fails when you are in a sub-dir off the root is due to the implementation of the TcxShellTreeView Path and AbsolutePath. I don't know why DX has market AbsolutePath as deprected in the source - it is the thing that isn't returning the correct path!
IMHO the Path property should return the fully qualified path. And the AbsolutePath should return the relative path - relative to the root. Of course it looks like DX is thinking about heading that direction based on this:
// property RelativePIDL: PItemIDList write SetRelativePIDL; // TODO
Yes please TODO it as I'm fairly sure that if you did, my problem is solved. But since it doens't exist I need to role-my-own. Anyway, since both AbsolutePath and Path return a fully qualified path, and I am working in a different root then bfDesktop, it fails.
So my end objective is to press a button that creates a new directory (a tree node) and then set focus to that node. The above does all that except the last bit: it doesn't set focus to the newly created node.
So, we can solve this in one of two ways - 1) a sample that demonstrates how to do this or 2) a sample that shows how I can get the relative path (relative to the root.pidl)
FWIW, I've seen a LOT of people struggling and asking questions about PIDLs and such out here. And I reckon one of the core reasons the shell components have so many problems is that DX (IMHO) is making the assumption that everyone wants to work off bfDesktop. Myself, I want the user to be as focused as I can get them into some special folder such as My docs or Common Docs, etc. to remove the clutter.
thanx,
-randall sell
I need some additional time to process your inquire. Please bear with me.
Hello,
I regret to inform you that this information is not enough for us to find the cause of the issue.
Would you please provide us with a small sample project that illustrates this issue?
We will test it and do our best to find an acceptable solution for you.
Can you provide an example whereby the user can click a button to create a new sub-folder (in a TcxShellTreeView) and then move focus to that new folder. In other words, I am attempting to emulate what Windows Explorer does, but inside a TcxShellTreeView vs a TcxShellListVew. And what I've tried is shown above. (remove the msgInfo call - that is y own debug code)
regards,
-randall
Hello,
I have created a sample project that demonstrates the functionality you requested. In this demo, I use your code with some modifications. Would you please clarify whether or not this approach is suitable for your needs?
Well, your sample works great. Sadly not so great in my app. I can kinda see why.
In your sample app, when you work out what the relative path is (ANewFolderRelativePath)- it works out to be a subdir of the current TreeView.Root.Folder.Path. But in my case… TreeView.Root.Path points off to my own space (C:\DocsNSettings\[me]\Desktop. But the location where I am creating my new folder in the public shared location (C:\DocsNSettings\All Users\Documents\New Folder). So my ANewFolderRelativePath ends up going up the tree:
"…\All Users\Documents\DecisionMax\Tenders\New Folder"
OK got it (can scrub most of what is above). The reason your demo worked was because you set the FolderView's.BrowseFolder to bfCommonDocuments. If you reset that to bfDesktop (the default) your demo app has the same problem I've been struggling with…
Seems like a catch 22… what works is to set the Path to a relative path off the root provided it is an actual sub-dir of the root (relative path is a bad term, subdirectory probably better). But since Shared Docs and My Desktop (eg the real desktop directory, not the virtual one) - the two don't intersect. Eg neither is a subdir of the other so I can always create a case where this will fail. Follow me?
Ideas?
regards,
-randall
Hello,
You are right. It is not a universal solution. Try another one, please (see the attached project).
Hi Chet,
Yes, this does seem to work under all cases. which is great. Now let's pick it apart a bit. Here's the source for anyone reading this:
type TcxShellTreeViewAccess = class(TcxShellTreeView);
procedure TForm1.Button1Click(Sender: TObject);
var ANewFolderName, ANewFolderPath : string;
ANewFolderRelativePidl : PItemIDList;
AShellFolder : IShellFolder;
begin
ANewFolderName := 'New Folder';
if not InputQuery('Create New Folder', 'New Folder Name', ANewFolderName) then
exit;
ANewFolderPath := IncludeTrailingPathDelimiter(FolderTreeView.Path) + ANewFolderName;
if not ForceDirectories(ANewFolderPath) then
raise exception.createFmt('New Folder "%s" could not be created.', [ANewFolderName]);
FolderTreeView.UpdateContent;
Application.ProcessMessages; // <-- CRITICAL
AShellFolder := FolderTreeView.Folders[FolderTreeView.InnerTreeView.Selected.AbsoluteIndex].ShellFolder;
ANewFolderRelativePidl := InternalParseDisplayName(AShellFolder, ANewFolderName, TcxShellTreeViewAccess(FolderTreeView).GetViewOptions);
FolderTreeView.AbsolutePIDL := ConcatenatePidls(FolderTreeView.AbsolutePIDL, ANewFolderRelativePidl);
FolderTreeView.SetFocus;
end;
So, in order to create a new folder and set focus to it in a cxShellTreeView requires hacking the component (TcxShellTreeViewAccess) and calling 2 undocumented functions (see http://www.devexpress.com/Support/Center/Question/Details/S171180). It is little wonder I couldn't make something so simple work without assistence.
Continuing on… I am now attempting to figure out why the application.processmessages is required. If it isn't there, the next line produces an AV because InnerTreeView.Selected is nil. So I guess the general rule of thumb is that one should always check if InnerTreeView.Selected is nil before attempting to access it. But what has me confused is why? I traced into what UpdateContent does, and it isn't calling PostMessage anywhere (all SendMessage calls) which wait for the message to process. So, why is application.processmessages needed? Clearly somewhere in there it must be calling something that either does a PostMessage or something is spawning a thread?
Finally, the reason I am nit-picking this to death is due to an error I'm seeing in my component which is essentially a ShellBrowser replacement. I can produce very strange results when the app is run outside the debugger, but it works perfectly when I'm debugging which makes me suspect a timing issue. Which is why I need a thorough understanding of how the DX components work internally and this app.processmessages is a good place to start.
regards,
-randall
Hello Randall,
Thank you for informing me that the problem is now resolved. I am glad to hear that you have found my assistance helpful. The ProcessMessages method I used is necessary to process all messages that are currently in the message queue. Not sure what else I can do to help you with this question. Should you have any problems with my solution, please feel free to update this report and describe them in detail.
Thank you Chet for explaining what ProcessMessages does. Unfortunately that's not the question I asked. I asked why it needs to be called.
Allow me to rephrase the question: Why is the InnterTreeView.Selected property nil immediately after calling TreeView.UpdateContent? I want to know what is asyncronous in the UpdateContent call.
regards,
-randall
Hello Randall,
I have examined our code and clearly see the PostMessage procedure in the cxShellControls unit:
procedure TcxCustomInnerShellTreeView.RestoreTreeState; //.. procedure RestoreCurrentPath; var ACurrentPath, ATempPIDL: PItemIDList; begin if FStateData.CurrentPath = nil then Exit; ACurrentPath := GetPidlCopy(FStateData.CurrentPath); try repeat if GetNodeByPIDL(ACurrentPath) <> nil then begin PostMessage(Handle, DSM_SHELLTREERESTORECURRENTPATH, WPARAM(GetPidlCopy(ACurrentPath)), 0); //<-- //..
This procedure is the cause of the Application.ProcessMessages method utilized in Chet's sample.
It looks strange that this code is not called on your side during the tracing process because when I set the breakpoint at this line, the compiler always stops here (on creating a new folder). Would you please check whether or not the debugging mode is enabled in your IDE? Please refer to the How to get the callstack in the IDE? Knowledge Base article that contains helpful information on the debugging mode.
Thanx Paulo. It does trigger on my end - I had just stepped over it. Hard to know what to step into and what to step over when it isn't your code. But now it all makes complete sense.
thanx
-randall
Thanks. I have posted Chet's demo to the answer section.