windowsifiledialog

IFileDialog: Add a Custom Place - and select that place


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


Solution

  • 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.