Shell 数据对象文档讨论了用于使用拖放或剪贴板传输 Shell 数据的一般方法。 但是,若要在应用程序中实现 Shell 数据传输,还必须了解如何将这些一般原则和技术应用于可以传输 Shell 数据的各种方法。 本文档介绍了常见的 Shell 数据传输方案,并讨论了如何在应用程序中实现每个方案。
- 通用指南
- 将文件名从剪贴板复制到应用程序
- 将已删除文件的内容复制到应用程序中
- 处理优化的移动操作
- 处理粘贴时删除操作
- 向/从虚拟文件夹传输数据
- 将文件拖到回收站中
- 创建和导入废旧文件
- 异步拖放 Shell 对象
注意
尽管每个方案都讨论了特定的数据传输操作,但其中许多方案适用于各种相关方案。 例如,大多数剪贴板和拖放传输之间的主要区别在于数据对象到达目标的方式。 一旦目标具有指向数据对象的 IDataObject 接口的指针,提取信息的过程在两种类型的数据传输中基本上相同。 但是,某些方案仅限于特定类型的操作。 有关详细信息,请参阅个别场景。
通用准则
以下每个部分都讨论了一个相当具体的数据传输方案。 但是,数据传输通常更为复杂,并且可能涉及多个方案的各个方面。 通常,你事先不知道实际需要处理哪种方案。 以下是一些要记住的一般准则。
对于数据源:
- Shell 剪贴板格式(CF_HDROP除外)未预定义。 必须通过调用 RegisterClipboardFormat 来注册要使用的每个格式。
- 数据对象中的格式按源的首选项顺序提供。 枚举数据对象,然后选择可以使用的第一个对象。
- 请支持尽可能多的格式。 通常不知道数据对象将被删除的位置。 这种做法改进了数据对象将包含放置目标可以接受的格式的几率。
- 应以 CF_HDROP 格式提供现有文件。
- 提供类似文件的数据,具有CFSTR_FILECONTENTS/CFSTR_FILEDESCRIPTOR格式。 此方法允许目标从数据对象创建文件,而无需知道有关基础数据存储的任何信息。 通常应将数据呈现为 IStream 接口。 此数据传输机制比全局内存对象更灵活,并且使用的内存要少得多。
- 拖动源应在拖动 Shell 项时提供 CFSTR_SHELLIDLIST 格式。 可以通过 IShellFolder::GetUIObjectOf 或 IShellItem::BindToHandler 方法来获取项的数据对象。 数据源可以使用 SHCreateDataObject 创建支持CFSTR_SHELLIDLIST格式的标准数据对象实现。
- 拖放目标希望使用 shell 项编程模型来分析拖动项目的情况下,可以使用 SHCreateShellItemArrayFromDataObject 将 IDataObject 转换为 IShellItemArray。
- 使用标准反馈游标。
- 支持左右拖拽。
- 使用嵌入对象中的数据对象本身。 此方法允许应用程序检索数据对象必须提供的任何额外格式,并避免创建额外的包含层。 例如,服务器 A 中的嵌入对象从服务器/容器 B 中拖动并丢弃在容器 C 上。C 应创建服务器 A 的嵌入对象,而不是服务器 A 包含嵌入式对象的服务器 B 的嵌入对象。
- 请记住,Shell 在移动文件时可能会使用 优化的移动 或 粘贴时删除 操作。 应用程序应能够识别这些操作并相应地做出响应。
对于数据目标:
- Shell 剪贴板格式(CF_HDROP除外)未预定义。 必须通过调用 RegisterClipboardFormat 来注册要使用的每个格式。
- 实现并注册 OLE 拖放目标。 如果可能,请避免使用 Windows 3.1 目标或 WM_DROPFILES 消息。
- 数据对象包含的格式因对象的来源而异。 由于通常事先不知道数据对象来自何处,因此不要假定存在特定格式。 数据对象应按质量顺序枚举格式,从最佳开始。 因此,为了获得最佳可用格式,应用程序通常会枚举可用的格式,并在枚举中使用它们可支持的第一种格式。
- 支持右拖动。 可以通过创建 拖放处理程序来自定义拖动快捷菜单。
- 如果应用程序将接受现有文件,则必须能够处理 CF_HDROP 格式。
- 通常,接受文件的应用程序还应处理CFSTR_FILECONTENTS/CFSTR_FILEDESCRIPTOR格式。 虽然文件系统中的文件具有CF_HDROP格式,但来自命名空间扩展等提供程序的文件通常使用/。 示例包括WINDOWS CE 文件夹、文件传输协议 (FTP) 文件夹、Web 文件夹和 CAB 文件夹。 源通常实现 IStream 接口,以将数据从其存储中呈现为文件。
- 请记住,在移动文件时,Shell 可能会使用 优化移动 或 粘贴时删除操作。 应用程序应能够识别这些操作并相应地做出响应。
将文件名从剪贴板复制到应用程序
Scenario: 用户选择Windows资源管理器中的一个或多个文件,并将其复制到剪贴板。 应用程序提取文件名并将其粘贴到文档中。
例如,此方案可用于允许用户通过剪切文件并将其粘贴到应用程序来创建 HTML 链接。 然后,应用程序可以从数据对象中提取文件名,并对其进行处理以创建定位标记。
当用户在Windows资源管理器中选择文件并将其复制到剪贴板时,Shell 将创建一个数据对象。 然后,它调用 OleSetClipboard ,将指针置于剪贴板上的数据对象的 IDataObject 接口。
当用户从应用程序的菜单或工具栏中选择 “粘贴” 命令时:
- 调用 OleGetClipboard 以检索数据对象的 IDataObject 接口。
- 调用 IDataObject::EnumFormatEtc 以请求枚举器对象。
- 使用枚举器对象的 IEnumFORMATETC 接口枚举数据对象包含的格式。
注意
此过程的最后两个步骤为完整起见包含在内。 对于简单的文件传输,通常不需要它们。 用于此类数据传输的所有数据对象都应包含 CF_HDROP 格式,该格式可用于确定对象包含的文件的名称。 但是,对于更常规的数据传输,应枚举格式并选择应用程序可以处理的最佳格式。
从数据对象中提取文件名
下一步是从数据对象中提取一个或多个文件名并将其粘贴到应用程序中。 请注意,从数据对象中提取文件名的过程同样适用于拖放传输。
从数据对象 检索文件名的最简单方法是CF_HDROP 格式:
调用 IDataObject::GetData。 将 FORMATETC 结构的 cfFormat 成员设置为CF_HDROP,将 tymed 成员设置为TYMED_HGLOBAL。 dwAspect 成员通常设置为DVASPECT_CONTENT。 但是,如果需要采用短(8.3)格式的文件路径,请将dwAspect设置为DVASPECT_SHORT。
当 IDataObject::GetData 返回时,STGMEDIUM 结构的 hGlobal 成员指向一个包含数据的全局内存对象。
创建 HDROP 变量并将其设置为 STGMEDIUM 结构的 hGlobal 成员。 HDROP 变量现在是一个指向 DROPFILES 数据结构的句柄,后面紧跟着一个以双空字符结尾的字符串,该字符串包含已复制文件的完全限定路径。
通过将 DragQueryFile 的 iFile 参数设置为 0xFFFFFFFF 来调用这段代码,以确定列表中有多少个文件路径。 该函数返回列表中的文件路径数。 此列表中的文件路径的零起始索引用于下一步来标识特定路径。
通过为每个文件调用 DragQueryFile 一次,将 iFile 设置为文件的索引,从全局内存对象中提取文件路径。
根据需要处理文件路径,并将其粘贴到应用程序中。
调用 ReleaseStgMedium,并传入指向 STGMEDIUM 结构的指针,该指针在步骤 1 中传递给 IDataObject::GetData。 释放结构后,在步骤 2 中创建的 HDROP 值不再有效,不应使用。
将已删除文件的内容复制到应用程序中
Scenario: 用户从Windows资源管理器拖动一个或多个文件,并将其拖放到应用程序的窗口中。 应用程序提取文件的内容并将其粘贴到应用程序中。
此方案使用拖放将文件从Windows资源管理器传输到应用程序。 在操作之前,应用程序必须:
- 调用 RegisterClipboardFormat 以注册任何所需的 Shell 剪贴板格式。
- 调用 RegisterDragDrop 以注册目标窗口和应用程序的 IDropTarget 接口。
用户通过选择一个或多个文件并开始拖动它们来启动操作后:
- Windows资源管理器创建一个数据对象,并将支持的格式加载到其中。
- Windows资源管理器调用 DoDragDrop启动拖动循环。
- 当拖动图像到达目标窗口时,系统会通过调用 IDropTarget::D ragEnter 通知你。
- 若要确定数据对象包含的内容,请调用数据对象的 IDataObject::EnumFormatEtc 方法。 使用方法返回的枚举器对象来枚举数据对象所包含的格式。 如果应用程序不想接受这些格式中的任何一种,请返回DROPEFFECT_NONE。 出于此方案的目的,应用程序应忽略不包含用于传输文件的格式的任何数据对象,例如 CF_HDROP。
- 当用户删除数据时,系统会调用 IDropTarget::D rop。
- 使用 IDataObject 接口提取文件的内容。
可通过多种不同的方法从数据对象中提取 Shell 对象的内容。 一般情况下,请使用以下顺序:
- 如果文件包含 CF_TEXT 格式,则数据为 ANSI 文本。 可以使用CF_TEXT格式提取数据,而不是打开文件本身。
- 如果文件包含链接的或嵌入的 OLE 对象,则数据对象包含CF_EMBEDDEDOBJECT格式。 使用标准 OLE 技术提取数据。 碎片文件 始终包含CF_EMBEDDEDOBJECT格式。
- 如果 Shell 对象来自文件系统,则数据对象包含具有 文件名称的 CF_HDROP格式。 从 CF_HDROP中提取文件名,并调用 OleCreateFromFile 以创建新的链接对象或嵌入对象。 有关如何从 CF_HDROP 格式检索文件名的讨论,请参阅 将文件名从剪贴板复制到应用程序。
- 如果数据对象包含CFSTR_FILEDESCRIPTOR格式,则可以从文件的CFSTR_FILECONTENTS格式中提取文件的内容。 有关此过程的讨论,请参阅 使用CFSTR_FILECONTENTS格式从文件中提取数据。
- 在 Shell 版本 4.71 之前,应用程序通过在 FILEDESCRIPTOR 结构的 dwFlags 成员中设置 FD_LINKUI 来表示它正在传输快捷方式文件类型。 对于较新版本的 Shell,表示要传输的快捷方式的首选方法是使用设置为DROPEFFECT_LINK的CFSTR_PREFERREDDROPEFFECT格式。 这种方法比提取 FILEDESCRIPTOR 结构来检查标志要高效得多。
如果数据提取过程较长,可能需要在后台线程上异步执行该操作。 然后,主线程可以继续,而不会造成不必要的延迟。 有关如何处理异步数据提取的讨论,请参阅 异步拖放 Shell 对象。
使用CFSTR_FILECONTENTS格式从文件中提取数据
CFSTR_FILECONTENTS格式提供了一种非常灵活且强大的方法来传输文件的内容。 甚至不需要将数据存储为单个文件。 此格式所需的所有内容就是数据对象将数据呈现给目标,就好像是文件一样。 例如,实际数据可能是文本文档的一部分或从数据库中提取的数据块。 目标可以将数据视为文件,不需要知道有关基础存储机制的任何信息。
命名空间扩展通常使用 CFSTR_FILECONTENTS 来传输数据,因为此格式不假定任何特定的存储机制。 命名空间扩展可以使用任何方便的存储机制,并使用此格式将其对象呈现给应用程序,就像它们是文件一样。
CFSTR_FILECONTENTS数据传输机制通常是TYMED_ISTREAM。 传输 IStream 接口指针所需的内存比将数据加载到全局内存对象要少得多,IStream 是表示数据比 IStorage 更简单的方法。
CFSTR_FILECONTENTS格式始终附带CFSTR_FILEDESCRIPTOR格式。 必须首先检查此格式的内容。 如果要传输多个文件,则数据对象实际上将包含多个 CFSTR_FILECONTENTS 格式,每个文件对应一个。 CFSTR_FILEDESCRIPTOR格式包含每个文件的名称和属性,并为提取特定文件的CFSTR_FILECONTENTS格式所需的每个文件提供索引值。
若要提取 CFSTR_FILECONTENTS 格式,请执行以下操作:
- 将 CFSTR_FILEDESCRIPTOR 格式提取为一个 TYMED_HGLOBAL 值。
- 返回的 STGMEDIUM 结构的 hGlobal 成员指向全局内存对象。 通过将hGlobal值传递给GlobalLock来锁定该对象。
- 将 GlobalLock 返回的指针强制转换为 FILEGROUPDESCRIPTOR 指针。 它将指向一个FILEGROUPDESCRIPTOR结构,后跟一个或多个FILEDESCRIPTOR结构。 每个 FILEDESCRIPTOR 结构都包含由随附 CFSTR_FILECONTENTS 格式之一包含的文件的说明。
- 检查 FILEDESCRIPTOR 结构,以找出与要提取的文件匹配的结构。 FILEDESCRIPTOR 结构中的零基索引用于标识文件的 CFSTR_FILECONTENTS 格式。 由于全局内存块的大小不是字节精确的,因此请使用结构的 nFileSizeLow 和 nFileSizeHigh 成员来确定全局内存对象中表示文件的字节数。
- 调用 IDataObject::GetData,其 FORMATETC 结构的 cfFormat 成员设置为 CFSTR_FILECONTENTS 值,lIndex 成员设置为在上一步中确定的索引。 tymed 成员通常设置为TYMED_HGLOBAL |TYMED_ISTREAM |TYMED_ISTORAGE。 然后,数据对象可以选择其首选数据传输机制。
- 由 IDataObject::GetData 返回的 STGMEDIUM 结构将包含一个指向文件数据的指针。 检查结构的 tymed 成员以确定数据传输机制。
- 如果 tymed 设置为 TYMED_ISTREAM 或TYMED_ISTORAGE,请使用接口提取数据。 如果 tymed 设置为TYMED_HGLOBAL,则数据包含在全局内存对象中。 有关如何从全局内存对象中提取数据的讨论,请参阅 Shell 数据对象的“从数据对象中提取全局内存对象”部分。
- 调用 GlobalLock 以解锁在步骤 2 中锁定的全局内存对象。
处理优化的移动操作
场景: 使用优化的移动将文件从文件系统移动到命名空间扩展。
在传统的移动操作中,目标创建数据的副本,源会删除原始数据。 此过程可能效率低下,因为它需要两个数据副本。 对于大型对象(如数据库),传统的移动操作甚至可能并不实用。
通过优化的迁移操作,目标利用其对数据存储方式的理解来处理整个迁移过程。 数据永远不会有第二个副本,并且不需要源删除原始数据。 Shell 数据非常适合优化移动,因为目标可以使用 Shell API 处理整个操作。 典型的示例是移动文件。 目标具有要移动的文件的路径后,可以使用 SHFileOperation 移动它。 无需源删除原始文件。
注意
Shell 通常使用优化的移动操作来传输文件。 若要正确处理 Shell 数据传输,应用程序必须能够检测和处理经过优化的传输操作。
优化移动的处理方式如下:
源调用 DoDragDrop,并将 dwEffect 参数设置为 DROPEFFECT_MOVE,以表示源对象可以移动。
目标通过其 IDropTarget 方法之一接收DROPEFFECT_MOVE值,指示允许移动。
目标要么拷贝对象(未优化移动),要么移动对象(优化移动)。
然后,目标会告知源是否需要删除原始数据。
优化的移动是默认操作,数据由目标删除。 若要通知源已执行优化移动,请执行:
-
- 目标通过其 IDropTarget::Drop 方法将收到的 pdwEffect 值设置为除 DROPEFFECT_MOVE 以外的其他值。 它通常设置为DROPEFFECT_NONE或DROPEFFECT_COPY。 该值将由 DoDragDrop 返回到源。
- 目标还调用数据对象的 IDataObject::SetData 方法,并将CFSTR_PERFORMEDDROPEFFECT格式标识符设置为DROPEFFECT_NONE。 此方法调用是必需的,因为某些放置目标可能无法正确设置 DoDragDrop 的 pdwEffect 参数。 CFSTR_PERFORMEDDROPEFFECT格式是指示已进行优化移动的可靠方法。
如果目标执行了未经过优化的移动操作,则源必须删除数据。 若要通知源,已执行未优化移动:
-
- 目标通过其IDropTarget::Drop 方法将接收到的 pdwEffect 值设置为 DROPEFFECT_MOVE。 该值将由 DoDragDrop 返回到源。
- 目标还调用数据对象的IDataObject::SetData方法,并传递一个格式标识符CFSTR_PERFORMEDDROPEFFECT,其值设置为DROPEFFECT_MOVE。 此方法调用是必需的,因为某些放置目标可能无法正确设置 DoDragDrop 的 pdwEffect 参数。 CFSTR_PERFORMEDDROPEFFECT格式是表示未经过优化的移动操作的可靠方法。
-
源检查目标可返回的两个值。 如果两者都设置为DROPEFFECT_MOVE,则通过删除原始数据来完成未优化移动。 否则,目标执行了优化迁移,并且删除了原始数据。
处理粘贴即删除操作
Scenario: 从Windows资源管理器中的文件夹剪切一个或多个文件并粘贴到命名空间扩展中。 Windows资源管理器会将文件保持突出显示状态,直到收到粘贴操作结果的反馈。
传统上,当用户剪切数据时,它会立即从视图中消失。 这可能效率不高,如果用户担心数据的变化,这可能会导致可用性问题。 一种替代方法是使用“粘贴时删除”操作。
使用删除粘贴操作,所选数据不会立即从视图中删除。 相反,源应用程序会通过更改字体或背景色将其标记为选中。 在目标应用程序粘贴数据后,它会通知源操作的结果。 如果目标执行 优化移动,则源只需更新其显示即可。 如果目标执行了正常移动,则源还必须删除其数据副本。 如果粘贴失败,源应用程序会将所选数据还原为其原始外观。
注意
当剪切/粘贴操作用于移动文件时,Shell 通常在操作中使用粘贴后删除功能。 使用 Shell 对象的删除粘贴操作通常使用 经过优化的移动 来移动文件。 若要正确处理 Shell 数据传输,应用程序必须能够检测和处理粘贴时删除操作。
实现删除粘贴的基本要求是目标必须将操作结果反馈给源。 但是,标准剪贴板技术不能用于实现粘贴时删除,因为它们不提供目标与源通信的方法。 相反,目标应用程序使用数据对象的 IDataObject::SetData 方法向数据对象报告结果。 然后,数据对象可以通过专用接口与源通信。
删除粘贴操作的基本过程如下所示:
- 源标记了所选数据在屏幕上的显示。
- 源创建数据对象。 它通过添加 数据值为 DROPEFFECT_MOVE 的CFSTR_PREFERREDDROPEFFECT 格式来指示剪切操作。
- 源使用 OleSetClipboard 在剪贴板上放置数据对象。
- 目标使用 OleGetClipboard 从剪贴板检索数据对象。
- 目标提取 CFSTR_PREFERREDDROPEFFECT 数据。 如果将其设置为只允许DROPEFFECT_MOVE,那么目标可以执行优化的移动操作或仅复制数据。
- 如果目标未执行优化的移动,它将调用 IDataObject::SetData 方法,并将CFSTR_PERFORMEDDROPEFFECT格式设置为DROPEFFECT_MOVE。
- 粘贴完成后,目标将调用 IDataObject::SetData 方法,并将CFSTR_PASTESUCCEEDED格式设置为DROPEFFECT_MOVE。
- 当调用源的 IDataObject::SetData 方法时,将 CFSTR_PASTESUCCEEDED 格式设置为DROPEFFECT_MOVE,必须检查是否还收到 CFSTR_PERFORMEDDROPEFFECT 格式,并设置为DROPEFFECT_MOVE。 如果这两种格式都由目标发送,则源必须删除数据。 如果仅收到CFSTR_PASTESUCCEEDED格式,源只需从其界面中删除数据。 如果传输失败,源会将显示更新为其原始外观。
向/从虚拟文件夹传输数据
场景: 用户从虚拟文件夹中拖动对象或将其拖放到虚拟文件夹中。
虚拟文件夹包含通常不属于文件系统的对象。 某些虚拟文件夹(如回收站)可以表示存储在硬盘上的数据,而不是普通文件系统对象。 有些可以表示远程系统上的存储数据,例如手持电脑或 FTP 站点。 其他对象(如打印机文件夹)包含的对象完全不代表存储数据。 虽然某些虚拟文件夹是系统的一部分,但开发人员还可以通过实现命名空间扩展来创建和安装自定义虚拟文件夹。
无论数据类型或其存储方式如何,虚拟文件夹中包含的文件夹和文件对象都会由 Shell 呈现,就像它们是普通文件和文件夹一样。 虚拟文件夹负责获取它包含的任何数据,并将其适当地呈现给 Shell。 此要求意味着虚拟文件夹通常支持拖放和剪贴板数据传输。
因此,需要关注向虚拟文件夹传输和从虚拟文件夹传输数据的两组开发人员:
- 应用程序需要接受从虚拟文件夹传输的数据的开发人员。
- 需要确保其命名空间扩展正确支持数据传输的开发人员。
接受虚拟文件夹中的数据
虚拟文件夹几乎可以表示任何类型的数据,并且可以以任何方式存储该数据。 某些虚拟文件夹实际上可能包含正常的文件系统文件和文件夹。 例如,其他人可能将其所有对象打包到单个文档或数据库中。
将文件系统对象传输到应用程序时,数据对象通常包含具有 该对象的完全限定路径的 CF_HDROP格式。 应用程序可以提取此字符串,并使用普通文件系统函数打开文件并提取其数据。 但是,由于虚拟文件夹通常不包含普通文件系统对象,因此它们通常不使用 CF_HDROP。
与CF_HDROP不同,数据通常通过虚拟文件夹使用CFSTR_FILEDESCRIPTOR/和CFSTR_FILECONTENTS格式传输。 CFSTR_FILECONTENTS格式与CF_HDROP有两个优势:
- 不采用任何特定的数据存储方法。
- 格式更灵活。 它支持三种数据传输机制:全局内存对象、IStream 接口或 IStorage 接口。
全局内存对象很少用于将数据传输到虚拟对象或从虚拟对象传输数据,因为数据必须全部复制到内存中。 传输接口指针几乎不需要内存,而且效率更高。 对于非常大的文件,接口指针可能是唯一可行的数据传输机制。 通常,数据由 IStream 指针表示,因为该接口比 IStorage 更灵活。 目标从数据对象中提取指针,并使用接口方法提取数据。
有关如何处理CFSTR_FILEDESCRIPTOR/CFSTR_FILECONTENTS格式的进一步讨论,请参阅使用CFSTR_FILECONTENTS格式从文件中提取数据。
向/从 NameSpace 扩展传输数据
实现命名空间扩展时,通常需要支持拖放功能。 按照一般准则中讨论的删除源和目标的建议进行操作。 具体而言,命名空间扩展必须:
- 能够处理CFSTR_FILEDESCRIPTOR/CFSTR_FILECONTENTS格式。 这两种格式通常用于在命名空间扩展之间传输对象。
- 能够处理 优化的移动。 Shell 期望其对象会通过经过优化的移动方式移动。
- 能够处理 粘贴时删除 操作。 当使用剪切/粘贴操作从 Shell 移动对象时,Shell 使用粘贴即删除操作。
- 能够通过 IStream 或 IStorage 接口处理数据传输。 通常通过传输这两个接口指针之一(通常是 IStream 指针)来处理虚拟文件夹的数据传输。 然后,目标调用接口方法来提取数据:
-
- 作为拖放源,命名空间扩展必须从存储中提取数据,并通过该接口传递到目标。
- 作为删除目标,命名空间扩展必须通过此接口接受来自源的数据并正确存储数据。
-
将文件拖放至回收站
场景:用户删除回收站上的文件。 应用程序或命名空间扩展将删除原始文件。
回收站是一个虚拟文件夹,用作不再需要的文件的存储库。 只要回收站尚未清空,用户就可以稍后恢复该文件并将其返回到文件系统。
在大多数情况下,将 Shell 对象传输到回收站的工作方式非常类似于任何其他文件夹。 但是,当用户将文件拖放到回收站时,即便回收站的反馈显示为复制操作,源仍需删除原始文件。 通常,删除源无法知道其数据对象已删除的文件夹。 但是,对于 Windows 2000 及更高版本系统,当数据对象删除到 Recycle Bin 时, Shell 将调用数据对象的 IDataObject::SetData 方法,并将CFSTR_TARGETCLSID格式设置为回收站的类标识符(CLSID)(CLSID_RecycleBin)。 若要正确处理回收站的情况,数据对象应能够识别此格式,并通过私有接口将信息传达给源。
注意
当调用IDataObject::SetData时,如果CFSTR_TARGETCLSID格式设置为CLSID_RecycleBin,数据源应在返回方法之前关闭所有要传输对象的打开句柄。 否则,您可能会导致共享冲突。
创建和导入废旧文件
Scenario: 用户从 OLE 应用程序的数据文件中拖动某些数据,并将其拖放到桌面或Windows资源管理器中。
Windows允许用户从 OLE 应用程序的数据文件中拖动对象,并将其拖放到桌面或文件系统文件夹中。 此操作将创建一个 临时文件,其中包含数据或指向数据的链接。 文件名取自为对象的 CLSID 注册的短名称以及 CF_TEXT 数据。 若要使 Shell 创建包含数据的废旧文件,应用程序的 IDataObject 接口必须支持剪贴板格式CF_EMBEDSOURCE。 若要创建包含链接的文件, IDataObject 必须支持CF_LINKSOURCE格式。
应用程序还可以实现三个可选功能来支持废旧文件:
- 往返支持
- 缓存的数据格式
- 延迟呈现
往返支持
往返涉及将数据对象传输到另一个容器,然后传回原始文档。 例如,用户可以将一组单元格从电子表格传输到桌面,从而创建包含数据的废旧文件。 如果用户随后将废品传输回电子表格,则需要将数据集成到文档中,就像原始传输之前一样。
当 Shell 创建报废文件时,它将数据表示为嵌入对象。 当废品转移到另一个容器时,它作为嵌入对象传输,即使它被返回到原始文档也是如此。 应用程序负责确定临时存储区中包含的数据格式,并在必要时将数据放回其原始格式。
若要建立嵌入对象的格式,请通过检索对象的CF_OBJECTDESCRIPTOR格式来确定其 CLSID。 如果 CLSID 指示属于应用程序的数据格式,则应传输本机数据,而不是调用 OleCreateFromData。
缓存的数据格式
当 Shell 创建报废文件时,它会检查注册表中是否有可用格式的列表。 默认情况下,有两种格式可用:CF_EMBEDSOURCE和CF_LINKSOURCE。 但是,在某些情况下,应用程序可能需要采用不同格式的废旧文件:
- 允许将碎片传输到无法接受嵌入式对象格式的非 OLE 容器。
- 允许应用程序套件使用私有格式进行通信。
- 使往返行程更容易处理。
应用程序可以通过在注册表中缓存格式来向废品添加格式。 有两种类型的缓存格式:
- 优先级缓存格式。 对于这些格式,数据将全部复制到数据对象的废品中。
- 延迟呈现的格式。 对于这些格式,数据对象不会复制到废品。 相反,在目标请求数据之前,呈现会延迟。 下一部分将更详细地讨论延迟呈现。
若要添加优先级缓存或延迟呈现的格式,请在作为数据源的应用程序的 CLSID 键下创建 DataFormat 子项。 在该子项下,创建 PriorityCacheFormats 或 DelayRenderFormats 子项。 对于每个优先缓存或延迟渲染格式,请创建以零开头的编号子键。 将此键的值设置为格式的已注册名称的字符串或 #X 值,其中 X 表示标准剪贴板格式的格式号。
以下示例显示了两个应用程序的缓存格式。 MyProg1 应用程序将富文本格式设为优先缓存格式,并将专用格式“My Format”设为延迟渲染格式。 MyProg2 应用程序将 CF_BITMAP 格式 (#8“) 作为优先级缓存格式。
HKEY_CLASSES_ROOT
CLSID
{GUID}
(Default) = MyProg1
DataFormats
PriorityCacheFormats
0
(Default) = Rich Text Format
DelayRenderFormats
0
(Default) = My Format
{GUID}
(Default) = MyProg2
DataFormats
PriorityCacheFormats
0
(Default) = #8
可以通过创建其他编号的子键来添加其他格式。
延迟呈现
延迟渲染格式允许应用程序创建一个临时文件,但将数据渲染的耗费延迟到目标请求数据时。 IDataObject 接口将在 scrap 中为目标提供延迟呈现格式、本机数据和缓存的数据。 如果目标请求延迟呈现格式,Shell 将运行应用程序,并从活动对象向目标提供数据。
注意
由于延迟呈现有点危险,因此应谨慎使用。 如果服务器不可用或未启用 OLE 的应用程序,则它不起作用。
异步拖放 Shell 对象
场景: 用户将一大块数据从源传输到目标。 为了避免在相当长时间内阻止这两个应用程序,目标异步提取数据。
通常,拖放是同步操作。 简单地说:
- 拖放源调用 DoDragDrop 并挂起其主线程,直到函数返回。 阻止主线程通常会阻止 UI 处理。
- 调用目标的 IDropTarget::D rop 方法后,目标将从其主线程上的数据对象中提取数据。 此过程通常会在提取过程期间阻止目标的 UI 处理。
- 提取数据后,目标将返回 IDropTarget::Drop 调用,系统将返回 DoDragDrop,并且两个线程都可以继续。
简言之,同步数据传输可能会长时间阻止这两个应用程序的主线程。 具体而言,当目标提取数据时,两个线程必须等待。 对于少量数据,提取数据所需的时间很小,同步数据传输效果很好。 但是,同步提取大量数据可能会导致长时间的延迟,并干扰目标和源的 UI。
IAsyncOperation
使用 IASyncOperation/IDataObjectAsyncCapability
注意
接口最初名为 IAsyncOperation,但后来更改为 IDataObjectAsyncCapability。 否则,这两个接口是相同的。
IAsyncOperation
- 创建一个数据对象,公开 IAsyncOperation 和 IDataObjectAsyncCapability。
- 调用SetAsyncMode,将fDoOpAsync设置为VARIANT_TRUE,以指示支持异步操作。
- DoDragDrop 返回后,调用InOperation:
- 如果 InOperation 失败或返回 VARIANT_FALSE,则发生了正常的同步数据传输,并且数据提取过程已完成。 源需要进行必要的清理,然后继续操作。
- 如果 InOperation 返回 VARIANT_TRUE,则以异步方式提取数据。 清理操作应由 EndOperation 处理。
- 释放数据对象。
- 异步数据传输完成后,数据对象通常会通过专用接口通知源。
以下过程概述了放置目标如何使用 IAsyncOperation 和 IDataObjectAsyncCapability 接口来异步提取数据:
- 当系统调用 IDropTarget::D rop 时,请调用 IDataObject::QueryInterface,并从数据对象请求 IAsyncOperation/ 接口(IID_IAsyncOperation/IID_IDataObjectAsyncCapability)。
- 调用 GetAsyncMode。 如果方法返回 VARIANT_TRUE,则数据对象支持异步数据提取。
- 创建单独的线程来处理数据提取和调用 StartOperation。
- 调用IDropTarget::Drop,就像正常数据传输操作一样。 DoDragDrop 将返回并解除对拖放源的阻止。 不要调用 IDataObject::SetData 来指示优化移动或删除粘贴操作的结果。 等待操作完成。
- 提取后台线程上的数据。 目标的主线程被解除阻塞,并可以继续运行。
- 如果数据传输是经过优化的移动或删除粘贴操作,请调用IDataObject::SetData 来指示结果。
- 通过调用 EndOperation 通知数据对象提取已完成。