使用IDropTarget接口同时支持文本和文件拖放(二)
2008-11-04 11:05
337 查看
使用IDropTarget接口同时支持文本和文件拖放(二)
2008-01-12 07:47
2008-01-12 07:47
在这里我们最感兴趣的是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(STGMEDIUM)); 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 DragLeave: Drop 响应 对此句柄调用 鼠标拖动对象进入窗口之后,将会在窗口范围内移动,这时DoDragDrop就会调用IDropTarget的DragOver接口。其原形为: HRESULT DragOver( DWORD grfKeyState POINTL pt, DWORD * pdwEffect ) 相对来说对于这个接口方法的实现可以简单的多:只要根据grfKeyState判断键盘和鼠标的状态是否符合要求,根据pt传入的鼠标点判断该点是否支持拖放(比如将拖放区域限制在窗口的一部分的话),然后为*pdwEffect赋值为DROPEFFECT_COPY或DROPEFFECT_NONE.当然,还可以做一些你喜欢的事情,比如把鼠标坐标打印到屏幕上。不过为了性能和安全起见,建议不要做延时明显的操作。 这个方法没有传入参数,相当简单。 当拖动的鼠标离开了窗口区域,这个方法将被调用,你可以在这里写一些清理内存的代码。在 CDropTargetEx类里,由于在DragEnter里new了一些数据结构,并加到一个指针数组里,所以我必须在这里对此数据进行清理,对此结构里的STDMEDIUM调用ReleaseStgMedium然后Delete该结构。 另外,如果需要的话,可以通知用户鼠标指针已经离开了拖放区域。 如果鼠标没有离开窗口,而是在窗口内释放按纽,那么拖放时间的“放”就在这时发生, 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<Count;i++) { if(::DragQueryFile(hDrop,i,Buffer,sizeof(Buffer))) { //Got the FileName in Buffer } } ::DragFinish(hDrop); return true; } 获得的 Buffer是就是拖放的文件名,如果拖放的是多个文件,在for循环里可以依次获取这些文件的文件名。这样就实现了文件的拖放。 CDropTargetEx类使用非常简单: 在客户窗口的相关文件中,定义一个CDropTargetEx实例:CDropTargetEx DropTarget; 在窗口创建之后,将窗口句柄进行拖放注册: DropTarget.DragDropRegister(hWnd); 或者 DropTarget.DragDropRegister(hWnd,MK_CONTROL|MK_LBUTTON); 表示鼠标左键按下并且按住Ctrl键的拖放有效; 对于获取拖放的结果,我使用的是回调函数方式: 回调原形typedef VOID (_stdcall *DROPCALLBACK)(LPCSTR Buffer,int type); 在适当的地方(比如窗口的实现CPP里)定义函数DropCallback: void _stdcall DropCallBack(LPCSTR Buffer,int type) 并且将其地址赋于DropTarget实例: DropTarget.SetCallBack(DropCallBack); 这样,拖放文本到客户窗口,回调函数将被调用,参数Buffer为拖放的文本,format为CF_TEXT。而拖放文件的时候,对每个被拖放的文件都调用一次回调函数,参数Buffer为文件全路径名,format为CF_HDROP。 示例的DropCallBack代码为: void _stdcall DropCallBack(LPCSTR Buffer,int format) { switch(format) { case CF_TEXT: { SetWindowText(hEdit,Buffer); break; } case CF_HDROP: { TCHAR Buf[2048]=""; sprintf(Buf,"File : <%s> is Drag and Drop to this Windows ,Open it?",Buffer); if(MessageBox(hMainWnd,Buf,"Question",MB_YESNO)==IDYES) { 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的代码 对于拖放的全面阐述,请参见MSDN->PlatformSDK Document->User Interface Services->Windows Shell里关于“Transferring Shell Objects with Drag-and-Drop and the Clipboard”一章。Windows Shell系统提供了很多接口,让用户利用和扩充这些接口,很方便的开发和使用丰富的shell服务,确实是一种很聪明的设计。 |
相关文章推荐
- 使用IDropTarget接口同时支持文本和文件拖放
- 使用IDropTarget接口同时支持文本和文件拖放(一)
- 使用IDropTarget接口同时支持文本和文件拖放
- [VC]使用IDropTarget接口同时支持文本和文件拖放(1)(zt)
- 使用IDropTarget接口同时支持文本和文件拖放
- 使用IDropTarget接口同时支持文本和文件拖放
- 使用POI分割纯文本Excel文件 Version0.2 支持XLSX\XLS
- 关于使用jsp实现文本和file文件同时长传的问题
- 让窗口支持拖放(文件,文本,URL...)
- [转]自定义ASP.NET AJAX拖放功能示例程序:实现IDragSource和IDropTarget接口将商品拖放至购物车中
- Tolua使用,利用pkg文件,封装自己的lua支持的cocos2dx的接口
- IDropTarget 封装 让窗口支持拖放(文件,文本,URL...)
- Android学习之使用HttpURLConnection同时上传文本和多个文件
- 让窗口支持拖放(文件,文本,URL...)
- 使用POI分割纯文本Excel文件 支持XLSX\XLS
- net控件中数据导到Excel的格式 首先,我们了解一下excel从web页面上导出的原理。当我们把这些数据发送到客户端时,我们想让客户端程序(浏览器)以excel的格式读取它,所以把mime类型设为:application/vnd.ms-excel,当excel读取文件时会以每个cell的格式呈现数据,如果cell没有规定的格式,则excel会以默认的格式去呈现该cell的数据。这样就给我们提供了自定义数据格式的空间,当然我们必须使用excel支持的格式。下面就列出常用的一些格式: 1) 文本
- 经常使用的一个python logging封装,支持同时向console和文件输出
- 自定义ASP.NET AJAX拖放功能示例程序:实现IDragSource和IDropTarget接口将商品拖放至购物车中
- 经常使用的一个python logging封装,支持同时向console和文件输出
- struts2使用action方式实现文件下载(支持中文文件名)