Drag & Drop 全解析
来源:互联网 发布:js 多维数组排序 编辑:程序博客网 时间:2024/05/22 12:46
一、基本概念
拖 放,是指用鼠标拖动的方法,在不同程序的窗口之间、同一个程序的不同窗口之间或同一程序同一窗口的不同控件之间,进行移动、复制和粘贴等操作的技术。拖放 操作是在操作系统的帮助下完成的。被拖动的对象首先向操作系统注册它使用的数据格式,并按指定的数据格式提供数据,拖放操作结束时,接收拖放的窗口按指定 的数据格式提取有关数据,并根据提取的数据生成相应的对象。
二、两种拖放方式
拖放有两种类型:OLE拖放和文件管理器拖放。这两种方式是 完全不同的机制。文件管理器拖放只能处理文件名,通过映射目的窗口的WM_DROPFILES消息,窗口就可以收到拖放进来的文件名。OLE拖放则更加通 用一些,它允许你拖放可同时被保存在剪贴板上的任何数据。本文首先介绍文件管理器拖放,然后再介绍OLE拖放,最后给出一个用OLE实现的,支持文件拖放 操作的增强列表控件CListCtrlEx。
三、文件管理器拖放原理及实例
这种方式的实质就是产生一个消息WM_DROPFILES。技术上没有什么难点,主要用到下面几个API函数:DragQueryFile、DragQueryPoint、DragFinish。它们的原型和注解分别如下:
UINT DragQueryFile(HDROP hDrop, UINT iFile, LPTSTR lpszFile, UINT cch)
本 函数用来取得拖放的文件名。其中,hDrop是一个指向含有被拖放的文件名的结构体的句柄;iFiles是要查询的文件序号,因为一次可能同时拖动很多个 文件;lpszFiles是出口缓冲区指针,保存iFiles指定序号的文件的路径名,cch指定该缓冲区的大小。有两点值得注意,第一,如果我们在调用 该函数的时候,指定iFile为0xFFFFFFFF,则DragQueryFile将忽略lpszFile和cch参数,返回本次拖放操作的文件数目; 第二,如果指定lpszFile为NULL,则函数将返回实际所需的缓冲区长度。
BOOL DragQueryPoint(HDROP hDrop, LPPOINT lppt);
本函数用来获取,当拖放操作正在进行时,鼠标指针的位置。第二个参数lppt是一个指向POINT结构体的指针,用来保存文件放下时,鼠标指针的位置。窗口可以调用该函数以查询文件是否落在自己的窗口矩形中。
void DragFinish(HDROP hDrop);
当拖放操作处理完毕后需调用该函数释放系统分配来传输文件名的内存。
首先,建立一个对话框工程,确保选中对话框的Accept Files属性。如果不选,也可以在窗口创建的时候(譬如OnCreate函数中)调用DragAcceptFiles(TRUE),效果是一样的。
然 后映射WM_DROPFILES消息。该消息处理函数原型如下:void OnDropFiles(HDROP hDrop),注意入口参数为HDROP型,它是一个结构体指针,所指向的结构体中包含了被拖放的文件的名称。接下来我们主要要完成两个动作:第一,通过 调用DragQueryFile并指定其iFile参数为0xFFFFFFFF,得到本次拖放操作的文件数目;第二步,用一个循环依次取出各个文件名。示 例如下:
void CListCtrlEx::OnDropFiles(HDROP hDrop)
{
UINT
}
四、OLE拖放原理
MFC为实现对象拖放提供了如下类:COleDataSource、COleDataObject、COleDropTarget、COleDropSource。下面分别介绍它们,然后通过一个实例讲述实现的具体步骤。
·
启动一次拖放操作,保存拖放的数据,并向系统提供拖放对象的数据。类中重要的成员函数根据用途分为如下三种:
1.用于设定提供数据的方式和使用的数据格式。
提 供数据的方式有两种,一种是即时方式,另一种是延迟方式。延迟方式不需要立即提供数据,当需要提供数据时,系统将调用对应的函数来获得数据,一般都是重载 OnRenderData函数或其他虚函数,以响应数据请求。数据格式可以是CF_TEXT等常用的剪贴板格式,也可以是自己利用函数 RegisterClipboardFormat函数注册的特定格式。
CacheData:提供指定格式的数据,格式由结构STGMEDIUM指定,即时方式;
CacheGlobalData:利用全局句柄HGLOBAL,为指定格式提供数据,即时方式,适用小数据量;
DelayRenderData:使用延迟方式按指定格式提供数据,当系统需要数据时,会调用函数OnRenderGlobalData/OnRenderData来取得数据;
DelayRenderFileData:使用延迟方式利用CFile为指定格式提供数据,当需要数据时,会调用函数OnRenderFileData来取得数据;
2.响应请求,提供数据
OnRenderFileData:为延迟方式提供CFile型数据。
OnRenderGlobalData:为延迟方式提供HGLOBAL数据。
OnRenerData:为延迟方式提供各种所支持的类型的数据。
3.实施拖放操作
DoDragDrop:开始实施拖放操作
·
用于代表拖放的数据,它是作为COleDataSource类的成员,类中主要成员函数有:
BeginEnumFormat:为枚举数据格式作准备;
GetNextFormat:返回下一个数据格式;
IsDataAvailable:检查指定的数据格式是否可用;
GetData:按指定数据格式,获得数据;
GetFileData:按指定数据格式,获得CFile型数据;
GetGlobalData:按指定数据格式,获得HGLOBAL型数据;
·
用于在窗口和OLE库之间提供通讯机制。任何一个窗口,要想能够接收拖放,必须包含一个COleDropTarget对象,并注册之。其中的成员函数可分为两大类:
1.注册
Register:注册该对象,以便使窗口能够接收拖放对象
2.响应拖放过程中的动作(虚函数)
OnDragEnter:当鼠标首次进入窗口时被调用;
OnDragLeave:当鼠标移出窗口时被调用;
OnDragOver:当鼠标停留在窗口内时,被重复调用;
OnDrop:当鼠标在窗口内落下被调用;
虚函数onDragEnter和OnDragOver的返回值具有重要的含义,一般为以下三种之一:
DROPEFFECT_MOVE:移动操作,允许对象落在此窗口,落下时要删除原来的对象;
DROPEFFECT_COPY:复制操作,允许对象落在此窗口,落下时不删除原来的对象;
DROPFFECT_NONE:不允许对象落在此窗口;
·
COleDropSource允许数据被拖放到一个拖放目标,它负责对何时启动一个拖放操作进行决断,反馈拖放操作状态,以及判断拖放操作何时结束。这个类比较简单,用得也较少。它的成员函数只有三个:
GiveFeedback:用于改变拖放期间鼠标的光标,把拖放状态反馈给用户知晓;
OnBeginDrag:在拖放期间捕捉鼠标指针,当应用程序框架觉得可能要发生一个拖放操作时,它会调用该函数;
QueryContinueDrag:检测拖放操作是否还在继续中。
五、OLE拖放实现
MFC 本身的CView类是支持拖放操作的,通过研究CView类的源码,大体知道它的实现原理是这样的:CView类中有一个COleDropTarget类 的对象,在视图窗口初始化时,调用COleDropTarget类成员函数Register(),以此在系统中注册该视图窗口为拖放接收窗口。当进行拖放 操作的鼠标指针处于视图窗口范围内时,COleDropTarge类会做出反应,它的OnDragEnter、OnDragOver、OnDropEx、 OnDrop等成员函数被依次调用,这些函数默认均是调用与其相对应的CView类成员函数OnDragEnter、OnDragOver、 OnDropEx、OnDrop等,程序员只需重载这些CView类成员函数,即可对拖动的过程及结果进行控制。
因为 COleDropTarget默认只对CView提供支持,所以如果要让其他的窗口支持拖放,我们必须同时对要支持拖放的窗口类和 COleDropTarget类进行派生。把对拖放操作具体进行处理的代码封装成派生窗口类的成员函数,然后重载COleDropTarget中对应的五 个虚函数,当它接收到拖放动作时,调用窗口派生类的处理函数即可。但这里有一个问题,就是我们怎么知道何时调用派生类的处理函数呢?答案是运用RTTI技 术。如果COleDropTarget派生类收到的窗口指针类型,就是我们派生的窗口类,那么就调用它的处理函数,否则调用基类进行处理。
首先生成一个对话框工程,添加二个新类。
第 一个类名为CListCtrlEx,父类为CListCtrl。添加完毕后,在CListCtrlEx的定义头文件中加入 DECLARE_DYNAMIC(CListCtrlEx),在其实现文件中加入 IMPLEMENT_DYNAMIC(CListCtrlEx,CListCtrl),这样就对CListCtrlEx类添加了RTTI运行期类型识别 (Run Time Type Information)支持。
第二个类名为COleDropTargetEx,父类为COleDataTarget。
在CListCtrlEx中添加COleDropTargetEx类的对象,并添加下列公有虚函数的声明:
Initialize函数用于注册CListCtrlEx成为拖放接收窗口;
OnDragOver 在拖放鼠标进入窗口时被调用。此函数的返回值决定了后续的动作的类型:如果返回DROPEFFECT_MOVE,则产生一个剪切动作;如果返回 DROPEFFECT_COPY,则产生一个复制动作,如果返回DROPEFFECT_NONE,则不会产生拖放动作,因为OnDropEx、 OnDrop函数将不会被调用(OnDragLeave函数仍会被调用)。
OnDropEx函数会在OnDrop函数之前调用,如果 OnDropEx函数没有对拖放动作进行处理,则应用程序框架会接着调用OnDrop函数进行处理。所以必须要在派生类中重载OnDropEx函数——即 使什么动作都都没有做——否则我们的OnDrop函数将不会被执行到,因为没有重载的话,将会调用基类的OnDropEx函数,而基类的OnDropEx 函数对拖放是进行了处理的——尽管不是我们所想要的动作。当然你也可以把对拖放进行处理的动作放在OnDropEx中——那样就不需要重载OnDrop 了。
OnDragLeave函数会在鼠标离开窗口时被调用,在此可以进行一些简单的清理工作。譬如在OnDragEnter或者OnDragOver函数中,我们改变了光标的形态,那么此时我们就应该把光标恢复过来。
这些函数中最重要的是OnDrop函数,拖放动作将在此进行处理,它的全部源码如下:
BOOL CListCtrlEx::OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point)
{
char chTemp[_MAX_PATH+1] = {0};