歡迎你到OLE拖放操作的第六章!這里將著重于一個實現(xiàn)了drop-target的小程序,這就意味著我們的程序能夠接收拖到它上面的對象(文件、圖片、文本)了。
我們實現(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運行時將在拖放操作的過程中調(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ù)需要實現(xiàn),當(dāng)然,也要實現(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運行時在一個對象被拖到我們注冊窗口的時候來調(diào)用。就象上表顯示的一樣,每個函數(shù)都有不同的任務(wù),我們需要做的就是實現(xiàn)這些函數(shù)。
實現(xiàn)IDropTarget
以我的經(jīng)驗,IDropTarget接口非常難以寫為不涉及特定程序的代碼,例如:寫成可以在所有程序都使用的通用IDropTarget COM對象是很難的。
這是因為IDropTarget要求在一個對象拖過你的目標(biāo)窗口時顯示圖形效果,且也只有特定程序代碼才可以訪問這些數(shù)據(jù)對象內(nèi)容。
在我們的拖放接口之外,IDropTarget是最容易被集成到你窗口類的對象。例如:假定你已經(jīng)用C++類實現(xiàn)了一個自定義的窗口,為這個窗口添加一個多drop目標(biāo)支持的最好方法就是從IDropTarget直接繼承,而不需要單獨定義一個CDropTarget類;這意味著你的drop-target代碼能夠訪問所有你的窗口狀態(tài)。
然而,我們這里提供完整的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;
};
除引用記數(shù)器外,我們需要存儲另外兩個變量:m_hWnd變量是drop-target窗口的HANDLE,這個在提供可見效果的時候需要;m_fAllowDrop用來指示被拖動的數(shù)據(jù)對象是否包含我們需要的有用數(shù)據(jù)。因此我們沒有連續(xù)查詢數(shù)據(jù)對象,這是一個最優(yōu)的辦法。
IDropTarget::DragEnter方法
讓我們首先看一下IDropTarget函數(shù),因為這是在一個對象被拖過我們窗口時最先被COM調(diào)用的函數(shù):
HRESULT DragEnter (
IDataObject * pDataObject,
DWORD grfKeyState,
POINTL pt,
DWORD * pdwEffect
);
仔細(xì)看一下上面函數(shù)的原型,因為這對于理解每個參數(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實現(xiàn)需要做幾個通常的工作,另外畫一個圖形的反饋:
1. 檢查提供的數(shù)據(jù)對象,然后判斷它是否包含任何有用的數(shù)據(jù)
2. 檢查存儲在grfKeyState的鍵盤狀態(tài),并且計算應(yīng)該是什么樣的drop效果,例如:如果Control鍵按下,drop效果應(yīng)該是復(fù)制,如果Shift被按下,drop效果應(yīng)該是移動。
3. 驗證這些效果是否與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)
{
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;
}
除了設(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 };
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;
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協(xié)助函數(shù)用來計算基于鍵盤狀態(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效果,以及選擇使用哪個效果;在我們的實現(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相同,我們使用前面計算過的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ù)對象了,這里不在不厭其煩的介紹,你可以看源代碼。