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.