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

5
在我的理解中,目前有两种方法可以从Shell命名空间扩展中复制虚拟文件,以便向用户显示复制GUI界面:
  1. 通过 IDataObject 接口:

    通过 IDataObject::GetData 读取文件,该接口应支持至少 CFSTR_FILEDESCRIPTORWCFSTR_FILECONTENTSCFSTR_SHELLIDLIST 剪贴板格式。从 IDataObject::GetData 请求 CFSTR_FILECONTENTS 应创建一个用于访问数据的 IStream。当请求 CFSTR_FILEDESCRIPTORW 时,通过设置 FD_PROGRESSUI 标志来启用 UI。

    IDataObject copy UI

  2. 通过 ITransferSource 接口:

    通过请求 IShellItemResourcesITransferSource::OpenItem 来读取文件。然后,IShellItemResources 应报告支持 {4F74D1CF-680C-4EA3-8020-4BDA6792DA3C} 资源(表示该项有一个 IStream)。最后,通过父级 ShellFolder::BindToObject 请求 IStream 来访问数据。UI 由资源管理器自己处理,它总是显示。

    ITransferSource copy UI

我的问题是:这两个机制单独使用时都能正常工作(从截图中可以看出)。但一旦我同时启用来自IShellFolder::GetUIObjectOfIDataObject和来自IShellFolder::CreateViewObjectITransferSource,则始终使用通过IDataObject的方法导致旧的复制GUI界面(如第一张截图所示)。我从跟踪日志中看到ITransferSource被多次请求,但没有执行任何操作,它只是立即被释放和销毁。

那么我该如何强制资源管理器在从我的Shell命名空间扩展复制时显示漂亮的复制GUI界面呢?


这里可以找到一个最小化可复现的例子:https://github.com/BilyakA/SO_73938149


在处理 Minimal Reproducible example 时,我成功地使其可以同时支持 IDataObject 和 ITranfserSource 接口后,发生了以下情况:
  1. 取消注册 x64 构建的 SNE 示例(regsvr32 /u
  2. 注册 x32 构建的 SNE 示例(在 x64 Explorer 中无法工作,根目录未打开)
  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 值,并在之后重新启动资源管理器。仍然是旧 GUI。
我怀疑有一种缓存(不同于注册表),记录NSE是否支持ITransferSource。由于我一开始开发和测试时没有使用ITransferSource,所以被缓存为我的NSE不支持它,即使我后来添加了它。而且某种方式注册32位重置了该缓存值。

我可以说的是两者应该可以很好地一起工作。如果您提供一个可重现的示例,我们可以更深入地了解问题,否则就无法诊断。 - Simon Mourier
你是说如果实现了IDataObject和ITransferSource,就会使用旧的UI吗?需要同时实现这两个接口吗? - Jonathan Potter
是的,如果同时实现了 IDataObjectITransferSource,则会使用旧的用户界面。我认为我应该同时实现两者,因为我预计旧软件只能处理 IDataObject。另外,据我所知,拖放和复制粘贴也在很大程度上依赖于 IDataObject。最后,我发现系统中的默认文件夹都同时实现了这两个接口,那么我为什么不呢? - ElDorado
@SimonMourier,请在更新的问题中找到可重现示例的链接。 - ElDorado
1
没有像这样的缓存(在Shell中有很多缓存,但不是在这里——如果不确定,只需终止所有的explorer.exe)。#1问题:只需执行SHCreateDataObject(m_pidl, cidl, apidl, NULL, riid, ppv);,不要向Shell提供的内容添加一个内部(损坏的)IDataObject。您可以丢弃您的CDataObject类,您永远不需要它,Shell为您提供了所有HIDA支持。其他事项:#2除非需要,否则不要实现ITransferMediumItem。#3在ITransferSource::OpenItem中只返回S_FALSE(未记录),并且放弃您的自定义IShellItemResources实现。 - Simon Mourier
显示剩余6条评论
2个回答

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

SHCreateDataObject 已经创建了一个完整的 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);
    }
    ...
}

事实上,Shell 提供的 IDataObject 是完整的(支持 SetData 等方法),所以你不应该自己去实现它,它比看起来更加复杂。你可以将其重用为通用的 IDataObject(你可以将前四个参数传递为 null 和 0)。
PS:Shell 还提供了“反向”方法:SHCreateShellItemArrayFromDataObject,它可以从一个 IDataObject 中获取 Shell 项列表(如果数据对象包含预期的剪贴板格式)。

0

对于那些需要在SHCreateDataObject中使用内部IDataObject以获取其他格式的人:

内部IDataObject应该支持CFSTR_SHELLIDLIST剪贴板格式在GetData()中,并在EnumFormatEtc()中报告此格式。支持SetData()CFSTR_SHELLIDLIST格式在这里真的很有用,因为DefDataObject使用已经完全构造好的CIDA设置内部数据对象,您只需存储其副本即可。

一旦内部数据对象在EnumFormatEtc()中报告CFSTR_SHELLIDLIST并在GetData()中返回有效的CIDA,现代复制UI将被使用,因为它仅依赖于项目的PIDL。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接