第四部分:IDropSource实现

上一节,我们讲了如何实现一个自己的IDataObject接口,在开始这一部分之前,我还想再说一下,IDataObject有一个接口------ EnumFormatEtc,这个接口用来枚举当前data object所支持的数据格式,它相当重要。在上一节中,我们给出了它的一个实现,它内部本质是用API SHCreateStdEnumFmtEtc来实现的,这里再来看一看它的实现:

STDMETHODIMP SdkDataObject::EnumFormatEtc(DWORD dwDirection, 
    IEnumFORMATETC **ppenumFormatEtc)
{
    if ( NULL == ppenumFormatEtc )
    {
        return E_INVALIDARG;
    }
    *ppenumFormatEtc = NULL;
    HRESULT hr = E_NOTIMPL;
    if ( DATADIR_GET == dwDirection )
    {
        FORMATETC rgfmtetc[] =
        {
            { CF_UNICODETEXT, NULL, DVASPECT_CONTENT, 0, TYMED_HGLOBAL },
        };
        hr = SHCreateStdEnumFmtEtc(ARRAYSIZE(rgfmtetc), 
            rgfmtetc, ppenumFormatEtc);
    }
    return hr;
}

在调用API SHCreateStdEnumFmtEtc时,需要传一个FORMATETC的指针,上面的实现只给出了一种,即

{ CF_UNICODETEXT, NULL, DVASPECT_CONTENT, 0, TYMED_HGLOBAL }

当然这里面你还可以多给出几种。这里要注意了,根据MSDN上说明,SHCreateStdEnumFmtEtc的最低操作系统的版本是Windows 2000,也就是说,如果你的Data Object想在Windows 98操作系统之下也能工作,那么你就应当实现一个你自己的IEnumFORMATETC接口。事实上,实现这个接口并不难,考虑到位Windows 98操作系统太古老了,这里就不考虑实现IEnumFORMATETC接口了。

以上都是关于上一节的一点补充,好,开始正题。

1.实现IDropSource

我们需要实现的接口就是IDropSource了,它可以接收Drag过程之中的反馈,根据反馈来更改拖放源的状态,可以取消Drag操作等。它有两个方法:

QueryContinueDrag:决定拖放操作是否应当继续,通过返回DRAGDROP_S_CANCEL来取消拖放操作。DoDragDrop函数在检测到一个键盘或鼠标按钮状态变化时,就会调用这个函数。QueryContinueDrag必须根据传入的状态值来确定拖放操作是否继续,取消还是完成。

GiveFeedback:得到最终用户在拖放过程之中的视觉反馈。这个方法有一个参数:DWORD dwEffect,它是DROPEFFECT,表示当前的拖放状态,如DROPEFFECT_COPY,DROPEFFECT_MOVE等。这个函数一般用来改变鼠标的光标样式,或者根据DROPEFFECT来使拖放源高亮等。如果你想默认的光标样式,可以返回DRAGDROP_S_USEDEFAULTCURSORS。这个函数会在DoDragDrop循环中频繁调用,所以这个函数的实现应当尽可能高效。

下面给出IDropSource的实现

1.1 SdkDropSource.h 

#ifdef __cplusplus
#ifndef _SDKDROPSOURCE_H_
#define _SDKDROPSOURCE_H_

#include 
#include 

class CLASS_DECLSPEC SdkDropSource : public IDropSource
{
public:

    SdkDropSource();
    ~SdkDropSource();

 // Methods of IUnknown
    IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv);
    IFACEMETHODIMP_(ULONG) AddRef(void);
    IFACEMETHODIMP_(ULONG) Release(void);

 // Methods of IDropSource
    IFACEMETHODIMP QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState);
    IFACEMETHODIMP GiveFeedback(DWORD dwEffect);

private:

    volatile LONG   m_lRefCount;        //!< The reference count
};

#endif // _SDKDROPSOURCE_H_
#endif // __cplusplus

1.2 SdkDropSource.cpp 

#include "SdkDropSource.h"

SdkDropSource::SdkDropSource(void)
{
    m_lRefCount = 1;
}

SdkDropSource::~SdkDropSource(void)
{
    m_lRefCount = 0;
}

STDMETHODIMP SdkDropSource::QueryInterface(REFIID riid, void **ppv)
{
    static const QITAB qit[] =
    {
        QITABENT(SdkDropSource, IDropSource),
        { 0 }
    };

    return QISearch(this, qit, riid, ppv);
}

STDMETHODIMP_(ULONG) SdkDropSource::AddRef()
{
    return InterlockedIncrement(&m_lRefCount);
}

STDMETHODIMP_(ULONG) SdkDropSource::Release()
{
    ULONG lRef = InterlockedDecrement(&m_lRefCount);
    if (0 == lRef)
    {
        delete this;
    }
    return m_lRefCount;
}

STDMETHODIMP SdkDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState)
{
    if( TRUE == fEscapePressed )
    {
        return DRAGDROP_S_CANCEL;
    }

    // If the left button of mouse is released
    if( 0 == (grfKeyState & (MK_LBUTTON) )
    {
        return DRAGDROP_S_DROP;
    }

    return S_OK;
}

STDMETHODIMP SdkDropSource::GiveFeedback(DWORD dwEffect)
{
    UNREFERENCED_PARAMETER(dwEffect);
    return DRAGDROP_S_USEDEFAULTCURSORS;
}

这里没有什么多说明的,实现都很简单,重点说一下QueryContinueDrag的实现吧,它首先判断了fEscapePressed是否为TRUE,也就是说ESC按键是否按下,如果按下的话,就取消拖放操作。如果grfKeyState里面不包含了MB_LBUTTON的话(鼠标左键释放),就执行DRAGDROP_S_DROP,最后返回S_OK,让拖放操作继续。

2.开始拖放


实现了IDropSource,那么些我们要怎么开始拖放操作呢?

调用DoDragDrop API,它的原型如下:

WINOLEAPI DoDragDrop(
  IDataObject * pDataObject,  //Pointer to the data object
  IDropSource * pDropSource,  //Pointer to the source
  DWORD dwOKEffect,           //Effects allowed by the source
  DWORD * pdwEffect           //Pointer to effects on the source
);

第一个参数就是要传输的IDataObject接口,上一节我们已经讲过了。第二个参数就是拖放源,第三个参数是一个DWROD值,它表示源允许拖动效果,通常是DROPEFFECT_XXX值,如DROPEFFECT_MOVE和DROPEFFECT_COPY的联合。最后一个指向DWORD的指针,该值在DoDragDrop返回后,能得到最终执行完拖放后的效果,例如可能想知道用户到底是MOVE还是COPY等。

注意,当调用了DoDragDrop后,它会进行一个循环,在这个循环之中,它会调用IDropSource, IDropTarget的各种方法,它接管了鼠标事件,并且也是阻塞的,如果拖放没有完成,这个函数也不会继续往下执行。

反正记住了,我们应当调用DoDragDorp这个函数来开始拖放操作。还有一个API SHDoDragDrop函数,也可以开始一个拖放操作它的最低操作系统的版本是XP,而DoDragDrop是Windows 95。





你可能感兴趣的:(COM)