Shell数据对象(二):传输源如何创建数据对象

来源:互联网 发布:不一样的天空知乎 编辑:程序博客网 时间:2024/05/24 02:31

MSDN 2005 -> Win32 和 COM 开发 -> User Interface -> Windows User Experience -> Windows Shell -> Windows Shell -> Shell Programmer's Guide -> Shell Basics -> The Shell Data Object 

2. 传输源如何创建数据对象 

用户发起Shell数据传输时,传输源需要创建数据对象并载入数据,过程如下:

  1. 调用RegisterClipboardFormat为包含在数据对象中的每种格式获取有效的剪贴板格式值。CF_HDROP已经是有效的剪贴板格式,不需要再注册了。
  2. 对于每种将传输的格式,把相关数据放到全局存储对象中,或者创建可以通过其IStream或者IStorage接口访问数据的对象。IStreamIStorage接口通过标准COM技术创建。对于如何处理全局存储对象,见本文的How to Add a Global Memory Object to a Data Object节。
  3. 为每种格式创建FORMATETCSTGMEDIUM结构体。
  4. 实例化一个数据对象。
  5. 为每种支持的格式调用IDataObject::SetData方法,传入格式的FORMATETCSTGMEDIUM结构体,把数据载入到数据对象中。
  6. 对于剪贴板数据传输,调用OleSetClipboard把数据对象的IDataObject接口指针放置到剪贴板中;对于拖动传输,调用DoDragDrop发起拖动循环。放开数据,结束拖动循环时,IDataObject接口指针会被传递给放置目标。

经过这几步之后,数据对象就可以被传递给目标了。对于剪贴板数据传输,直到目标通过OleGetClipboard请求数据对象前,对象都被简单地保持在剪贴板中。对于拖放数据传输,数据对象必须创建代表数据的图标,并且在用户移动光标时相应移动图标。在拖动循环中,传输源通过数据对象的IDropSource接口获取其状态信息。更深入的讨论,见本文的Implementing IDropSource节。

如果目标从剪贴板取得数据对象,传输源不会收到通知。而对于拖放操作,数据对象被放置到目标中时,发起拖动循环的DoDragDrop函数调用会返回。 

2.1 如何向数据对象添加全局存储对象

很多Shell数据格式以全局存储对象的形式存在。创建包含全局存储对象的剪贴板格式,并载入到数据对象中的过程如下:

  1. 创建FORMATETC结构体,设置cfFormat成员为恰当的剪贴板格式值,tymed成员为TYMED_HGLOBAL
  2. 创建STGMEDIUM结构体,设置tymed成员为TYMED_HGLOBAL
  3. 调用GlobalAlloc分配适当大小的内存块,创建全局存储对象。
  4. 把要传输的数据块复制到从GlobalAlloc返回的地址开始的内存中。
  5. 把全局存储对象的地址赋给STGMEDIUM结构体的hGlobal成员
  6. 调用IDataObject::SetData把数据载入到数据对象中,传入上一步创建的FORMATETCSTGMEDIUM结构体。

下面的例子函数创建一个含有DWORD值的全局存储对象,并且载入到数据对象中。pdtobj参数是到数据对象IDataObject接口的指针,cf是剪贴板格式值,dw是数据值。 

<…… 省略示例代码 ……> 

2.2 实现IDataObject

IDataObject是数据对象的主要接口,所有数据对象都必须实现它。传输源和目标把IDataObject用于各种目的:

  • 把数据载入到数据对象中
  • 从数据对象中取出数据
  • 确定数据对象包含什么类型的数据
  • 为数据对象提供关于数据传输的反馈

IDataObject支持很多方法。本节讨论如何实现IDataObject的三个最重要方法:SetDataEnumFormatEtcGetData。关于IDataObject其他方法的讨论,见IDataObject的参考文档。 

2.2.1. SetData 方法

IDataObject::SetData方法的主要功能是让数据源把数据装载到数据对象中。对于要包含的每种格式,数据源会创建用以标识格式的FORMATETC结构体和用以存储数据指针的STGMEDIUM结构体,随后数据源会在调用IDataObject::SetData时会传入这两个结构体。IDataObject::SetData方法必须保存这两个结构体信息,这样传输目标调用IDataObject::GetData时才能从数据对象中取出数据。

然而,在传输文件的时候,Shell通常把要传输的每个文件的信息放到单独的CFSTR_FILECONTENTS格式的数据项中。为了区分不同的文件,每个文件的FORMATETC结构体的lIndex成员被设置为标识特定成员的索引值。IDataObject::SetData实现必须能够存储多个只是lIndex成员有所不同的CFSTR_FILECONTENTS格式的数据项。

光标位于目标窗口上时,目标可以使用拖放辅助对象来指定拖动图像。拖放辅助对象调用IDataObject::SetData把私有格式装载到用于跨进程支持的数据对象中。为支持拖放辅助对象,IDataObject::SetData实现必须能够接受和存储任何私有格式的数据项。

某些类型的Shell数据传输要求在放下数据后,目标调用IDataObject::SetData为数据对象提供关于放下操作的反馈。比如说,在优化的移动操作中,移动文件后,目标通常会删除原文件,但这不是必须的。目标调用IDataObject::SetData,传入CFSTR_LOGICALPERFORMEDDROPEFFECT(LogicalPerformedDropEffect)格式,从而通知数据对象它是否该删除了原文件。目标还使用其他一些Shell剪贴板格式来把信息传递给数据对象。IDataObject::SetData实现必须能够识别这些格式并分别作出不同响应。更深入的讨论,见Handling Shell Data Transfer Scenarios 

2.2.2. EnumFormatEtc方法

目标收到一个数据对象后,通常通过FORMATETC来确定对象包含什么格式的数据。IDataObject::EnumFormatEtc方法会创建OLE枚举对象并返回其IEnumFORMATETC接口指针,然后目标可以使用这个接口指针来枚举可用的格式。

枚举对象应该按照质量从高到低的次序枚举可用的格式。格式的相对质量高低由放置源决定。一般来说,质量最高的格式包含最丰富完整的数据。比如说,通常认为24位色图像比灰度级图像质量高。按照质量高低次序枚举格式的原因是:目标通常枚举可用格式,直到找到一个它所支持的格式,然后使用这个格式取出数据。为使目标能够取得最佳的可用格式,必须以质量高低次序来枚举可用格式。

用于Shell数据的枚举对象的实现通常跟用于其它类型数据的相同,除了一个例外。因为通常只为每种格式包含一个数据项,数据对象通常会枚举通过IDataObject::SetData传入的每种格式。然而,正如在SetData方法节讨论的那样,Shell数据对象可以包含多个CFSTR_FILECONTENTS格式的数据项。因为IDataObject::EnumFormatEtc的目的是让目标可以确定存在哪些类型的数据,所以没有必要枚举多个CFSTR_FILECONTENTS格式。如果需要知道数据对象包含多少个这种格式的数据项,可以从相关的CFSTR_FILEDESCRIPTOR格式数据项中获取。关于IDataObject::EnumFormatEtc的更深入讨论,见方法的参考文档。 

2.2.3. GetData 方法

目标调用IDataObject::GetData取出特定格式的数据。格式是用FORMATETC结构体指定的。方法会返回格式的STGMEDIUM结构体。

目标可以把FORMATETC结构体的tymed成员设置为TYMED_XXX值来指定要使用哪种数据传输机制。然而,目标也可以发出更通用的请求,让数据对象决定使用哪种数据传输机制。如果要让数据对象选择使用的数据传输机制,把tymed设置为支持的所有TYMED_XXX值。这样IDataObject::GetData会从这些数据传输机制中选择一种,返回其STGMEDIUM结构体。比如说,通常把tymed设置为TYMED_HGLOBAL | TYMED_ISTREAM | TYMED_ISTORAGE 来请求三种Shell数据传输机制中的任何一种。

注意:因为可以有多个CFSTR_FILECONTENTS格式的数据项,所以FORMATETC结构体的cfFormattymed成员是不足以指示IDataObject::GetData应该返回哪个STGMEDIUM结构体的。对于CFSTR_FILECONTENTS格式,IDataObject::GetData还应该检查FORMATETC结构体的lIndex成员,以保证返回正确的STGMEDIUM结构体。

数据对象中放置有CFSTR_INDRAGLOOP格式的数据,让目标可以检查拖动循环的状态,而避免密集地从存储器提取对象数据。这种格式的数据是一个DWORD值,非零表示数据对象在拖动循环中;数据被放下时,这个值会被设置为零。如果目标请求这种格式的数据,但源没有载入这种数据,IDataObject::GetData应该表现为已经载入值为零的这种数据。

光标位于目标窗口上时,目标可以使用拖放辅助对象来指定拖动图像。拖放辅助对象调用IDataObject::SetData把私有格式装载到用于跨进程支持的数据对象中。后续它会调用IDataObject::GetData来获取这种私有数据。为支持拖放辅助对象,Shell数据对象实现应该可以返回任何私有格式的数据项。 

2.3 实现IDropSource

拖放源必须创建展示IDropSource接口的对象。这个对象让拖放源可以更新用以指示当前光标位置的拖动图像,以及为系统提供如何终止拖放操作的反馈。IDropSource有两个方法:GiveFeedbackQueryContinueDrag 

2.3.1. GiveFeedback 方法

在拖动循环中,由放置源负责跟踪光标位置并显示合适的拖动图像。然而,在一些情况下,光标位于放置目标窗口上时,可能需要改变拖动图像。光标进入和离开目标窗口,以及滑过目标窗口时,系统会不时地调用放置目标的IDropTarget接口。目标会以DROPEFFECT值响应调用,这个值会通过GiveFeedback方法转发给拖动源。拖动源可以根据这个值来修改拖动图像的外观。更深入的讨论,见GiveFeedbackDoDragDrop方法的参考文档。 

2.3.2. QueryContinueDrag 方法

数据对象处于拖放循环中时,如果鼠标或者键盘状态发生改变,这个方法会被调用。方法会通知拖动源ESC键是否被按下,并且提供CTRL或者SHIFT等修饰键状态。方法的返回值指定下列三种动作之一:

  • S_OK: 继续拖动操作
  • DRAGDROP_S_DROP: 放下数据。系统会调用目标的IDropTarget::Drop方法。
  • DRAGDROP_S_CANCEL: 不放下数据而终止拖动循环。通常在按下ESC键时会返回这个值。

更深入的讨论,见GiveFeedbackDoDragDrop方法的参考文档。


菊子曰 这就是菊子曰啦!
原创粉丝点击