65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (39投票s)

2006年9月13日

CPOL

3分钟阅读

viewsIcon

301014

downloadIcon

2163

展示如何使用 CFSTR_FILEDESCRIPTOR 和 CFSTR_FILECONTENTS 来执行虚拟文件的拖放操作

引言

我当时正在开发一个 FTP 客户端应用程序,需要支持与 Windows 资源管理器之间的拖放操作。将文件从资源管理器拖放到客户端非常简单,没有给我带来太多麻烦,但是将文件拖放回资源管理器却不容易。由于源文件实际上并不存在,需要从 FTP 服务器下载才能让资源管理器访问,因此排除了使用 CF_HDROP。在尝试了各种方法之后,我终于找到了使用 CFSTR_FILEDESCRIPTORCFSTR_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 日 - 首次发表文章
© . All rights reserved.