在Windows操作系统中,资源管理器(Explorer)是用户与文件系统交互的主要界面。实现与资源管理器的拖放(Drag and Drop)功能可以极大地提高应用程序的用户体验。本文将介绍如何在MFC应用程序中实现拖放文件到资源管理器,以及从资源管理器拖放文件回应用程序的技术。
要实现拖放文件到资源管理器,通常会使用COleDataSource
类。这个类提供了处理OLE数据源的功能,允许定义拖放操作的数据格式。以下是实现拖放文件到资源管理器的基本步骤:
COleDataSource
派生一个类。CFSTR_FILEDESCRIPTOR
格式的数据,然后使用CacheGlobalData
方法。OnRenderFileData
方法。OnRenderFileData
方法中处理CFSTR_FILECONTENTS
,并直接写入数据。
首先,使用Visual Studio 2005(或更早版本)生成一个基于对话框的MFC应用程序。然后,使用资源编辑器向对话框中添加一个列表控件,并将其与类型为CListCtrl
的DDX控件变量关联,命名为m_fileList
。接下来,在OnInitDialog
方法中添加以下代码来设置列表控件:
BOOL CExplorerDelayDropDlg::OnInitDialog()
{
CDialog::OnInitDialog();
AfxOleInit();
m_fileList.SetExtendedStyle(LVS_EX_FULLROWSELECT);
m_fileList.InsertColumn(0, _T("文件"), LVCFMT_LEFT, 75);
m_fileList.InsertColumn(1, _T("详情"), LVCFMT_LEFT, 175);
for (TCHAR c = _T('A'); c < _T('F'); c++)
{
CString text1, text2;
text1.Format(_T("%c.txt"), c);
text2.Format(_T("文件满是%cs"), c);
m_fileList.SetItemText(m_fileList.InsertItem(c - _T('A'), text1), 1, text2);
}
return TRUE;
}
上述代码仅用一些虚拟的文件名填充了列表控件。注意,调用了AfxOleInit
方法(如果应用程序已经支持OLE,则不需要这样做)。
COleDataSource
派生一个类
由于使用的是延迟数据渲染,需要从COleDataSource
派生一个类,以便可以重写OnRenderFileData
方法(默认版本仅返回FALSE)。首先,向项目中添加一个名为CMyOleDataSource
的类(派生自COleDataSource
)。
class CMyOleDataSource : public COleDataSource
{
DECLARE_DYNAMIC(CMyOleDataSource)
}
然后,需要添加一个OnRenderFileData
的重写,如下所示:
BOOL CMyOleDataSource::OnRenderFileData(LPFORMATETC lpFormatEtc, CFile* pFile)
{
if (lpFormatEtc->cfFormat == RegisterClipboardFormat(CFSTR_FILECONTENTS))
{
HGLOBAL hGlob = NULL;
const int buffSize = 512;
hGlob = GlobalAlloc(GMEM_FIXED, buffSize);
if (hGlob)
{
LPBYTE pBytes = (LPBYTE)GlobalLock(hGlob);
if (pBytes)
{
memset(pBytes, (int)m_Files.GetAt(lpFormatEtc->lindex)[0], buffSize);
pFile->Write(pBytes, buffSize);
}
GlobalUnlock(hGlob);
}
GlobalFree(hGlob);
return TRUE;
}
return COleDataSource::OnRenderFileData(lpFormatEtc, pFile);
}
在上述代码中,通过填充512字节的特定字符来创建虚拟文件。在更实际的场景中,需要根据lindex
参数检索特定文件,然后从远程源或可能的存档中检索该文件。
LVN_BEGINDRAG
通知
需要处理LVN_BEGINDRAG
通知,如下所示。可以使用属性框添加一个处理程序,或者在对话框类中手动添加一个ON_NOTIFY
处理程序。
void CExplorerDelayDropDlg::OnBeginDrag(NMHDR *pNMHDR, LRESULT *pResult)
{
UINT uFileCount = m_fileList.GetSelectedCount();
UINT uBuffSize = sizeof(FILEGROUPDESCRIPTOR) + (uFileCount-1) * sizeof(FILEDESCRIPTOR);
HGLOBAL hFileDescriptor = GlobalAlloc(GHND | GMEM_SHARE, uBuffSize);
if (hFileDescriptor)
{
FILEGROUPDESCRIPTOR* pGroupDescriptor = (FILEGROUPDESCRIPTOR*)GlobalLock(hFileDescriptor);
if (pGroupDescriptor)
{
FILEDESCRIPTOR* pFileDescriptorArray = (FILEDESCRIPTOR*)((LPBYTE)pGroupDescriptor + sizeof(UINT));
pGroupDescriptor->cItems = uFileCount;
POSITION pos = m_fileList.GetFirstSelectedItemPosition();
int index = 0;
m_DataSrc.m_Files.RemoveAll();
while (NULL != pos)
{
int nSelItem = m_fileList.GetNextSelectedItem(pos);
ZeroMemory(&pFileDescriptorArray[index], sizeof(FILEDESCRIPTOR));
lstrcpy(pFileDescriptorArray[index].cFileName, m_fileList.GetItemText(nSelItem, 0));
m_DataSrc.m_Files.Add(pFileDescriptorArray[index].cFileName);
pFileDescriptorArray[index].dwFlags = FD_FILESIZE|FD_ATTRIBUTES;
pFileDescriptorArray[index].nFileSizeLow = 512;
pFileDescriptorArray[index].nFileSizeHigh = 0;
pFileDescriptorArray[index].dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
index++;
}
}
else
{
GlobalFree(hFileDescriptor);
}
}
GlobalUnlock(hFileDescriptor);
FORMATETC etcDescriptor = { RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
m_DataSrc.CacheGlobalData(RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR), hFileDescriptor, &etcDescriptor);
FORMATETC etcContents = { RegisterClipboardFormat(CFSTR_FILECONTENTS), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
m_DataSrc.DelayRenderFileData(RegisterClipboardFormat(CFSTR_FILECONTENTS), &etcContents);
DROPEFFECT dwEffect = m_DataSrc.DoDragDrop(DROPEFFECT_COPY | DROPEFFECT_MOVE);
if (dwEffect == DROPEFFECT_NONE)
{
GlobalFree(hFileDescriptor);
}
*pResult = 0;
}
这就是基本的技术。显然,这只是展示了基本的技术。需要编写额外的代码来使整个过程更加流畅。例如,如果从远程设备拉取文件,文件写入之前会有延迟,在这种情况下,可能需要显示一个进度条,或者确保主应用程序不会完全冻结。但基本技术将保持不变。