OLE Drag&Drop | OLE 数据传送(Data Transfer)

第一部分:OLE Drag&Drop 介绍  

1.基本概念


    拖放,是指用鼠标拖动的方法,在不同程序的窗口之间、同一程序的不同窗口之间或同一程序同一窗体的不同控件之间,进行移动、复制、粘贴数据等操作的技术。拖放操作是依靠操作系统来完成的,被拖动的对象首先向操作系统注册一种它所使用数据的格式,并且按照这种格式来提供数据,拖放结束时,目标窗口提取出数据,并根据提取的数据生成相应的对象。

    拖放方式有两种,一种是OLE拖放(这个比较复杂)和文件管理器拖放。它们利用了不同的机制。

2.文件管理器拖放


    只能处理文件名,比如,你可以在资源管理器中选择一个或多个文件,拖到应用程序上面(这个程序的dwExStyle要加上WS_EX_ACCEPTFILES),那么这个窗体就会收到一个WM_DROPFILES消息。它主要用到下面几个API函数:DragQueryFile、DragQueryPoint、DragFinish。函数原型如下:

a)UINT DragQueryFile(HDROP hDrop, UINT iFile, LPTSTR lpszFile, UINT cch)

b)BOOL DragQueryPoint(HDROP hDrop, LPPOINT lppt)

c)void DragFinish(HDROP hDrop)


 一般用法如下:


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);

}

    由于这里不重点讲这种方式的拖放,所以具体的用法讲参考MSDN,上面都有详细说明。

3.OLE拖放原理

    拖放是用来描述用鼠标装数据从一个地方传输到另一个地方,OLE拖放比文件管理拖放更加通用得多,能在不同的应用程序中交互任务类型的数据(先要向系统注册该类型)。每一个拖放操作都须包含三个元素,当然,这些都是COM对象,需要我们去实现这些接口。

    1)IDropSource,表示拖放操作的源,它包含了拖放目标要使用的数据(IDataObject),它能在拖放的过程生成一些可视化的反馈,如设置鼠标样式等。
    2)IDropTarget,表示拖放操作的目标,它决定拖放的效果、接收任何合法数据、给拖放源一些反馈等。
    3)IDataObject,表示拖放源与目标之间传输的数据。

    注意了,一个应用程序不需要支持所有的COM接口。如果你的程序想做为一个拖放目标,那么你就实现IDropTarget接口,同样的,如果你需要支持作为数据源的程序,那么就应该实现IDropSource和IDataObject接口。当然,程序也可以同时实现这三个接口,从而可以在同一程序中支持拖放操作。

上面的图描述了拖放操作中所需要的关键组件。左边的表示拖放操作的源,它已经包含了两个对象,一个是IDropSource,另一个是IDataObject,最终是通过API函数DoDragDrop来发现拖放操作。

    右边描述了拖放操作的目标,它需要实现IDropTarget接口。这个目标中,它能接收IDataObject对象。当鼠标拖动到目标窗体时,OLE传递一个IDataObject接口到目标对象上面,这是源传给目标的数据,它不能以任何方式得到一个副本。这目标这边它可以根据IDataObject得到该数据格式,从而判断这种格式是不是目标能识别的,如果不能识别,就不接收。它本质上需要调用RegisterDragDrop来把当前目标窗体注册成一个拖放目标。

    注意,上面的图所示,源和目标可以是同一进程,也可以是不同进程。在使用之前,需要调用COM和OLE的初始化,使用完后,再调用其反初始化。
 

    WINOLEAPI OleInitialize(LPVOID pvReserved);
    WINOLEAPI OleUninitialize();


 

4.开始拖放

    上面也提到了,DoDragDrop函数,它用来开始拖放操作,原型如下:

WINOLEAPI DoDragDrop(

IDataObject * pDataObject, // Pointer to the data object

IDropSource * pDropSource, // Pointer to the source

DWORD dwOKEffect, // Effects allowed by the source

DWORD * pdwEffect // Pointer to effects on the source

);

    在调用这个方法时,就要把你实现的源(IDragSource)和数据(IDataObject)传给他,至于如何创建源和数据的对象,在后面介绍。
    当调用DoDragDrop时,它会进入一个模态的消息循环,用来监视鼠标和简单的消息,它是会阻塞住的。

5.注册目标

    要想一个窗体成为一个拖放的接收方,它必须调用RegisterDragDrop函数来注册。其原型如下:

WINOLEAPI RegisterDragDrop(

HWND hwnd, // Handle to a window that can accept drops

IDropTarget * pDropTarget // Pointer to object that is to be target of drop

);


    它的第一个参数就是目标窗体的句柄。
    与它作用相反的函数是RevokeDragDrop函数,释放注册时用到的IDropTarget接口对象。这个函数在窗体销毁时应该调用。

WINOLEAPI RevokeDragDrop(

HWND hwnd // Handle to a window that can accept drops

);

第二部分:OLE 数据传送(Data Transfer)

    这一部分讲一讲OLE数据传输相关知识,多数来自于MSDN和网上其同志们的观点,加以总结而成。
    COM接口提供了一种用于在不同的应用程序中交换数据的机制,这就是我们要讲的数据对象,对应的COM接口就是IDataObject。   
    大多数平台,包括Windows,都定义了一个用于在应用程序之间传输数据的标准协议,基于一系列的剪切板的函数。应用程序使用这些函数可以共享数据,即使它们的数据格式相差甚远,一般说来,剪切板有两个缺点:
    1,不能指定目标设备,不灵活。
    2,如果传输的数据很大,就有可能用到虚拟内存,效率不高。

1.OLE数据描述

    一般情况下,数据传输会用到两种方式,一种是剪切板,另一种是DragDrop。不管用哪种方式,都要用到IDataObject这个COM接口,在关注IDataObject之前,我们必须要了解数据传输所要用到一些辅助结构,FORMATETC和STGMEDIUM,它们用来描述OLE数据的格式和存储等信息。
 
    FORMATETC结构体,用来表示IDataObject提供或接收的数据类型,是标准Windows粘贴格式(CF_TEXT)的扩展,它包含了数据格式外,还有一些其他信息,看看它的定义:

typedef struct tagFORMATETC

{

CLIPFORMAT cfFormat; // 数据格式,如CF_TEXT

DVTARGETDEVICE *ptd; // 一般为NULL,目标设备

DWORD dwAspect; // DV_CONTENT rendering详细信息

LONG lindex; // 一般为-1

DWORD tymed; // 用于数据传输的存储媒体(HGLOBAL,IStream)

} FORMATETC, *LPFORMATETC;


    FORMATETC结构的成员描述如下:
    cfFormat:数据格式,可以是系统定义的(CF_TEXT、CF_BITMAP等),也可以是用RegisterClipboardFormat注册的自定义格式。
    ptd:一般为NULL,提供已经rendered数据的设备信息,正常的粘贴板操作和拖放都是NULL。
    dwAspect:描述数据的细节信息,有DV_CONTENT、DVASPECT_THUMBNAIL等。
    lindex:最常用的值是-1。
    tymed:描述用于存储数据的存储媒体,TYMED_XXX等值。
    具体的描述,大家可以参考MSDN,它上面讲得比较详细。

2.OLE数据存储

    结构体STGMEDIUM(STORAGE MEDIUM的缩写)提供一个用来存储数据的容器,因此叫存储媒体:

typedef struct

{

    DWORD tymed;                               // TYEMD_HGLOBAL、TYPED_ISTREAM等。

    union

    {

        HBITMAP        hBitmap;

        HMETAFILEPICT  hMetaFilePict;

        HENHMETAFILE   hEnhMetaFile;

        HGLOBAL        hGlobal;

        LPWSTR         lpszFileName;

        IStream        *pstm;

        IStorage       *pstg;

    };

    IUnknown *pUnkForRelease;

} STGMEDIUM;
 

3.IDataObject成员

    下面看一看IDataObject接口的成员方法:
  

    GetData:Render在FORMATETC结构体中描述的数据,并通过STGMEDIUM结构体来传递数据。
    GetDataHere:同上一方法相似,只是STGMEDIUM结构的内在是由调用者分配的。
    QueryGetData:决定数据对象是否能够render在FORMATETC结构中描述的数据。
    GetCanonicalFormatEtc:提供一下潜在不同的但逻辑上相同的FORMATETC结构。
    SetData:提供一个用FORMATETC结构和STGMEDIUM结构描述的数据源对象。
    EnumFormatEtc:创建并返回一个IEnumFORMATETC接口的指针来枚举数据对象支持的FORMATETC。
    DAdvise:创建一个在数据对象和通知接收器之间的连接,因此通知接收器收到数据对象中通知的改变。
    DUnadvise:销毁一个前面使用DAdvise方法安装的通知。
    EnumDAdvise:创建和返回一个指向枚举当前通知的接口指针。

    上面的接口,不用每个方法都要实现,只实现几个重要的,如GetData、SetData等。
   

4.用IDataObject来访问剪切板(Clipboard)

    先看看简单的访问剪切板的代码,很了一下IDataObject如何使用。
    代码如下:

void TestGetDataFromClipboard()

{

    OleInitialize(NULL);

    IDataObject *pDataObject = NULL;

    HRESULT hr = OleGetClipboard(&pDataObject);

    if (SUCCEEDED(hr))

    {

        FORMATETC fmtetc = { CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };

        STGMEDIUM stgmed;

        hr = pDataObject->GetData(&fmtetc, &stgmed);

        if (SUCCEEDED(hr))

        {

            PVOID pData = GlobalLock(stgmed.hGlobal);

            CHAR *pText = (CHAR*)pData;

            MessageBoxA(NULL, pText, "GetDataFromClipboard", 0);



            GlobalUnlock(stgmed.hGlobal);

            ReleaseStgMedium(&stgmed);

        }

        SAFE_RELEASE(pDataObject);

    }

    OleUninitialize();

}
    上面的方法演示了如何从剪切板里面取得字符串数据,最本质的方法还是通过IDataObject::GetData方法来取得数据。取数据的类型在FORMATETC结构体里面指定。

    注意,对于CF_TEXT,根据MSDN上面的解释,字符串必须是ANSI,所以要把通过GlobalLock得到的指针(PVOID)转换成CHAR*,最后调用ReleaseStgMedium来释放程序分配的内存。
    好了,到这里,应该明白数据传输格式及数据存储是怎么回了,这里只是一个大概,还有很多细节需要仔细阅读MSDN才行。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值