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