本文主要是介绍2408,02资管与拖放,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
原文
介绍
演示如何勾挂拖放
,这样程序可接受资管
窗口的拖放
,并变成拖放源
,这样用户可拖文件
到资管
窗口中.
示例项是一个MFC
应用,本文假定你熟悉C++,MFC
及COM
对象和接口的使用
.
该程序是MultiFiler
,一个小工具,其作用类似拖放"临时区".可拖放多个文件
到MultiFiler
中,它再按列表
显示所有文件
.
然后,可分别用上档
或Ctrl
键,告诉资管
,移动或复制
原文件,来拖动文件回资管窗口
.
使用资管
拖放
资管
允许你在资管
窗口和桌面
间拖放文件
.当你开始拖放操作
时,从拖放的资管窗口
(放源),创建一个实现IDataObject
接口的COM
对象,并在该对象中放入一些数据
.
拖放进的窗口
(放目标
),然后使用IDataObject
方法读取该数据
;因此它知道正在拖放的文件
.
如果使用ClipSpy
等查看器检查IDataObject
中包含的数据
,会看到资管
,在数据对象
中放置了多种数据格式
.
重要格式
是CF_HDROP
.其他格式
是资管
注册供其自己使用的自定义格式
.如果编写一个按放目标
注册其窗口
的应用,且知道
如何读取CF_HDROP
数据,则可接受
拖放文件.
同样,如果可用CF_HDROP
数据填充数据对象
,资管
让应用
变成拖放源
.则,CF_HDROP
格式?
DROPFILES
数据结构
CF_HDROP
格式到底是什么?表明,它只是一个DROPFILES
结构.还有个只是DROPFILES
结构指针的HDROP
类型.
DROPFILES
是个简单结构
.以下是它的定义
:
struct DROPFILES
{DWORD pFiles; //文件列表偏移,POINT pt; //(客户坐标)放置点,BOOL fNC; //是在非客户区,且`pt`在屏幕坐标BOOL fWide; //宽符标志
};
结构
定义中没有列举文件名列表
.按双无效
结尾的串列表
表示该列表
.但是它实际上在fWide
成员后立即存储
它,pFiles
保存列表
在内存中(相对结构开头
)的偏移
.
拖放中使用的唯一其他成员
是,指示文件名
是美标
符还是统一
符的fWide
.
接受从资管
中拖放
接受拖放
比启动拖放
更简单.
窗口
有两个方法
可接受拖放
.或从窗口3.1
保留的使用WM_DROPFILES
消息.或按OLE
放目标注册窗口
.
老办法WM_DROPFILES
要用旧方法
,首先在窗口中设置"接收文件"
风格.
如果想要运行时
设置此风格
,调用DragAcceptFiles()API
,该API
带两个参数
.第一个
是主窗口句柄
,第二个是表示可接受拖放
的真
.
如果主窗口
是CView
而不是对话框
,则需要运行时
设置此风格
.
总之,你的窗口
会变成放目标
.当你从资管
窗口拖放文件或目录
到窗口
中时,该窗口
会收到一条WM_DROPFILES
消息.
WM_DROPFILES
消息的WPARAM
是一个列举
了要拖放
文件的HDROP
.有三个API
可来从HDROP
中取文件列表
:DragQueryFile(),DragQueryPoint()
和DragFinish()
.
1,DragQueryFile()
干2件事:返回正在拖放
的文件数
,并枚举文件列表
.DragQueryPoint()
返回DROPFILES
结构的pt
成员.DragFinish()
释放在拖放
过程中分配的内存
.
2,DragQueryFile()
带四个参数
:HDROP
,要返回的文件名的索引
,调用
者分配的用来保存
名字的缓冲
及缓冲(按符)的大小
.
如果按-1
索引传递
,则DragQueryFile()
返回列表中的文件数
.否则,它返回文件名
中的字符数
.你可对照0
测试此返回值
,以判断是否成功调用
.
3,DragQueryPoint()
带两个参数
:HDROP
和接收DROPFILES
结构的pt
成员中值的点
结构的指针
.DragFinish()
只接受一个HDROP
参数.
典型的WM_DROPFILES
处理器如下
:
void CMyDlg::OnDropFiles ( HDROP hdrop )
{UINT uNumFiles;TCHAR szNextFile [MAX_PATH];//取被拖放的文件的`#`.uNumFiles = DragQueryFile ( hdrop, -1, NULL, 0 );for ( UINT uFile = 0; uFile < uNumFiles; uFile++ ){//从`HDROP`信息中取下个文件名.if ( DragQueryFile ( hdrop, uFile, szNextFile, MAX_PATH ) > 0 ){//操作`szNextFile`中名字.}}//释放内存.DragFinish ( hdrop );
}
如果只想要文件列表
,则不需要DragQueryPoint()
.
新方法,使用OLE
放目标
按OLE
放目标注册窗口
来接受拖放
.一般,这样做需要编写实现IDropTarget
接口的C++
类.但是,MFC
有一个COleDropTarget
类可帮助
处理它.
根据主窗口
是对话框
还是CView
,该过程会略有不同.
把CView
设为放目标
CView
已内置了一些拖放支持
,但一般不会激活它
.要激活它
,需要添加COleDropTarget
成员变量到视图
中,然后在视图
的OnInitialUpdate()
中调用其Register()
函数,来把视图变成放目标
,如下:
void CMyView::OnInitialUpdate()
{CView::OnInitialUpdate();//按放目标注册视图.`m_droptarget`是`CMyView`的`COleDropTarget`成员.m_droptarget.Register ( this );
}
完成后,覆盖当用户拖放
你的视图时调用
的四个虚函数
:
1,OnDragEnter()
:光标进入窗口
时调用.
2,OnDragOver()
:光标移进窗口
时调用.
3,OnDragLeave()
:光标离开窗口
时调用.
4,OnDrop()
:用户在你的窗口
放置时调用.
OnDragEnter()
OnDragEnter()
是调用的第一个函数
.它的原型是:
DROPEFFECT CView::OnDragEnter( COleDataObject* pDataObject, DWORD dwKeyState, CPoint point );
参数是:
1,pDataObject
:包含正在拖放
数据的COleDataObject
指针.
2,dwKeyState
:一组指示点击了哪个鼠标按钮
及按下哪些(如果有)上档
键标志.标志
包括MK_CONTROL,MK_SHIFT,MK_ALT,MK_LBUTTON,MK_MBUTTON
和MK_RBUTTON
.
3,点
:按视图客户坐标
表示的光标位置
.
OnDragEnter()
返回一个告诉OLE
是否接受放置的DROPEFFECT
值,如果接受
,则应显示哪个光标
.值及其含义
为:
1,DROPEFFECT_NONE
:不接受该放置
.光标将变为停止
.
2,DROPEFFECT_MOVE
:按放目标移动数据
.光标将变为移动
.
3,DROPEFFECT_COPY
:按放目标
复制数据.光标将变为复制
:
4,DROPEFFECT_LINK
:按放目标链接数据
.光标将变为复制链接
.
一般,在OnDragEnter()
中,可检查正在拖放的数据
,并查看它是否符合条件
.如果不符合,则返回DROPEFFECT_NONE
以拒绝拖放
.
否则,可相应返回其他值之一
.
OnDragOver()
如果从OnDragEnter()
返回的值不是DROPEFFECT_NONE
,则每当鼠标光标
移进窗口
时,都会调用OnDragOver()
.OnDragOver()
的原型是:
DROPEFFECT CView::OnDragOver ( COleDataObject* pDataObject, DWORD dwKeyState, CPoint point );
参数和返回值
与OnDragEnter()
相同.OnDragOver()
允许根据光标位置
和上档
键状态返回不同DROPEFFECT
值.
如,如果主视图窗口
有几个显示不同信息列表
的区域
,且只想在一个部分
放置,需要检查点
参数中的光标位置
,如果光标
不在该区域
,则返回DROPEFFECT_NONE
.
对上档
键,一般会如下响应
它们:
1,按下上档
键(在dwKeyState
中是MK_SHIFT
):返回DROPEFFECT_MOVE
.
2,按下CONTROL
键(MK_CONTROL)
:返回DROPEFFECT_COPY
.
3,都按了(MK_SHIFT|MK_CONTROL)
:返回DROPEFFECT_LINK
.
这些只是准则,但最好遵守它们
.
如,在MultiFiler
中,OnDragOver()
总是返回DROPEFFECT_COPY
.只需确保返回正确的值
,这样光标
准确地用户指示如果放置进
窗口会怎样.
OnDragLeave()
如果用户拖出
窗口而不放置,则调用OnDragLeave()
.原型是:
void CView::OnDragLeave();
它无参或返回值
,目的是让你清理在OnDragEnter()
和OnDragOver()
时分配的内存
.
OnDrop()
如果用户拖过你的窗口
(且没有从最近一次调用OnDragOver()
返回DROPEFFECT_NONE
),则会调用OnDrop()
,这样可操作拖放
.OnDrop()
的原型是:
BOOL CView::OnDrop ( COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point );
dropEffect
参数等于OnDragOver()
的最后返回值
,其他参数
与OnDragEnter()
相同.如果成功拖放
,则返回值
为真
,否则为假
.
OnDrop()
是动作的地方,可任意操作数据
.在MultiFiler
中,把放置文件
添加到主窗口的列表控件
中.
按放目标设置对话框
如果主窗口
是个对话框
(或不是从CView
继承的内容),则情况会稍微困难一些.因为基本COleDropTarget
实现按仅适合CView
继承的窗口
设计,因此需要从COleDropTarget
继承一个新类
,并覆盖
上述四种方法.
典型的COleDropTarget
继承类声明如下:
class CMyDropTarget : public COleDropTarget
{
public:DROPEFFECT OnDragEnter ( CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point );DROPEFFECT OnDragOver ( CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point );BOOL OnDrop ( CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point );void OnDragLeave ( CWnd* pWnd );CMyDropTarget ( CMyDialog* pMainWnd );virtual ~CMyDropTarget();
protected:CMyDialog* m_pParentDlg; //在`构造器`中初化>
};
在此例中,给构造器
传递了主窗口的指针
,因此放目标
方法可发送消息
,并在对话框
中干其他操作
.
然后,可实现前面描述的四种拖放方法
.唯一
区别是附加的调用
时光标
所悬停窗口
指针的CWnd*
参数.
有该新类
后,把放目标
成员变量添加到对话框
中,并在OnInitDialog()
中,调用其Register()
函数:
BOOL CMyDialog::OnInitDialog()
{//按放目标注册对话.`m_droptarget`是`CMyDialog`的`CMyDropTarget`成员.m_droptarget.Register ( this );
}
访问CDataObject
中的HDROP
数据
如果使用OLE
放目标,则拖放函数
接收COleDataObject
指针.这是个实现IDataObject
并包含在开始拖放
时拖放源
创建的所有数据
的MFC
类.
你需要一些代码
来在数据
对象中,查找CF_HDROP
并取HDROP
句柄.取得HDROP
后,可如前用DragQueryFile()
读取已放置文件的列表
.
以下是从COleDataObject
取HDROP
的代码:
BOOL CMyDropTarget::OnDrop ( CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point )
{HGLOBAL hg;HDROP hdrop;//从`数据`对象取`HDROP`数据.hg = pDataObject->GetGlobalData ( CF_HDROP );if ( NULL == hg )return FALSE;hdrop = (HDROP) GlobalLock ( hg );if ( NULL == hdrop ){GlobalUnlock ( hg );return FALSE;}//在此处读文件列表...GlobalUnlock ( hg );//返回`TRUE/FALSE`以指示成功/失败
}
两个方法总结
处理WM_DROPFILES
:
1,窗口3.1
的保留;未来可能会删除
它.
2,无法自定义拖放过程
,只能在拖放
后操作.
3,不能检查被拖放的原始数据
.
4,如果不需要优雅的自定义
,该方法更容易编码
.
使用OLE
放目标:
1,使用COM
接口,这是个现代
且更好支持
的机制.
2,CView
和COleDropTarget
提供良好的MFC
支持.
3,允许完全控制
拖放操作.
4,允许访问原始IDataObject
,这样可访问
任意数据格式
.
5,需要更多的代码
,但一旦编写一次
,就可剪切其并粘贴
到新项中.
MultiFiler
如何接受拖放
当用户拖放
到MultiFiler
窗口上时,把所有放置文件
都添加到列表控件
中.
MultiFiler
会自动消除重复文件
,因此在列表中文件
只出现一次.
启动拖放
要让资管
接受拖放文件
,只需创建一些CF_HDROP
数据,并把它放进数据
对象中.
因为它的大小
并不总是相同,创建DROPFILES
结构有点麻烦.
当你在列表控件
中,选择文件
并拖放
它们时,MultiFiler
会启动拖放操作
.发生时,该控件
会发送一条LVN_BEGINDRAG
通知消息,这样MultiFiler
会创建数据对象
,并移交给OLE
以开始拖放操作
.
创建DROPFILES
的步骤如下:
1,在列表控件
中,枚举所有选中项
,在串列表
中放入它们.
2,跟踪添加到串列表
中的每个串的长度
.
3,为DROPFILES
自身和文件名列表
分配内存.
4,填充DROPFILES
成员.
5,复制文件名列表
到分配的内存
中.
现在,介绍MultiFiler
代码,这样你可确切地了解如何设置DROPFILES
.
第一步
是在一个列表
中放入所有选中的文件名
,并跟踪保存所有串
的内存.
void CMultiFilerDlg::OnBegindragFilelist(NMHDR* pNMHDR, LRESULT* pResult)
{
CStringList lsDraggedFiles;
POSITION pos;
CString sFile;
UINT uBuffSize = 0;//对列表中的每个选中项,在`lsDraggedFiles`中放入文件名.`c_FileList`是对话框的`CListCtrl`.pos = c_FileList.GetFirstSelectedItemPosition();while ( NULL != pos ){nSelItem = c_FileList.GetNextSelectedItem ( pos );//在`sFile`中放入文件名sFile = c_FileList.GetItemText ( nSelItem, 0 );lsDraggedFiles.AddTail ( sFile );//计算保存此串期望的#符数.uBuffSize += lstrlen ( sFile ) + 1;}
此时,uBuffSize
保存(按符)包括无效
的所有串
的总长度
.最后加1,表示无效
来终止列表
,然后乘以sizeof(TCHAR)
以按字节转换符
.
然后,添加sizeof(DROPFILES)
以取得最终期望缓冲大小
.
//为最终的`无效`符和`DROPFILES`结构的大小额外加1.
uBuffSize = sizeof(DROPFILES) + sizeof(TCHAR) * (uBuffSize + 1);
现在知道需要多少内存
,可分配它.拖放操作
时,使用GlobalAlloc()
从堆中分配内存
:
HGLOBAL hgDrop;
DROPFILES* pDrop;//对`DROPFILES`结构,从堆中分配内存.hgDrop = GlobalAlloc ( GHND | GMEM_SHARE, uBuffSize );if ( NULL == hgDrop )return;
然后,用GlobalLock()
直接访问内存
:
pDrop = (DROPFILES*) GlobalLock ( hgDrop );
if ( NULL == pDrop ){GlobalFree ( hgDrop );return;}
现在可开始填充DROPFILES
.GlobalAlloc()
调用中的GHND
标志初化内存为零
,因此只需要设置几个成员
:
//填充`DROPFILES`结构.pDrop->pFiles = sizeof(DROPFILES);
#ifdef _UNICODE//如果要为`统一`编译,请在结构中设置`统一`标志以指示它包含`统一`串.pDrop->fWide = TRUE;
#endif
注意,pFiles
成员不指示DROPFILES
结构的大小;它是filelist
的偏移
.但是因为文件列表
在结构尾的正后,因此其偏移与结构的大小
相同.
现在可复制
所有文件名
到内存
中,然后解锁缓冲
.
TCHAR* pszBuff;//在`DROPFILES`结构结束后的内存中,复制所有文件名.pos = lsDraggedFiles.GetHeadPosition();pszBuff = (TCHAR*) (LPBYTE(pDrop) + sizeof(DROPFILES));while ( NULL != pos ){lstrcpy ( pszBuff, (LPCTSTR) lsDraggedFiles.GetNext ( pos ) );pszBuff = 1 + _tcschr ( pszBuff, '\0' );}GlobalUnlock ( hgDrop );
下一步
是构造一个COleDataSource
对象,并在里面放置数据
.还需要一个描述(CF_HDROP)
剪切板格式的FORMATETC
结构和数据
的(HGLOBAL)
存储方式.
COleDataSource datasrc;
FORMATETC etc = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };//在数据源中放入数据.datasrc.CacheGlobalData ( CF_HDROP, hgDrop, &etc );
现在,可启动拖放操作
,但还有个细节
要注意.因为MultiFiler
接受拖放操作
,因此它会很高兴地接受
我们启动的拖放操作
.
因此,给数据源
添加另一位数据
,来注册自定义剪切板格式
.OnDragEnter()
和OnDragOver()
函数检查此格式
,如果有它,则不接受
.
HGLOBAL hgBool;hgBool = GlobalAlloc ( GHND | GMEM_SHARE, sizeof(bool) );if ( NULL == hgBool ){GlobalFree ( hgDrop );return;}//在数据源中放入数据.etc.cfFormat = g_uCustomClipbrdFormat;datasrc.CacheGlobalData ( g_uCustomClipbrdFormat, hgBool, &etc );
注意,不必按指定值
设置数据.
现在已把数据
放在一起,可开始拖放操作
了!调用COleDataSource
的在完成拖放
前不会返回的DoDragDrop()
方法.
唯一
参数是多个
指示允许用户
操作的DROPEFFECT
值.它返回一个指示用户想对数据的操作
的DROPEFFECT
值,或是否中止拖放
或未被目标接受的DROPEFFECT_NONE
.
DROPEFFECT dwEffect;dwEffect = datasrc.DoDragDrop ( DROPEFFECT_COPY | DROPEFFECT_MOVE );
这里,只允许复制和移动
.在拖放过程
中,用户可按Ctrl
或上档
键来更改操作
.
有时,传递DROPEFFECT_LINK
不会使资管
创建快捷方式
,因此上面调用中不包含DROPEFFECT_LINK
.
DoDragDrop()
返回后,检查
返回值.如果是DROPEFFECT_MOVE
或DROPEFFECT_COPY
,则成功拖放
,因此从主窗口的列表控件
中删除所有选中的文件
.
如果是DROPEFFECT_NONE
,事情就有点麻烦了.
在NT
上,必须手动检查
是否已移动文件
,如果是,则从列表控件
中删除它
.
如果对MultiFiler
的工作原理
感兴趣,请查看MultiFiler
源码.
最后是,如果取消拖放
,则释放分配的内存
.如果完成
,则放目标
拥有内存,不能释放它
.下面是检查DoDragDrop()
返回值的代码.
switch ( dwEffect ){case DROPEFFECT_COPY:case DROPEFFECT_MOVE:{//复制或移动`文件`.注意:不要调用`GlobalFree()`,因为放目标释放了`数据`.**省略了来删除列表控件项的代码. **}break;case DROPEFFECT_NONE:{//**省略了NT/2000的代码,来检查操作是否真成功.**拖放操作未被接受或被取消,因此应该调用GlobalFree()清理. GlobalFree ( hgDrop );GlobalFree ( hgBool );}break;}
}
其他详情
还可右击MultiFiler
列表控件,以取包含四个命令
的环境菜单
.它们来管理列表中的选择和清理列表
,非常简单.
壳
支持叫CLSID_DragDropHelper
的新coclass
,它有两个接口:IDragSourceHelper
和IDropTargetHelper
.
IDropTargetHelper
绘画拖放图像
.它有四个
名字应该很熟悉的方法:DragEnter(),DragOver(),DragLeave()
和Drop()
.
你只需普通
拖放处理,确定返回的DROPEFFECT
,然后调用与COleDropTarget
方法对应的IDropTargetHelper
方法.
IDropTargetHelper
方法需要DROPEFFECT
来正确绘画拖放图像
,因此需要先确定它
.
如果查看使用CView
的MultiFiler
示例,会看到两个成员变量
:
IDropTargetHelper* m_piDropHelper;
bool m_bUseDnDHelper;
在视图
的构造器中,代码
创建拖放助手COM
对象并取IDropTargetHelper
接口.根据此操作
是否成功,设置m_bUseDnDHelper
,这样其他函数
知道是否可用该COM
对象.
CMultiFilerView::CMultiFilerView() : m_bUseDnDHelper(false), m_piDropHelper(NULL)
{//创建`壳`拖放助手对象的实例.if ( SUCCEEDED( CoCreateInstance ( CLSID_DragDropHelper, NULL, CLSCTX_INPROC_SERVER, IID_IDropTargetHelper, (void**) &m_piDropHelper ) )){m_bUseDnDHelper = true;}
}
然后,四个拖放
函数调用IDropTargetHelper
方法.下面是一例:
DROPEFFECT CMultiFilerView::OnDragEnter(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point)
{
DROPEFFECT dwEffect = DROPEFFECT_NONE;//**省略,来确定dwEffect的代码.**调用拖放助手.if ( m_bUseDnDHelper ){//`DnD`助手需要一个`IDataObject`接口,因此请从`COleDataObject`取一个接口.注意,`假`参数表明`GetIDataObject`不会处理`AddRef()`返回的接口,因此不要`Release()`它. IDataObject* piDataObj = pDataObject->GetIDataObject ( FALSE );m_piDropHelper->DragEnter ( GetSafeHwnd(), piDataObj, &point, dwEffect );}return dwEffect;
}
GetIDataObject()
是COleDataObject
中返回IDataObject
接口的一个未记录的函数
.
最后,视图的析构器
释放COM
对象.
CMultiFilerView::~CMultiFilerView()
{if ( NULL != m_piDropHelper )m_piDropHelper->Release();
}
顺便,如果没有安装PlatformSDK
,则可能没有IDropTargetHelper
接口和关联GUID
的定义.我在每个MultiFiler
示例中都包含
了必要的定义;
只需取消注释它们,你就可以开始了.
如果想在当MultiFiler
是拖放源
时,使用IDragSourceHelper
绘画整齐的拖放图像
,则要自己摸索了.
这篇关于2408,02资管与拖放的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!