How do I open SHBrowseForFolder at a pre-determined location?

SHBrowseForFolder is a very useful function, but it would be even more useful if you could open it at a given location, such as the last folder the user accessed. This makes your app much more user-friendly if the user is repetitively working on files in the same location. There's precious few things more irritating than an app that can't remember where it was a scant few minutes ago...

The secret here is to make use of the function's callback interface. The BROWSEINFO struct looks like this:

typedef struct _browseinfo
   HWND hwndOwner; 
   LPTSTR pszDisplayName; 
   LPCTSTR lpszTitle; 
   UINT ulFlags; 
   LPARAM lParam; 
   int iImage; 


Note that BFFCALLBACK member? You can set that up to point at a function of your own devising. The browse window calls this function once it's up and running, and you can then send a message to the browse window to tell it to go to the pre-defined place you stored in the structure earlier (boy, this is counter-intuitive, right ?).

So, let's say we have a button which launches the browse dialog. We start by getting a pointer to the Shell allocators IMalloc interface, in order to reserve some memory to store the PIDL we'll need later. We then set up the BROWSEINFO struct, then finally call SHBrowseForFolder. This code is storing the folder path in an edit control, referenced by the ClassWizard-created member variable m_ctrl_FolderSrcEdit

void CMyDlg::OnSrcFolderBrowseBtnClick()
   LPMALLOC pMalloc;
   CString csInitial;
   LPSTR lpBuffer;
   LPITEMIDLIST pidlBrowse; //PIDL selected by user
   CString csSrcFolder; 

   // We need to get a handle to the Shell allocator's IMalloc interface
   if (!SUCCEEDED (SHGetMalloc (&pMalloc)))
      AfxMessageBox ("Unable to link to Shell IMalloc interface");

   // Allocate a buffer to receive browse information.
   lpBuffer = (LPSTR) (pMalloc->Alloc (_MAX_PATH));

   if (lpBuffer == NULL)

   // Fill in the BROWSEINFO structure.
   bi.hwndOwner = GetSafeHwnd ();
   bi.pidlRoot = NULL;
   bi.pszDisplayName = lpBuffer;
   bi.lpszTitle = "Select source folder for files";
   bi.ulFlags = 0;

   // Add your own function here to retrieve a stored starting folder:
   csInitial = GetInitialFolder ();

   if (csInitial.GetLength() > 0)
      // Set the callback Fn pointer to my function:
      bi.lpfn = SetSelProc;
      // Set the lParam to point at my buffer containing the folder:
      bi.lParam = (LPARAM)(csInitial.GetBuffer (1));
      bi.lpfn = NULL;
      bi.lParam = 0;

   // Browse for a folder and return its PIDL.
   pidlBrowse = SHBrowseForFolder(&bi);

   if (pidlBrowse != NULL)
      SHGetPathFromIDList(pidlBrowse, lpBuffer);
      csSrcFolder = lpBuffer;
      m_ctrl_FolderSrcEdit.SetWindowText (csSrcFolder);

      // Free the PIDL returned by SHBrowseForFolder.
      pMalloc -> Free (pidlBrowse);
   // Clean up.
   pMalloc->Free (lpBuffer);

and here's the callback function. It sends a BFFM_SETSELECTION message back to the browse window, passing in the LPARAM value we set up in the BROWSEINFO struct above.


int CALLBACK SetSelProc (HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
      ::SendMessage (hWnd, BFFM_SETSELECTION, TRUE, lpData);
   return 0;

and that's all there is to it. But you'd be hard put to figure out it worked in this weird way.