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