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