I am using IFileDialog::AddPlace
to add e.g. "c:\\my\\custom\\location"
as a custom place to select files from to the navigation panel on the left, and set that as (default/forced) initial folder.
However, when the dialog opens, the root drive (C: in the example) is selected instead of the custom place.
(I use SHCreateItemFromParsingName
to create the IShellItem
from the path, and use the same Shellitem in both AddPlace
and SetFolder
)
Result: https://i.sstatic.net/Rf44R.jpg
Full source: http://pasted.co/17cb14c2
I'm not sure you can select the virtual folder directly from the IFileDialog interface. But you can subscribe to the file dialog events and have access to the explorer's left tree view from the inside.
The tree view implements the INameSpaceTreeControl interface
int main()
{
CoInitialize(NULL);
{
LPCWSTR customPath = L"c:\\temp"; // use a path that exists...
CComPtr<IFileDialog> dlg;
Check(CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&dlg)));
// subscribe to events
MyFileDialogEvents* fde = new MyFileDialogEvents();
DWORD cookie;
Check(dlg->Advise(fde, &cookie));
CComPtr<IShellItem> location;
Check(SHCreateItemFromParsingName(customPath, nullptr, IID_PPV_ARGS(&location)));
Check(dlg->AddPlace(location, FDAP_TOP));
Check(dlg->SetFolder(location));
Check(dlg->Show(0));
Check(dlg->Unadvise(cookie));
delete fde;
}
CoUninitialize();
return 0;
}
class MyFileDialogEvents : public IFileDialogEvents
{
// called when selection has changed
HRESULT STDMETHODCALLTYPE OnSelectionChange(IFileDialog* pfd)
{
// get comdlg service provider
CComPtr<IServiceProvider> sp;
Check(pfd->QueryInterface(&sp));
// get explorer browser
// note this call would fail if we call it from IFileDialog* directly instead of from an event
CComPtr<IUnknown> unk;
Check(sp->QueryService(SID_STopLevelBrowser, &unk));
// get its service provider
CComPtr<IServiceProvider> sp2;
Check(unk->QueryInterface(&sp2));
// get the tree control
CComPtr<INameSpaceTreeControl> ctl;
Check(sp2->QueryService(IID_INameSpaceTreeControl, &ctl));
// get all roots, "Application Links" is a root
CComPtr<IShellItemArray> roots;
Check(ctl->GetRootItems(&roots));
DWORD count;
Check(roots->GetCount(&count));
// search for "Application Links" folder
for (DWORD i = 0; i < count; i++)
{
CComPtr<IShellItem> root;
Check(roots->GetItemAt(i, &root));
// get the normalized name, not the display (localized) name
CComHeapPtr<wchar_t> name;
Check(root->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &name));
// CLSID_AppSuggestedLocations?
if (!lstrcmpi(name, L"::{C57A6066-66A3-4D91-9EB9-41532179F0A5}"))
{
// found, expand it
ctl->SetItemState(root, NSTCIS_EXPANDED, NSTCIS_EXPANDED);
// get the first child
// TODO: loop over all suggested location (places) and use the one we're after instead of blindly taking the first one...
CComPtr<IShellItem> child;
ctl->GetNextItem(root, NSTCGNI_CHILD, &child);
if (child.p) // this will probably not succeed the first time we're called
{
// select the item
CComHeapPtr<wchar_t> childName;
ctl->SetItemState(child, NSTCIS_SELECTED, NSTCIS_SELECTED);
}
else
{
// select something so we can get back here
HRCHECK (pfd->SetFolder(location));
}
break;
}
}
return S_OK;
}
// poor-man's COM implementation for demo purposes...
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject)
{
*ppvObject = NULL;
if (riid == IID_IFileDialogEvents)
{
*ppvObject = (IFileDialogEvents*)this;
return S_OK;
}
if (riid == IID_IUnknown)
{
*ppvObject = (IUnknown*)this;
return S_OK;
}
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE AddRef() { return 1; }
ULONG STDMETHODCALLTYPE Release() { return 1; }
HRESULT STDMETHODCALLTYPE OnFileOk(IFileDialog* pfd) { return S_OK; }
HRESULT STDMETHODCALLTYPE OnFolderChanging(IFileDialog* pfd, IShellItem* psiFolder) { return S_OK; }
HRESULT STDMETHODCALLTYPE OnFolderChange(IFileDialog* pfd) { return S_OK; }
HRESULT STDMETHODCALLTYPE OnShareViolation(IFileDialog* pfd, IShellItem* psi, FDE_SHAREVIOLATION_RESPONSE* pResponse) { return S_OK; }
HRESULT STDMETHODCALLTYPE OnTypeChange(IFileDialog* pfd) { return S_OK; }
HRESULT STDMETHODCALLTYPE OnOverwrite(IFileDialog* pfd, IShellItem* psi, FDE_OVERWRITE_RESPONSE* pResponse) { return S_OK; }
};
note: I use Visual Studio's ATL smart pointers classes for simplicity.