
Introduction
This code demonstrates the use of Windows shell interfaces.
Shell Interfaces and Functions Used
The interfaces are:
The shell functions are:
SHGetMalloc
SHGetDesktopFolder
SHGetFileInfo
SHGetSpecialFolderLocation
The idea behind it
I've never worked with the Windows shell API before and this is my first try at it.
I wanted to implement a common way of retrieving shell namespaces, using provided and
documented shell interfaces.
This sample is a substitute for SHBrowseForFolder shell function. This code does not
implement evrything SHBrowseForFolder does, but it provides basic functionality
for navigating namespaces and selecting a desired path.
The source code is separated into a shell extension DLL BFF{D}.DLL and
a sample app App{D}.exe. The DLL contains everything that you need.
The sample app shows how to use the DLL's functionality.
How to use
When you need to call the "browse for folder" dialog put in the source code that will
look like this:
CBrowseForFolderDlg::BFFINFO info;
memset(&info, 0, sizeof(info));
info.m_pParent = this;
strcpy(info.m_szTitle, "Browse For Folder");
strcpy(info.m_szMsg, "Select the Voice Mail Folder");
info.m_pidlRoot = m_pidlMainPath;
info.m_pidlSelected = m_pidlSelected;
info.m_nFlags |= BFF_IDL_ROOT | BFF_IDL_SELECTED;
info.m_nFlags |= (m_bIncFiles) ? BFF_INCLUDE_FILES : info.m_nFlags;
info.m_nFlags |= (m_bSysOnly) ? BFF_SYSTEM_ONLY : info.m_nFlags;
CBrowseForFolderDlg dlg(&info);
if (dlg.DoModal() == IDCANCEL)
return;
SHFree(m_pidlSelected);
m_pidlSelected = dlg.GetTreeSelectedFullIdl();
The above code assumes that you have preallocated and assigned the m_pidlMainPath
and m_pidlSelected values. If you wish to work with null terminated strings,
rather than with ITEMIDLIST you would change line 1 and 2 as follows:
strcpy(info.m_szRoot, "c:\\windows\\system");
info.m_nFlags |= BFF_CHAR_ROOT;
Note, that you can specify network paths as well.
strcpy(info.m_szRoot, "\\\\ABERRESFORD\\views");
info.m_nFlags |= BFF_CHAR_ROOT;
Or CSIDL values.
info.m_pidlRoot = CSIDL_DESKTOP;
info.m_nFlags |= BFF_CSIDL_ROOT;
Exactly the same rules apply to the m_pidlSelected and m_szSelected
member variable of the CBrowseForFolderDlg::BFFINFO structure. Just make
sure that selected path is a subpath of your base root, otherwise you
will get a message that specified path does not exist.
I've added a button on the dialog, "Set as Root", and made it invisible
for the debugging purposes. You can make it visible and set the roots
dynamically in the demo application.
You also have to add the BFF{D}.LIB library to your link list.
The letter "D" signifies that you're linking with debug version of DLL.
That's all. You should be able to run your app and use CBrowseForFolderDlg class.
Data and Visual separation
I've applied the "Visitor" design patter to this example. I've introduced an abstract
ITreeNode interface and made all its member variables virtual. ITreeNode
represents the "data". The meat of the functionality resides in the CShellTreeNode class.
CShellTreeNode uses the shell API to get to shell objects. The "visual" is the
MFC's CTreeCtrl class, and the "visitor" is the CTreeVisitor class, which has
a derivable CUITreeVisitor. CUITreeVisitor
knows what methods of CTreeCtrl to tackle to make it work and reflect the
hierarchy built upon ITreeNode. CUITreeVisitor knows nothing
about the concrete CShellTreeNode and only deals with the
methods exposed by ITreeNode, so you can easily change the implementation of this interface.
The CShellHelper class is used by the CShellTreeNode class and
is a keeper of all "static" data. That is, data that is not about to change during the lifetime
of the application. This class attaches to the global system image list (on WinNT I believe,
each process gets its own copy of the system image list) and lets others read/write from/to this list.
Data retrieval
I only extract immediate children for a selected node if any. You can specify the depth of
the search by using ITreeNode::SetFindDepth() method. the depth of <1> means just get
immediate children.
Displaying Tree
In order to achieve high display speed of the tree I used a widely known display technique. The
core idea behind it, is that you only provide information that is visible at a given moment in time.
CTreeCtrl will be sending a message for each visible node when it needs text and an icon for it -
CTreeCtrl::OnGetdispinfo. The TVITEM::lParam member points at an instance of ITreeNode.
Only then I provide the tree control with the text and the icon id in the
image list.
According to the data retrieval approach I described above, whenever a user expands a node
I will request this ITreeNode to go and fetch its immediate children. Then I will request
CUITreeVisitor to go and visit (display) found nodes in the tree control. This is done
through a method CTreeCtrl calls every time a node with children gets expanded
CTreeCtrl::OnItemexpanding(). Therefore, whether a user expands just
one level or the "*" button on the numpad was clicked the same algorithm does the job.
Sorting
The shell objects are displayed in a sorted descending order. The system folders take
precedence over the files. This is done using standard Microsoft's approach.
There is a callback function that you need to set up, which will be called when
Microsoft algorithm needs to compare two items from the list being sorted.
This is the static CFileTreeCtrl::CompFunc() method in our case.
Updates
I have updated the source code and the picture. Enjoy!
If you find bugs or have any suggestions for improvements, let me know.
Updates again... (01/30/2001)
I've fixed a couple of bugs. One of them was pretty nasty. The list control didn't work
on WinNT because of that. All fixed now.
Now you can specify any node in the shell namespace as a base root for your shell browser.
The CBrowseForFolderDlg::BFFINFO structure has been extended to support an arbitrary root.
You can specify the base root in the form of an ITEMIDLIST, CSIDL or
char*. The same goes to the default selection. You can type in a network path
and the synchronize button should be working.
| You must Sign In to use this message board. |
|
|
 |
|
 |
Can somebody tell me what is SHGDN_INCLUDE_NONFILESYS for? If using SHGDN_INCLUDE_NONFILESYS, the folder(?) "download" under "X:\WINDOWS\assembly" cannot be displayed. The OS I'm using is Windows XP.
|
| Sign In·View Thread·PermaLink | 2.40/5 |
|
|
|
 |
|
 |
Hi everyone: I am trying to create a tool to compare files and directories....just binary and tree structure comparesion. i want to be able to execute the tool from the command line and also I want to be able to get an XML output file. I know there are are a lot of these tools out there, but there are not what I exactly need...I don't know where to start...so if you have any input, please help me out.... I was thinking about using 'com" such as IShellFolder, but i never used it befor
I have Visual studio...
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
why I'm getting this error when compiling:
exports.h(20) : error C2373: 'SHFree' : redefinition; different type modifiers E:\Program Files\Microsoft SDK\include\shlobj.h(149) : see declaration of 'SHFree' Error executing cl.exe.
Thanks Israel Ruiz
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
|
 |
|
 |
Hi Marat, thanks for that control. 
I the umlaut ä when using it on a german setting. I looks like it was the call WideCharToMultiByte which used the OEM codepage instead of the ansi codepage.
I changed it to ansi and now it works. Is it save to use ansi??
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
What a great work you've done! Thanks.
I noticed you wrote your own ILCombine/ILClone/ILGetNext (something like that)functions instead of the original Shell functions come with MS. What's the reason? Is it because the Shell DLL version? You want your code fit all the environment so that you stay away from the newest functions?
|
| Sign In·View Thread·PermaLink | 4.00/5 |
|
|
|
 |
|
 |
All SHSomething functions don't run under WinNT. At least that's what Win32 API Help says.
Nothing is impossible. .
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Is it possible to know what kind of folder is return with the ISellFolder enumerator. I would like to show for example only folders reltated to the network (domain, computer, ...).
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
ITreeNode exposes the following methods for your convenience:
bool ITreeNode::IsSearched() const; bool ITreeNode::IsDirectory() const; bool ITreeNode::IsSystemDirectory() const; bool ITreeNode::IsNetworkRoot() const; bool ITreeNode::IsNetworkNode() const; bool ITreeNode::HasChildren() const;
From example that you've given, however, it sounds like all you need is to be able to assign an arbitrary node as a root node for your tree. In the demo app the default root is CSIDL_DESKTOP. As I mentioned in the article you can use and predefined CSIDL_... values as a dynamic root. One would use: * CSIDL_DRIVES to display local only files/folders * CSIDL_NETWORK to display all published entities on the Microsoft network * CSIDL_PRINTERS to get to published printers and so forth
I hope that answers your question.
Cheers, Marat
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I just found out, that the code that does the comparison of two ITreeNode instances is not acting the same on different platforms (win98/win me).
I have a fix for it. Please substitute the implementation of ==>> int CShellTreeNode::operator==(const ITreeNode& node) <<== to
{ IShellFolder* pFolder = 0; if (m_pParent) pFolder = m_pParent->m_pShellFolder;
if (!pFolder) return 0;
LPITEMIDLIST pidl1 = (LPITEMIDLIST)GetRelativeUniqueId(); LPITEMIDLIST pidl2 = (LPITEMIDLIST)node.GetRelativeUniqueId();
HRESULT h = pFolder->CompareIDs(0, pidl1, pidl2); return (short)HRESULT_CODE(h); }
This should fix the problem. 
Cheers, Marat Bedretdinov
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I am having trouble with Windows 2000. Has anyone been successful running "Browse for Folder" code under Windows 2000? Are there any known fixes availabe? Thanks.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi there, Unfortunatelly, I haven't had a chance to test it on Windows 2000.
Sorry, couldn't really tell you where to look, since you din't describe the problem.
But here is a guess. If you are having trouble enumerating IShellFolder, then I would suspect Microsoft might have changed the way their Windows Shell Interfaces work, which would be very typical for Microsoft. I would recommend to look into the implementation of CShellTreeNode and CShellHelper.
Cheers, Marat
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
It's just the same old boring code it always was:
const char* BrowseForDir(const char* szCaption, HWND hWndOwner) { static char szRetBuf[MAX_PATH]; char lpBuffer[MAX_PATH];
BROWSEINFO bi; LPITEMIDLIST PidlAnchor;
bi.pidlRoot = NULL;
bi.hwndOwner = hWndOwner; bi.pszDisplayName = lpBuffer; bi.lpszTitle = szCaption; bi.ulFlags = BIF_RETURNONLYFSDIRS; bi.lpfn = NULL; bi.lParam = 0;
szRetBuf[0] = '\0';
PidlAnchor = SHBrowseForFolder(&bi);
if (PidlAnchor != NULL) { LPMALLOC pMalloc; if (SHGetPathFromIDList(PidlAnchor, lpBuffer)) { lstrcpy(szRetBuf, lpBuffer); } if (SUCCEEDED(SHGetMalloc(&pMalloc))) { pMalloc->lpVtbl->Free(pMalloc, PidlAnchor); } }
return szRetBuf; }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Very clever (not) I don't think you get the point of all this.
You've got the source - hence you've got the flexibility. Tell me something. Can you take the tree control that displays the file system from the SHBrowseForFolder function and plug it into your application, so the file tree would appear as a part of your window?
The fundamental difference between having the source code and not having it is to actually understand how it all works.
Peace, Marat
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
> Very clever (not) I don't think you get the point > of all this.
I'm sorry. I read the introduction "This code demonstrates the use of Windows shell interfaces" and since what was displayed was only the filesystem branch of the shell namespace I assumed it was only about the filesystem namespace... Well, "assumption is the mother of all fsckups". I was wrong.
What got me writing the response in the first place was the amount of client code displayed for the task of browsing for a folder, it was never my intention to say that the code I displayed was in any way more clever or generic - just simpler to use for a single-threaded client for the case of getting an existing folder on a drive-letter mounted filesystem.
On a sidenote I might add that when using C++ you usually don't have to memset an "auto" (stack) POD struct, just
structname instancename = { 0 };
and the compiler takes care of the initialization.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Just to clarify. There was no problem. Pat had made a mistake preparing the CBrowseForFolderDlg::BFFINFO structure.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|