本文转自:http://blog.csdn.net/codewarrior/archive/2004/06/15/12040.aspx
原文分为(一)、(二),此处将它们和为一篇,方便大家阅读
一、基本概念
拖放,是指用鼠标拖动的方法,在不同程序的窗口之间、同一个程序的不同窗口之间或同一程序同一窗口的不同控件之间,进行移动、复制和粘贴等操作的技术。拖放操作是在操作系统的帮助下完成的。被拖动的对象首先向操作系统注册它使用的数据格式,并按指定的数据格式提供数据,拖放操作结束时,接收拖放的窗口按指定的数据格式提取有关数据,并根据提取的数据生成相应的对象。
二、两种拖放方式
拖放有两种类型: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)
{
char szFilePathName[_MAX_PATH+1] = {0};
UINT nNumOfFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0); //得到文件个数
for (UINT nIndex=0 ; nIndex< nFileCount; ++nIndex)
{
DragQueryFile(hDrop, nIndex, szFilePathName, _MAX_PATH); //得到文件名
}
DragFinish(hDrop);
}
四、OLE拖放原理
MFC为实现对象拖放提供了如下类:COleDataSource、COleDataObject、COleDropTarget、COleDropSource。下面分别介绍它们,然后通过一个实例讲述实现的具体步骤。
· COleDataSource
启动一次拖放操作,保存拖放的数据,并向系统提供拖放对象的数据。类中重要的成员函数根据用途分为如下三种:
1. 用于设定提供数据的方式和使用的数据格式。
提供数据的方式有两种,一种是即时方式,另一种是延迟方式。延迟方式不需要立即提供数据,当需要提供数据时,系统将调用对应的函数来获得数据,一般都是重载OnRenderData函数或其他虚函数,以响应数据请求。数据格式可以是CF_TEXT等常用的剪贴板格式,也可以是自己利用函数RegisterClipboardFormat函数注册的特定格式。
CacheData:提供指定格式的数据,格式由结构STGMEDIUM指定,即时方式;
CacheGlobalData:利用全局句柄HGLOBAL,为指定格式提供数据,即时方式,适用小数据量;
DelayRenderData:使用延迟方式按指定格式提供数据,当系统需要数据时,会调用函数OnRenderGlobalData/OnRenderData来取得数据;
DelayRenderFileData:使用延迟方式利用CFile为指定格式提供数据,当需要数据时,会调用函数OnRenderFileData来取得数据;
2. 响应请求,提供数据
OnRenderFileData:为延迟方式提供CFile型数据。
OnRenderGlobalData:为延迟方式提供HGLOBAL数据。
OnRenerData:为延迟方式提供各种所支持的类型的数据。
3. 实施拖放操作
DoDragDrop:开始实施拖放操作
· COleDataObject
用于代表拖放的数据,它是作为COleDataSource类的成员,类中主要成员函数有:
BeginEnumFormat:为枚举数据格式作准备;
GetNextFormat:返回下一个数据格式;
IsDataAvailable: 检查指定的数据格式是否可用;
GetData:按指定数据格式,获得数据;
GetFileData:按指定数据格式,获得CFile型数据;
GetGlobalData:按指定数据格式,获得HGLOBAL型数据;
· COleDropTarget
用于在窗口和OLE库之间提供通讯机制。任何一个窗口,要想能够接收拖放,必须包含一个COleDropTarget对象,并注册之。其中的成员函数可分为两大类:
1. 注册
Register:注册该对象,以便使窗口能够接收拖放对象
2. 响应拖放过程中的动作(虚函数)
OnDragEnter:当鼠标首次进入窗口时被调用;
OnDragLeave:当鼠标移出窗口时被调用;
OnDragOver:当鼠标停留在窗口内时,被重复调用;
OnDrop: 当鼠标在窗口内落下被调用;
虚函数onDragEnter和OnDragOver的返回值具有重要的含义,一般为以下三种之一:
DROPEFFECT_MOVE:移动操作,允许对象落在此窗口,落下时要删除原来的对象;
DROPEFFECT_COPY:复制操作,允许对象落在此窗口,落下时不删除原来的对象;
DROPFFECT_NONE:不允许对象落在此窗口;
· COleDropSource
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类的对象,并添加下列公有虚函数的声明:
virtual BOOL Initialize();
virtual DROPEFFECT OnDragOver(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point);
virtual DROPEFFECT OnDropEx(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, DROPEFFECT dropList, CPoint point);
virtual BOOL OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point);
virtual void OnDragLeave(CWnd* pWnd);
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)
{
UINT nFileCount = 0;
HDROP hDropFiles = NULL;
HGLOBAL hMemData = NULL;
AfxMessageBox("OnDrop");
if(pDataObject->IsDataAvailable(CF_HDROP))
{
hMemData = pDataObject->GetGlobalData(CF_HDROP);
hDropFiles = (HDROP)GlobalLock((HGLOBAL)hMemData); //锁定内存块
if(hDropFiles != NULL)
{
char chTemp[_MAX_PATH+1] = {0};
nFileCount = DragQueryFile(hDropFiles, 0xFFFFFFFF, NULL, 0);
for(UINT nCur=0; nCur<nFileCount; ++nCur) //遍历取得每个文件名
{
ZeroMemory(chTemp, _MAX_PATH+1);
DragQueryFile(hDropFiles, nCur, (LPTSTR)chTemp, _MAX_PATH+1);
AddAllFiles(chTemp);
}
}
GlobalUnlock(hMemData);
return TRUE;
}
else
{
return FALSE;
}
}
在第二个类COleDropTarget中添加如下对应的函数:
virtual DROPEFFECT OnDragEnter(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point);
virtual DROPEFFECT OnDragOver(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point);
virtual DROPEFFECT OnDropEx(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, DROPEFFECT dropList, CPoint point);
virtual BOOL OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point);
virtual void OnDragLeave(CWnd* pWnd);
它们的动作都差不多:先用RTTI判断窗口指针pWnd的类型,如果是CListCtrlEx,则调用CListCtrlEx中对应的处理函数,否则调用基类的处理函数。以OnDrop为例:
BOOL COleDropTargetEx::OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point)
{
ASSERT_VALID(this);
ASSERT(IsWindow(pWnd->m_hWnd));
CListCtrlEx* pListCtrlEx = NULL;
if(pWnd->IsKindOf(RUNTIME_CLASS(CListCtrlEx)))
{
pListCtrlEx = (CListCtrlEx*)pWnd;
return pListCtrlEx->OnDrop(pWnd, pDataObject, dropEffect, point);
}
else
{
return COleDropTarget::OnDrop(pWnd, pDataObject, dropEffect, point);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· [AI/GPT/综述] AI Agent的设计模式综述