OLE Drag&Drop | OLE 數據傳送(Data Transfer)

第一部分:OLE Drag&Drop 介紹  

1.基本概念


    拖放,是指用鼠標拖動的方法,在不一樣程序的窗口之間、同一程序的不一樣窗口之間或同一程序同一窗體的不一樣控件之間,進行移動、複製、粘貼數據等操做的技術。拖放操做是依靠操做系統來完成的,被拖動的對象首先向操做系統註冊一種它所使用數據的格式,而且按照這種格式來提供數據,拖放結束時,目標窗口提取出數據,並根據提取的數據生成相應的對象。

    拖放方式有兩種,一種是OLE拖放(這個比較複雜)和文件管理器拖放。它們利用了不一樣的機制。html

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接口。固然,程序也能夠同時實現這三個接口,從而能夠在同一程序中支持拖放操做。code

上面的圖描述了拖放操做中所須要的關鍵組件。左邊的表示拖放操做的源,它已經包含了兩個對象,一個是IDropSource,另外一個是IDataObject,最終是經過API函數DoDragDrop來發現拖放操做。

    右邊描述了拖放操做的目標,它須要實現IDropTarget接口。這個目標中,它能接收IDataObject對象。當鼠標拖動到目標窗體時,OLE傳遞一個IDataObject接口到目標對象上面,這是源傳給目標的數據,它不能以任何方式獲得一個副本。這目標這邊它能夠根據IDataObject獲得該數據格式,從而判斷這種格式是否是目標能識別的,若是不能識別,就不接收。它本質上須要調用RegisterDragDrop來把當前目標窗體註冊成一個拖放目標。

    注意,上面的圖所示,源和目標能夠是同一進程,也能夠是不一樣進程。在使用以前,須要調用COM和OLE的初始化,使用完後,再調用其反初始化。
 orm

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


 

4.開始拖放

    上面也提到了,DoDragDrop函數,它用來開始拖放操做,原型以下:htm

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接口對象。這個函數在窗體銷燬時應該調用。blog

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才行。