第六部分:實(shí)現(xiàn)Drop目標(biāo)對象(OLE drag&drop 之旅)
歡迎你到OLE拖放操作的第六章!這里將著重于一個實(shí)現(xiàn)了drop-target的小程序,這就意味著我們的程序能夠接收拖到它上面的對象(文件、圖片、文本)了。
我們實(shí)現(xiàn)一個IDropTarget的COM接口允許OLE程序拖動數(shù)據(jù)到我們的程序上;這里僅僅是一個簡單的EDIT控件,所以他將CF_TEXT數(shù)據(jù)作為目標(biāo)。
成為一個“Drop Target”
為了時窗口可以接收拖放操作的數(shù)據(jù),窗口必須注冊為drop目標(biāo);有一個OLE的API調(diào)用RegisterDragDrop來完成這個事情,函數(shù)的原型是:
WINOLEAPI RegisterDragDrop (HWND hwnd, IDropTarget * pDropTarget);第一個參數(shù)是窗口的HANDLE,這個窗口是拖動的目標(biāo)窗口;第二個參數(shù)是一個指向IDropTarget COM對象的指針,COM/OLE運(yùn)行時將在拖放操作的過程中調(diào)用這個方法。
同樣有一個OLE API調(diào)用來將window從拖放操作中刪除:
WINOLEAPI RevokeDragDrop(HWND hwnd);我們所要做的就是在窗口創(chuàng)建的時候調(diào)用RegisterDragDrop,在窗口銷毀的時候調(diào)用RevokeDragDrop。在我們調(diào)用RegisterDragDrop之前,我們需要構(gòu)造一個COM對象來支持IDropTarget接口。
IDropTarget接口
IDropTarget接口相對比較簡單,有四個函數(shù)需要實(shí)現(xiàn),當(dāng)然,也要實(shí)現(xiàn)IUnknown接口,不過我們前面已經(jīng)介紹了。
|
IDropTarget 方法 |
描述 |
|
DragEnter |
判斷是否可以接受一個拖操作,以及接受之后的效果 |
|
DragOver |
提供通過DoDragDrop函數(shù)執(zhí)行的目標(biāo)反饋 |
|
DragLeave |
導(dǎo)致一個drop目標(biāo)掛起它的返回行為 |
|
Drop |
數(shù)據(jù)放進(jìn)目標(biāo)窗口 |
這些函數(shù)都由COM/OLE運(yùn)行時在一個對象被拖到我們注冊窗口的時候來調(diào)用。就象上表顯示的一樣,每個函數(shù)都有不同的任務(wù),我們需要做的就是實(shí)現(xiàn)這些函數(shù)。
實(shí)現(xiàn)IDropTarget
以我的經(jīng)驗(yàn),IDropTarget接口非常難以寫為不涉及特定程序的代碼,例如:寫成可以在所有程序都使用的通用IDropTarget COM對象是很難的。
這是因?yàn)?/SPAN>IDropTarget要求在一個對象拖過你的目標(biāo)窗口時顯示圖形效果,且也只有特定程序代碼才可以訪問這些數(shù)據(jù)對象內(nèi)容。
在我們的拖放接口之外,IDropTarget是最容易被集成到你窗口類的對象。例如:假定你已經(jīng)用C++類實(shí)現(xiàn)了一個自定義的窗口,為這個窗口添加一個多drop目標(biāo)支持的最好方法就是從IDropTarget直接繼承,而不需要單獨(dú)定義一個CDropTarget類;這意味著你的drop-target代碼能夠訪問所有你的窗口狀態(tài)。
然而,我們這里提供完整的CDropTarget類:
class CDropTarget : public IDropTarget{public: // IUnknown implementation HRESULT __stdcall QueryInterface (REFIID iid, void ** ppvObject); ULONG __stdcall AddRef (void);ULONG __stdcall Release (void); // IDropTarget implementation HRESULT __stdcall DragEnter(IDataObject * pDataObject, DWORD grfKeyState, POINTL pt, DWORD * pdwEffect); HRESULT __stdcall DragOver(DWORD grfKeyState, POINTL pt, DWORD * pdwEffect); HRESULT __stdcall DragLeave(void); HRESULT __stdcall Drop(IDataObject * pDataObject, DWORD grfKeyState, POINTL pt, DWORD * pdwEffect); // Constructor CDropTarget(HWND hwnd); ~CDropTarget();private: // internal helper function DWORD DropEffect (DWORD grfKeyState, POINTL pt, DWORD dwAllowed); bool QueryDataObject(IDataObject *pDataObject); // Private member variables long m_lRefCount; HWND m_hWnd; bool m_fAllowDrop; // Other internal window members};除引用記數(shù)器外,我們需要存儲另外兩個變量:m_hWnd變量是drop-target窗口的HANDLE,這個在提供可見效果的時候需要;m_fAllowDrop用來指示被拖動的數(shù)據(jù)對象是否包含我們需要的有用數(shù)據(jù)。因此我們沒有連續(xù)查詢數(shù)據(jù)對象,這是一個最優(yōu)的辦法。
IDropTarget::DragEnter方法
讓我們首先看一下IDropTarget函數(shù),因?yàn)檫@是在一個對象被拖過我們窗口時最先被COM調(diào)用的函數(shù):
HRESULT DragEnter (IDataObject * pDataObject,//指向源數(shù)據(jù)對象的接口指針
DWORD grfKeyState, // 當(dāng)前鍵盤修飾符的狀態(tài)
POINTL pt, // 當(dāng)前鼠標(biāo)的坐標(biāo)
DWORD * pdwEffect // 指向拖放操作的效果指針
);仔細(xì)看一下上面函數(shù)的原型,因?yàn)檫@對于理解每個參數(shù)怎么樣使用很重要:
l IDataObject-第一個參數(shù)是拖放操作的源對象通過COM傳遞來的數(shù)據(jù)對象指針。IDataObject是拖放操作帶來數(shù)據(jù)的傳輸媒體,我們在DragEnter的時候查看數(shù)據(jù)對象來看是否有我們想要的任何數(shù)據(jù)。
l grfKeyState-保留鍵盤修飾符的狀態(tài),例如:Control、Alt、和Shift以及鼠標(biāo)按鍵的狀態(tài)。是有一到多個MK_CONTROL、MK_SHIFT、MK_ALT、MK_BUTTON、MK_LBUTTON等組成的簡單DWORD變量
l pt-一個POINTL結(jié)構(gòu)體,包含了鼠標(biāo)進(jìn)入我們窗口的坐標(biāo);在許多程序中,這個參數(shù)用來檢查鼠標(biāo)是否放置在允許的drop區(qū)域上,或者用來簡單的放置某些插入光標(biāo)來指示drop數(shù)據(jù)放在那里。
l pdwEffect-一個DWORD的指針,指出drop源允許的drop效果。這個值和DoDragDrop的dwOKEffect值相同。
我們的DragEnter實(shí)現(xiàn)需要做幾個通常的工作,另外畫一個圖形的反饋:
1. 檢查提供的數(shù)據(jù)對象,然后判斷它是否包含任何有用的數(shù)據(jù)
2. 檢查存儲在grfKeyState的鍵盤狀態(tài),并且計(jì)算應(yīng)該是什么樣的drop效果,例如:如果Control鍵按下,drop效果應(yīng)該是復(fù)制,如果Shift被按下,drop效果應(yīng)該是移動。
3. 驗(yàn)證這些效果是否與drop源的效果相兼容
4. 存儲最終的drop效果到pdwEffect的DWORD指針。
不要如此復(fù)雜吧!DragEnter的目的就是簡單的對拖放操作說“yes還是NO”,指定采用什么drop效果以便于OLE更新鼠標(biāo)光標(biāo)。
HRESULT __stdcall CDropTarget::DragEnter(IDataObject *pDataObject, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect){ // does the dataobject contain data we want? m_fAllowDrop = QueryDataObject (grfKeyState, pdwEffect, pDataObject); if(m_fAllowDrop) { // get the dropeffect based on keyboard state *pdwEffect = DropEffect (grfKeyState, pt, *pdwEffect); SetFocus (m_hWnd); PositionCursor (m_hWnd, pt); } else { *pdwEffect = DROPEFFECT_NONE; } return S_OK;}除了設(shè)置光標(biāo)下的窗口和設(shè)置EDIT位置外,DragEnter的功能已經(jīng)由兩個內(nèi)部協(xié)助函數(shù)代理而簡化了:
bool CDropTarget::QueryDataObject(IDataObject *pDataObject){ FORMATETC fmtetc = { CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; // does the data object support CF_TEXT using a HGLOBAL? return pDataObject->QueryGetData(&fmtetc) == S_OK ? true : false;}QueryDataObject是一個私有函數(shù),純粹用來檢查提供的數(shù)據(jù),然后決定它是否包含對我們的drop目標(biāo)有用的數(shù)據(jù)。在我們的例子中,我們僅僅接受CF_TEXT數(shù)據(jù)存儲為HGLOBAL,因此這是我們請求的類型。一個私有成員變量m_fAllowDrop用來記住這個決定。
DWORD CDropTarget::DropEffect (DWORD grfKeyState, POINTL pt, DWORD dwAllowed){ DWORD dwEffect = 0;// 1. 檢查pt來看是否允許drop操作在某個位置
// 2. 計(jì)算出基于grfKeyState的drop效果
if(grfKeyState & MK_CONTROL) { dwEffect = dwAllowed & DROPEFFECT_COPY; } else if(grfKeyState & MK_SHIFT) { dwEffect = dwAllowed & DROPEFFECT_MOVE; }// 3. 非鍵盤修飾符指定(或drop效果不允許),因此基于drop源的效果
if(dwEffect == 0) { if(dwAllowed & DROPEFFECT_COPY) dwEffect = DROPEFFECT_COPY; if(dwAllowed & DROPEFFECT_MOVE) dwEffect = DROPEFFECT_MOVE; } return dwEffect;}DropEffect協(xié)助函數(shù)用來計(jì)算基于鍵盤狀態(tài)的drop效果,并且這個效果是達(dá)到源允許的。
首先grfKeyState變量用來檢查看是否使用了Control或Shift鍵;這些鍵的標(biāo)準(zhǔn)的OLE行為是Control應(yīng)該是復(fù)制數(shù)據(jù),shift應(yīng)該是移動數(shù)據(jù)。如果兩個都按下,數(shù)據(jù) 應(yīng)該是連接(例如:源應(yīng)該建立一個到目標(biāo)的快捷方式),但我們不支持這個功能。
主要的事情是使用位與操作符來對dwEffect賦drop效果值的時候:
dwEffect = dwAllowed & DROPEFFECT_COPY;這個分配的結(jié)構(gòu)很簡單-dwEffect將擁有DROPEFFECT_COPY,但只有在dwAllowed變量中僅僅包含這個值的時候起作用;這種邏輯用法防止我們強(qiáng)制執(zhí)行一個源不允許的drop效果。
下面是看一下在沒有鍵盤修飾符的時候怎么做,例如:Control和Shift沒有使用。在這種情況我,我們檢查拖放的源對象允許的drop效果,以及選擇使用哪個效果;在我們的實(shí)現(xiàn)中,我們是移動數(shù)據(jù)而不是復(fù)制。
IDropTarget::DragOver方法
這個函數(shù)在拖放操作的整個生命周期中被多次調(diào)用,因此,高效的寫這個函數(shù)很重要;DragOver在鍵盤修飾符改變(shift/control等)或當(dāng)鼠標(biāo)移動的時候被調(diào)用。告訴OLE采用什么樣基于鍵盤狀態(tài)和鼠標(biāo)位置的drop效果是這個函數(shù)的責(zé)任:
HRESULT __stdcall CDropTarget::DragOver(DWORD grfKeyState, POINTL pt, DWORD * pdwEffect){ if(m_fAllowDrop) { *pdwEffect = DropEffect(grfKeyState, pt, *pdwEffect); PositionCursor(m_hWnd, pt); } else { *pdwEffect = DROPEFFECT_NONE; } return S_OK;}DragOver寫的很簡單,邏輯上與DragEnter相同,我們使用前面計(jì)算過的m_fAllowDrop和DropEffect協(xié)助函數(shù)來通過pdwEffect指針返回drop效果。
IDropTarget::DragLeave函數(shù)
這個函數(shù)在鼠標(biāo)光標(biāo)移到drop目標(biāo)窗口外面的時候調(diào)用,或者按下Escape鍵來取消拖放操作時。它的原型如下:
HRESULT __stdcall CDropTarget::DragLeave (void){ return S_OK;}這是這個函數(shù)的基本寫法;這個函數(shù)存在的唯一原因是便于程序在鼠標(biāo)移到窗口外面的時候使用圖形返回效果來得到一個機(jī)會清理。例如:想象下面的場景,無論什么東西都拖過目標(biāo)對象,DragEnter函數(shù)用來改變窗口邊界的顏色;在這種情況下,DragLeave函數(shù)用來恢復(fù)窗口邊界的顏色。
IDropTarget::Drop函數(shù)
Drop函數(shù)的原型與DragEnter函數(shù)相同:
HRESULT __stdcall CDropTarget::Drop (IDataObject *pDataObject, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect){ PositionCursor(m_hWnd, pt); if(m_fAllowDrop) { DropData (m_hWnd, pDataObject); *pdwEffect = DropEffect (grfKeyState, pt, *pdwEffect); } else { *pdwEffect = DROPEFFECT_NONE; } return S_OK;}在OLE判斷拖放操作到頭的時候調(diào)用該函數(shù),我們得到一個在DragEnter同樣的IDataObject的接口指針,我們可以從中得到數(shù)據(jù)并粘貼到我們的編輯窗口中。
DropData協(xié)助函數(shù)用來訪問數(shù)據(jù)對象內(nèi)部的CF_TEXT數(shù)據(jù),并插入到edit控件中;這個程序是是純理論的,我們已經(jīng)知道怎么樣訪問一個數(shù)據(jù)對象了,這里不在不厭其煩的介紹,你可以看源代碼。posted on 2006-03-06 08:51 笨笨 閱讀(5355) 評論(12) 編輯 收藏 引用 所屬分類: OLE Drag&Drop

