1

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.stack.imgur.com/Rf44R.jpg

Full source: http://pasted.co/17cb14c2

peterchen
  • 40,917
  • 20
  • 104
  • 186
  • Your code seems to work for me (I mean almost your code, your Finally thing doesn't compile for me so I don't use it). Note the custom location is selected from the root drive (like C:), not from the "Application Links" virtual folder, but it's selected. – Simon Mourier Sep 13 '19 at 10:55
  • I ripped the Finally out of a longer impl. Yes, I see the same behavior as you describe, but question is if there's a way to have select the virtual folder selected. – peterchen Sep 13 '19 at 12:55

1 Answers1

3

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.

zastrowm
  • 8,017
  • 3
  • 43
  • 63
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • Hey thanks! This is almost working except for (as you note in the comments) the requried interfaces are available too late, so it still starts at the wrong place. I'll tinker around with it a little more, maybe I get it to work without being too brittle. Ready to accept your answer, just want to keep it open another two days, mabye though I doubt a better answer appears... – peterchen Sep 17 '19 at 13:07
  • Strange, as for me, the custom path is selected in the virtual "Application Links" when the dialog opens. You can get the top level browser reference in OnFolderChanging/OnFolderChange (they are raised earlier) and keep it as a member to be used when called on other events. – Simon Mourier Sep 17 '19 at 15:19
  • I tried all events ("until one gets through"), but that happens for me only after some user action. Maybe creatign a temp invisible HWND, setting a 0ms-Timer there, and trying again in the WM_TIMER response does solve it, but that feels quite hacky already. – peterchen Sep 17 '19 at 15:51
  • The thing is you have to pass twice in the OnSelectionChange. It used to work for me with the code as it was, but I couldn't make it work today... weird. Anyway, I've added a bit of code in the else statement for `if (child.p)` to make sure something is selected so we we'll be called again in OnSelectionChange and this time the first child.p call should succeed. – Simon Mourier Sep 17 '19 at 17:03