歡迎你到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:
// 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
};
除引用記數器外,我們需要存儲另外兩個變量: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)
{
// 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;
}
除了設定光标下的視窗和設定EDIT位置外,DragEnter的功能已經由兩個内部協助函數代理而簡化了:
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是一個私有函數,純粹用來檢查提供的資料,然後決定它是否包含對我們的drop目标有用的資料。在我們的例子中,我們僅僅接受CF_TEXT資料存儲為HGLOBAL,是以這是我們請求的類型。一個私有成員變量m_fAllowDrop用來記住這個決定。
DWORD CDropTarget::DropEffect (DWORD grfKeyState, POINTL pt, DWORD dwAllowed)
{
DWORD dwEffect = 0;
// 1. 檢查pt來看是否允許drop操作在某個位置
// 2. 計算出基于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協助函數用來計算基于鍵盤狀态的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控件中;這個程式是是純理論的,我們已經知道怎麼樣通路一個資料對象了,這裡不在不厭其煩的介紹,你可以看源代碼。