第四部分:IDropSource实现

时间:2022-09-29 08:56:19

上一节,我们讲了如何实现一个自己的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 <shlobj.h>
#include <shlwapi.h>

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。