歡迎來到OLE拖放旅程系列的第五部分,我們機會到了OLE拖放實現的最后階段,現在需要做的事情就是實現IDropSource和IDropTarget接口;一般我們完成這些,我們就可以在任何程序中添加拖放操作了。
本部分的目的在于實現一個用作拖放源的簡單程序,它不能接收任何拖放的數據,但這不要緊,因為我們能使用任何平常支持拖放操作的windows程序(例如:WordPad)來測試,程序就是一個windows的Edit控件,它是子類化的,且支持拖操作。
這個子類的細節在這里不討論,但源碼可以很清晰的說明這個任務。
成為一個拖放的源對象
初始化一個拖放操作很簡單,只要調用DoDragDrop這個API就足夠了。
WINOLEAPI DoDragDrop (
IDataObject * pDataObject,
IDropSource * pDropSource,
DWORD dwOKEffect,
DWORD * pdwEffect
);
一旦你調用這個AIP,OLE運行時就代表你的程序來接管并處理所有必要的鼠標和鍵盤windows消息,因此你基本上將控制權在調用這個函數的時候交給了OLE。
前兩個參數是COM接口,一個是IDataObject-我們前面已經介紹了這個接口。
第三個參數是一個DWORD值,它表示源允許的拖動效果,其以位掩碼的方式給出。這些效果是DROPEFFECT_XXX值,通常是DROPEFFECT_MOVE和DROPEFFECT_COPY的聯合。如果你想僅僅允許從我們的源復制數據,那么我們應該就指定DROPEFFECT_COPY。
最后一個參數是指向DWORD的指針。該值在DoDragDrop返回的時候可以訪問,包含OLE期望源對象執行的效果和動作,例如:用戶選擇移動還是復制數據?
執行拖放操作的代碼實際上分成三步:首先我們需要寫一個小的功能函數叫做StringToHandle,它轉換一個char*字符為HGLOBAL,從而我們可以在OLE中使用:
HANDLE StringToHandle (char *szText, int nTextLen)
{
void *ptr;
if(nTextLen == -1)
nTextLen = lstrlen (szText) + 1;
ptr = (void *)GlobalAlloc(GMEM_FIXED, nTextLen);
memcpy (ptr, szText, nTextLen);
return ptr;
}
StringToHandle完全不執行錯誤簡單,因此這是你的責任;下一步是準備拖放操作使用的數據:
FORMATETC fmtetc =
{
CF_TEXT,
0,
DVASPECT_CONTENT,
-1,
TYMED_HGLOBAL
};
STGMEDIUM stgmed =
{
TYMED_HGLOBAL,
{0},
0
};
stgmed.hGlobal = StringToHandle ("Hello, World", -1);
緊接著就是創建兩個拖放操作的COM接口:IDropSource和IDataObject。我們在前面的旅程中實現了CreateDataObject,馬上會實現CreateDropSource:
IDropSource *pDropSource;
IDataObject *pDataObject;
CreateDropSource (&pDropSource);
CreateDataObject (&pDataObject, &fmtetc, &stgmed, 1);
在創建好IDataObject和IDropSource之后就可以調用DoDragDrop了:
DWORD dwResult;
dwResult = DoDragDrop(pDataObject, pDropSource, DROPEFFECT_COPY, &dwEffect);
if(dwResult == DRAGDROP_S_DROP)
{
if(dwEffect == DROPEFFECT_MOVE)
{
}
}
最后一件事情就是清除所有我們使用過的資源,首先刪除我們使用的兩個COM接口,然后刪除包含我們文本的HGLOBAL內存緩沖區。
pDropSource->Release();
pDataObject->Release();
ReleaseStgMedium(&stgmed);
什么時候調用DoDragDrop方法來
知道了怎么樣初始化拖放操作非常好,但真正重要的是理解將上面的代碼放到你程序的什么位置?
因為拖放操作是基于鼠標的,它通常在處理windows鼠標消息的時候被初始化,如果你在支持拖放操作的程序中測試(例如WordPad),你會觀察到RichEdit控件有下面的行為:
1. 當鼠標移動過選中的文本,它的光標形狀變成箭頭
2. 當按下左鍵的時候,被選擇的不會被刪除,且設置內部狀態來指示可能要開始拖放操作
3. 當鼠標被第一次移動(并且內部狀態指示左鍵一直按在被選中的文本區域中),拖動操作開始。
4. 在這個時候,OLE接管并處理所有鼠標消息,直到操作完成
5. 然而,如果左鍵被釋放了,或者鼠標根本沒有移動,RichEdit選擇部分被清除。
這些行為在C或C++中實現非常簡單,對于我們的子類化的控件,可能是這樣:
case WM_LBUTTONDOWN:
if(MouseInSelection(hwndEdit))
{
fMouseDown = TRUE;
return 0;
}
break;
case WM_MOUSEMOUVE:
if(fMouseDown == TRUE)
{
DoDragDrop (...);
}
fMouseDown = FALSE;
break;
case WM_LBUTTONUP:
fMouseDown = FALSE;
break;
IDropSource接口
IDropSource是拖放操作中最簡單的接口,除了IUnknown函數外,它僅僅包含兩個需要實現的函數:
IDropSource 方法 |
描述 |
QueryContinueDrag |
決定是否應該繼續、取消或完成一個拖放操作,基于鼠標和<Escape>、 <Control> 和 <Shift> 鍵的狀態。 |
GiveFeedback |
為拖放操作的源提供一個用來給出可視化效果的方法,基于鼠標鍵、escape、control等鍵的狀態。 |
兩個函數由COM/OLE運行時在拖放操作的修飾鍵狀態改變的時候調用,需要做的工作就是實現這個接口-實際上,在準備拖放的時候已經做了很多編碼了。
實現IDropSource
同樣我們用一個源文件來實現drop源。在dropsource.cpp中是類的定義,下面就是這些代碼:
class CDropSource : public IDropSource
{
public:
HRESULT __stdcall QueryInterface (REFIID iid, void ** ppvObject);
ULONG __stdcall AddRef (void);
ULONG __stdcall Release (void);
HRESULT __stdcall QueryContinueDrag (BOOL fEscapePressed, DWORD grfKeyState);
HRESULT __stdcall GiveFeedback (DWORD dwEffect);
CDropSource();
~CDropSource();
private:
LONG m_lRefCount;
};
該類的構造函數除了執行初始化對象引用記數的操作外不執行任何其他的工作。
IDropSource::QueryContinueDrag
下面是QueryContinueDrag函數的定義:
HRESULT QueryContinueDrag(
BOOL fEscapePressed,
DWORD grfKeyState,
);
函數返回三個值的一個:
1. S_OK,拖動操作應該繼續;如果沒有檢查到錯誤就是這個結果,開始拖放操作的鼠標沒有被釋放,也沒有ESC鍵
2. DRAGDROP_S_DROP,放操作發生來完成拖操作;如果grfKeyState指出啟動拖放操作的鍵松開了就會發生這個結果。
3. DRAGDROP_S_CANCEL;沒有放操作發生時拖操作被放棄,這是fEscapePressed是真的表示ESC鍵被按下的結果。
作為COM的習慣,下面的兩個行為在拖放操作中應該遵守:
1. 按下Escape鍵時取消拖放操作
2. 松開鼠標左鍵的時候,應該執行放操作
為了符合這些規則,QueryContinueDrag如下實現:
HRESULT __stdcall CDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState)
{
if(fEscapePressed == TRUE)
return DRAGDROP_S_CANCEL;
if((grfKeyState & MK_LBUTTON) == 0)
return DRAGDROP_S_DROP;
return S_OK;
}
IDropSource::GiveFeedback
這個函數通常在每個程序中都不同,因為沒有一個程序是相同的。然而除非我們提供一個圖形反饋效果,否則我們的是很簡單:
HRESULT __stdcall CDropSource::GiveFeedback (DWORD dwEffect)
{
return DRAGDROP_S_USEDEFAULTCURSORS;
}
參數dwEffect(它告訴我們哪個鼠標鍵被按下,哪個鍵盤修飾在使用)在許多拖放操作程序中都可以忽略,簡單的DRAGDROP_S_USEDEFAULTCURSORS來告訴COM在發生任何修飾符改變的時候更新鼠標光標。
當然,我們可以檢查dwEffect的DROPEFFECT_XXX標志來PAINT我們的源窗口并返回S_OK,但為什么要這么麻煩來?