【问题标题】:IFileDialog: Add a Custom Place - and select that placeIFileDialog:添加自定义位置 - 并选择该位置
【发布时间】:2020-01-11 11:55:27
【问题描述】:

我正在使用IFileDialog::AddPlace 添加例如"c:\\my\\custom\\location" 作为从左侧导航面板中选择文件的自定义位置,并将其设置为(默认/强制)初始文件夹。

但是,当对话框打开时,会选择根驱动器(示例中为 C:)而不是自定义位置。

(我使用SHCreateItemFromParsingName 从路径中创建IShellItem,并在AddPlaceSetFolder 中使用相同的Shellitem)

结果:https://imgur.com/w1pZhtd

完整来源:http://pasted.co/17cb14c2

【问题讨论】:

  • 你的代码似乎对我有用(我的意思是几乎你的代码,你的最后的东西不适合我,所以我不使用它)。请注意,自定义位置是从根驱动器(如 C:)中选择的,而不是从“应用程序链接”虚拟文件夹中选择的,但它已被选中。
  • 我从一个更长的 impl 中删除了 finally。是的,我看到了与您描述的相同的行为,但问题是是否有办法选择已选择的虚拟文件夹。

标签: windows ifiledialog


【解决方案1】:

我不确定您是否可以直接从 IFileDialog 界面选择虚拟文件夹。但是您可以订阅file dialog events 并从内部访问explorer's left tree view

树视图实现INameSpaceTreeControl接口

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; }
};

注意:为了简单起见,我使用 Visual Studio 的 ATL 智能指针类。

【讨论】:

  • 嘿,谢谢!这几乎可以正常工作,除了(正如您在 cmets 中指出的那样)所需的接口可用得太晚,因此它仍然从错误的位置开始。我会再修改一下,也许我可以让它工作而不会太脆弱。准备好接受你的答案,只是想让它再开放两天,尽管我怀疑会出现更好的答案......
  • 奇怪,就我而言,对话框打开时在虚拟“应用程序链接”中选择了自定义路径。您可以在 OnFolderChanging/OnFolderChange 中获取顶级浏览器引用(它们是较早提出的)并将其保留为在其他事件上调用时使用的成员。
  • 我尝试了所有事件(“直到一个通过”),但只有在一些用户操作后才会发生这种情况。也许创建一个临时不可见的 HWND,在那里设置一个 0ms-Timer,然后在 WM_TIMER 响应中再次尝试确实可以解决它,但这已经感觉很hacky了。
  • 问题是你必须在 OnSelectionChange 中传递两次。它曾经对我使用代码原样,但我今天无法让它工作......很奇怪。无论如何,我在 if (child.p) 的 else 语句中添加了一些代码,以确保选择了某些内容,因此我们将在 OnSelectionChange 中再次调用,这次第一个 child.p 调用应该成功。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多