上一节,我们讲了如何实现一个自己的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接口了。
以上都是关于上一节的一点补充,好,开始正题。
我们需要实现的接口就是IDropSource了,它可以接收Drag过程之中的反馈,根据反馈来更改拖放源的状态,可以取消Drag操作等。它有两个方法:
QueryContinueDrag:决定拖放操作是否应当继续,通过返回DRAGDROP_S_CANCEL来取消拖放操作。DoDragDrop函数在检测到一个键盘或鼠标按钮状态变化时,就会调用这个函数。QueryContinueDrag必须根据传入的状态值来确定拖放操作是否继续,取消还是完成。
GiveFeedback:得到最终用户在拖放过程之中的视觉反馈。这个方法有一个参数:DWORD dwEffect,它是DROPEFFECT,表示当前的拖放状态,如DROPEFFECT_COPY,DROPEFFECT_MOVE等。这个函数一般用来改变鼠标的光标样式,或者根据DROPEFFECT来使拖放源高亮等。如果你想默认的光标样式,可以返回DRAGDROP_S_USEDEFAULTCURSORS。这个函数会在DoDragDrop循环中频繁调用,所以这个函数的实现应当尽可能高效。
下面给出IDropSource的实现#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
#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; }
调用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。