这一节我们讲如何来实现一个自己的Drop Target,我们需要实现IDropTarget接口。
1.如何成为一个"Drop Target"
为了使窗体能接收拖放的数据,窗口必须注册为drop目标,调用OLE API RegisterDragDrop来完成这件事情,函数原型如下:
WINOLEAPI RegisterDragDrop(
HWND hwnd, //Handle to a window that can accept drops
IDropTarget * pDropTarget //Pointer to object that is to be target of drop
);
这个函数第一个参数就是一个窗体的HWND,表示哪个窗体注册为drop目标。第二个参数就是一个IDropTarget指针,在拖放过程中,就会调用到这个接口的各种方法。
与这个函数功能相反的一个API RevokeDragDrop,表示取消这个窗体drop目标,其原型如下:
WINOLEAPI RevokeDragDrop(
HWND hwnd //Handle to a window that can accept drops
);
这个方法在窗体销毁之前调用,在注册窗口成为一个drop目标时,它内部会调用IDropTarget接口的AddRef函数,相反,在反注册时,应该会调用这个IDataObject接口的Release函数。
在调用RegisterDragDrop之间,你应当保证OleInitialize调用,不要用CoInitialize或CoInitializeEx来初始化COM,应当初始化OLE。
2.IDropTarget接口实现
IDropTarget接口有四个方法:
a) DragEnter: 判断是否可以接受一个拖放操作,以及接受之后的效果。
b) DragOver: 提供通过DoDragDrop函数执行的目标反馈。
c) DragLeave: 导致一个drop 目标挂起它的返回行为。
d) Drop: 数据放在目标窗口。
这些函数都是由COM/OLE运行时在一个对象被拖到我们注册的窗口的时候来调用,最本质是在DoDragDrop函数里面被告调用。它们每一个函数都有不同的功能,我们要最的就是实现这些函数,使拖放操作表现正确。
2.2 SdkDropTarget.h
#ifdef __cplusplus
#ifndef _SDKDROPTARGET_H_
#define _SDKDROPTARGET_H_
#include "SdkDataObject.h"
typedef DWORD DROPEFFECT;
class CLASS_DECLSPEC SdkDropTarget : public IDropTarget
{
public:
SdkDropTarget();
~SdkDropTarget();
BOOL RegisterDropTarget(HWND hWnd);
void RevokeDropTarget();
void AllowDragDrop(BOOL bAllowDrop = TRUE);
DROPEFFECT FilterDropEffect(DWORD grfKeyState, DROPEFFECT dwEffectSrc);
IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv);
IFACEMETHODIMP_(ULONG) AddRef(void);
IFACEMETHODIMP_(ULONG) Release(void);
IFACEMETHODIMP DragEnter(IDataObject *pDataObj,
DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
IFACEMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
IFACEMETHODIMP DragLeave(void);
IFACEMETHODIMP Drop(IDataObject *pDataObj,
DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
private:
//!< The flag indicates whether allow drag or not
BOOL m_isAllowDrag;
//!< The flag indicates the data available or not
BOOL m_isDataAvailable;
//!< The target window handle
HWND m_hTargetWnd;
//!< The reference count
volatile LONG m_lRefCount;
//!< The pointer of IDropTargetHelper interface
IDropTargetHelper *m_pDropTargetHelper;
};
上面是SdkDropTarget的类声明,除了存引用计数的变量之外,我们还有一个BOOL类型的变量m_isDataAvailable,用来表示当前的是否包含我们需要的数据,这样做就不会每次都去调用方法检测了。
m_hTargetWnd就是我们注册drop目标的窗体的HWND。
m_pDropTargetHelper这是一个Shell的接口,它帮我们实现了一些拖放操作,这里我们用它来帮我们实现,当然你可以不用,关键是要明白本质。
上面还提供了一些公有的方法,如AllowDragDrop表示是否允许拖放操作,RevokeDropTarget用来反注册窗体,取消drop目标,这些函数都是一些辅助性函数,不是最本质的。
关于AddRef, Release, QueryInterface这几个函数,我就不重点说明了。
3.DragEnter实现
IDropTarget::DragEnter的函数原型如下:
HRESULT DragEnter(
IDataObject * pDataObject, //Pointer to the interface of the source data object
DWORD grfKeyState, //Current state of keyboard modifier keys
POINTL pt, //Current cursor coordinates
DWORD * pdwEffect //Pointer to the effect of the drag-and-drop operation
);
pDataObject: 一个IDataObject指针,就是调用DoDragDrop传的那个指针。我们在DragEnter里面检查数据对象是否包含我们需要的数据。
grfKeyState: 保留键盘修饰符的状态,如Ctrl, Shift, Alt以及鼠标按键状态。
pt: 表示鼠标在我们窗口的位置,我们可以利用这个点来判断拖放是在发生在我们指定的拖放区域。
pdwEffect: 输出参数,表示当前允许源的drop效果。
我们在实现DragEnter时,通常需要做以下几件事:
1、检查数据对象,看数据对象是否包含我们的数据。
2、检查存储在grfKeyState的键盘状态,并且计算是什么的样drop效果,然后通过输出参数返回,如果返回一个DROPEFFECT_NONE的话,表明这个数据不能被接受。
3、存储最终的drop效果到pdwEffect的指针。
最本质的,我们应当在这个函数里面决定数据是否可以被接受。
实现代码如下:
STDMETHODIMP SdkDropTarget::DragEnter(IDataObject *pDataObj,
DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
if ( NULL == pDataObj )
{
return E_INVALIDARG;
}
POINT ppt = { pt.x, pt.y };
DROPEFFECT dwEffect = OnDragEnter(m_hTargetWnd,
(SdkDataObject*)pDataObj, grfKeyState, ppt);
*pdwEffect = dwEffect;
m_isDataAvailable = (DROPEFFECT_NONE == dwEffect) ? FALSE : TRUE;
if ( NULL != m_pDropTargetHelper )
{
m_pDropTargetHelper->DragEnter(m_hTargetWnd, pDataObj, &ppt, *pdwEffect);
}
return S_OK;
}
这里面调用了我自己定义的一个虚方法OnDragEnter,这是我们自己定义的一个方法,以使派生类可以得到drag enter的通知。头文件里没有给出声明,因为它不是很重要。这里面,可以调用IDataObject的QueryGetData函数来检查指定的数据是否被当前的数据所支持。
4.DragOver实现
这个函数会被频繁调用,因些这个函数的实现最好尽量高效。当键盘状态改变或鼠标在窗口上面移动时,这个函数会被调用,我们一般在这个函数里面更改鼠标光标的状态。比如,当ctrl按下时,我们应当呈现什么光标,当shift按下时,又应该是什么光标等,下在是这个函数的原型。HRESULT DragOver(
DWORD grfKeyState, //Current state of keyboard modifier keys
POINTL pt, //Current cursor coordinates
DWORD * pdwEffect //Pointer to the effect of the drag-and-drop operation
);
它的实现也比较简单。
STDMETHODIMP SdkDropTarget::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
POINT ppt = { pt.x, pt.y };
DWORD dwEffect = OnDragOver(m_hTargetWnd, grfKeyState, ppt);
*pdwEffect = (FALSE == m_isDataAvailable) ? DROPEFFECT_NONE : dwEffect;
if ( NULL != m_pDropTargetHelper )
{
m_pDropTargetHelper->DragOver(&ppt, *pdwEffect);
}
return S_OK;
}
5.DragLeave实现
这个函数在鼠标光标移动到窗体(drop目标)之外时会被调用,或者当ESC按下时。这个函数一般不用做什么操作,可以用来作一些标志清理工作等。例如,当一个数据拖过目标窗体时,窗体改变一下背景色,当Drag Leave时,可以在这个函数里面来恢复原来的颜色。
STDMETHODIMP SdkDropTarget::DragLeave(void)
{
OnDragLeave(m_hTargetWnd);
m_isDataAvailable = TRUE;
if ( NULL != m_pDropTargetHelper )
{
m_pDropTargetHelper->DragLeave();
}
return S_OK;
}
6.Drop实现
这个函数在是鼠标左键(一般情况是左键)释放时会被调用。在这个函数中,我们就可以把IDataObject里面的数据取出来,执行相应的逻辑操作。
STDMETHODIMP SdkDropTarget::Drop(IDataObject *pDataObj,
DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
if ( NULL == pDataObj )
{
return E_INVALIDARG;
}
POINT ppt = { pt.x, pt.y };
if ( NULL != m_pDropTargetHelper )
{
m_pDropTargetHelper->Drop(pDataObj, &ppt, *pdwEffect);
}
BOOL isOK = OnDrop(m_hTargetWnd,
(SdkDataObject*)pDataObj, grfKeyState, ppt);
return (TRUE == isOK) ? S_OK : E_FAIL;
}
我们在这一节给出了IDropTarget接口的实现,下一节我们将给出一个例子,完整说明这些接口之间的调用关系。