Win32 - FolderDialog - Multiple File & Folder selection

Category: visual studio vc

Question

Blacktempel on Sun, 21 Aug 2016 10:04:15


I'm trying to add a kind of FileDialog, which should be able to select both files AND folders.
At the moment I'm only able to select multiple files or multiple folders at best.
I've tried to figure out how to do this (w/o MFC !), but I'm totally stuck.
If someone did such a thing before, I'd be glad to get some advise.
- This code fetches only 1 result from the dialog. Please ignore that it's not adjusted for multiple results in that code snippet.

enum FILE_DIALOG_TYPE
{
    OPEN_FILE_DIALOG,
    SAVE_FILE_DIALOG
};

#if defined(_WIN32) || defined(WIN32) || defined (_WIN64) || defined(WIN64) bool FileOperation::FileDialog(FILE_DIALOG_TYPE fdt, const HWND hParent, const std::wstring &caption, const std::wstring &fileExtensions, wchar_t *outPath, const DWORD outSize) { OPENFILENAMEW openInfo = { 0 }; openInfo.lStructSize = sizeof(openInfo); openInfo.hwndOwner = hParent; openInfo.lpstrTitle = caption.c_str(); openInfo.lpstrFilter = fileExtensions.empty() ? nullptr : fileExtensions.c_str(); openInfo.nFilterIndex = 0; openInfo.lpstrFile = outPath; openInfo.nMaxFile = outSize; openInfo.Flags = OFN_HIDEREADONLY | OFN_EXPLORER | OFN_ENABLESIZING; switch (fdt) { case OPEN_FILE_DIALOG: { return GetOpenFileNameW(&openInfo) == TRUE; } break; case SAVE_FILE_DIALOG: { return GetSaveFileNameW(&openInfo) == TRUE; } break; } return false; } #pragma warning(disable:4191) namespace Win32 { 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; } HRESULT Downlevel_SHCreateItemFromParsingName(PCWSTR pszPath, IBindCtx* pbc, REFIID riid, void** ppv) { _ASSERTE(IsWindowsVistaOrGreater()); 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; } std::wstring ShowFolderBrowserDialog(HWND hwndOwner, const std::wstring &strDlgTitle, const wchar_t *strStartPath) { CComPtr<IFileOpenDialog> pFileOpenDlg; if (SUCCEEDED(pFileOpenDlg.CoCreateInstance(__uuidof(FileOpenDialog)))) { if (SUCCEEDED(pFileOpenDlg->SetTitle(strDlgTitle.c_str()))) { 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 std::wstring(pszSelectedItem); } } } } } } } } return std::wstring(); } }; #pragma warning(default:4191) bool FileOperation::FolderDialog(const HWND hParent, const std::wstring &caption, wchar_t *outPath, std::size_t outPathSize) { if (IsWindowsVistaOrGreater()) { auto folder = Win32::ShowFolderBrowserDialog(hParent, caption, L""); if (folder.empty()) return false; wcscpy_s(outPath, outPathSize, folder.c_str()); return true; } else { LPMALLOC pMalloc = nullptr; LPITEMIDLIST pItemList = nullptr; BROWSEINFOW bi = { 0 }; bi.hwndOwner = hParent; bi.lpszTitle = caption.c_str(); bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT; pItemList = SHBrowseForFolderW(&bi); if (pItemList) { SHGetPathFromIDListW(pItemList, outPath); if (SUCCEEDED(SHGetMalloc(&pMalloc))) { pMalloc->Free(pItemList); pMalloc->Release(); return true; } } } return false; } #endif //Windows





Replies

RLWA32 on Sun, 21 Aug 2016 11:39:22


The CommonFileDialogModes sample at https://github.com/Microsoft/Windows-classic-samples/tree/master/Samples/Win7Samples/winui/shell/appplatform/CommonFileDialogModes does exactly what you are looking for.

Blacktempel on Sun, 21 Aug 2016 17:25:34


Thank you for your answer and direction to the repo.

I've tried around a little and I'm unable get the actual filename (e.g. C:\Bla\Bla\File.txt) from this code.
It returns some kind of "DisplayName".

See:
1. ReportSelectedItemsFromSite
2. ReportSelectedItems
3. GetIDListName

In here it calls a IShellItem's function ->GetDisplayName.
I tried around with the enum _SIGDN parameter, though nothing seemed to work.

RLWA32 on Sun, 21 Aug 2016 18:05:32


In the function GetIDListName on line 154 of the sample make the following change to use SIGDN_FILESYSPATH

hr = psi->GetDisplayName(SIGDN_FILESYSPATH, &pszName);//SIGDN_PARENTRELATIVE

Blacktempel on Sun, 21 Aug 2016 18:25:11


I've tried this, it returns E_INVALIDARG.

E_INVALIDARG One or more arguments are invalid.

RLWA32 on Sun, 21 Aug 2016 18:50:47


It runs in a loop. Just let it run to completion and you see you will retrieve full paths.  I didn't bother to filter out the non-filesystem objects that are in the shellitems so that is why you will get a couple of errors.  Try again. :)

Blacktempel on Mon, 22 Aug 2016 06:29:16


Ah damnit. Stopped debugging after the first error. Thank you, works fine.

RLWA32 on Mon, 22 Aug 2016 09:27:07


To focus only on file system objects you can use IShellItem::GetAttributes to test for SFGAO_FILESYSTEM.