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

如何实现程序与资源管理器之间的拖放

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (105投票s)

2000 年 11 月 25 日

viewsIcon

914942

downloadIcon

11522

逐步介绍如何实现应用程序和资源管理器窗口之间的文件拖放。

引言

最近在 CodeProject 论坛上看到不少关于如何在程序和资源管理器窗口之间实现拖放的问题。正如 Windows 中的许多事情一样,一旦知道怎么做,它似乎就变得容易了,但找到答案却可能相当费力。在这篇文章中,我将演示如何设置拖放功能,使您的程序能够接受来自资源管理器窗口的拖放,并充当拖放源,让您的用户可以将文件拖到资源管理器窗口中。

示例项目是一个 MFC 应用程序,文章假设您熟悉 C++、MFC 以及 COM 对象和接口的使用。如果您需要有关 COM 对象和接口的帮助,请查看我的 COM 入门 文章。该程序名为 MultiFiler,是一个简单的实用工具,它充当拖放的“暂存区域”。您可以将任意数量的文件拖到 MultiFiler 中,它会在列表中显示所有这些文件。然后,您可以使用 Shift 或 Ctrl 键将文件拖回到资源管理器窗口中,分别指示资源管理器移动或复制原始文件。

与资源管理器进行拖放

如您所知,资源管理器允许您在资源管理器窗口和桌面之间拖动文件。当您开始一个拖放操作时,拖动源(drop source)的资源管理器窗口会创建一个实现 IDataObject 接口的 COM 对象,并将一些数据放入该对象中。拖入的窗口(drop target)随后使用 IDataObject 方法读取该数据;这就是它知道要放置哪些文件的方式。

如果您使用 ClipSpy 这样的查看器检查 IDataObject 中的数据,您会发现资源管理器将多种数据格式放入了数据对象中。

重要的格式是 CF_HDROP。其他格式是资源管理器为其自身注册的自定义格式。如果我们编写一个注册了其窗口作为放置目标的应用程序,并且知道如何读取 CF_HDROP 数据,那么我们就能够接受放置的文件。同样,如果我们能够用 CF_HDROP 数据填充数据对象,资源管理器将允许我们的应用程序成为拖动源。那么,CF_HDROP 格式中包含什么呢?继续阅读...

DROPFILES 数据结构

那么 CF_HDROP 格式究竟是什么?事实证明,它只是一个 DROPFILES struct。还有一个 HDROP 类型,它只是一个指向 DROPFILES struct 的指针。

DROPFILES 并不是一个非常复杂的结构。这是它的定义:

struct DROPFILES
{
    DWORD pFiles;    // offset of file list
    POINT pt;        // drop point (client coords)
    BOOL fNC;        // is it on NonClient area and pt is in screen coords
    BOOL fWide;      // wide character flag
};

结构定义中没有列出文件名列表。该列表被格式化为双空终止的字符串列表。但是它存储在哪里?它实际上存储在 fWide 成员之后,而 pFiles 保存着(相对于结构开头的)列表中存储在内存中的偏移量。在拖放操作中唯一使用的另一个成员是 fWide,它指示文件名是 ANSI 字符还是 Unicode 字符。

接受来自资源管理器的拖放

接受拖放比发起拖放要容易得多,所以我将先介绍接受。

您的窗口有两种方式可以接受拖放。第一种方式是 Windows 3.1 的遗留方法,使用 WM_DROPFILES 消息。另一种方式是注册您的窗口为 OLE 放置目标。

旧方法 - WM_DROPFILES

要使用旧方法,您首先需要在窗口中设置“接受文件”样式。对于对话框,这在“扩展样式”页面上,如下图所示:

如果您想在运行时设置此样式,请调用 DragAcceptFiles() API,它接受两个参数。第一个是您的主窗口句柄,第二个是 TRUE,表示您可以接受拖放。如果您的主窗口是 CView 而不是对话框,您需要在运行时设置此样式。

无论您使用哪种方法,您的窗口都将成为放置目标。当您从资源管理器窗口拖动文件或文件夹并将其放入您的窗口时,您的窗口将接收到 WM_DROPFILES 消息。WM_DROPFILES 消息的 WPARAM 是一个 HDROP,其中列出了被放置的文件。您可以使用三个 API 从 HDROP 中获取文件列表:DragQueryFile()DragQueryPoint()DragFinish()

  • DragQueryFile() 执行两项操作:返回正在拖动的文件数量,并枚举文件列表。DragQueryPoint() 返回 DROPFILES structpt 成员。DragFinish() 释放拖放过程中分配的内存。
  • DragQueryFile() 接受四个参数:HDROP、要返回的文件名的索引、由调用者分配的用于保存名称的缓冲区以及缓冲区的大小(以字符为单位)。如果将索引设置为 -1,DragQueryFile() 将返回列表中文件的数量。否则,它将返回文件名中的字符数。您可以将此返回值与 0 进行比较,以判断调用是否成功。
  • DragQueryPoint() 接受两个参数:HDROP 和指向 POINT struct 的指针,该结构接收 DROPFILES structpt 成员的值。DragFinish() 只接受一个参数,即 HDROP

典型的 WM_DROPFILES 处理程序如下所示:

void CMyDlg::OnDropFiles ( HDROP hdrop ) 
{
UINT  uNumFiles;
TCHAR szNextFile [MAX_PATH];

    // Get the # of files being dropped.
    uNumFiles = DragQueryFile ( hdrop, -1, NULL, 0 );

    for ( UINT uFile = 0; uFile < uNumFiles; uFile++ )
        {
        // Get the next filename from the HDROP info.
        if ( DragQueryFile ( hdrop, uFile, szNextFile, MAX_PATH ) > 0 )
            {
            // ***
            // Do whatever you want with the filename in szNextFile.
            // ***
            }
        }

    // Free up memory.
    DragFinish ( hdrop );
}

如果您只需要文件列表,则不需要 DragQueryPoint()。(事实上,我从来没有真正需要使用它。)

新方法 - 使用 OLE 放置目标

接受拖放的另一种方法是注册您的窗口为 OLE 放置目标。通常,这样做需要您编写一个实现 IDropTarget 接口的 C++ 类。但是,MFC 有一个 COleDropTarget 类,它为我们处理了这个问题。过程根据您的主窗口是对话框还是 CView 有所不同,所以我将在下面分别介绍。

使 CView 成为放置目标

CView 本身已经内置了一些拖放支持,但它通常不被激活。要激活它,请在视图中添加一个 COleDropTarget 成员变量,然后在视图的 OnInitialUpdate() 中调用其 Register() 函数,将视图注册为放置目标,如下所示:

void CMyView::OnInitialUpdate()
{
    CView::OnInitialUpdate();

    // Register our view as a drop target.
    // m_droptarget is a COleDropTarget member of CMyView.
    m_droptarget.Register ( this );
}

完成后,您需要重写四个虚拟函数,当用户在视图上拖动时会调用它们:

  • OnDragEnter():当光标进入您的窗口时调用。
  • OnDragOver():当光标在您的窗口内移动时调用。
  • OnDragLeave():当光标离开您的窗口时调用。
  • OnDrop():当用户在您的窗口中放置时调用。
OnDragEnter()

OnDragEnter() 是第一个被调用的函数。其原型是:

DROPEFFECT CView::OnDragEnter( COleDataObject* pDataObject, 
               DWORD dwKeyState, CPoint point );

参数如下:

  • pDataObject:指向 COleDataObject 的指针,其中包含正在拖动的数据。
  • dwKeyState:一组标志,指示按下了哪个鼠标按钮以及按下了哪些 Shift 键(如果有)。标志为 MK_CONTROLMK_SHIFTMK_ALTMK_LBUTTONMK_MBUTTONMK_RBUTTON
  • point:光标位置,以视图的客户端坐标表示。

OnDragEnter() 返回一个 DROPEFFECT 值,该值告诉 OLE 是否接受放置,如果接受,则显示什么光标。这些值及其含义如下:

  • DROPEFFECT_NONE:不接受放置。光标变为:
  • DROPEFFECT_MOVE:数据将被放置目标*移动*。光标变为:
  • DROPEFFECT_COPY:数据将被放置目标*复制*。光标变为:
  • DROPEFFECT_LINK:数据将与放置目标*链接*。光标变为:

通常,在 OnDragEnter() 中,您会检查正在拖动的数据,看它是否满足您的标准。如果不满足,则返回 DROPEFFECT_NONE 以拒绝拖放。否则,您可以根据您打算如何处理数据返回其他值之一。

OnDragOver()

如果您从 OnDragEnter() 返回的值不是 DROPEFFECT_NONE,那么每当鼠标光标在您的窗口内移动时,都会调用 OnDragOver()OnDragOver() 的原型是:

DROPEFFECT CView::OnDragOver ( COleDataObject* pDataObject, 
               DWORD dwKeyState, CPoint point );

参数和返回值与 OnDragEnter() 相同。OnDragOver() 允许您根据光标位置和 Shift 键状态返回不同的 DROPEFFECT 值。例如,如果您的主视图窗口有几个区域,显示不同的信息列表,并且您只想在一个区域允许放置,您会检查 point 参数中的光标位置,如果光标不在该区域,则返回 DROPEFFECT_NONE

关于 Shift 键,您通常按照以下方式响应它们:

  • 按住 SHIFT 键(dwKeyState 中的 MK_SHIFT):返回 DROPEFFECT_MOVE
  • 按住 CONTROL 键(MK_CONTROL):返回 DROPEFFECT_COPY
  • 同时按住(MK_SHIFT | MK_CONTROL):返回 DROPEFFECT_LINK

这些只是指导方针,尽管最好遵守它们,因为它们是资源管理器使用的。但是,如果某些操作(复制、移动或链接)对您的应用程序没有意义,您不必*返回*相应的 DROPEFFECT。例如,在 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() 相同。如果放置成功完成(“成功”的定义由您自己决定),则返回值为 TRUE,否则返回 FALSE

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;  // initialized in constructor
};

在此示例中,构造函数传递了指向主窗口的指针,以便放置目标方法可以发送消息并在对话框中执行其他操作。您可以根据需要进行更改。然后,您需要实现前一节所述的四个拖放方法。唯一的区别是额外的 CWnd* 参数,它是调用时鼠标光标所在的窗口的指针。

有了这个新类后,您需要向对话框添加一个放置目标成员变量,并在 OnInitDialog() 中调用其 Register() 函数:

BOOL CMyDialog::OnInitDialog()
{
    // Register our dialog as a drop target.
    // m_droptarget is a CMyDropTarget member of CMyDialog.
    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;

    // Get the HDROP data from the data object.
    hg = pDataObject->GetGlobalData ( CF_HDROP );
    
    if ( NULL == hg )
        return FALSE;

    hdrop = (HDROP) GlobalLock ( hg );

    if ( NULL == hdrop )
        {
        GlobalUnlock ( hg );
        return FALSE;
        }

    // Read in the list of files here...

    GlobalUnlock ( hg );

    // Return TRUE/FALSE to indicate success/failure 
}

两种方法的总结

处理 WM_DROPFILES

  • Windows 3.1 的遗留方法;将来可能会被移除。
  • 无法自定义拖放过程 - 只能在放置发生后采取行动。
  • 无法检查要放置的原始数据。
  • 如果您不需要任何花哨的自定义,这种方法编写起来要容易得多。

使用 OLE 放置目标

  • 使用 COM 接口,这是一种更现代、支持更好的机制。
  • 通过 CViewCOleDropTarget 提供良好的 MFC 支持。
  • 允许对拖放操作进行完全控制。
  • 提供对原始 IDataObject 的访问,以便您可以访问任何数据格式。
  • 需要更多的代码,但一旦您编写一次,就可以将其复制粘贴到新项目中。

MultiFiler 如何接受拖放

演示项目 ZIP 文件实际上包含三个 MultiFiler 项目。每个项目都使用我描述的一种技术来接受来自资源管理器窗口的拖放。当用户在 MultiFiler 窗口上放置时,所有被放置的文件都会被添加到列表控件中,如下图所示:

MultiFiler 会自动消除重复文件,因此每个文件最多只会在列表中出现一次。

如果您使用的是 Windows 2000 并运行使用 OLE 放置目标的 MultiFiler,您会注意到拖动图像是新式淡入效果的样式:

这不是免费获得的,但它是使用 OLE 放置目标时可以进行的自定义的一个很好的例子。我将在文章末尾解释如何实现这一点。

发起拖放

要让资源管理器接受拖动的文件,我们只需要创建一个 CF_HDROP 数据并将其放入数据对象中。当然,如果*那么*简单,我就没什么可写的了。DROPFILES struct 的创建有点棘手(因为它的大小不固定),但同样,一旦您编写了代码(或者,一旦*我*写了代码!),您就可以在任何地方重用它。

当您在列表控件中选择文件并拖动它们时,MultiFiler 会发起拖放。该控件在发生这种情况时发送一个 LVN_BEGINDRAG 通知消息,因此 MultiFiler 会在该时间创建数据对象并将其交给 OLE 来启动拖放操作。

创建 DROPFILES 的步骤如下:

  1. 枚举列表控件中所有选定的项目,将它们放入字符串列表中。
  2. 跟踪每个字符串添加到字符串列表时所需的长度。
  3. DROPFILES 本身以及文件名列表分配内存。
  4. 填充 DROPFILES 成员。
  5. 将文件名列表复制到分配的内存中。

我将逐步介绍 MultiFiler 代码,以便您确切地了解如何设置 DROPFILES。如果您想查看 MultiFiler 项目中的源代码,所有三个项目都以相同的方式执行此操作,因此您可以查看其中任何一个。

第一步是将所有选定的文件名放入一个列表中,并跟踪容纳所有字符串所需的内存。

void CMultiFilerDlg::OnBegindragFilelist(NMHDR* pNMHDR, LRESULT* pResult) 
{
CStringList    lsDraggedFiles;
POSITION       pos;
CString        sFile;
UINT           uBuffSize = 0;

    // For every selected item in the list,
    // put the filename into lsDraggedFiles.
    // c_FileList is our dialog's CListCtrl.
    pos = c_FileList.GetFirstSelectedItemPosition();

    while ( NULL != pos )
        {
        nSelItem = c_FileList.GetNextSelectedItem ( pos );
        // put the filename in sFile
        sFile = c_FileList.GetItemText ( nSelItem, 0 );    

        lsDraggedFiles.AddTail ( sFile );

        // Calculate the # of chars required to hold this string.
        uBuffSize += lstrlen ( sFile ) + 1;
        }

此时,uBuffSize 保存所有字符串(包括空终止符)的总长度(以字符为单位)。我们加 1 是为了最后的空终止符,然后乘以 sizeof(TCHAR) 将字符转换为字节。然后我们加上 sizeof(DROPFILES) 以获得最终所需的缓冲区大小。

    // Add 1 extra for the final null char,
    // and the size of the DROPFILES struct.
    uBuffSize = sizeof(DROPFILES) + sizeof(TCHAR) * (uBuffSize + 1);

现在我们知道需要多少内存,我们就可以分配它。在进行拖放时,您可以使用 GlobalAlloc() 从堆中分配内存。

HGLOBAL    hgDrop;
DROPFILES* pDrop;

    // Allocate memory from the heap for the DROPFILES struct.
    hgDrop = GlobalAlloc ( GHND | GMEM_SHARE, uBuffSize );

    if ( NULL == hgDrop )
        return;

然后我们使用 GlobalLock() 直接访问内存。

    pDrop = (DROPFILES*) GlobalLock ( hgDrop );

    if ( NULL == pDrop )
        {
        GlobalFree ( hgDrop );
        return;
        }

现在我们可以开始填充 DROPFILESGlobalAlloc() 调用中的 GHND 标志会初始化内存为零,因此我们只需要设置几个成员。

    // Fill in the DROPFILES struct.
    pDrop->pFiles = sizeof(DROPFILES);

#ifdef _UNICODE
    // If we're compiling for Unicode, set the Unicode flag in the struct to
    // indicate it contains Unicode strings.
    pDrop->fWide = TRUE;
#endif

请注意,pFiles 成员并不指示 DROPFILES struct 的大小;它是文件列表的偏移量。但由于文件列表位于结构末尾之后,因此其偏移量与结构的大小相同。

现在我们可以将所有文件名复制到内存中,然后解锁缓冲区。

TCHAR* pszBuff;

    // Copy all the filenames into memory after
    // the end of the DROPFILES struct.
    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 对象并将我们的数据放入其中。我们还需要一个 FORMATETC struct 来描述剪贴板格式(CF_HDROP)以及数据如何存储(HGLOBAL)。

COleDataSource datasrc;
FORMATETC etc = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };

    // Put the data in the data source.
    datasrc.CacheGlobalData ( CF_HDROP, hgDrop, &etc );

现在,这足以发起拖放,但还有最后一件事需要处理。由于 MultiFiler 接受拖放,它会愉快地接受我们即将发起的拖放。虽然这并不会造成灾难,但也不太好。因此,我们将向数据源添加另一条数据,采用我们注册的自定义剪贴板格式。我们的 OnDragEnter()OnDragOver() 函数将检查此格式,如果存在,它们将不接受放置。

// The global g_uCustomClipbrdFormat is set like this:
// UINT g_uCustomClipbrdFormat = RegisterClipboardFormat 
// ( _T("MultiFiler_3BCFE9D1_6D61_4cb6_9D0B_3BB3F643CA82") );

    // Add our own custom data, so we know that
    // the drag originated from our window.
    // The data will just be a dummy bool.
HGLOBAL hgBool;

    hgBool = GlobalAlloc ( GHND | GMEM_SHARE, sizeof(bool) );

    if ( NULL == hgBool )
        {
        GlobalFree ( hgDrop );
        return;
        }

    // Put the data in the data source.
    etc.cfFormat = g_uCustomClipbrdFormat;

    datasrc.CacheGlobalData ( g_uCustomClipbrdFormat, hgBool, &etc );

请注意,我们不必将数据设置为任何特定值 - 数据存在于数据源中这一点很重要。

现在我们已经组织好了数据,我们可以开始拖放操作了!我们调用 COleDataSourceDoDragDrop() 方法,该方法直到拖放完成才会返回。唯一的参数是一个或多个 DROPEFFECT 值,它们指示我们将允许用户执行哪些操作。它返回一个 DROPEFFECT 值,指示用户想对数据做什么,或者如果放置被中止或未被目标接受,则返回 DROPEFFECT_NONE

DROPEFFECT dwEffect;

    dwEffect = datasrc.DoDragDrop ( DROPEFFECT_COPY | DROPEFFECT_MOVE );

在我们的例子中,我们只允许复制和移动。在拖放过程中,用户可以按住 Control 或 Shift 键来更改操作。出于某种原因,传递 DROPEFFECT_LINK 不会让资源管理器创建快捷方式,这就是为什么我在上面的调用中没有包含 DROPEFFECT_LINK

DoDragDrop() 返回后,我们检查返回值。如果它是 DROPEFFECT_MOVEDROPEFFECT_COPY,则拖放成功完成,因此我们从主窗口的列表控件中删除所有选定的文件。如果是 DROPEFFECT_NONE,事情就有点棘手了。在 Windows 9x 上,这意味着操作被取消了。然而,在 NT/2000 上,shell 也为移动操作返回 DROPEFFECT_NONE。(这是故意的!有关详细信息,请参阅 KB 文章 Q182219。)因此,在 NT 上,我们必须手动检查文件是否已被移动,如果是,则从列表控件中删除它们。代码有点长,所以我不会在这里重复。如果您有兴趣了解它的工作原理,请查看 MultiFiler 源代码。

我们做的最后一件事是,如果拖放被取消,则释放分配的内存。如果已完成,则放置目标拥有内存,我们不得释放它。下面是检查 DoDragDrop() 返回值的代码,只是省略了刚才提到的 NT/2000 代码。

    switch ( dwEffect )
        {
        case DROPEFFECT_COPY:
        case DROPEFFECT_MOVE:
            {
            // The files were copied or moved.
            // Note: Don't call GlobalFree() because
            // the data will be freed by the drop target.

            // ** Omitted code to remove list control items. **
            }
        break;

        case DROPEFFECT_NONE:
            {
            // ** Omitted code for NT/2000 that checks
            // if the operation actually succeeded. **

            // The drag operation wasn't accepted, or was canceled, so we 
            // should call GlobalFree() to clean up.

            GlobalFree ( hgDrop );
            GlobalFree ( hgBool );
            }
        break;
        }
}

其他细节

您还可以右键单击 MultiFiler 列表控件,以获取一个包含四个命令的上下文菜单。它们非常简单,用于管理列表中的选择和清空列表。

哦,对了,我承诺要解释如何在 Windows 2000 上获得酷炫的拖动图像!实际上非常简单。shell 支持一个新的 COM 类,称为 CLSID_DragDropHelper,它有两个接口:IDragSourceHelperIDropTargetHelperIDropTargetHelper 负责绘制拖动图像。它有四个方法,其名称应该很熟悉: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)
{
    // Create an instance of the shell drag and drop helper object.
    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;

    // ** Omitted - code to determine dwEffect. **

    // Call the drag and drop helper.
    if ( m_bUseDnDHelper )
        {
        // The DnD helper needs an IDataObject interface, so get one from
        // the COleDataObject.  Note that the FALSE param means that
        // GetIDataObject will not AddRef() the returned interface, so 
        // we do not Release() it.
        IDataObject* piDataObj = pDataObject->GetIDataObject ( FALSE ); 

        m_piDropHelper->DragEnter ( GetSafeHwnd(), piDataObj, 
                                    &point, dwEffect );
        }

    return dwEffect;
}

GetIDataObject()COleDataObject 中一个未公开的函数,它返回一个 IDataObject 接口。(有时您可以通过查看 MFC 头文件来找到一些有用的东西!)

最后,视图的析构函数释放 COM 对象。

CMultiFilerView::~CMultiFilerView()
{
    if ( NULL != m_piDropHelper )
        m_piDropHelper->Release();
}

顺便说一句,如果您没有安装最新的 Platform SDK,您可能没有 IDropTargetHelper 接口和相关 GUID 的定义。我在每个 MultiFiler 示例中都包含了必要的定义;只需取消注释它们即可。

如果您想知道如何反向操作 - 在 MultiFiler 作为拖动源时使用 IDragSourceHelper 来绘制炫酷的拖动图像 - 该主题的文档不是很清晰,而且肯定比使用 IDropTargetHelper 更难,所以我还没有开始研究它。

© . All rights reserved.