Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

"Browse For Folder" dialog alike with source

0.00/5 (No votes)
5 Sep 2001 1  
Shell interfaces in use. IShellFolder, IEnumIDList, etc.

Sample Image - BFF_new.jpg

Introduction

This code demonstrates the use of Windows shell interfaces.

Shell Interfaces and Functions Used

The interfaces are:

  • IShellFolder
  • IEnumIDList

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; /* Line 1 */
  info.m_pidlSelected = m_pidlSelected;
  info.m_nFlags |= BFF_IDL_ROOT | BFF_IDL_SELECTED; /* Line 2 */
  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;

// release memory for the previous default

  SHFree(m_pidlSelected);
// allocate and copy newly selected

  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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here