拖放概述

本主题概述了 Windows Presentation Foundation (WPF) 应用程序中的拖放支持。 拖放通常指一种数据传输方法:使用鼠标(或一些其他指针设备)选择一个或多个对象,将其拖至用户界面 (UI) 中的某些所需拖放目标之上并放置。

WPF 中的拖放支持

拖放操作通常涉及两个参与方:拖动对象所源自的拖动源和接收放置对象的拖放目标。 拖放源和拖放目标可以是在同一个应用程序中或不同应用程序中的 UI 元素。

可以使用拖放操作的对象的类型和数量是完全任意的。 例如,文件、文件夹和内容选择是通过拖放操作操作的一些更常见的对象。

拖放操作期间执行的特定操作特定于应用程序,通常由上下文确定。 例如,将所选文件从一个文件夹拖动到同一存储设备上的另一个文件夹会默认移动文件,而将文件从通用命名约定(UNC)共享拖动到本地文件夹会默认复制文件。

WPF 提供的拖放设施拥有高度的灵活性并可自定义,以便支持各种拖放方案。 拖放支持在单个应用程序中或在不同应用程序之间操作对象。 WPF 应用程序和其他 Windows 应用程序之间的拖放功能也完全受支持。

在 WPF 中,任何 UIElementContentElement 都可以参与拖放。 拖放操作所需的事件和方法在 DragDrop 类中定义。 UIElementContentElement 类包含 DragDrop 附加事件的别名,以便在将 UIElementContentElement 继承为基元素时,这些事件显示在类成员列表中。 附加到这些事件的事件处理程序会附加到基础 DragDrop 附加事件,并接收相同的事件数据实例。 有关详细信息,请参阅 UIElement.Drop 事件。

重要

在 Internet 区域中 OLE 拖放无效。

数据传输

拖放属于广义的数据传输。 数据传输包括拖放和复制和粘贴操作。 拖放操作类似于使用系统剪贴板将数据从一个对象或应用程序传输到另一个对象的复制和粘贴或剪切和粘贴操作。 这两种类型的操作都需要:

  • 提供数据的源对象。

  • 临时存储已传输数据的方法。

  • 接收数据的目标对象。

在复制和粘贴操作中,系统剪贴板用于临时存储传输的数据;在拖放操作中,使用 DataObject 来存储数据。 从概念上讲,数据对象由包含实际数据的一个或多个 Object 对和相应的数据格式标识符组成。

拖动源通过调用静态 DragDrop.DoDragDrop 方法并将传输的数据传递给它来启动拖放操作。 如有必要,DoDragDrop 方法会自动将数据包装在 DataObject 中。 为了更好地控制数据格式,可以将数据包装在 DataObject 中,然后再将其传递给 DoDragDrop 方法。 拖放目标负责从 DataObject 中提取数据。 有关处理数据对象的详细信息,请参阅 数据和数据对象

拖放操作的源和目标为 UI 元素;但是,实际传输的数据通常没有可视表示形式。 可以编写代码来为拖动操作提供数据的可视化表现形式,一种常见情况是在 Windows 资源管理器中拖动文件时。 默认情况下,系统通过更改游标来提示拖放操作对数据的影响,例如数据是被移动还是被复制,以此向用户提供反馈。

拖放效果

拖放操作可能会对传输的数据产生不同的影响。 例如,可以复制数据,也可以移动数据。 WPF 定义一个 DragDropEffects 枚举,可用于指定拖放操作的效果。 在拖动源中,可以指定源将在 DoDragDrop 方法中允许的效果。 在拖放目标中,可以指定目标在 Effects 类的 DragEventArgs 属性的中预期的效果。 当拖放目标指定其在 DragOver 事件中的预期效果时,该信息将被传递回 GiveFeedback 事件中的拖动源。 拖动源则使用此信息通知用户拖放目标想要对数据产生的效果。 放置数据时,拖放目标指定其在 Drop 事件中的实际效果。 该信息作为 DoDragDrop 方法的返回值传回拖动源。 如果拖放目标返回的效果不在 allowedEffects的拖动源列表中,则拖放操作将取消,而不会发生任何数据传输。

请务必记住,在 WPF 中,DragDropEffects 值仅用于在拖动源和拖放目标之间提供有关拖放操作效果的通信。 拖放操作的实际效果取决于你在应用程序中编写适当的代码。

例如,拖放目标可以指定在其中放置数据的效果是移动数据。 但是,若要移动数据,必须将数据添加到目标元素并从源元素中删除。 源元素可能指示它允许移动数据,但如果不提供从源元素中删除数据的代码,最终结果将是复制数据,而不是移动数据。

拖放事件

拖放操作支持事件驱动模型。 拖动源和拖放目标都使用一组标准事件来处理拖放操作。 下表总结了标准的拖放事件。 它们是 DragDrop 类中的附加事件。 有关附加事件的详细信息,请参阅 附加事件概述

拖动源事件

事件 总结
GiveFeedback 此事件在拖放操作期间持续发生,并使拖放源能够向用户提供反馈信息。 通常通过更改鼠标指针外观来指示拖放目标允许的效果这一方式来提供这种反馈。 这是浮升事件。
QueryContinueDrag 当拖放操作期间键盘或鼠标按钮状态发生更改,并使拖放源能够根据键/按钮状态取消拖放操作时,会发生此事件。 这是浮升事件。
PreviewGiveFeedback GiveFeedback 的隧道版本。
PreviewQueryContinueDrag QueryContinueDrag 的隧道版本。

删除目标事件

事件 总结
DragEnter 将对象拖到拖放目标的边界中时发生此事件。 这是浮升事件。
DragLeave 将对象拖出拖放目标边界时发生此事件。 这是浮升事件。
DragOver 在拖放目标的边界内拖动(移动)对象时会持续发生此事件。 这是浮升事件。
Drop 将对象放置在拖放目标上时发生此事件。 这是浮升事件。
PreviewDragEnter DragEnter 的隧道版本。
PreviewDragLeave DragLeave 的隧道版本。
PreviewDragOver DragOver 的隧道版本。
PreviewDrop Drop 的隧道版本。

若要处理对象的实例的拖放事件,请为上表中列出的事件添加处理程序。 若要处理类级别的拖放事件,请重写相应的虚拟 On*Event 和 On*PreviewEvent 方法。 有关详细信息,请参阅 控制基类对于路由事件的类处理

实现拖放

UI 元素可以是拖动源、拖放目标或两者均可。 若要实现基本的拖放操作,请编写代码来启动拖放操作并处理已删除的数据。 可以通过处理可选拖放事件增强拖放体验。

若要实现基本的拖放操作,需要完成以下任务:

  • 确定将成为拖动源的元素。 拖动源可以是 UIElementContentElement

  • 在拖动源上创建一个事件处理程序,用于启动拖放操作。 此事件通常是 MouseMove 事件。

  • 在拖动源事件处理程序中,调用 DoDragDrop 方法以启动拖放操作。 在 DoDragDrop 调用中,指定要传输的拖动源、要传输的数据以及允许的效果。

  • 标识将作为拖放目标的元素。 拖放目标可以是 UIElementContentElement

  • 在拖放目标上,将 AllowDrop 属性设置为 true

  • 在拖放目标中,创建一个 Drop 事件处理程序来处理拖放的数据。

  • Drop 事件处理程序中,使用 GetDataPresentGetData 方法从 DragEventArgs 中提取数据。

  • Drop 事件处理程序中,使用数据执行所需的拖放操作。

可以通过创建自定义 DataObject 和处理可选拖动源和拖放目标事件来增加拖放实现,如以下任务中所示:

  • 若要传输自定义数据或多个数据项,请创建 DataObject 以传递给 DoDragDrop 方法。

  • 若要在拖动过程中执行其他操作,请处理拖放目标上的 DragEnterDragOverDragLeave 事件。

  • 若要更改鼠标指针的外观,请处理拖动源上的 GiveFeedback 事件。

  • 若要更改取消拖放操作的方式,请处理拖动源上的 QueryContinueDrag 事件。

拖放示例

本部分介绍如何为 Ellipse 元素实现拖放。 Ellipse 既是拖动源也是拖放目标。 传输的数据是椭圆 Fill 属性的字符串表示形式。 以下 XAML 显示了 Ellipse 元素及其处理的拖放相关事件。 有关如何实现拖放的完整步骤,请参阅 演练:在用户控件上启用拖放

<Ellipse Height="50" Width="50" Fill="Green"
     MouseMove="ellipse_MouseMove"
     GiveFeedback="ellipse_GiveFeedback"
     AllowDrop="True"
     DragEnter="ellipse_DragEnter" DragLeave="ellipse_DragLeave"
     DragOver="ellipse_DragOver" Drop="ellipse_Drop" />

使元素成为拖动源

拖动源对象用于:

  • 标识拖动发生的时候。

  • 启动拖放操作。

  • 标识要传输的数据。

  • 指定允许拖放操作对传输的数据产生的效果。

拖动源还可以向用户提供有关允许的操作(移动、复制、无)的反馈,并且可以根据其他用户输入取消拖放操作,例如在拖动过程中按 ESC 键。

应用程序负责确定何时发生拖动,然后通过调用 DoDragDrop 方法启动拖放操作。 通常情况下,这是在按下鼠标按钮的同时,要拖动的元素上发生 MouseMove 事件时。 以下示例演示如何从 Ellipse 元素的 MouseMove 事件处理程序中启动拖放操作,使其成为拖动源。 传输的数据是椭圆 Fill 属性的字符串表示形式。

private void ellipse_MouseMove(object sender, MouseEventArgs e)
{
    Ellipse ellipse = sender as Ellipse;
    if (ellipse != null && e.LeftButton == MouseButtonState.Pressed)
    {
        DragDrop.DoDragDrop( ellipse,
                             ellipse.Fill.ToString(),
                             DragDropEffects.Copy);
    }
}
Private Sub Ellipse_MouseMove(ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseEventArgs)
    Dim ellipse = TryCast(sender, Ellipse)
    If ellipse IsNot Nothing AndAlso e.LeftButton = MouseButtonState.Pressed Then
        DragDrop.DoDragDrop(ellipse, ellipse.Fill.ToString(), DragDropEffects.Copy)
    End If
End Sub

MouseMove 事件处理程序内部,调用 DoDragDrop 方法以启动拖放操作。 DoDragDrop 方法采用三个参数:

  • dragSource – 对作为传输数据的源的依赖项对象的引用;这通常是 MouseMove 事件的源。

  • data - 包含传输的数据的对象,包装在 DataObject中。

  • allowedEffects - 指定拖放操作允许效果的 DragDropEffects 枚举值之一。

任何可序列化对象都可以在 data 参数中传递。 如果数据尚未被封装在 DataObject中,它将自动被封装在新的 DataObject中。 若要传递多个数据项,必须自行创建 DataObject,并将其传递给 DoDragDrop 方法。 有关详细信息,请参阅 数据和数据对象

allowedEffects 参数用于指定拖动源允许拖放目标对传输的数据进行什么操作。 拖动源的常见值为 CopyMoveAll

备注

删除目标还能够指定它打算对已删除的数据做出哪些影响。 例如,如果拖放目标无法识别需拖入的数据类型,则可以通过将允许的效果设置为 None来拒绝拖入的数据。 它通常在其 DragOver 事件处理程序中执行此操作。

拖动源还可以可选地处理 GiveFeedbackQueryContinueDrag 事件。 这些事件都具有使用的默认处理程序,除非将事件标记为已处理。 通常将忽略这些事件,除非有特定需要更改其默认行为。

对拖动源进行拖动时,持续引发 GiveFeedback 事件。 此事件的默认处理程序会检查拖动源是否在有效放置目标之上。 如果是,它会检查拖放目标的允许的效果。 然后向最终用户提供有关允许的放置效果的反馈。 通常通过将鼠标光标更改为非放置、复制或移动光标实现此操作。 仅当需要使用自定义游标向用户提供反馈时,才应处理此事件。 如果处理此事件,请确保将其标记为已处理,以便默认处理程序不会替代你的处理程序。

对拖动源进行拖动时,持续引发 QueryContinueDrag 事件。 您可以处理此事件,通过检测 ESC、SHIFT、CTRL 和 ALT 键的状态以及鼠标按钮的状态,来确定结束拖放操作的动作。 如果按下 ESC 键,则此事件的默认处理程序将取消拖放操作,并在释放鼠标按钮时删除数据。

谨慎

在拖放操作过程中,将持续引发这些事件。 因此,应避免事件处理程序中的资源密集型任务。 例如,使用缓存的游标,而不是每次引发 GiveFeedback 事件时创建新游标。

使元素作为拖放目标

作为拖放目标的对象用于:

  • 指定其是有效的拖放目标。

  • 当它拖动到目标之上时,向拖动源作出响应。

  • 检查传输的数据的格式是否为可以接收的格式。

  • 处理已放置的数据。

若要指定一个元素是拖放目标,请将其 AllowDrop 属性设置为 true。 然后,元素中将引发拖放目标事件,以便处理这些事件。

评估拖放目标时,将执行命中测试以确定光标是否位于元素的视觉对象上。 某些控件(如 Canvas)没有可视元素,不能用作拖放目标,除非添加了可视元素。 将 Canvas.Background 属性设置为任何颜色,以创建用颜色填充 Canvas 的视觉对象。 若要保持 Canvas 透明并将其启用为拖放目标,请将 Background 属性设置为 Transparent

在拖放操作期间,在拖放目标上发生以下事件序列:

  1. DragEnter

  2. DragOver

  3. DragLeaveDrop

将数据拖到拖放目标的边界中时发生 DragEnter 事件。 通常处理此事件以提供拖放操作效果的预览(如果适合您的应用程序)。 不要在 DragEnter 事件中设置 DragEventArgs.Effects 属性,因为它将在 DragOver 事件中覆盖。

以下示例演示 Ellipse 元素的 DragEnter 事件处理程序。 此代码通过保存当前 Fill 画笔来预览拖放操作的效果。 然后,它使用 GetDataPresent 方法检查被拖动到椭圆上的 DataObject 是否包含可以转换为 Brush的字符串数据。 如果是,则使用 GetData 方法提取数据。 然后,它将转换为 Brush 并应用于椭圆。 在 DragLeave 事件处理程序中还原更改。 如果无法将数据转换为 Brush,则不执行任何操作。

private Brush _previousFill = null;
private void ellipse_DragEnter(object sender, DragEventArgs e)
{
    Ellipse ellipse = sender as Ellipse;
    if (ellipse != null)
    {
        // Save the current Fill brush so that you can revert back to this value in DragLeave.
        _previousFill = ellipse.Fill;

        // If the DataObject contains string data, extract it.
        if (e.Data.GetDataPresent(DataFormats.StringFormat))
        {
            string dataString = (string)e.Data.GetData(DataFormats.StringFormat);

            // If the string can be converted into a Brush, convert it.
            BrushConverter converter = new BrushConverter();
            if (converter.IsValid(dataString))
            {
                Brush newFill = (Brush)converter.ConvertFromString(dataString);
                ellipse.Fill = newFill;
            }
        }
    }
}
Private _previousFill As Brush = Nothing
Private Sub Ellipse_DragEnter(ByVal sender As System.Object, ByVal e As System.Windows.DragEventArgs)
    Dim ellipse = TryCast(sender, Ellipse)
    If ellipse IsNot Nothing Then
        ' Save the current Fill brush so that you can revert back to this value in DragLeave.
        _previousFill = ellipse.Fill

        ' If the DataObject contains string data, extract it.
        If e.Data.GetDataPresent(DataFormats.StringFormat) Then
            Dim dataString = e.Data.GetData(DataFormats.StringFormat)

            ' If the string can be converted into a Brush, convert it.
            Dim converter As New BrushConverter()
            If converter.IsValid(dataString) Then
                Dim newFill As Brush = CType(converter.ConvertFromString(dataString), Brush)
                ellipse.Fill = newFill
            End If
        End If
    End If
End Sub

将数据拖动到拖放目标上方时持续发生 DragOver 事件。 此事件和拖动源上的 GiveFeedback 事件成对出现。 在 DragOver 事件处理程序中,通常使用 GetDataPresentGetData 方法来检查传输的数据是否符合放置目标可处理的格式。 还可以检查是否按下任何修饰键,这通常指示用户是否打算移动或复制操作。 执行这些检查后,设置 DragEventArgs.Effects 属性,以通知拖动源删除数据将产生什么效果。 拖动源在 GiveFeedback 事件参数中接收此信息,并可以设置适当的光标来向用户提供反馈。

以下示例演示 Ellipse 元素的 DragOver 事件处理程序。 此代码检查被拖动到椭圆上的 DataObject 是否包含可转换为 Brush的字符串数据。 如果是,则它将 DragEventArgs.Effects 属性设置为 Copy。 这将向拖动源指示可以将数据复制到椭圆。 如果无法将数据转换为 Brush,则 DragEventArgs.Effects 属性设置为 None。 这将向拖动源指示椭圆不是数据的有效拖放目标。

private void ellipse_DragOver(object sender, DragEventArgs e)
{
    e.Effects = DragDropEffects.None;

    // If the DataObject contains string data, extract it.
    if (e.Data.GetDataPresent(DataFormats.StringFormat))
    {
        string dataString = (string)e.Data.GetData(DataFormats.StringFormat);

        // If the string can be converted into a Brush, allow copying.
        BrushConverter converter = new BrushConverter();
        if (converter.IsValid(dataString))
        {
            e.Effects = DragDropEffects.Copy | DragDropEffects.Move;
        }
    }
}
Private Sub Ellipse_DragOver(ByVal sender As System.Object, ByVal e As System.Windows.DragEventArgs)
    e.Effects = DragDropEffects.None

    ' If the DataObject contains string data, extract it.
    If e.Data.GetDataPresent(DataFormats.StringFormat) Then
        Dim dataString = e.Data.GetData(DataFormats.StringFormat)

        ' If the string can be converted into a Brush, convert it.
        Dim converter As New BrushConverter()
        If converter.IsValid(dataString) Then
            e.Effects = DragDropEffects.Copy Or DragDropEffects.Move
        End If
    End If
End Sub

当数据被拖出目标边界而不被删除时,将发生 DragLeave 事件。 处理此事件以撤消在 DragEnter 事件处理程序中执行的任何操作。

以下示例演示 Ellipse 元素的 DragLeave 事件处理程序。 此代码通过将保存的 Brush 应用到椭圆来撤销 DragEnter 事件处理程序中执行的预览。

private void ellipse_DragLeave(object sender, DragEventArgs e)
{
    Ellipse ellipse = sender as Ellipse;
    if (ellipse != null)
    {
        ellipse.Fill = _previousFill;
    }
}
Private Sub Ellipse_DragLeave(ByVal sender As System.Object, ByVal e As System.Windows.DragEventArgs)
    Dim ellipse = TryCast(sender, Ellipse)
    If ellipse IsNot Nothing Then
        ellipse.Fill = _previousFill
    End If
End Sub

数据放置在拖放目标上方时,发生 Drop 事件;默认情况下,释放鼠标按钮时,发生此事件。 在 Drop 事件处理程序中,使用 GetData 方法从 DataObject 提取传输的数据,并执行应用程序所需的任何数据处理。 Drop 事件结束拖放操作。

以下示例演示 Ellipse 元素的 Drop 事件处理程序。 此代码应用拖放操作的效果,类似于 DragEnter 事件处理程序中的代码。 它会检查被拖动到椭圆上的 DataObject 是否包含可以转换为 Brush的字符串数据。 如果是,则 Brush 应用于椭圆。 如果无法将数据转换为 Brush,则不执行任何操作。

private void ellipse_Drop(object sender, DragEventArgs e)
{
    Ellipse ellipse = sender as Ellipse;
    if (ellipse != null)
    {
        // If the DataObject contains string data, extract it.
        if (e.Data.GetDataPresent(DataFormats.StringFormat))
        {
            string dataString = (string)e.Data.GetData(DataFormats.StringFormat);

            // If the string can be converted into a Brush,
            // convert it and apply it to the ellipse.
            BrushConverter converter = new BrushConverter();
            if (converter.IsValid(dataString))
            {
                Brush newFill = (Brush)converter.ConvertFromString(dataString);
                ellipse.Fill = newFill;
            }
        }
    }
}
Private Sub Ellipse_Drop(ByVal sender As System.Object, ByVal e As System.Windows.DragEventArgs)
    Dim ellipse = TryCast(sender, Ellipse)
    If ellipse IsNot Nothing Then

        ' If the DataObject contains string data, extract it.
        If e.Data.GetDataPresent(DataFormats.StringFormat) Then
            Dim dataString = e.Data.GetData(DataFormats.StringFormat)

            ' If the string can be converted into a Brush, convert it.
            Dim converter As New BrushConverter()
            If converter.IsValid(dataString) Then
                Dim newFill As Brush = CType(converter.ConvertFromString(dataString), Brush)
                ellipse.Fill = newFill
            End If
        End If
    End If
End Sub

另请参阅