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

Browse for Folder -- Advanced Options PART TWO

This article describes how to set an initial/default folder when you use the SHBrowseForFolder API.  You really don't want to force your user to drill down multiple levels, and you want to prevent the user from accidentally locating a similar folder on a different drive or network share.  It's just plain good U/I design to remember a previously-used location and let the user start there the next time he or she browses for a folder.

In PART ONE we covered the basics of using SHBrowseForFolder and also show how to pre-set a root node so that the user is forced to browse in a specific part of the directory tree. In this part, PART TWO, we look at how to set a default folder when using SHBrowseForFolder.
ShBrowseForFolder with initial/default setYou probably know that the Common Dialog GetSave/OpenFileName APIs "remember" the last used location automatically (see Setting View and Sort in a File Open Dialog for some details about that and how to do some cool things for your user such as setting to thumbnail view when browsing photos).  Alas, the SHBrowseForFolder function provides no such convenience.  It seems to remember the size of the dialog box, but not much else.  We must use a technique similar to that we used for GetOpenFileName... create a callback function and take action at just the right time in order to set the initial folder to something other than Desktop.

Here's the example code:
                      MyBrowseCallbackProc( HWND hwnd,UINT uMsg, LPARAM, LPARAM lpData )
                          if ( uMsg == BFFM_INITIALIZED ) {
                              CMySettingsDlg* pThis= (CMySettingsDlg*)lpData; // set in BROWSEINFO
                              if ( pThis && pThis->GetSafeHwnd() ) { // forgot to set it?
                                  CString sInitialDir;
                                  LPCTSTR pDir= (LPCTSTR)sInitialDir;
                                  ::SendMessage( hwnd,BFFM_SETSELECTION,TRUE,(LPARAM)pDir );
                          return 0;
                      void CMySettingsDlg::OnBnClickedBrowseForFolder()
                          BROWSEINFO rBI= {0};
                          rBI.hwndOwner= GetSafeHwnd();
                          rBI.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI;
                          rBI.lpszTitle= L"Select Folder to Store Backup Data";
                          rBI.lpfn= MyBrowseCallbackProc;
                          rBI.lParam= (DWORD)this;   // used by static callback fn
                          LPITEMIDLIST pidl= SHBrowseForFolder( &rBI ); 
                          if ( pidl != NULL ) {
                              TCHAR szPath[MAX_PATH];
                              BOOL fRet= SHGetPathFromIDList( pidl, szPath );
                              m_ctlFolderName.SetWindowTextW( szPath );

Open in new window

See BrowseCallbackProc Function for a detailed description of the callback function.

The above code is only slightly more complex than it needs to be.  In the callback function, we need to know the pathname of the initial directory.  But the callback is a local function that is not a member of the CDialog-derived settings dialog.  Most examples show this function accessing a global variable -- either the CString member variable itself or a pointer to the dialog object in which the string variable resides.

But this is one of those many cases where the callback function is provided with an LPARAM that is set by the caller (see How to provide a CALLBACK function into a C++ class object).  So in line 23 we set BROWSEINFO.lParam to be a pointer to the dialog box object.  Then, when the callback takes control (lines 4-7), it coerces lpData into the right kind of pointer and uses that pointer to obtain the correct initial setting value from the dialog.  You may have a CString member variable that contains the value from the edit control and you could use that just as well.

Note that I could have made the MyBrowseCallbackProc a static member function of the dialog box (and most programmers would), but doing so does not buy me much... I'd still need to access the this pointer from the LPARAM because static member functions can only access static member variables and functions.

Some Other Options
As long as you are writing a callback function, there are some other things you could do to customize the SHBrowseForFolder functionality.  You have the HWND of the shell-supplied dialog, so you can move controls around or otherwise monkey with the interface.  Probably the most useful option is handling the BFFM_SELCHANGED notification and displaying some sort of information about each folder the user clicks while browsing.  Or you could handle the BFFM_VALIDATEFAILED message (it is sent if the user types a folder name into the text box and you have set the BIF_VALIDATE option in the BROWSEINFO structure).

In this two-part article, we have looked at how and when to use the SHBrowseForFolder API function.  I've covered the two most-commonly asked questions about advanced use of the function:  In PART ONE, how to set a specific root (other than the Desktop) to limit the user's browsing range.  And in this part (PART TWO) I showed how to pre-set a browsing initial/default folder in order to save your user time.  These little things go a long way toward making your U/I more professional and providing a more pleasant experience for your users.

Article PART ONE

How to provide a CALLBACK function into a C++ class object

Setting View and Sort in a File Open Dialog

SHBrowseForFolder Function


SHGetPathFromIDList Function

SHParseDisplayName Function

SHSimpleIDListFromPath Function

BrowseCallbackProc Function

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!

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.