如何将虚拟文件从您的应用程序拖放到 Windows Explorer






4.96/5 (39投票s)
展示如何使用 CFSTR_FILEDESCRIPTOR 和 CFSTR_FILECONTENTS 来执行虚拟文件的拖放操作
引言
我当时正在开发一个 FTP 客户端应用程序,需要支持与 Windows 资源管理器之间的拖放操作。将文件从资源管理器拖放到客户端非常简单,没有给我带来太多麻烦,但是将文件拖放回资源管理器却不容易。由于源文件实际上并不存在,需要从 FTP 服务器下载才能让资源管理器访问,因此排除了使用 CF_HDROP
。在尝试了各种方法之后,我终于找到了使用 CFSTR_FILEDESCRIPTOR
和 CFSTR_FILECONTENTS
,它们很适合我的目的。在本文中,我将演示一个简单的对话框应用程序,它允许您将不存在的文件从您的应用程序拖放到资源管理器中。
基本技术
- 从
COleDataSource
派生一个类 - 分配全局内存,并创建
CFSTR_FILEDESCRIPTOR
格式的数据,然后对COleDataSource
派生类使用CacheGlobalData
- 在此派生类中重写
OnRenderFileData
- 在
OnRenderFileData
重写中,处理CFSTR_FILECONTENTS
,并直接写入到CFSTR_FILECONTENTS
创建演示应用程序
使用 Visual Studio 2005(或者如果您没有 2005,则可以使用更早的版本)生成一个基于对话框的默认 MFC 应用程序。使用资源编辑器将列表控件添加到对话框,并将其与类型为 CListCtrl
且名为 m_fileList
的 DDX 控件变量关联。现在将以下代码添加到 OnInitDialog
以设置列表控件。
BOOL CExplorerDelayDropDlg::OnInitDialog() { CDialog::OnInitDialog(); . . . AfxOleInit(); m_fileList.SetExtendedStyle(LVS_EX_FULLROWSELECT); m_fileList.InsertColumn(0, _T("File"), LVCFMT_LEFT,75); m_fileList.InsertColumn(1, _T("Details"), LVCFMT_LEFT,175); for(TCHAR c = _T('A'); c < _T('F'); c++) { CString text1, text2; text1.Format(_T("%c.txt"),c); text2.Format(_T("File full of %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) { // We only need to handle CFSTR_FILECONTENTS 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) { // lpFormatEtc->lindex can be used to identify // the file that's being copied memset(pBytes, (int) m_Files.GetAt( lpFormatEtc->lindex)[0], buffSize); pFile->Write(pBytes,buffSize); } GlobalUnlock(hGlob); } GlobalFree(hGlob); // Need to return TRUE to indicate success to Explorer return TRUE; } return COleDataSource::OnRenderFileData( lpFormatEtc, pFile); }
请注意,在上面的代码中,我通过用特定的字符填充 512 字节来创建虚拟文件,该字符标识该文件。在更真实的场景中,您需要检索由 lindex
参数标识的特定文件,然后从远程源或从存档中检索该文件。
现在,我们只需要处理 LVN_BEGINDRAG
通知,如下所示。您可以使用“属性”框添加处理程序,或者手动在对话框类中添加 ON_NOTIFY
处理程序。
void CExplorerDelayDropDlg::OnBeginDrag(NMHDR *pNMHDR, LRESULT *pResult) { UINT uFileCount = m_fileList.GetSelectedCount(); // The CFSTR_FILEDESCRIPTOR format expects a // FILEGROUPDESCRIPTOR structure followed by an // array of FILEDESCRIPTOR structures, one for // each file being dropped UINT uBuffSize = sizeof(FILEGROUPDESCRIPTOR) + (uFileCount-1) * sizeof(FILEDESCRIPTOR); HGLOBAL hFileDescriptor = GlobalAlloc ( GHND | GMEM_SHARE, uBuffSize ); if(hFileDescriptor) { FILEGROUPDESCRIPTOR* pGroupDescriptor = (FILEGROUPDESCRIPTOR*) GlobalLock ( hFileDescriptor ); if(pGroupDescriptor) { // Need a pointer to the FILEDESCRIPTOR array 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 ); // For the CFSTR_FILEDESCRIPTOR format, we use // CacheGlobalData since we make that data available // immediately FORMATETC etcDescriptor = { RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; m_DataSrc.CacheGlobalData ( RegisterClipboardFormat( CFSTR_FILEDESCRIPTOR), hFileDescriptor, &etcDescriptor ); // For CFSTR_FILECONTENTS, we use DelayRenderFileData // as this data will have to come from a non-physical // device, like an FTP site, an add-on device, or an archive 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 ); // Free memory in case of failure if(dwEffect == DROPEFFECT_NONE ) { GlobalFree( hFileDescriptor ); } *pResult = 0; }
结论
就这样。 显然,这只是展示了基本技术。您需要编写额外的代码才能使整个过程顺利进行。例如,如果您正在从远程设备拉取文件,则在写入文件之前会有延迟,在这种情况下,您需要显示进度条,或确保您的主应用程序不会完全冻结。但基本技术将保持不变。
参考
有关与资源管理器的拖放的更多信息,请阅读 Mike Dunn 的精彩文章:如何在您的程序和资源管理器之间实现拖放,其中介绍了如何使用 CF_HDROP
将现有文件传输到资源管理器。
历史
- 2006 年 9 月 13 日 - 首次发表文章