一.适用于拖放一般文件的WM_DROPFILES消息
WM_DROPFILES消息支持我们拖动文件资源管理器下面的文件夹和文件到自己的窗口,为此我们首先要在接受文件拖放窗口的初始化函数中添加如下代码,表示该窗口支持文件拖放:
this->DragAcceptFiles();
利用类向导或手动添加消息映射处理WM_DROPFILES消息,其中映射函数
_afx_msg void OnDropFiles(HDROP hDropInfo)_中参数hDropInfo保存着拖放到窗口的文件信息,可以通过下面的代码获取到拖放到当前窗口的文件路径,
TCHAR szFileName[MAX_PATH + 1] = {};
UINT nFiles = DragQueryFile(hDrop, 0xFFFFFFFF, szFileName, MAX_PATH); //获取拖放文件的个数
for (int i = 0; i < nFiles; ++i)
{
DragQueryFile(hDrop, i, szFileName, MAX_PATH);
if (PathIsDirectory(szFileName))
{
//处理目录程序
}
}
DragFileName函数的详细用法可以参考:https://docs.microsoft.com/zh-cn/windows/win32/api/shellapi/nf-shellapi-dragqueryfilea?redirectedfrom=MSDN.
处理WM_DROPFILES消息非常简单,但是局限性也很大,他不支持显示拖放的缩略图并且不支持拖放文件以外的数据类型例如从outlook中直接拖放邮件到我们的程序中,要实现这样的效果我们需要使用更高级的OLE拖放。
二.ole拖放
本节我会详细介绍如何使用ole支持多个outlook邮件拖放,并在自己的窗口显示拖放缩略图,并在结尾附上源代码
在MFC程序中要使用ole相关接口,需要在程序实例初始化函数InitInstance()中调用
AfxOleInit();
ole拖放的关键是COleDropTarget类,这是一个利用嵌套类实现的COM对象,有关COM的知识非常难理解,但是好在我们并不需要知道他的原理就可以使用他。
为了使自己的窗口(演示使用的是派生于CDialogEx的对话框类型窗口)支持ole拖放我们首先要做的是实现一个派生于的COleDropTarget的myOleDropTarget类,并重写下面几个虚函数:
virtual DROPEFFECT OnDragEnter(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point);
virtual BOOL OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point);
virtual void OnDragLeave(CWnd* pWnd);
virtual DROPEFFECT OnDragOver(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point);
这四个函数分别是拖放进入窗口,执行拖放,离开窗口和结束拖放时会调用的函数。为了使我们的窗口在拖放时能通知到这些函数我们需要以下两步操作(如果窗口派生于CView的话可以直接重写这四个虚函数,CView中提供了这些虚函数):**
1.在窗口类中添加成员变量:myOleDrop m_oleDrop;
2. 在窗口OnCreate函数中调用下面代码j注册窗口:
m_oleDrop.Register(this);
OnDragEnter和OnDragOver的返回值DROPEFFECT决定了拖放鼠标进入窗口时显示的情况,
一般会用到下面三种:
DROPEFFECT_COPY:复制拖放,
DROPEFFECT_NONE:禁止拖放,
DROPEFFECT_MOVE:移动拖放,
当我们发现拖放的数据是我们想要处理的数据类型时我们可以返回DROPEFFECT_MOVE或DROPEFFECT_COPY,如果数据不是我们感兴趣的可以返回DROPEFFECT_NONE。
拖放操作中我们最关心的是怎么获取拖放数据,一般情况下我们在OnDrop函数中获取拖放数据最合理,OnDrop函数有四个参数:
CWnd* pWnd 窗口类
COleDataObject* pDataObject 保存IDataObject接口的类对象
DROPEFFECT dropEffect 前面提到的拖放类型
CPoint point 保存执行时鼠标的位置
最值得我们注意的是pDataObject,这是一个COleDataObject类型的指针,COleDataObject其实是MFC对IDataObject接口的一个包装,可以让我们方便的操作IDataObject接口,IDataObject是一个com接口,它保存了拖放到窗口的数据,下面我给出在OnDrop中取出拖动数据是普通文件的代码
BOOL myOleDrop::OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point)
{
FORMATETC fc; //拖放数据的类型
STGMEDIUM stgm; //数据的传输方式和实际数据
pDataObject->BeginEnumFormats(); //开始枚举所有数据类型
while (pDataObject->GetNextFormat(&fc))
{
if (pDataObject->IsDataAvailable(fc.cfFormat)) //数据可以获取
{
pDataObject->GetData(fc.cfFormat, &stgm); //获取数据
//普通文件的拖放
if (fc.cfFormat == CF_HDROP&& stgm.tymed==TYMED_HGLOBAL)
{
HDROP hDrop= (HDROP)GlobalLock(stgm.hGlobal);
TCHAR szFileName[MAX_PATH + 1] = {};
UINT nFiles = DragQueryFile(hDrop, 0xFFFFFFFF, szFileName, MAX_PATH); //获取拖放文件的个数
for (int i = 0; i < nFiles; ++i)
{
DragQueryFile(hDrop, i, szFileName, MAX_PATH);
if (PathIsDirectory(szFileName))
{
//处理目录
}
}
GlobalUnlock(stgm.hGlobal);
}
}
}
return true;
}
上面的代码简单演示了如何获取ole拖放的数据,其中FORMATETC和STGMEDIUM是比较重要的两个结构体,FORMATETC中关键的字段,cfFormat 保存了拖放的数据类型,tymed保存了数据的传输方式。STGMEDIUM中tymed保存了数据的存储方式,指引我们该以那种方式获取数据,有下面几种数据传输方式:
enum tagTYMED
{
TYMED_HGLOBAL = 1 全局类型,
TYMED_FILE = 2 文件类型,
TYMED_ISTREAM = 4 流类型,
TYMED_ISTORAGE = 8, 复合文档,有层次结构的多个流和Storage集合
TYMED_GDI = 16, gdi
TYMED_MFPICT = 32, 没用过,是一种图片类型
TYMED_ENHMF = 64, 没有过,图元文件
TYMED_NULL = 0 空,不传输任何数据
} TYMED;
例如拖动普通文件时cfFormat 是CF_HDROP类型,_pDataObject->GetData(fc.cfFormat, &stgm)_获取STGMEDIUM信息,stgm.tymed=TYMED_HGLOBAL表示数据保存在全局,此时,stgm.hGlobal是有效的,表明我们需要通过stgm.hGlobal获取数据,此外我们还可以处理任何我们感兴趣的数据。
但是很遗憾我们并不能通过以上代码获取到从outlook拖动到我们窗口的邮件,因为它并不是以CF_HDROP类型来传递数据,下面我给出OnDrop函数如何取得拖动到我们窗口的多个outlook邮件的信息,并把它们保存为msg文件的代码(代码为了简洁我没有做太多执行成功的判断):
BOOL myOleDrop::OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point)
{
// TODO: Add your specialized code here and/or call the base class
FORMATETC fc;
STGMEDIUM stgm;
HRESULT hr;
FORMATETC file_desc = { RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR) ,0,DVASPECT_CONTENT,-1,TYMED_HGLOBAL }; //注册剪贴板数据类型
if (pDataObject->GetData(file_desc.cfFormat, &stgm))
{
LPFILEGROUPDESCRIPTOR p = (LPFILEGROUPDESCRIPTOR)GlobalLock(stgm.hGlobal);
for (int n = 0; n < p->cItems; ++n)
{
FORMATETC file_content= { RegisterClipboardFormat(CFSTR_FILECONTENTS) ,0,DVASPECT_CONTENT, n , TYMED_HGLOBAL | TYMED_ISTREAM | TYMED_ISTORAGE };
IDataObject *poj = pDataObject->GetIDataObject(FALSE); //获取IDataObject接口
hr = poj->GetData(&file_content, &stgm);
if(stgm.tymed==TYMED_ISTREAM)
{
}
if (stgm.tymed == TYMED_ISTORAGE)
{
TCHAR szTempPath[MAX_PATH + 1] = {};
::GetTempPath(MAX_PATH, szTempPath);
CString strFileName = szTempPath;
strFileName += (p->fgd[n]).cFileName;
IStorage * pp;
hr = ::StgCreateDocfile(strFileName.GetString(), STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,0,&pp); //创建复合文档
hr=stgm.pstg->CopyTo(NULL, NULL, NULL, pp); //给文件中填入数据
if (pp)
pp->Release();
}
if(stgm.tymed==TYMED_HGLOBAL)
{
}
}
GlobalUnlock(stgm.hGlobal);
}
}
上面的代码可以实现将从outlook中拖放到窗口的邮件保存成msg文件放到用户的temp文件夹下,
outlook邮件拖放采用的数据格式是非常规的LPFILEGROUPDESCRIPTOR格式,需要我们提前注册这种数据格式,具体方法参考上面的代码,LPFILEGROUPDESCRIPTOR中有两项cItems和fgd数组,分别保存着文件个数和文件信息。
接着我们注册 file_content类型(方法参考上面的代码),他保存着LPFILEGROUPDESCRIPTOR中文件的数据,FORMATETC中lindex表示第几个数据(如果有两个邮件有两个数据可以生成两个msg文件),它采用的传输方法是TYMED_ISTORAGE,这是一种结构化的文件存储方式,我们可以用它创建复合文档,outlook文件也是一种复合文档,关于IStorage接口不做多介绍,感兴趣可以在https://docs.microsoft.com/zh-cn/windows/win32/api/objidl/nn-objidl-istorage参考用法。
现在还剩一个问题就是我们的程序不支持拖放时显示缩略图,首先我们在myoleDrag类中添加一个IDropTargetHelper 的指针,这是一个com接口指针,
IDropTargetHelper * pDrgHelper;
接下来我们在构造函数中初始化这个接口指针,这会牵扯到一些com的知识,不过我们不需要了解,因为这些代码是固定的:
HRESULT hr = ::CoCreateInstance(CLSID_DragDropHelper, NULL, CLSCTX_INPROC_SERVER, IID_IDropTargetHelper, (LPVOID*)&pDrgHelper);
最后我们只需分别在OnDragEnter函数结尾添加
if (pDrgHelper)
pDrgHelper->DragEnter(pWnd->GetSafeHwnd(), pDataObject->GetIDataObject(FALSE), &point, DROPEFFECT_COPY);
在OnDrop函数结尾添加
if (pDrgHelper)
pDrgHelper->Drop( pDataObject->GetIDataObject(FALSE), &point, DROPEFFECT_COPY);
** OnDragLeave结尾添加**
if (pDrgHelper)
pDrgHelper->DragLeave();
OnDragOver结尾添加
if (pDrgHelper)
pDrgHelper->DragOver(&point, DROPEFFECT_COPY);
这样我们就实现了当拖放在我们的窗口时,会显示拖放的缩略图。
本文演示了拖放普通文件和outlook邮件的处理方法,实际开发时可以选择处理自己感兴趣的数据,包括自己定义注册的剪贴板数据格式,方法类似
下面给出完整的myOleDrop.h和myOleDrop.cpp处理邮件拖放和普通拖放文件,并将拖放到窗口的邮件保存在temp文件夹下,
myOleDrop.h
#pragma once
// myOleDrop command target
class myOleDrop : public COleDropTarget
{
DECLARE_DYNAMIC(myOleDrop)
public:
myOleDrop();
virtual ~myOleDrop();
private:
IDropTargetHelper * pDrgHelper;
DROPEFFECT m_dpEffect;
protected:
DECLARE_MESSAGE_MAP()
public:
virtual DROPEFFECT OnDragEnter(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point);
virtual BOOL OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point);
virtual void OnDragLeave(CWnd* pWnd);
virtual DROPEFFECT OnDragOver(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point);
};
myOleDrop.cpp
#include "stdafx.h"
#include "myOleDrop.h"
// myOleDrop
IMPLEMENT_DYNAMIC(myOleDrop, COleDropTarget)
myOleDrop::myOleDrop():m_dpEffect(DROPEFFECT_NONE)
{
//获取IDragTargetHelper接口
HRESULT hr = ::CoCreateInstance(CLSID_DragDropHelper, NULL, CLSCTX_INPROC_SERVER, IID_IDropTargetHelper, (LPVOID*)&pDrgHelper);
}
myOleDrop::~myOleDrop()
{
if (pDrgHelper)
pDrgHelper->Release();
}
BEGIN_MESSAGE_MAP(myOleDrop, COleDropTarget)
END_MESSAGE_MAP()
// myOleDrop message handlers
DROPEFFECT myOleDrop::OnDragEnter(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point)
{
// TODO: Add your specialized code here and/or call the base class
m_dpEffect = DROPEFFECT_NONE;
FORMATETC file_desc = { RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR) ,0,DVASPECT_CONTENT,-1,TYMED_HGLOBAL };
FORMATETC fc;
pDataObject->BeginEnumFormats();
while (pDataObject->GetNextFormat(&fc))
{
if (fc.cfFormat == file_desc.cfFormat||fc.cfFormat==CF_HDROP)
{
m_dpEffect = DROPEFFECT_COPY;
break;
}
}
if (pDrgHelper)
pDrgHelper->DragEnter(pWnd->GetSafeHwnd(), pDataObject->GetIDataObject(FALSE), &point, m_dpEffect);
return m_dpEffect;
}
BOOL myOleDrop::OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point)
{
// TODO: Add your specialized code here and/or call the base class
HDROP hDrop=NULL;
FORMATETC fc;
STGMEDIUM stgm;
HRESULT hr;
FORMATETC file_desc = { RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR) ,0,DVASPECT_CONTENT,-1,TYMED_HGLOBAL };
if (pDataObject->GetData(file_desc.cfFormat, &stgm))
{
LPFILEGROUPDESCRIPTOR p = (LPFILEGROUPDESCRIPTOR)GlobalLock(stgm.hGlobal);
for (int n = 0; n < p->cItems; ++n)
{
FORMATETC file_content= { RegisterClipboardFormat(CFSTR_FILECONTENTS) ,0,DVASPECT_CONTENT, n , TYMED_HGLOBAL | TYMED_ISTREAM | TYMED_ISTORAGE };
IDataObject *poj = pDataObject->GetIDataObject(FALSE);
hr = poj->GetData(&file_content, &stgm);
if(stgm.tymed==TYMED_ISTREAM)
{
}
if (stgm.tymed == TYMED_HGLOBAL)
{
}
if (stgm.tymed == TYMED_ISTORAGE)
{
TCHAR szTempPath[MAX_PATH + 1] = {};
::GetTempPath(MAX_PATH, szTempPath);
CString strFileName = szTempPath;
strFileName += (p->fgd[n]).cFileName;
IStorage * pp;
hr = ::StgCreateDocfile(strFileName.GetString(), STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,0,&pp);
hr=stgm.pstg->CopyTo(NULL, NULL, NULL, pp);
if (pp)
pp->Release();
}
}
GlobalUnlock(stgm.hGlobal);
}
else if (pDataObject->GetData(CF_HDROP,&stgm))
{
hDrop = (HDROP)::GlobalLock(stgm.hGlobal);
TCHAR szFileName[MAX_PATH + 1] = {};
UINT nFiles = DragQueryFile(hDrop, -1, szFileName, MAX_PATH);
for (int i = 0; i < nFiles; ++i)
{
DragQueryFile(hDrop, i, szFileName, MAX_PATH);
if (PathIsDirectory(szFileName))
{
//处理目录
}
}
::GlobalUnlock(stgm.hGlobal);
}
if (pDrgHelper)
pDrgHelper->Drop( pDataObject->GetIDataObject(FALSE), &point, dropEffect);
return COleDropTarget::OnDrop(pWnd, pDataObject, dropEffect, point);
}
void myOleDrop::OnDragLeave(CWnd* pWnd)
{
// TODO: Add your specialized code here and/or call the base class
if (pDrgHelper)
pDrgHelper->DragLeave();
COleDropTarget::OnDragLeave(pWnd);
}
DROPEFFECT myOleDrop::OnDragOver(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point)
{
// TODO: Add your specialized code here and/or call the base class
if (pDrgHelper)
pDrgHelper->DragOver(&point, m_dpEffect);
return m_dpEffect;
}
由于本人水平有限,代码中难免有错误和写的不好的地方,希望路过各位批评指正,感谢!