2

I'm trying to figure out if it is possible to use OR re-create the CFolderPickerDialog dialog without using MFC, or if there has been an attempt. So far I did not find a lot of hints. This old question does not seem to help me either.

I currently open the normal folder dialog with SHBrowseForFolder. But I need an Explorer-style dialog.

Here is the Explorer-style dialog (MFC) from another application:

Image

#include <afxdlgs.h> requires MFC. I cannot use MFC in this specific project.

Is there a way to do this without using MFC ?

Community
  • 1
  • 1
User9123
  • 53
  • 7
  • 4
    Why don't you have a look at the MFC source code? It'll show you how to do it without MFC. – IInspectable Jul 07 '16 at 12:45
  • 3
    Possible duplicate of [How to use IFileDialog with FOS\_PICKFOLDER while still displaying file names in the dialog](http://stackoverflow.com/questions/8269696/how-to-use-ifiledialog-with-fos-pickfolder-while-still-displaying-file-names-in) – IInspectable Jul 07 '16 at 12:49
  • 2
    https://msdn.microsoft.com/en-us/library/windows/desktop/bb776913.aspx – Cody Gray - on strike Jul 07 '16 at 16:16
  • 1
    The MFC version is buggy. Sometimes it doesn't restore the title bar of the original window. Use the links above instead. Note that after calling `GetDisplayName` you have to release the `wchar_t*` buffer with `CoTaskMemFree` – Barmak Shemirani Jul 07 '16 at 18:50

1 Answers1

5

Honestly, I didn't even know that MFC had wrapped this. My class library has its own implementation. And, as Barmak points out, the MFC implementation may even be buggy, and certainly has usage caveats that would not be obvious had you failed to read the documentation carefully.

That said, in general, it is good advice to use functionality that is already wrapped up in a library because this makes your life easier. If you don't want to use the whole library, but still want to see how it implements a particular feature, you can check the library's source code. MFC is provided with reference source so that you can do this easily (also so you can debug it). Although it would probably be a violation of the license to copy and paste code directly out of MFC (it would also be nigh-impossible, since it uses so many MFC-specific idioms), you can look at the code to see what they're doing, then go back to the Windows SDK documentation to figure out how to write the code yourself.

In this case, the relevant SDK documentation is here. Modern versions of Windows (since Vista) use the Common Item Dialog API to display open/save file/folder dialogs. The API consists of a base IFileDialog interface, with two sub-interfaces, IFileOpenDialog and IFileSaveDialog. There is a lot of flexibility here; the details are in the documentation, along with sample code.

Note that the Common Item Dialog is only available on Windows Vista and later. If you need to support older operating systems (I still support Windows XP), you need a fallback. The SHBrowseForFolder dialog is that fallback. It certainly has its design flaws, but it is better than nothing.

If all you want is a simple folder-picker dialog, here is an approximation of the code that I use. It uses a couple of ATL/MFC types, like the CString and CComPtr wrapper classes, but you can translate that to alternate classes of your own choosing (such as std::wstring and _com_ptr_t). It displays a simple browse-for-folder dialog, appropriate for the current operating system, with a caller-specified title and starting path. If it succeeds, it returns a string containing the path to the folder selected by the user; otherwise, it returns an empty string.

namespace
{
   HRESULT Downlevel_SHCreateItemFromParsingName(PCWSTR pszPath, IBindCtx* pbc, REFIID riid, void** ppv)
   {
      _ASSERTE(IsWinVistaOrLater());

      HRESULT hResult = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
      const HINSTANCE hinstLib = GetModuleHandle(TEXT("shell32"));
      if (hinstLib)
      {
         typedef HRESULT (WINAPI * pfSHCreateItemFromParsingName)(PCWSTR, IBindCtx*, REFIID, void**);
         const pfSHCreateItemFromParsingName pf = reinterpret_cast<pfSHCreateItemFromParsingName>(GetProcAddress(hinstLib, _CRT_STRINGIZE(SHCreateItemFromParsingName)));
         if (pf)
         {
            hResult = pf(pszPath, pbc, riid, ppv);
         }
      }
      return hResult;
   }

   int CALLBACK BrowseForFolderCallbackProc(HWND hWnd, UINT uMsg, LPARAM /* lParam */, LPARAM lData)
   {
      if (uMsg == BFFM_INITIALIZED)
      {
         // Start with BFFM_SETSELECTION, which is always available.
         SendMessage(hWnd, BFFM_SETSELECTION, TRUE, lData);

      #ifdef UNICODE
         // If possible, also try to use BFFM_SETEXPANDED, which was introduced with
         // version 6.0 of the shell (Windows XP).
         SendMessage(hWnd, BFFM_SETEXPANDED, TRUE, lData);

         // You can also set the caption for the dialog's "OK" button here, if you like
         // (e.g., by loading a string from a resource).
         //SendMessage(hWnd,
         //            BFFM_SETOKTEXT,
         //            0,
         //            reinterpret_cast<LPARAM>(pszOKBtnCaption));
      #endif  // UNICODE
      }
      return 0;
   }
}

CString ShowFolderBrowserDialog(HWND hwndOwner, const CString& strDlgTitle, const CString& strStartPath)
{
   if (IsWinVistaOrLater())
   {
      CComPtr<IFileOpenDialog> pFileOpenDlg;
      if (SUCCEEDED(pFileOpenDlg.CoCreateInstance(__uuidof(FileOpenDialog))))
      {
        if (SUCCEEDED(pFileOpenDlg->SetTitle(strDlgTitle)))
        {
            FILEOPENDIALOGOPTIONS options;
            if (SUCCEEDED(pFileOpenDlg->GetOptions(&options)))
            {
               if (SUCCEEDED(pFileOpenDlg->SetOptions(options | FOS_PATHMUSTEXIST | FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM)))
               {
                  CComPtr<IShellItem> psiStartPath;
                  if (SUCCEEDED(Downlevel_SHCreateItemFromParsingName(static_cast<const TCHAR*>(strStartPath),
                                                                      NULL,
                                                                      IID_PPV_ARGS(&psiStartPath))))
                  {
                     if (SUCCEEDED(pFileOpenDlg->SetFolder(psiStartPath)))
                     {
                        if (SUCCEEDED(pFileOpenDlg->Show(hwndOwner)))
                        {
                           CComPtr<IShellItem> pShellItemResult;
                           pFileOpenDlg->GetResult(&pShellItemResult);
                           CComHeapPtr<TCHAR> pszSelectedItem;
                           if (SUCCEEDED(pShellItemResult->GetDisplayName(SIGDN_FILESYSPATH, &pszSelectedItem)))
                           {
                              return pszSelectedItem;
                           }
                        }
                     }
                  }
               }
            }
         }
      }
   }
   else
   {
      TCHAR szBuffer[MAX_PATH + 1];
      szBuffer[0] = TEXT('\0');

      BROWSEINFO bi;
      bi.hwndOwner      = hwndOwner;
      bi.pidlRoot       = nullptr;
      bi.pszDisplayName = szBuffer;
      bi.lpszTitle      = strDlgTitle;
      bi.ulFlags        = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE | BIF_SHAREABLE | BIF_NONEWFOLDERBUTTON;
      bi.lpfn           = BrowseForFolderCallbackProc;
      bi.lParam         = reinterpret_cast<LPARAM>(static_cast<const TCHAR*>(strStartPath));

      CComHeapPtr<ITEMIDLIST> pidl(SHBrowseForFolder(&bi));
      if (pidl && SHGetPathFromIDList(pidl, szBuffer))
      {
         return pszSelectedItem;
      }
   }
   return TEXT("");
}

The dialog only shows actual folders in the filesystem. Although the Common Item Dialog API supports other types of special folders and namespaces, I don't need that in my app, so my code doesn't deal with the complexity. Use this as a starting point, along with the documentation, if you need more features. The most notable aspect is probably the use of SHCreateItemFromParsingName (which I have wrapped up in a dynamic call so that the code continues to run on older operating systems) to translate the caller-specified starting path (which is a string) to a Shell item object (as required by the Common Item Dialog API).

Community
  • 1
  • 1
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • Thank you for your detailed answer and the MSDN link. Just leaving a side note: before calling `CoCreateInstance` a call to `CoInitializeEx` is needed. – User9123 Jul 08 '16 at 12:17
  • Yes, that's correct. Normally that code goes in your application's initialization method, as it only needs to be called once. Presumably this is not the only COM API used by a Windows application. – Cody Gray - on strike Jul 08 '16 at 12:21
  • Really complex compared to my imagination :( – Dang D. Khanh Apr 16 '20 at 06:43