DRAG & DROP
2011年03月08日
使用IDropTarget接口同时支持文本和文件拖放
关于Windows的外壳扩展编程,拖放是比较简单的一种,在网上可以找到不少介绍这个技巧的文章。大部分是介绍使用MFC的COleDropTarget实现的,
我觉得一般使用COleDropTarget已经很好了,但是我习惯在一些程序模块中,完全的不使用MFC,比如纯SDK编程,还有用在ATL的时候,MFC是相当累
赘的。所以COleDropTarget在这个意义上讲不够完美。
IDropTarget是系统留给支持拖放的客户程序的一个纯虚接口,事先没有对接口的任何函数进行实现,而是让用户通过实现接口函数来接管拖放的
结果。IDropTarget接口有以下成员函数:
基本COM成员函数
QueryInterface
AddRef
Release
接管拖放事件的成员函数:
DragEnter
DragOver
DragLeave
Drop
也就是说,要在客户程序里实现以上7个函数的实体。
系统在检测到拖放发生的时候,会在合适的时候依次调用客户程序里实现的IDropTarget接口相应函数,检查用户在这些函数里返回的标志,
决定鼠标外观表现和拖放结果。
-------------------------------------------------- ------------------------------
实现IDropTarget接口
为此建立一个基类为IDropTarget的类:
class CDropTargetEx : public IDropTarget
IDropTarget接口在OLEIDL.h里定义,为纯虚接口。
在CDropTargetEx里依次声明接口所包含的7个函数,原形为:
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void ** ppvObject);
ULONG STDMETHODCALLTYPE AddRef(void);
ULONG STDMETHODCALLTYPE Release(void);
HRESULT STDMETHODCALLTYPE DragOver(DWORD grfKeyState,
POINTL pt,
DWORD *pdwEffect);
HRESULT STDMETHODCALLTYPE DragEnter(IDataObject * pDataObject,
DWORD grfKeyState, POINTL pt,
DWORD * pdwEffect);
HRESULT STDMETHODCALLTYPE DragLeave(void);
HRESULT STDMETHODCALLTYPE Drop(IDataObject *pDataObj,
DWORD grfKeyState,
POINTL pt,
DWORD __RPC_FAR *pdwEffect);
(为了实现Addref计数,还有一个ULONG tb_RefCount成员变量是必须的。QueryInterface,AddRef,Release这3个函数的实现是COM知识中最基本的)
在讲解IDropTarget其他函数的具体实现之前,有必要介绍一下一个你可能永远不会直接调用但是确实存在的函数:DoDragDrop函数.此函数在某
数据源的数据被拖动的时候就被调用,它负责检测目标窗口是否支持拖放,发现目标窗口的IDropTarget接口随时跟踪鼠标和键盘的状态,根据
状态决定调用其DrageEnter,DragMove,Drop或DragLeave接口,从这些接口获取客户程序的返回值,根据这些值和用户界面以及数据源进行交互。
可以说DoDragDrop控制拖放的整个过程,我们要做的,只是将这个过程里发生的事件,接管下来并得到相应的信息,和DoDragDrop进行交互而已。
了解了这一点有助于我们理解为什么通过区区一个接口4个函数就可以实现了拖放的效果,因为系统为我们已经做了很多。
另一个非常重要的API是RegisterDragDrop,这个函数的原形是这样的:
WINOLEAPI RegisterDragDrop(HWND hwnd, IDropTarget * pDropTarget);
不用被WINOLEAPI吓到,这是一个宏:#define STDAPI EXTERN_C HRESULT STDAPICALLTYPE
也就是表示一个标准的WIN API函数,返回一个HRESULT的值。
函数RegisterDragDrop的作用是告诉系统:某个窗口(hwnd参数指定)可以接受拖放,接管拖放的接口是pDropTarget。
记住在调用RegisterDragDrop之前,一定要先调用OleInitialize初始化OLE环境。
在类CDropTargetEx里设计了一个函数
BOOL CDropTargetEx::DragDropRegister(HWND hWnd,
DWORD AcceptKeyState=|MK_LBUTTON)
{
if(!IsWindow(hWnd))return false;
HRESULT s = ::RegisterDragDrop (hWnd,this);
if(SUCCEEDED(s))
{
m_hTargetWnd = hWnd;
m_AcceptKeyState = AcceptKeyState;
return true;
}
else
{ return false; }
}
在这个函数里调用RegisterDragDrop,将this指针传入,表示本类实现了IDropTarget.,由本类接管拖放事件。另外顺便定义了一下拖放鼠标和键盘
特性常数,对这个类来说,我希望默认的只接受鼠标左键的拖放,所以,默认的AcceptKeyState值是MK_LBUTTON。相关的键盘鼠标常数
还有MK_SHIFT,MK_ALT,MK_RBOTTON,MK_MBUTTON,MK_BOTTON等几个,我想这个几个常数从字面上就可以理解它的意思了。这些常数可以用"位与"的
操作组合。
以下具体讨论IDropTarget的拖放相关接口函数(4个),这里的拖放对象以文本和文件为主。
-------------------------------------------------- ------------------------------
DragEnter
当你用鼠标选中了某一个文件或一段文本,并且将鼠标移到某个可以接受拖放(已经调用过RegisterDragDrop)的窗口里,DragEnter将第一时间
被调用。再看一下其原形:
HRESULT DragEnter( IDataObject * pDataObject,
DWORD grfKeyState,
POINTL pt,
DWORD * pdwEffect )
pDataobject 是从拖放的原数据中传递过来的一个IDataObject接口实例,包含数据对象的一些相关方法,可以通过此接口获得数据。
grfKeyState 为DragEnter被调用时当前的键盘和鼠标的状态,包含上面介绍过的键盘鼠标状态常数。
pt 表示鼠标所在的点。是以整个屏幕为参考坐标的。
pdwEffect 是DoDragDrop提供的一个DWORD指针,客户程序通过这个指针给DoDragDrop返回特定的状态。有效的状态包括:
DROPEFFECT_NONE=0 表示此窗口不能接受拖放。
DROPEFFECT_MOVE=1 表示拖放的结果将使源对象被删除
DROPEFFECT_COPY=2 表示拖放将引起源对象的复制。
DROPEFFECT_LINK =4 表示拖放源对象创建了一个对自己的连接
DROPEFFECT_SCROLL=0x80000000表示拖放目标窗口正在或将要进行卷滚。此标志可以和其他几个合用
对于拖放对象来说,一般只要使用DROPEFFECT_NONE和DROPEFFECT_COPY即可。
在DragEnter里要做什么呢?主要是告知拖放已经进入窗口区域,并判断是否支持某具体类型的拖放。
首先,要判断键盘的状态。在调用DragDropRegister时我传入了一个AcceptKeyState并将其保存在m_AcceptKeyState成员变量里,现在可以拿它
跟这里得到的grfKeyState比较:
if(grfKeyState!=m_AcceptKeyState )
{
*pdwEffect = DROPEFFECT_NONE;
return S_OK;
}
如果键盘和鼠标的状态和我期望的不一样,那么pdwEffect里返回DROPEFFECT_NONE表示不接受拖放。
然后,判断拖放过来的IDataObject对象里有没有我感兴趣的数据。
这里要介绍的是两个关键的结构体FORMATETC和STDMEDIUM
FORMATETC是OLE数据交换的一个关键结构,对某种设备,数据,和相关媒体做了格式上的描述。
其定义为
typedef struct tagFORMATETC
{ CLIPFORMAT cfFormat; DVTARGETDEVICE *ptd; DWORD dwAspect; LONG lindex; DWORD tymed; }FORMATETC, *LPFORMATETC; 在这里我们最感兴趣的是cfFormat和tymed两个数据。cfFormat是标准的"粘帖板"数据类型比如CF_TEXT之类。tymed表示数据所依附的媒介,
比如内存,磁盘文件,存储对象等等。其他的成员可以参见MSDN。
一个典型的FORMATETC结构变量定义如下:
FORMATETC cFmt = {(CLIPFORMAT) CF_TEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
IDataObject提供了一个GetData接口来获取其实例里包含的数据,比如:
STGMEDIUM stgMedium;
ret = pDataObject->GetData(&cFmt, &stgMedium);
GetData传入cFmt,以指出所感兴趣的数据,并将返回在stgMedium结构里。
STGMEDIUM的定义如下1
typedef struct tagSTGMEDIUM
{
DWORD tymed;
[switch_type(DWORD), switch_is((DWORD) tymed)]
union {
[case(TYMED_GDI)] HBITMAP hBitmap;
[case(TYMED_MFPICT)] HMETAFILEPICT hMetaFilePict;
[case(TYMED_ENHMF)] HENHMETAFILE hEnhMetaFile;
[case(TYMED_HGLOBAL)] HGLOBAL hGlobal;
[case(TYMED_FILE)] LPWSTR lpszFileName;
[case(TYMED_ISTREAM)] IStream *pstm;
[case(TYMED_ISTORAGE)] IStorage *pstg;
[default] ;
};
[unique] IUnknown *pUnkForRelease;
}STGMEDIUM;
typedef STGMEDIUM *LPSTGMEDIUM;
看起来颇为复杂,其实主要是一系列句柄或数据对象接口的联合,根据数据具体的类型,使用其中之一即可。tymed和FORMATETC里一样,指出数据
的载体类型(遗憾的是它不能指出具体的标准类型比如CF_TEXT或者其他)。至于pUnkForRelease,是源数据指定的一个接口,用来传递给
ReleaseStgMedium函数,如果它不为NULL,则ReleaseStgMedium函数使用这个接口释放数据。如果为NULL,则ReleaseStgMedium函数使用默认
的IUnknown接口。对于常规的拖放来说,这个对象指针应该为NULL.
得到了句柄或数据对象接口,也相当于得到了拖放的数据。
定义一个特定的FORMATETC结构实例传递给IDataObject的GetData,可以直接询问和获取某一种特定的数据。如果我们对我们想要的数据是非常确定
的,这是比较有效率的方法。但是如果我们期望能够对拖放的对象进行自适应的话,我们可以采取枚举IDataObject里包含的所有数据类型的方案。
这就要用到IEnumFORMATETC 接口了。
IEnumFORMATETC接口从IDataObject接口里获取:
IEnumFormatETC *pEnumFmt = NULL;
ret = pDataObject->EnumFormatEtc (DATADIR_GET,&pEnumFmt);
如果获取成功,则可以通过IEnumFORMATETC接口的Next方法,来枚举所有的数据格式:
pEnumFmt->Reset ();
HRESULT Ret=S_OK
while(Ret!=S_OK)
{
Ret=pEnumFmt->Next(1,&cFmt,&Fetched);
if(SUCCEEDED(ret))
if( cFmt.cfFormat == CF_TEXT
||cFmt.cfFormat == CF_HDROP)
{
if(GetDragData(pDataObject,cFmt))
EnterResult = true;
}
}
第一个参数表示一次获取的FORMATETC结构数据的数量,cFmt是一个FORMATETC指针,指向一个数据缓冲,用来返回FORMATETC数据。,Fetched
是Next调用后得到的FORMATETC数据个数。一般一次获取一个,直到Next返回不为S_OK。
我们可以对每个得到cFmt调用IDataObject->GetData方法,但是一般来说,一个数据对象包含的数据不止一种,而且一般有一些自定义的
数据类型(关于自定义数据类型,参见:RegisterClipboardFormat,如果要自己实现Drag/Drop源数据,这个函数是有用的),对此我们
不感兴趣,因为这里只要求处理文本和文件的拖动,为此,只处理cfFormat为CF_TEXT和CF_HROP的数据:
GetDragData为CDropTargetEx类的一个成员函数:
////////////////////////////////////////////////// /
//Get The DragData from IDataObject ,save in HANDEL
BOOL CDropTargetEx::GetDragData(IDataObject *pDataObject,FORMATETC cFmt)
{
HRESULT ret=S_OK;
STGMEDIUM stgMedium;
ret = pDataObject->GetData(&cFmt, &stgMedium);//GetData(CF_TEXT, &stgMedium);
if (FAILED(ret))
{
return FALSE;
}
if (stgMedium.pUnkForRelease != NULL)
{
return FALSE; } /////////////////////////////////////////// switch (stgMedium.tymed) { case TYMED_HGLOBAL: { LPDRAGDATA pData = new DRAGDATA; pData->cfFormat = cFmt.cfFormat ; memcpy(&pData->stgMedium,&stgMedium,sizeof(STGMEDIU M)); m_Array.push_back(pData); return true; break; } default: // type not supported, so return error { ::ReleaseStgMedium(&stgMedium); } break; } return false; } 在这个成员函数里,根据cFmt,调用IDataObject->GetData函数获得数据(对于CF_TEXT和CF_HROP来说,数据的媒介载体tymed都是HGLOBAL类型的)。
在具体实现的时候,我定义了一个结构:
typedef struct _DRAGDATA
{
int cfFormat;
STGMEDIUM stgMedium;
}DRAGDATA,*LPDRAGDATA;
将STGMEDIUM和数据类型(比如CF_TEXT,记录在cfFormat)都记录在DRAGDATA里。并且使用了一个vector数组,将这个结构保存在数组里。对于
不是我们想要的STGMEDIUM数据,我们马上调用ReleaseStgMedium函数进行释放,免得造成内存泄露。
这样,DragEnter的工作就基本完成了,最后需要做的就是给DoDragDrop返回相应的状态:如果我们获得了想要的数据就给* pdwEffect赋值
为DROPEFFECT_COPY,否则,就是DROPEFFECT_NONE;
如果支持拖放,鼠标形状将变成一个有接受意义的图标,否则,是一个拒绝意义的图标。
-------------------------------------------------- ------------------------------
DragOver
鼠标拖动对象进入窗口之后,将会在窗口范围内移动,这时DoDragDrop就会调用IDropTarget的DragOver接口。其原形为:
HRESULT DragOver(
DWORD grfKeyState
POINTL pt,
DWORD * pdwEffect
)
相对来说对于这个接口方法的实现可以简单的多:只要根据grfKeyState判断键盘和鼠标的状态是否符合要求,根据pt传入的鼠标点判断该点
是否支持拖放(比如将拖放区域限制在窗口的一部分的话),然后为*pdwEffect赋值为DROPEFFECT_COPY或DROPEFFECT_NONE.当然,还可以做
一些你喜欢的事情,比如把鼠标坐标打印到屏幕上。不过为了性能和安全起见,建议不要做延时明显的操作。
-------------------------------------------------- ------------------------------
DragLeave:
这个方法没有传入参数,相当简单。
当拖动的鼠标离开了窗口区域,这个方法将被调用,你可以在这里写一些清理内存的代码。在CDropTargetEx类里,由于在DragEnter里new了
一些数据结构,并加到一个指针数组里,所以我必须在这里对此数据进行清理,对此结构里的STDMEDIUM调用ReleaseStgMedium然后Delete该结构。
另外,如果需要的话,可以通知用户鼠标指针已经离开了拖放区域。
-------------------------------------------------- ------------------------------
Drop
如果鼠标没有离开窗口,而是在窗口内释放按纽,那么拖放时间的"放"就在这时发生,IDropTarget接口的Drop方法被调用。其原形为
HRESULT Drop(
IDataObject * pDataObject,
DWORD grfKeyState,
POINTL pt,
DWORD * pdwEffect
)
有些资料建议在这里才调用pDataObject->GetData方法获取数据,在CDropTargetEx类里,数据实际上已经在DragEnter里获取了。这样做的理由
是我希望一开始就获得数据,从它本身进行判断是否支持拖放,而不是在"放"的时候才判断是否合法数据。既然数据已经获得,那么我就可以从
保存数据的指针数组里提取出STGMEDIUM数据来,并根据数据的具体格式进行处理(最后一定要记住对STGMEDIUM进行ReleaseStgMedium)
对于CF_TEXT类型的数据,STGMEDIUM的成员hGlobal里包含的是一段全局内存数据。获取这些数据的方法是:
TCHAR *pBuff = NULL;
pBuff=(LPSTR)GlobalLock(hText);
GlobalUnlock(hText);
则得到一个指向内存数据的指针pBuff。在我这个例子里一般是一段"\0"结尾的文本字符串。这样就实现了文本的拖放。
对于CF_HDROP类型的数据,STGMEDIUM成员hGlobal是一个HDROP类型的句柄。通过这个句柄,可以获得拖放的文件列表。如:
BOOL CDropTargetEx::ProcessDrop(HDROP hDrop)
{
UINT iFiles,ich =0;
TCHAR Buffer[MAX_PATH]="";
memset(&iFiles,0xff,sizeof(iFiles));
int Count = ::DragQueryFile(hDrop,iFiles,Buffer,0); //Get the Drag _Files Number.
if(Count)
for (int i=0;i is Drag and Drop to this Windows ,Open it?",Buffer);
if(MessageBox(hMainWnd,Buf,"Question",MB_YESNO)==I DYES)
{
ShellExecute(0,"open",Buffer,"","",SW_SHOW);
}
}
default:
break;
}
}
总结:使用IDropTarget实现通用的拖放,只要实现其7个接口,并且对得到的IDataObject用正确的格式(FORMATETC)调用正确的GetData获取数据,
返回DROPEFFECT决定拖放的特征和结果,并处理拖放结果即可。
要注意的小问题是:
要调用OleInitialize而不是CoInitialize或CoInitializeEx对COM进行初始,否则RegisterDragDrop将不会成功,返回的错误是E_OUTOFMEMORY--
内存不够,无法进行该操作。
调用ReleaseStgMedium释放STGMEDIUM里的数据,而不是直接对其hGlobal成员调用CloseHandle.
拖放操作关系到两个进程的数据交换,会将两个进程都堵塞,直到拖放完成为止,所以,在接管拖放的接口方法中,不要进行过于耗时的运算。
这个例子相当简单,还可以简化,比如取消vector,将获得HGLOBAL句柄作为成员变量存储,或者将获取数据的操作全部放到Drop方法里。
对于拖放文件,还有一个更简单的方法:响应WM_DROPFILES 消息。步骤是:
对客户窗口调用DropAccepFiles,使该窗口可以接受文件拖放。
响应WM_DROPFILES消息,其wParam就是HDROP句柄
对此句柄调用DropQueryFiles获取拖放文件列表并结束拖放,参见上面关于ProcessDrop的代码
二、OLE拖放实现
MFC本身的CView类是支持拖放操作的,通过研究CView类的源码,大体知道它的实现原理是这样的:CView类中有一个COleDropTarget类的对象,
在视图窗口初始化时,调用COleDropTarget类成员函数Register(),以此在系统中注册该视图窗口为拖放接收窗口。当进行拖放操作的鼠标指
针处于视图窗口范围内时,COleDropTarge类会做出反应,它的OnDragEnter、OnDragOver、OnDropEx、OnDrop等成员函数被依次调用,这些函数
默认均是调用与其相对应的CView类成员函数OnDragEnter、OnDragOver、OnDropEx、OnDrop等,程序员只需重载这些CView类成员函数,即可对
拖动的过程及结果进行控制。
因为COleDropTarget默认只对CView提供支持,所以如果要让其他的窗口支持拖放,我们必须同时对要支持拖放的窗口类和COleDropTarget类进行
派生。把对拖放操作具体进行处理的代码封装成派生窗口类的成员函数,然后重载COleDropTarget中对应的五个虚函数,当它接收到拖放动作时,
调用窗口派生类的处理函数即可。但这里有一个问题,就是我们怎么知道何时调用派生类的处理函数呢?答案是运用RTTI技术。如果COleDropTarget
派生类收到的窗口指针类型,就是我们派生的窗口类,那么就调用它的处理函数,否则调用基类进行处理。
首先生成一个对话框工程,添加二个新类。
第一个类名为CListCtrlEx,父类为CListCtrl。添加完毕后,在CListCtrlEx的定义头文件中加入DECLARE_DYNAMIC(CListCtrlEx),在其实现文件中
加入IMPLEMENT_DYNAMIC(CListCtrlEx,CListCtrl),这样就对CListCtrlEx类添加了RTTI运行期类型识别(Run Time Type Information)支持。
第二个类名为COleDropTargetEx,父类为COleDataTarget。
在CListCtrlEx中添加COleDropTargetEx类的对象,并添加下列公有虚函数的声明:
virtual BOOL Initialize();
virtual DROPEFFECT OnDragOver(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point);
virtual DROPEFFECT OnDropEx(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, DROPEFFECT dropList,
CPoint point);
virtual BOOL OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point);
virtual void OnDragLeave(CWnd* pWnd);
Initialize函数用于注册CListCtrlEx成为拖放接收窗口;
OnDragOver在拖放鼠标进入窗口时被调用。此函数的返回值决定了后续的动作的类型:如果返回DROPEFFECT_MOVE,则产生一个剪切动作;如果
返回DROPEFFECT_COPY,则产生一个复制动作,如果返回DROPEFFECT_NONE,则不会产生拖放动作,因为OnDropEx、OnDrop函数将不会被调用
(OnDragLeave函数仍会被调用)。
OnDropEx函数会在OnDrop函数之前调用,如果OnDropEx函数没有对拖放动作进行处理,则应用程序框架会接着调用OnDrop函数进行处理。
所以必须要在派生类中重载OnDropEx函数--即使什么动作都都没有做--否则我们的OnDrop函数将不会被执行到,因为没有重载的话,
将会调用基类的OnDropEx函数,而基类的OnDropEx函数对拖放是进行了处理的--尽管不是我们所想要的动作。当然你也可以把对拖放进行处
理的动作放在OnDropEx中--那样就不需要重载OnDrop了。
OnDragLeave函数会在鼠标离开窗口时被调用,在此可以进行一些简单的清理工作。譬如在OnDragEnter或者OnDragOver函数中,我们改变
了光标的形态,那么此时我们就应该把光标恢复过来。
这些函数中最重要的是OnDrop函数,拖放动作将在此进行处理,它的全部源码如下:
BOOL CListCtrlEx::OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point)
{
UINT nFileCount = 0;
HDROP hDropFiles = NULL;
HGLOBAL hMemData = NULL; AfxMessageBox("OnDrop"); if(pDataObject->IsDataAvailable(CF_HDROP)) { hMemData = pDataObject->GetGlobalData(CF_HDROP); hDropFiles = (HDROP)GlobalLock((HGLOBAL)hMemData); //锁定内存块
if(hDropFiles != NULL)
{
char chTemp[_MAX_PATH+1] = {0};
nFileCount = DragQueryFile(hDropFiles, 0xFFFFFFFF, NULL, 0);
for(UINT nCur=0; nCurm_hWnd));
if(pWnd->IsKindOf(RUNTIME_CLASS(CListCtrlEx)))
{
pListCtrlEx = (CListCtrlEx*)pWnd;
return pListCtrlEx->OnDrop(pWnd, pDataObject, dropEffect, point);
}
else
{
return COleDropTarget::OnDrop(pWnd, pDataObject, dropEffect, point);
}
}
//倒霉的64K限制,只能再截断了:(
至此,我们成功地为CListCtrlEx添加了文件拖入操作的支持。一个完整的拖放操作,还包括拖出动作,所以必须要为该类再添加拖出操作,
即,将列表中的某一项或者多项拖出成为一个文件。这就需要用到另一个类:COleDataSource。具体步骤如下:
在CListCtrlEx中加入一个COleDataSource的实例,并映射列表框的LVN_BEGINDRAG消息处理函数,在此我们添加拖出操作的代码。
实现拖出非常简单,只需要依次调用COleDataSource的三个函数即可:Empty用于清空原先对象中缓存的数据,CacheGlobalData用来缓存数据
以进行拖放操作,最后调用DoDragDrop启动本次拖放操作。
但在调用之前,必须要做一些准备工作。主要的任务就是创建一个DROPFILES结构体,并拷贝要拖放的文件名到结构体后的内存中。DROPFILES
结构体定义了CF_HDROP剪贴板格式,紧跟它后面的是一系列被拖放文件的路径名。它的定义如下:
typedef struct _DROPFILES
{
DWORD pFiles; //文件名起始地址
POINT pt; //鼠标放下的位置,坐标由fNC成员指定
BOOL fNC; //为TRUE表示适用屏幕坐标系,否则使用客户坐标系
BOOL fWide; //文件名字符串是否使用宽字符
} DROPFILES, FAR* LPDROPFILES;
拖放之前的准备动作的代码如下:
uBufferSize = sizeof(DROPFILES) + uBufferSize + 1;
hMemData = GlobalAlloc(GPTR,uBufferSize);
ASSERT(hMemData != NULL);
lpDropFiles = (LPDROPFILES)GlobalLock(hMemData); //锁定之,并设置相关成员
ASSERT(lpDropFiles != NULL);
lpDropFiles->pFiles = sizeof(DROPFILES);
#ifdef _UNICODE
lpDropFiles->fWide = TRUE;
#else
lpDropFiles->fWide = FALSE;
#endif
//把选中的所有文件名依次复制到DROPFILES结构体后面(全局内存中)
pItemPos = strSelectedList.GetHeadPosition();
pszStart = (char*)((LPBYTE)lpDropFiles + sizeof(DROPFILES));
while(pItemPos != NULL)
{
lstrcpy(pszStart, (LPCTSTR)strSelectedList.GetNext(pItemPos));
pszStart = strchr(pszStart,'\0') + 1; //下次的起始位置是上一次结尾+1
}
准备完毕之后就可以进行拖放了,拖放动作有DoDragDrop函数触发,其原型如下:
DROPEFFECT DoDragDrop(
DWORD dwEffects = DROPEFFECT_COPY|DROPEFFECT_MOVE|DROPEFFECT_LINK, LPCRECT lpRectStartDrag = NULL,
COleDropSource* pDropSource = NULL
);
这里,dwEffects指定了允许施加于本COleDataSource实例之上的动作集:剪切、复制或无动作。
lpRectStartDrag指示拖放操作真正开始的矩形,如果鼠标没有移出该矩形,则拖放操作视作放弃处理。如果本成员设为NULL,则该起始
矩形将为一个像素大小。
pDropSource表明拖放所使用的COleDataSource对象。
而该函数的返回值,则表明本次拖放操作所实际产生的效果,至于具体产生何种效果,则由系统决定。譬如在拖放时按住Shift键,将产生剪切
效果;按住Ctrl键,将产生复制效果,等等。
拖放的代码如下:
m_oleDataSource.Empty();
m_oleDataSource.CacheGlobalData(CF_HDROP, hMemData);
DropResult = m_oleDataSource.DoDragDrop(DROPEFFECT_MOVE|DROPEFF ECT_COPY);
最后一点要注意的是,在Windows NT 4.0以上的系统中,即使实际产生的是DROPEFFECT_MOVE动作,DoDragDrop函数也只返回DROPEFFECT_NONE。
产生这个问题的原因在于,Windows NT 4.0的Shell会直接移动文件本身来对移动操作进行优化。返回值DROPEFFECT_MOVE最初的含义,就是通知
执行拖放操作的应用程序去删除原位置上的文件。但是因为Shell已经替应用程序完成了这个(删除)动作,所以,函数返回DROPEFFECT_NONE。
要想知道文件是否真的被移动了也很简单,只要在函数返回之后检查一下原位置上的文件是否存在就可以了
发表评论
-
VC++动态链接库(DLL)编程深入浅出(三)(上)
2012-01-20 02:05 722VC++动态链接库(DLL)编程 ... -
win7安装wince6.0中遇到的问题 CDeviceSDKInstallShim Add/Remove failed. HR=0x8007005
2012-01-20 02:05 1033win7安装wince6.0中遇到的 ... -
Visual C#中操作WMI的类库简介
2012-01-20 02:05 1186Visual C#中操作WMI的类库 ... -
在DirectX 全屏独占 方式下显示对话框和任意窗口
2012-01-20 02:05 1490在DirectX 全屏独占 方式 ... -
电脑天书(九)
2012-01-19 09:56 510电脑天书(九) 2011年04月 ... -
VISTA系统常识技巧集锦
2012-01-19 09:56 522VISTA系统常识技巧集锦 ... -
MAX三百问(珍藏版)上
2012-01-19 09:56 449MAX三百问(珍藏版)上 2011年06月13日 安装篇 ... -
“开始--运行”之命令集锦
2012-01-19 09:56 469“开始--运行”之命令集锦 2011年01月28日 记住 ... -
计算机一级考试选择题1
2012-01-19 09:55 1292计算机一级考试选择题1 ... -
ReleaseDC和DeleteDC的区别 (转)
2012-01-17 02:26 759ReleaseDC和DeleteDC的区别 (转) 2011 ... -
FlashBuilder4.5 下载 及破解方法
2012-01-17 02:26 682FlashBuilder4.5 下载 及破解方法 2011年 ... -
ROOT【个人学习,慎用】
2012-01-17 02:26 540ROOT【个人学习,慎用】 ... -
(转) iPhone UI 开发的几点建议
2012-01-17 02:26 477(转) iPhone UI 开发的几点 ... -
Android开发之--adb shell 命令大全
2012-01-17 02:26 548Android开发之--adb shell 命令大全 201 ... -
LAMNP 编译安装参数(一)---Apache 安装编译参数
2012-01-15 22:07 877LAMNP 编译安装参数(一)---Apache 安装编译参数 ... -
惠普 康柏 510 笔记本电脑
2012-01-15 22:07 651惠普 康柏 510 笔记本电脑 2011年12月17日 ... -
俺的电脑配置
2012-01-15 22:07 624俺的电脑配置 2011年12月21日 电脑型号 微星 ...
相关推荐
而且我已经把windows和linux的vmtools包,以及解决拖拽的问题脚本Drag&Drop_FixPatch一并加入进来,欢迎大家下载使用。 注意:因为大小问题,我分为两卷: VMware6.0深度完美汉化版+vmtools+Drag&Drop_FixPatch(1) ...
使用Drag&Drop接受文件名的输入框(13KB)
而且我已经把windows和linux的vmtools包,以及解决拖拽的问题脚本Drag&Drop_FixPatch一并加入进来,欢迎大家下载使用。 注意:因为大小问题,我分为两卷: VMware6.0深度完美汉化版+vmtools+Drag&Drop_FixPatch(1) ...
NULL 博文链接:https://mingren135.iteye.com/blog/2095635
Drag & drop source code android
Window下拖放操作Drag & Drop 全解析
很不错的drag & drop,类似于igoogle,在个人主页上都用得上,没有设置保存功能,有兴趣的朋友可以自己试一下
WPF 实现了两个listbox之间拖动,drag&drop,并且有上下按钮控制listbox里面元素的移动,还有拖动时边框变色,1分,你值得拥有。
Enhanced Drag & Drop(3KB)
【类库与框架】★★★★★-DragKit - an iOS framework for enabling drag & drop【类库与框架】★★★★★-DragKit - an iOS framework for enabling drag & drop 1.适合学生学习研究参考 2.适合个人学习研究参考 3...
IOS应用源码之【类库与框架】DragKit - an iOS framework for enabling drag & drop behavior
IOS应用源码之【类库与框架】-DragKit - an iOS framework for enabling drag & drop behavior.rar
eclipse rcp 关于拖动的文档,里面也有代码例子,但不是完整demo
wpf拖拽移动列表项的功能,两个datagrid中可以互相拖拽并移动一行数据
基于 Windows Shell 的拖放支持,跨应用。
一个不错的示例,自己也收藏一下.
Optimize your workflow with Drag and Drop Import for Blender. Quickly and easily import files by dragging them from your file explorer and dropping them into Blender’s 3D Viewport or Outliner. No ...
在同个页面的两个列表框之间互相拖动列表项!运行环境 Qt5.6.0 minGW;拖动到另一个列表框的同时要删除当前列表框的列表项
这个版本还挺好用的,所以上传上来,但是我的权限不够,所以没有传vmware tools文件,等有权限后,我在补上,谢谢