第五部分:IDropTarget实现

这一节我们讲如何来实现一个自己的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接口的实现,下一节我们将给出一个例子,完整说明这些接口之间的调用关系。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值