Window下拖放操作Drag & Drop 全解析 (整理)

Window下拖放操作Drag & Drop 全解析 (整理)  

2011-01-16 23:57:18|  分类: C/C++/C# |字号 订阅

http://hill2007.blog.163.com/blog/static/3872384620084863345840/

http://www.cppblog.com/andxie99/archive/2007/03/05/19228.html

http://www.vckbase.com/document/viewdoc/?id=258

http://blog.csdn.net/vcbear/archive/2002/01/25/5990.aspx

http://www.codeproject.com/KB/shell/NSExtDragDrop.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< ++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)

{

       CListCtrlEx*     pListCtrlEx = NULL;

      

       ASSERT_VALID(this);

       ASSERT(IsWindow(pWnd->m_hWnd));

      

       if(pWnd->IsKindOf(RUNTIME_CLASS(CListCtrlEx)))

       {

              pListCtrlEx = (CListCtrlEx*)pWnd;

              return pListCtrlEx->OnDrop(pWnd, pDataObject, dropEffect, point);

       }

       else

       {

              return COleDropTarget::OnDrop(pWnd, pDataObject, dropEffect, point);    

       }

}

 

//倒霉的64K限制,只能再截断了:(

至此,我们成功地为CListCtrlEx添加了文件拖入操作的支持。一个完整的拖放操作,还包括拖出动作,所以必须要为该类再添加拖出操作,即,将列表中的某一项或者多项拖出成为一个文件。这就需要用到另一个类:COleDataSource。具体步骤如下:

在CListCtrlEx中加入一个COleDataSource的实例,并映射列表框的LVN_BEGINDRAG消息处理函数,在此我们添加拖出操作的代码。

实现拖出非常简单,只需要依次调用COleDataSource的三个函数即可:Empty用于清空原先对象中缓存的数据,CacheGlobalData用来缓存数据以进行拖放操作,最后调用DoDragDrop启动本次拖放操作。

但在调用之前,必须要做一些准备工作。主要的任务就是创建一个DROPFILES结构体,并拷贝要拖放的文件名到结构体后的内存中。DROPFILES结构体定义了CF_HDROP剪贴板格式,紧跟它后面的是一系列被拖放文件的路径名。它的定义如下:

typedef struct _DROPFILES

{

    DWORD     pFiles;  //文件名起始地址

    POINT      pt;     //鼠标放下的位置,坐标由fNC成员指定

    BOOL        fNC;    //为TRUE表示适用屏幕坐标系,否则使用客户坐标系

    BOOL        fWide;  //文件名字符串是否使用宽字符

} DROPFILES, FAR* LPDROPFILES;

拖放之前的准备动作的代码如下:

uBufferSize = sizeof(DROPFILES) + uBufferSize + 1;

    hMemData = GlobalAlloc(GPTR,uBufferSize);

    ASSERT(hMemData != NULL);

      

       lpDropFiles = (LPDROPFILES)GlobalLock(hMemData); //锁定之,并设置相关成员

       ASSERT(lpDropFiles != NULL);

       lpDropFiles->pFiles = sizeof(DROPFILES);

#ifdef _UNICODE

       lpDropFiles->fWide = TRUE;

#else

       lpDropFiles->fWide = FALSE;

#endif

 

       //把选中的所有文件名依次复制到DROPFILES结构体后面(全局内存中)

       pItemPos = strSelectedList.GetHeadPosition();

       pszStart = (char*)((LPBYTE)lpDropFiles + sizeof(DROPFILES));

       while(pItemPos != NULL)

       {

              lstrcpy(pszStart, (LPCTSTR)strSelectedList.GetNext(pItemPos));

        pszStart = strchr(pszStart,'\0') + 1; //下次的起始位置是上一次结尾+1

       }

准备完毕之后就可以进行拖放了,拖放动作有DoDragDrop函数触发,其原型如下:

DROPEFFECT DoDragDrop(

DWORD dwEffects = DROPEFFECT_COPY|DROPEFFECT_MOVE|DROPEFFECT_LINK, LPCRECT lpRectStartDrag = NULL,

COleDropSource* pDropSource = NULL

);

这里,dwEffects指定了允许施加于本COleDataSource实例之上的动作集:剪切、复制或无动作。

    lpRectStartDrag指示拖放操作真正开始的矩形,如果鼠标没有移出该矩形,则拖放操作视作放弃处理。如果本成员设为NULL,则该起始矩形将为一个像素大小。

    pDropSource表明拖放所使用的COleDataSource对象。

而该函数的返回值,则表明本次拖放操作所实际产生的效果,至于具体产生何种效果,则由系统决定。譬如在拖放时按住Shift键,将产生剪切效果;按住Ctrl键,将产生复制效果,等等。

拖放的代码如下:

       m_oleDataSource.Empty();

       m_oleDataSource.CacheGlobalData(CF_HDROP, hMemData);

       DropResult = m_oleDataSource.DoDragDrop(DROPEFFECT_MOVE|DROPEFFECT_COPY);

最后一点要注意的是,在Windows NT 4.0以上的系统中,即使实际产生的是DROPEFFECT_MOVE动作,DoDragDrop函数也只返回DROPEFFECT_NONE。产生这个问题的原因在于,Windows NT 4.0的Shell会直接移动文件本身来对移动操作进行优化。返回值DROPEFFECT_MOVE最初的含义,就是通知执行拖放操作的应用程序去删除原位置上的文件。但是因为Shell已经替应用程序完成了这个(删除)动作,所以,函数返回DROPEFFECT_NONE。要想知道文件是否真的被移动了也很简单,只要在函数返回之后检查一下原位置上的文件是否存在就可以了。

Windows 9x系列的操作系统也会对移动进行同样的优化动作,但是它不会返回DROPEFFECT_NONE来代替DROPEFFECT_MOVE。详细的解释参见MS知识库Q182219。

                扩展COleDropTarget类来支持任意窗口拖放

      本文详细论述了拖放的基本原理并提供一个支持窗口拖放的普遍类COleDropTargetEx,任何需要提供拖放的窗口包含此类后,将响应拖放消息函数加入,便可接收拖放。此类利用Windows消息来支持拖放,机制不同于COleDropTarget类与CView类那种直接的搭配关系,因此较以往的拖放方法简便、灵活,代码重用性非常好。
一、拖放原理及MFC类库对拖放的支持
    拖放(Drag and Drop)是OLE的一部分,是指对某一指定的对象,利用鼠标拖动的方法,在不同应用的窗口之间、同一应用的不同窗口之间或同一应用的同一窗口内进行移动、复制(粘贴)等操作的技术。
      MFC为实现对象拖放提供了如下类:COleDataSource、COleDropSource、COleDropTarget、COleDataObject。对于上述几个类的用法,读者可参考有关资料。因拖放操作中的启动拖放部分,实现较模式化,很多文献都有详细的实现,本文在此不再赘述。这里着重说明MFC拖放操作中接收部分的实现原理,以使读者了解为何要扩展MFC拖放类的功能。
MFC通过提供COleDropTarget和CView类来支持拖放操作的接收。在CView及其继承类中创建COleDropTarget类对象,并在视图窗口初始化时,调用其成员函数Register(),以此在系统中注册该视图窗口为拖放接收窗口。当进行拖放操作的鼠标指针处于视图窗口范围内时,COleDropTarge类的OnDragEnter、OnDragOver、OnDropEx、OnDrop等成员函数被依次调用,这些函数默认调用与其相对应的CView类成员函数OnDragEnter、OnDragOver、OnDropEx、OnDrop等,在这些CView类成员函数中,用户可对拖动的过程及结果进行控制。但MFC这种内在的对拖放的实现是很不够的,一个用户界面友好的应用程序,很多时候要求不仅视图窗口支持拖放,而且对话框以及编辑框等控制窗口也需要拖放的支持。虽然现在已经有一些拖放目标类的扩展类了,但通常都是需要按照CView类对拖放的实现模式来实现这些窗口类,再在COleDropTarge继承类的响应拖放函数中利用RTTI(Run-time type information)来调用这些特定的窗口类拖放响应函数。而利用RTTI机制不可避免地会使拖放类只支持特定的窗口类,如果要增加新的可支持拖放的窗口类,则必须改写已实现的COleDropTarge继承类,这种情况是我们所不愿看到的。如果直接在COleDropTarge继承类中处理拖放呢?显然,只会造就一个针对性更强的类。针对这种情况,需要运用其它方式来支持无类型要求的窗口类拖放操作。

二、拖放扩展类运行原理
    我们知道,使用消息是可以传递信息的。对于复杂的结构,可以通过在消息参数中传递结构的指针来传递结构信息。因此,在COleDropTarge扩展类响应拖放成员函数中,将拖放信息打包,存入拖放信息类中,向注册了拖放功能的控制窗口发送用户自定义消息,并传递拖放信息类地址。在窗口类中,映射该自定义消息到消息响应函数中,在该函数中,将消息参数转换回拖放信息类指针,以此来获得拖放信息,进行相应操作后,返回对拖放操作的控制信息。这样,经过将MFC对拖放操作的函数调用转化为类型无关的对窗口的消息发送,扩展的拖放目标类便具有了与拖放窗口类无关的特性,而且这种消息机制符合程序编写习惯。

三、实例实现
    下面我们通过一个程序实例来说明该扩展类的使用。在这个实例中演示几个通过嵌入COleDropTargetEx类方便的获得了拖放能力的控件,这里只介绍支持拖放的编辑控件的编程过程,其它控件过程类似。
利用AppWizard新建一工程"DropExDemo",选择基于对话框的应用,其它可取默认值。
将本文附录中拖放目标扩展类COleDropTargetEx的头文件与实现文件添加到工程中。
为了实现支持拖动的编辑框控制,从CEdit继承一新类CDropEdit。
添加如下成员变量与成员函数到CDropEdit类声明中并在文件首包含COleDropTargetEx类的头文件。

public:

virtual BOOL Register();

  // Generated message map functions

protected:

        COleDropTargetEx m_dropEx;virtual BOOL OnDrop(WPARAM pDropInfoClass, LPARAM lParm);

        virtual DROPEFFECT OnDropEx(WPARAM pDropInfoClass, LPARAM lParm);

        virtual DROPEFFECT OnDragOver(WPARAM pDropInfoClass,LPARAM lParm);在实现文件中添加消息映射如下:

        BEGIN_MESSAGE_MAP(CDropEdit, CEdit)

        //{{AFX_MSG_MAP(CDropEdit)

        // NOTE - the ClassWizard will add and remove mapping macros here.

        //}}AFX_MSG_MAP

        ON_MESSAGE(DROPM_DRAGOVER,OnDragOver)

        ON_MESSAGE(DROPM_DROPEX,OnDropEx)

        ON_MESSAGE(DROPM_DROP,OnDrop)

        END_MESSAGE_MAP()

在实现文件中消息响应函数定义如下:

/

// CDropEdit message handlers

BOOL CDropEdit::Register()

{

    return m_dropEx.Register( this );

}

DROPEFFECT CDropEdit::OnDragOver(WPARAM pDropInfoClass, LPARAM lParm)

{

        COleDropInfo* pInfo = (COleDropInfo* )pDropInfoClass;

        ASSERT(pInfo->IsKindOf(RUNTIME_CLASS(COleDropInfo)));

              if( pInfo->pDataObject->IsDataAvailable( CF_TEXT ) )

            return DROPEFFECT_COPY;

        else

            return DROPEFFECT_NONE;

}

DROPEFFECT CDropEdit::OnDropEx(WPARAM pDropInfoClass, LPARAM lParm)

{

        return (DROPEFFECT)-1;

}

BOOL CDropEdit::OnDrop(WPARAM pDropInfoClass, LPARAM lParm)

{

        COleDropInfo* pInfo = (COleDropInfo* )pDropInfoClass;

        ASSERT(pInfo->IsKindOf(RUNTIME_CLASS(COleDropInfo)));

        if( pInfo->pDataObject->IsDataAvailable( CF_TEXT ) )//拖动对象为文本

        {

        HGLOBAL hMem = pInfo->pDataObject->GetGlobalData( CF_TEXT );

        char* lp = (char *)GlobalLock((HGLOBAL) hMem);//lock source

        if ( lp != NULL)

        {

        //Set Windows title with Drop text

        SetWindowText( lp );

        }

        GlobalUnlock( hMem );//unlock source

        return TRUE;

        }

        else

        return FALSE;

}

现在,一个支持拖放的编辑框就做好了。那么把它放到对话框中测试一下。 添加一个编辑控制m_dropEdit到对话框模板中,使用ClassWizard声明该编辑控制为CDropEdit类型。在对话框类的OnInitDialog()函数中注册该编辑对象为拖放目标窗口。

BOOL CDropExDemoDlg::OnInitDialog()

{

        //其它内容

        if( !m_dropEdit.Register() )

        TRACE("register drop edit faile");

        //其它内容

}

最后,别忘了初始化OLE。在应用程序类初始化函数中添加OLE初始化代码。

BOOL CDropExDemoApp::InitInstance()

{

        //其它内容

        if( !AfxOleInit() )

        TRACE("Ole init faile");

        //其它内容

}

现在,编译并运行程序。从VC ++ 6.0编辑窗口中选择一词,用鼠标拖动其到编辑控制上并释放鼠标左键,编辑控制中内容变为拖动来的文本。至此,测试成功完成。

四、总结
    通过扩展MFC的拖放目标类,做到了拖放操作与窗口类类型无关,相应的拖放目标窗口只需要响应COleDropTargetEx发送的拖动消息便可方便的响应拖动事件,而且扩展的拖放目标类与支持拖放的窗口类皆可再被继承,因此代码的重用性较好。

参考文献:
1、 Bruce Eckel,Thinking in C++,机械工业出版社,2001.01
2、 MFC Library Reference,Microsoft Press,北京希望电脑公司,1999.02
3、 Wang Zemin,利用MFC实现对象拖放,互联网资料

 

我的经验总结

1.程序要想支持拖放要在应用程序初始化的时候加上 AfxOleInit(),即初始化Ole。

2.COleDropTarget类等要包含在中。

3.OnDragOver()默认的返回值是DROPEFFECT_NONE,要支持拖放要将返回值改成          DROPEFFECT_COPY等。

4.要使控件支持托出功能可以重载控件的BEGINDRAG消息。

5.一个为拖出准备数据的类及用法如下:

//头文件

#include "afxole.h"

class CMyDataSource : public COleDataSource  
{
public:
 CMyDataSource();
 virtual ~CMyDataSource();
public:
    COLORREF color;
    CString str;
protected:
    virtual BOOL OnRenderFileData(LPFORMATETC,CFile*);


};

//实现文件

CMyDataSource::CMyDataSource()
{

}

CMyDataSource::~CMyDataSource()
{

}

 

BOOL CMyDataSource::OnRenderFileData(LPFORMATETC lpFormatEtc,CFile* pFile)
{
 if(lpFormatEtc->cfFormat==CF_TEXT)
 {
  pFile->Write(str,13); //Magic String
  pFile->Write(&color,sizeof(COLORREF));
  int len= str.GetLength();
  pFile->Write(&len,sizeof(int));
  pFile->Write(str,len);
  return TRUE;
 }
 COleDataSource::OnRenderFileData(lpFormatEtc,pFile);
 return FALSE;
}

//用法是

在准备拖放的消息,如控件的BEGINDRAG,或者ON_LEFTBUTTONDOWN等消息的函数中写如下代码:

 CMyDataSource* pItemDragDrop=new CMyDataSource; 
 pItemDragDrop->str="Drag String"; //拖动的字符串
 pItemDragDrop->DelayRenderFileData(CF_TEXT,NULL);
 pItemDragDrop->DoDragDrop(DROPEFFECT_COPY);

6.例子工程,视图间拖放例子,以及控件拖放例子。两个工程保存在网盘“编程成功例子”->“拖放例子”中。


你可能感兴趣的:(编程杂烩)