拖放,是指用鼠標拖動的方法,在不一樣程序的窗口之間、同一程序的不一樣窗口之間或同一程序同一窗體的不一樣控件之間,進行移動、複製、粘貼數據等操做的技術。拖放操做是依靠操做系統來完成的,被拖動的對象首先向操做系統註冊一種它所使用數據的格式,而且按照這種格式來提供數據,拖放結束時,目標窗口提取出數據,並根據提取的數據生成相應的對象。
拖放方式有兩種,一種是OLE拖放(這個比較複雜)和文件管理器拖放。它們利用了不一樣的機制。html
只能處理文件名,好比,你能夠在資源管理器中選擇一個或多個文件,拖到應用程序上面(這個程序的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,上面都有詳細說明。操作系統
拖放是用來描述用鼠標裝數據從一個地方傳輸到另外一個地方,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();
上面也提到了,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時,它會進入一個模態的消息循環,用來監視鼠標和簡單的消息,它是會阻塞住的。
要想一個窗體成爲一個拖放的接收方,它必須調用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數據傳輸相關知識,多數來自於MSDN和網上其同志們的觀點,加以總結而成。
COM接口提供了一種用於在不一樣的應用程序中交換數據的機制,這就是咱們要講的數據對象,對應的COM接口就是IDataObject。
大多數平臺,包括Windows,都定義了一個用於在應用程序之間傳輸數據的標準協議,基於一系列的剪切板的函數。應用程序使用這些函數能夠共享數據,即便它們的數據格式相差甚遠,通常說來,剪切板有兩個缺點:
1,不能指定目標設備,不靈活。
2,若是傳輸的數據很大,就有可能用到虛擬內存,效率不高。接口
通常狀況下,數據傳輸會用到兩種方式,一種是剪切板,另外一種是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,它上面講得比較詳細。
結構體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;
下面看一看IDataObject接口的成員方法:
GetData:Render在FORMATETC結構體中描述的數據,並經過STGMEDIUM結構體來傳遞數據。 GetDataHere:同上一方法類似,只是STGMEDIUM結構的內在是由調用者分配的。 QueryGetData:決定數據對象是否可以render在FORMATETC結構中描述的數據。 GetCanonicalFormatEtc:提供一下潛在不一樣的但邏輯上相同的FORMATETC結構。 SetData:提供一個用FORMATETC結構和STGMEDIUM結構描述的數據源對象。 EnumFormatEtc:建立並返回一個IEnumFORMATETC接口的指針來枚舉數據對象支持的FORMATETC。 DAdvise:建立一個在數據對象和通知接收器之間的鏈接,所以通知接收器收到數據對象中通知的改變。 DUnadvise:銷燬一個前面使用DAdvise方法安裝的通知。 EnumDAdvise:建立和返回一個指向枚舉當前通知的接口指針。
上面的接口,不用每一個方法都要實現,只實現幾個重要的,如GetData、SetData等。
先看看簡單的訪問剪切板的代碼,很了一下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才行。