如何强制资源管理器使用带有 Shell 命名空间扩展的现代文件操作对话框

ElD*_*ado 5 winapi windows-shell shell-extensions shell-namespace-extension

据我了解,目前有两种方法可以使用资源管理器从 Shell 命名空间扩展复制虚拟文件,以便向用户显示复制 GUI:

  1. 通过IDataObject接口:

    读取文件是通过至少IDataObject::GetData支持CFSTR_FILEDESCRIPTORW,CFSTR_FILECONTENTS和剪贴板格式来完成的。CFSTR_SHELLIDLIST请求应创建一个用于访问数据的对象CFSTR_FILECONTENTS。当需要时,通过设置标志来启用 UI 。IDataObject::GetDataIStreamFD_PROGRESSUICFSTR_FILEDESCRIPTORW

    IDataObject 复制 UI

  2. 通过ITransferSource接口:

    读取文件是通过ITransferSource::OpenItem请求IShellItemResources. 然后IShellItemResources应将{4F74D1CF-680C-4EA3-8020-4BDA6792DA3C}资源报告为受支持(GUID 指示该项目有一个 IStream)。最后,IStream通过父级请求ShellFolder::BindToObject访问数据。UI 由资源管理器本身处理,它始终显示。

    ITransferSource 复制 UI

我的问题是:这两种机制单独工作得很好(正如您从屏幕截图中看到的)。但是一旦我启用了IDataObjectfromIShellFolder::GetUIObjectOfITransferSourcefrom IShellFolder::CreateViewObject- 总是使用 via 方法IDataObject,导致旧的副本 GUI(如第一个屏幕截图所示)。我从跟踪日志中看到ITransferSource多次请求,但没有执行任何操作,它只是立即释放并销毁。

那么,当从 Shell 命名空间扩展进行复制时,如何强制资源管理器显示精美的复制 GUI?


可以在这里找到最小的可重现示例:https://github.com/BilyakA/SO_73938149


在研究最小可重复示例时,我设法使其在启用IDataObjectITranfserSource接口的情况下按预期工作。事情发生在之后:

  1. 未注册的 x64 构建 SNE 示例 ( regsvr32 /u)
  2. 注册的 x32 构建 SNE 示例(它在 x64 资源管理器中不起作用,root 未打开)
  3. 未注册 x32
  4. 再次注册x64。

不知何故,复制文件时向我显示了新的复制 UI。但在取消注册 x64 SNE、重新启动资源管理器并再次注册 SNE x64 后,我无法不断重现此结果。

我尝试过的:

  • 注册了 x32 和 x64 SNE - 仍然是旧的 GUI
  • Computer\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Cached使用我的 NSE GUID删除值,然后重新启动资源管理器。还是旧的图形用户界面。

我怀疑有某种缓存(除了注册表之外)可以跟踪 NSE 是否支持ITransferSource. ITransferSource而且由于我一开始就没有开发和测试- 它被缓存,我的 NSE 不支持它,即使我后来添加了它。并且以某种方式注册 32 位重置该缓存值。

Sim*_*ier 3

当请求 IDataObject 时,当前代码正在执行类似的操作

HRESULT CFolderViewImplFolder::GetUIObjectOf(HWND hwnd, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT rgfReserved, void **ppv)
{
    ....
    if (riid == IID_IDataObject)
    {
        CDataObject* dataObject = new (std::nothrow) CDataObject(this, cidl, apidl);
        return ::SHCreateDataObject(m_pidl, cidl, apidl, dataObject, riid, ppv);
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

SHCreateDataObject已经创建了一个完整的 Shell,IDataObject其中包含(可选)输入 PIDL 所需的所有内容,包括 Shell 命名空间中项目的所有 Shell 格式(CFSTR_SHELLIDLIST等等)。

传递内部对象 1) 可能会与 Shell 的功能发生冲突,2) 需要良好的实现(我没有检查过)。

所以你可以像这样替换它:

HRESULT CFolderViewImplFolder::GetUIObjectOf(HWND hwnd, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT rgfReserved, void **ppv)
{
    ....
    if (riid == IID_IDataObject)
    {
        return ::SHCreateDataObject(m_pidl, cidl, apidl, NULL, riid, ppv);
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

事实上,IDataObjectShell 提供的功能是完整的(支持SetData方法等),因此您永远不需要自己实现它,它比看起来更复杂。您可以将其重用为通用用途IDataObject(您可以为第 4 个参数传递 null 和 0)。

PS:Shell 还提供了“反向”方法:SHCreateShellItemArrayFromDataObjectIDataObject ,它从(如果数据对象包含预期的剪贴板格式)获取 Shell 项目列表。