Community Pick: Many members of our community have endorsed this article.

Browse for Folder -- Advanced Options

DanRollins
CERTIFIED EXPERT
Published:
Updated:
This article describes how to use the SHBrowseForFolder function and includes how to pre-set an initial folder and how to set any file system folder or other shell object as the "root" folder.
SHBrowseForFolder with initial/default set
It is a common requirement that a program be able to let the user select a folder; for instance, to specify where to store backup or log files, or to identify where to find a specific kind of input documents.  If you didn't know about the SHBrowseForFolder function, you might try to use the GetSaveFileName API, but that is awkward, at best.  GetSaveFileName is designed to let the user select (or type-in) a filename, not pick a directory.  It can be coerced into working as a folder chooser, but it's really not the right tool.

Standard/Simple Usage
Let's start by looking at a typical usage of SHBrowseForFolder, with no special features:
void CSettingsDlg::OnBnClickedBrowseForFolder()
                      {
                          BROWSEINFO rBI= {0};
                          rBI.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI;
                          rBI.lpszTitle= L"Select Folder for Output Files";
                      
                          LPITEMIDLIST pidl= SHBrowseForFolder( &rBI ); 
                          if ( pidl != NULL ) {  // else, user canceled
                              TCHAR szPath[MAX_PATH];
                              BOOL fRet= SHGetPathFromIDList( pidl, szPath );
                              m_ctlFolderName.SetWindowTextW( szPath );
                          }
                      }
                      

Open in new window

Note the final sequence that obtains the selected pathname.  You won't find the pathname in the BROWSEINFO structure.  You need to use SHGetPathFromIDList, as shown.

All of the options are set in the BROWSEINFO structure.  In the example, we set ulFlags to:

     BIF_RETURNONLYFSDIRS | BIF_USENEWUI

Neither is needed, but without BIF_RETURNONLYFSDIRS, the user will see shell objects such as Recycle Bin and Control Panel, which we probably don't want him to select.   BIF_USENEWUI is also important.  Without it, the user sees a smaller, fixed-size dialog and is limited to only selecting a folder.  With BIF_USENEWUI, the user has a much richer U/I experience.  He can right-click to access shell functions such as opening an Explorer, doing drag-and-drop of folders, renaming, moving, and even deleting folders.  Your user will expect this flexibility.

You might be surprised at the variety of options that are available.  For instance, the BIF_BROWSEINCLUDEFILES flag makes this into an unusual browse-for-file dialog with all files listed in the tree.  BIF_BROWSEFORPRINTER and BIF_BROWSEFORCOMPUTER provide specialty functionality for selecting a printer or a computer on the network.

Setting a Specific Root Folder
Though most programmers want to give their user maximum flexibility, it is also sometimes important to limit the user's options.  For instance, when the user is allowed to choose a folder for output data, you might require that he select a folder on a specific network data drive.  Or when selecting a folder containing input data files, you might want to narrow his choices to just certain subfolders of a designated directory, say, where templates are stored.
SHBrowseForFolder with a custom root"The BROWSEINFO. pidlRoot field is the key to this.  If you pre-set that value before calling SHBrowseForFolder, the user won't be able to stray "above" it in the hierarchy.  For instance, if you set pidlRoot to "C:\" then he can only choose folders on drive C.  If you set it to
     C:\Program Files\WonderProg\DataBacku ps
then he'll only be able to select folders that are in that directory (perhaps avoiding that ugly "I can't find my backup!" help-desk phone call).  

So, all we need to do is set a pidl.  But what the heck is a pidl?  Well, it stands for Pointer to an ID List.  An ID List is a shell construct that lets it locate user-specific folder such as "My Documents" and virtual folders such as Fonts, Control Panel, Recycle Bin, special folders that are identified with GUIDs, and so forth.  If you had to look at the "List" you'd see a linked chain of nodes leading from the desktop to a location in desktop namespace.

That said... you can forget about it.  You don't need to know what a pidl is, you only need to create one that identifies the root location for the folder browsing.

You might try using the SHSimpleIDListFromPath API.  But dire warnings in the MSDN documentation indicate that that function may disappear one day.  Anyway, it is of limited use.  As a "simple" ID list, it is a single-node "list" and it appears that SHBrowseForFolder is uncomfortable with it.  I found that it can be used to set a "high-level" node, such as a drive ID or even a mapped network drive like so...
rBI.pidlRoot= SHSimpleIDListFromPath( L"C:\\");
                      ...
                      LPITEMIDLIST pidl= SHBrowseForFolder( &rBI );
                      

Open in new window

...but if you try to set it to, for instance,  
     C:\Program Files\WonderProg
it chokes:  It displays a popup error message, then displays only that one folder without the expected selectable tree of subfolders.

The documentation in SHSimpleIDListFromPath describes the alternative way to get a usable pidl, but the description is rather obtuse, and there is a much easier way::  Use the SHParseDisplayName API.  So, at last, here's the code for setting your custom folder-browsing root.
PIDLIST_ABSOLUTE pidlRoot;
                      HRESULT hR= SHParseDisplayName( 
                          L"C:\\AnyFolder\\OrSubFolder",
                          0, &pidlRoot, 0, 0 // can ignore the attributes-related parms
                      );
                      BROWSEINFO rBI= {0};
                      rBI.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI;
                      rBI.pidlRoot= pidlRoot;
                      
                      LPITEMIDLIST pidl= SHBrowseForFolder( &rBI ); 
                      if ( pidl == NULL ) {
                      	MessageBox( L"User Canceled" );
                      	return;
                      }
                      TCHAR szPath[MAX_PATH];
                      BOOL fRet= SHGetPathFromIDList( pidl, szPath );
                      MessageBox( szPath, L"Selected file is..." );
                      

Open in new window


Pre-setting an Initial Folder for Browsing
It is poor U/I design to force the user to drill down several layers to locate the same place that he used the previous time he browsed for a folder.  The SHBrowseForFolder API provides no direct way to pre-set an initial folder.  We cover how to do this in

      PART TWO

=-=-=-=-=-=-=-=-=-=-=-=-=- =-=-=-=-=- =-=-=-=-=- =-=-=-=-=- =-=-=-=-=- =-=-=-=-=- =-=-=-=
If you liked this article and want to see more from this author,  please click the Yes button near the:
      Was this article helpful?
label that is just below and to the right of this text.   Thanks!
=-=-=-=-=-=-=-=-=-=-=-=-=- =-=-=-=-=- =-=-=-=-=- =-=-=-=-=- =-=-=-=-=- =-=-=-=-=- =-=-=-=
9
16,663 Views
DanRollins
CERTIFIED EXPERT

Comments (0)

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.