Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
#include <wx/wx.h>
#include <wx/choicdlg.h>
#include <wx/dirdlg.h>
#include <wx/dynlib.h>
#include <wx/msw/private/comptr.h>
#include <wx/msw/wrapwin.h>
#include <wx/msw/wrapshl.h>
#include <initguid.h>
#define wxDD_MULTIPLE 0x0400
bool IShellItemToPath(wxCOMPtr<IShellItem> item, wxString& path)
{
LPOLESTR pathOLE = NULL;
const HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &pathOLE);
if ( FAILED(hr) )
{
wxLogApiError(wxS("IShellItem::GetDisplayName"), hr);
return false;
}
path = pathOLE;
CoTaskMemFree(pathOLE);
return true;
}
int ShowIFileOpenDialog(wxArrayString& paths,
wxWindow* parent,
const wxString& message = wxDirSelectorPromptStr,
const wxString& defaultPath = wxEmptyString,
long style = wxDD_DEFAULT_STYLE
)
{
HRESULT hr;
wxCOMPtr<IFileOpenDialog> fileDialog;
// allow user to select only a file system folder
long options = FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM;
hr = ::CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER,
wxIID_PPV_ARGS(IFileOpenDialog, &fileDialog));
if ( FAILED(hr) )
{
wxLogApiError(wxS("CoCreateInstance(CLSID_FileOpenDialog)"), hr);
return wxID_NONE;
}
if ( style & wxDD_MULTIPLE )
options |= FOS_ALLOWMULTISELECT;
hr = fileDialog->SetOptions(options);
if ( FAILED(hr) )
{
wxLogApiError(wxS("IFileOpenDialog::SetOptions"), hr);
return wxID_NONE;
}
hr = fileDialog->SetTitle(message.wc_str());
if ( FAILED(hr) )
{
// This error is not serious, let's just log it and continue even
// without the title set.
wxLogApiError(wxS("IFileOpenDialog::SetTitle"), hr);
}
// set the initial path
if ( !defaultPath.empty() )
{
// We need to link SHCreateItemFromParsingName() dynamically as it's
// not available on pre-Vista systems.
typedef HRESULT
(WINAPI *SHCreateItemFromParsingName_t)(PCWSTR,
IBindCtx*,
REFIID,
void**);
SHCreateItemFromParsingName_t pfnSHCreateItemFromParsingName = NULL;
wxDynamicLibrary dllShell32;
if ( dllShell32.Load(wxS("shell32.dll"), wxDL_VERBATIM | wxDL_QUIET) )
{
wxDL_INIT_FUNC(pfn, SHCreateItemFromParsingName, dllShell32);
}
if ( !pfnSHCreateItemFromParsingName )
{
wxLogLastError(wxS("SHCreateItemFromParsingName() not found"));
return wxID_NONE;
}
wxCOMPtr<IShellItem> folder;
hr = pfnSHCreateItemFromParsingName(defaultPath.wc_str(),
NULL,
wxIID_PPV_ARGS(IShellItem,
&folder));
// Failing to parse the folder name is not really an error, we'll just
// ignore the initial directory in this case, but we should still show
// the dialog.
if ( FAILED(hr) )
{
if ( hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) )
{
wxLogApiError(wxS("SHCreateItemFromParsingName"), hr);
return wxID_NONE;
}
}
else // The folder was parsed correctly.
{
hr = fileDialog->SetFolder(folder);
if ( FAILED(hr) )
{
wxLogApiError(wxS("IFileOpenDialog::SetFolder"), hr);
return wxID_NONE;
}
}
}
const HWND hParent = parent ? parent->GetHWND() : nullptr;
wxArrayString tempPaths;
hr = fileDialog->Show(hParent);
if ( SUCCEEDED(hr) )
{
wxString path;
if ( style & wxDD_MULTIPLE )
{
wxCOMPtr<IShellItemArray> itemArray;
hr = fileDialog->GetResults(&itemArray);
if ( SUCCEEDED(hr) )
{
DWORD count = 0;
hr = itemArray->GetCount(&count);
if ( SUCCEEDED(hr) )
{
for ( DWORD i = 0; i < count; ++i )
{
wxCOMPtr<IShellItem> item;
hr = itemArray->GetItemAt(i, &item);
if ( FAILED(hr) )
{
// do not attempt to retrieve any other items
// and just fail
wxLogApiError(wxS("IShellItemArray::GetItem"), hr);
tempPaths.clear();
break;
}
if ( !IShellItemToPath(item, path) )
{
// again, just fail
tempPaths.clear();
break;
}
tempPaths.push_back(path);
}
}
else
{
wxLogApiError(wxS("IShellItemArray::GetCount"), hr);
}
}
else
{
wxLogApiError(wxS("IFileOpenDialog::GetResults"), hr);
}
}
else
{
wxCOMPtr<IShellItem> item;
hr = fileDialog->GetResult(&item);
if ( SUCCEEDED(hr) )
{
if ( IShellItemToPath(item, path) )
{
tempPaths.push_back(path);
}
}
else
{
wxLogApiError(wxS("IFileOpenDialog::GetResult"), hr);
}
}
}
else if ( hr == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
{
return wxID_CANCEL; // the user cancelled the dialog
}
else
{
wxLogApiError(wxS("IFileOpenDialog::Show"), hr);
}
if ( tempPaths.empty() )
{
// the user didn't cancel the dialog and yet the path is empty
// it means there was an error, already logged by wxLogApiError()
// now report the error to the user and return
wxLogSysError(_("Couldn't obtain folder name(s)"), hr);
return wxID_CANCEL;
}
paths = tempPaths;
return wxID_OK;
}
class MyApp : public wxApp
{
public:
bool OnInit() override
{
wxArrayString paths;
long style = wxDD_DEFAULT_STYLE;
if ( wxMessageBox("Use multiple selection?", "Folder selection", wxYES_NO) == wxYES )
style |= wxDD_MULTIPLE;
if ( ShowIFileOpenDialog(paths,
nullptr, "Select folder(s)", "C:\\", style) == wxID_OK )
{
wxGetSingleChoice(wxString::Format("Number of directories selected: %zu", paths.size()),
"Directories selected", paths);
}
else
wxLogMessage("wxDirDialog cancelled or failed.");
return false;
}
}; wxIMPLEMENT_APP(MyApp);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment