指南:在用户控件上启用拖放功能

本演练演示如何创建自定义用户控件,该控件可以参与 Windows Presentation Foundation(WPF)中的拖放数据传输。

在本演练中,将创建一个表示圆形的自定义 WPF UserControl。 你将在控件上实现功能,以便通过拖放启用数据传输。 例如,如果从一个圆形控件拖到另一个圆形控件,则会将填充颜色数据从源圆形复制到目标圆形。 如果从一个圆形控件拖到 TextBox,则填充颜色的字符串表示形式将复制到 TextBox。 你还将创建一个小应用程序,该应用程序包含两个面板控件和一个 TextBox,用以测试拖放功能。 你将编写可使面板处理放置的圆形数据的代码,这样就可以将圆形从一个面板的 Children 集合移动或复制到其他面板。

本演练演示了以下任务:

  • 创建自定义用户控件。

  • 使用户控件成为拖动源。

  • 使用户控件成为拖放目标。

  • 使面板能够接收用户控件中放置的数据。

先决条件

需要 Visual Studio 才能完成本演练。

创建应用程序项目

在本节中,你将创建应用程序基础结构,其中包括一个具有两个面板和一个 TextBox 的主页。

  1. 在 Visual Basic 或名为 DragDropExample的 Visual C# 中创建新的 WPF 应用程序项目。 有关详细信息,请参阅 演练:我的第一个 WPF 桌面应用程序

  2. 打开 MainWindow.xaml。

  3. 在开始和结束 Grid 标记间添加以下标记。

    此标记为测试应用程序创建用户界面。

    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <StackPanel Grid.Column="0"
                Background="Beige">
        <TextBox Width="Auto" Margin="2"
                 Text="green"/>
    </StackPanel>
    <StackPanel Grid.Column="1"
                Background="Bisque">
    </StackPanel>
    

向项目添加新用户控件

在本部分中,你将向项目添加新的用户控件。

  1. 在“项目”菜单上,选择 “添加用户控件”

  2. 在“添加新项 对话框中,将名称更改为 Circle.xaml,然后单击 添加

    Circle.xaml和其后台代码已被添加到项目中。

  3. 打开 Circle.xaml。

    此文件将包含用户控件的用户界面元素。

  4. 将以下标记添加到根 Grid,以创建一个简单的用户控件,该控件具有蓝色圆圈作为其 UI。

    <Ellipse x:Name="circleUI" 
             Height="100" Width="100"
             Fill="Blue" />
    
  5. 打开Circle.xaml.cs或Circle.xaml.vb。

  6. 在 C# 中,在无参数构造函数后面添加以下代码以创建复制构造函数。 在 Visual Basic 中,添加以下代码来创建无参数构造函数和复制构造函数。

    若要允许用户复制用户控件,请在代码隐藏文件中添加复制构造函数方法。 在简化的圆形用户控件中,将只复制该用户控件的填充和大小。

    public Circle(Circle c)
    {
        InitializeComponent();
        this.circleUI.Height = c.circleUI.Height;
        this.circleUI.Width = c.circleUI.Height;
        this.circleUI.Fill = c.circleUI.Fill;
    }
    
    Public Sub New()
        ' This call is required by the designer.
        InitializeComponent()
    End Sub
    
    Public Sub New(ByVal c As Circle)
        InitializeComponent()
        Me.circleUI.Height = c.circleUI.Height
        Me.circleUI.Width = c.circleUI.Height
        Me.circleUI.Fill = c.circleUI.Fill
    End Sub
    

将用户控件添加到主窗口

  1. 打开 MainWindow.xaml。

  2. 将以下 XAML 添加到 Window 起始标记中,以创建对当前应用程序的 XML 命名空间引用。

    xmlns:local="clr-namespace:DragDropExample"
    
  3. 在第一个 StackPanel中,添加以下 XAML 以在第一个面板中创建 Circle 用户控件的两个实例。

    <local:Circle Margin="2" />
    <local:Circle Margin="2" />
    

    面板的完整 XAML 如下所示。

    <StackPanel Grid.Column="0"
                Background="Beige">
        <TextBox Width="Auto" Margin="2"
                 Text="green"/>
        <local:Circle Margin="2" />
        <local:Circle Margin="2" />
    </StackPanel>
    <StackPanel Grid.Column="1"
                Background="Bisque">
    </StackPanel>
    

在用户控件中实现拖动源事件

在本节中,将替代 OnMouseMove 方法并启动拖放操作。

如果开始拖动(按下鼠标按钮并移动鼠标),您将把要传输的数据打包成 DataObject。 在这种情况下,圆形控件将打包三个数据项:其填充颜色的字符串表示形式、其高度的双精度表示形式以及其自身的副本。

启动拖放操作

  1. 打开Circle.xaml.cs或Circle.xaml.vb。

  2. 添加以下 OnMouseMove 替代,以便为 MouseMove 事件提供类处理。

    protected override void OnMouseMove(MouseEventArgs e)
    {
        base.OnMouseMove(e);
        if (e.LeftButton == MouseButtonState.Pressed)
        {
            // Package the data.
            DataObject data = new DataObject();
            data.SetData(DataFormats.StringFormat, circleUI.Fill.ToString());
            data.SetData("Double", circleUI.Height);
            data.SetData("Object", this);
    
            // Initiate the drag-and-drop operation.
            DragDrop.DoDragDrop(this, data, DragDropEffects.Copy | DragDropEffects.Move);
        }
    }
    
    Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Input.MouseEventArgs)
        MyBase.OnMouseMove(e)
        If e.LeftButton = MouseButtonState.Pressed Then
            ' Package the data.
            Dim data As New DataObject
            data.SetData(DataFormats.StringFormat, circleUI.Fill.ToString())
            data.SetData("Double", circleUI.Height)
            data.SetData("Object", Me)
    
            ' Inititate the drag-and-drop operation.
            DragDrop.DoDragDrop(Me, data, DragDropEffects.Copy Or DragDropEffects.Move)
        End If
    End Sub
    

    OnMouseMove 替代执行下列任务:

    • 检查在鼠标移动时是否按下了鼠标左键。

    • 将圆形数据打包到 DataObject。 在这种情况下,圆形控件将打包三个数据项:其填充颜色的字符串表示形式、其高度的双精度表示形式以及其自身的副本。

    • 调用静态 DragDrop.DoDragDrop 方法以启动拖放操作。 将以下三个参数传递给 DoDragDrop 方法:

      • dragSource – 对此控件的引用。

      • data – 在前一段代码中创建的 DataObject

      • allowedEffects – 允许的拖放操作是 CopyMove

  3. F5 生成和运行应用程序。

  4. 单击一个圆形控件并将其拖到面板、另一个圆形和 TextBox 上。 拖动 TextBox时,光标将更改以指示移动。

  5. TextBox上拖动圆圈时,按 Ctrl 键。 请注意光标是如何更改以指示复制的。

  6. 将圆形拖放到 TextBox 上。 该圆形填充颜色的字符串表示形式会追加到 TextBox

    圆的填充颜色的字符串表示形式

默认情况下,光标将在拖放操作期间发生更改,以指示删除数据将产生什么效果。 可通过处理 GiveFeedback 事件并设置不同光标来自定义向用户提供的反馈。

向用户提供反馈

  1. 打开Circle.xaml.cs或Circle.xaml.vb。

  2. 添加以下 OnGiveFeedback 替代,以便为 GiveFeedback 事件提供类处理。

    protected override void OnGiveFeedback(GiveFeedbackEventArgs e)
    {
        base.OnGiveFeedback(e);
        // These Effects values are set in the drop target's
        // DragOver event handler.
        if (e.Effects.HasFlag(DragDropEffects.Copy))
        {
            Mouse.SetCursor(Cursors.Cross);
        }
        else if (e.Effects.HasFlag(DragDropEffects.Move))
        {
            Mouse.SetCursor(Cursors.Pen);
        }
        else
        {
            Mouse.SetCursor(Cursors.No);
        }
        e.Handled = true;
    }
    
    Protected Overrides Sub OnGiveFeedback(ByVal e As System.Windows.GiveFeedbackEventArgs)
        MyBase.OnGiveFeedback(e)
        ' These Effects values are set in the drop target's
        ' DragOver event handler.
        If e.Effects.HasFlag(DragDropEffects.Copy) Then
            Mouse.SetCursor(Cursors.Cross)
        ElseIf e.Effects.HasFlag(DragDropEffects.Move) Then
            Mouse.SetCursor(Cursors.Pen)
        Else
            Mouse.SetCursor(Cursors.No)
        End If
        e.Handled = True
    End Sub
    

    OnGiveFeedback 替代执行下列任务:

    • 检查在拖放目标的 DragOver 事件处理程序中设置的 Effects 值。

    • 基于 Effects 值设置自定义游标。 游标旨在向用户提供视觉反馈,了解删除数据将产生的影响。

  3. F5 生成和运行应用程序。

  4. 将一个圆形控件拖到面板、另一个圆形和 TextBox 上。 请注意,现在的光标是在 OnGiveFeedback 替代中指定的自定义光标。

    使用自定义光标拖放

  5. TextBox 中选择文本 green

  6. green 文本拖到一个圆形控件上。 请注意,将显示默认游标以指示拖放操作的效果。 反馈光标始终由拖动源设置。

在用户控件中实现拖放目标事件

在本节中,你将指定用户控件为拖放目标,替代可使用户控件成为拖放目标的方法,并处理用户控件上放置的数据。

使用户控件成为拖放目标

  1. 打开 Circle.xaml。

  2. 在打开的 UserControl 标记中,添加 AllowDrop 属性并将其设置为 true

    <UserControl x:Class="DragDropWalkthrough.Circle"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 mc:Ignorable="d" 
                 d:DesignHeight="300" d:DesignWidth="300"
                 AllowDrop="True">
    

AllowDrop 属性设置为 true,并将拖动源中的数据拖放到 Circle 用户控件上时,将调用 OnDrop 方法。 在这种方法中,将处理已放置的数据,并将这些数据应用于圆形。

处理已放置的数据

  1. 打开Circle.xaml.cs或Circle.xaml.vb。

  2. 添加以下 OnDrop 替代,以便为 Drop 事件提供类处理。

    protected override void OnDrop(DragEventArgs e)
    {
        base.OnDrop(e);
    
        // 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);
                circleUI.Fill = newFill;
    
                // Set Effects to notify the drag source what effect
                // the drag-and-drop operation had.
                // (Copy if CTRL is pressed; otherwise, move.)
                if (e.KeyStates.HasFlag(DragDropKeyStates.ControlKey))
                {
                    e.Effects = DragDropEffects.Copy;
                }
                else
                {
                    e.Effects = DragDropEffects.Move;
                }
            }
        }
        e.Handled = true;
    }
    
    Protected Overrides Sub OnDrop(ByVal e As System.Windows.DragEventArgs)
        MyBase.OnDrop(e)
        ' If the DataObject contains string data, extract it.
        If e.Data.GetDataPresent(DataFormats.StringFormat) Then
            Dim dataString As String = e.Data.GetData(DataFormats.StringFormat)
    
            ' If the string can be converted into a Brush, 
            ' convert it and apply it to the ellipse.
            Dim converter As New BrushConverter
            If converter.IsValid(dataString) Then
                Dim newFill As Brush = converter.ConvertFromString(dataString)
                circleUI.Fill = newFill
    
                ' Set Effects to notify the drag source what effect
                ' the drag-and-drop operation had.
                ' (Copy if CTRL is pressed; otherwise, move.)
                If e.KeyStates.HasFlag(DragDropKeyStates.ControlKey) Then
                    e.Effects = DragDropEffects.Copy
                Else
                    e.Effects = DragDropEffects.Move
                End If
            End If
        End If
        e.Handled = True
    End Sub
    

    OnDrop 替代执行下列任务:

    • 使用 GetDataPresent 方法检查拖动的数据是否包含字符串对象。

    • 使用 GetData 方法提取字符串数据(如果存在)。

    • 使用 BrushConverter 尝试将字符串转换为 Brush

    • 如果转换成功,则将画笔应用于提供圆形控件的 UI 的 EllipseFill

    • Drop 事件标记为已处理。 应将拖放事件标记为已处理,从而接收此事件的其他元素知道该事件已由 Circle 用户控件处理。

  3. F5 生成和运行应用程序。

  4. TextBox 中选择文本 green

  5. 将文本拖放到一个圆形控件上。 圆形从蓝色更改为绿色。

    将字符串转换为画笔

  6. TextBox中键入文本 green

  7. TextBox 中选择文本 gre

  8. 将其拖放到一个圆形控件上。 请注意,光标会更改以指示允许放置,但圆形的颜色不会更改,因为 gre 不是有效颜色。

  9. 从绿色圆形控件拖放到蓝色圆形控件。 圆形从蓝色更改为绿色。 请注意,显示的光标取决于拖动源是 TextBox 还是圆圈。

若要使某个元素成为拖放目标,只需将 AllowDrop 属性设置为 true 并处理放置的数据。 但是,为了提供更好的用户体验,还应处理 DragEnterDragLeaveDragOver 事件。 在这些事件中,可以在删除数据之前对用户执行检查并提供其他反馈。

将数据拖动到圆形用户控件上时,该控件应通知拖动源它是否可以处理所拖动的数据。 如果控件不知道如何处理数据,它应拒绝删除。 为此,你将处理 DragOver 事件并设置 Effects 属性。

验证是否允许数据放置

  1. 打开Circle.xaml.cs或Circle.xaml.vb。

  2. 添加以下 OnDragOver 替代,以便为 DragOver 事件提供类处理。

    protected override void OnDragOver(DragEventArgs e)
    {
        base.OnDragOver(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 or moving.
            BrushConverter converter = new BrushConverter();
            if (converter.IsValid(dataString))
            {
                // Set Effects to notify the drag source what effect
                // the drag-and-drop operation will have. These values are
                // used by the drag source's GiveFeedback event handler.
                // (Copy if CTRL is pressed; otherwise, move.)
                if (e.KeyStates.HasFlag(DragDropKeyStates.ControlKey))
                {
                    e.Effects = DragDropEffects.Copy;
                }
                else
                {
                    e.Effects = DragDropEffects.Move;
                }
            }
        }
        e.Handled = true;
    }
    
    Protected Overrides Sub OnDragOver(ByVal e As System.Windows.DragEventArgs)
        MyBase.OnDragOver(e)
        e.Effects = DragDropEffects.None
    
        ' If the DataObject contains string data, extract it.
        If e.Data.GetDataPresent(DataFormats.StringFormat) Then
            Dim dataString As String = e.Data.GetData(DataFormats.StringFormat)
    
            ' If the string can be converted into a Brush, allow copying or moving.
            Dim converter As New BrushConverter
            If converter.IsValid(dataString) Then
                ' Set Effects to notify the drag source what effect
                ' the drag-and-drop operation will have. These values are 
                ' used by the drag source's GiveFeedback event handler.
                ' (Copy if CTRL is pressed; otherwise, move.)
                If e.KeyStates.HasFlag(DragDropKeyStates.ControlKey) Then
                    e.Effects = DragDropEffects.Copy
                Else
                    e.Effects = DragDropEffects.Move
                End If
            End If
        End If
        e.Handled = True
    End Sub
    

    OnDragOver 替代执行下列任务:

    • Effects 属性设置为 None

    • 执行在 OnDrop 方法中执行的相同检查,以确定 Circle 用户控件是否可以处理拖动的数据。

    • 如果用户控件可以处理数据,将 Effects 属性设置为 CopyMove

  3. F5 生成和运行应用程序。

  4. TextBox 中选择文本 gre

  5. 将文本拖到圆形控件。 请注意,光标此时会更改以指示不允许放置,因为 gre 不是有效颜色。

可通过应用拖放操作预览进一步增强用户体验。 对于圆形用户控件,将替代 OnDragEnterOnDragLeave 方法。 将数据拖动到控件上时,当前背景 Fill 保存在占位符变量中。 随后字符串会转换为画笔并应用于提供圆形 UI 的 Ellipse。 如果将数据拖动出该圆形而没有放置,则原始 Fill 值将重新应用于该圆形。

预览拖放操作的效果

  1. 打开Circle.xaml.cs或Circle.xaml.vb。

  2. 在 Circle 类中,声明一个名为 _previousFill 的私有 Brush 变量并将其初始化为 null

    public partial class Circle : UserControl
    {
        private Brush _previousFill = null;
    
    Public Class Circle
        Private _previousFill As Brush = Nothing
    
  3. 添加以下 OnDragEnter 替代,以便为 DragEnter 事件提供类处理。

    protected override void OnDragEnter(DragEventArgs e)
    {
        base.OnDragEnter(e);
        // Save the current Fill brush so that you can revert back to this value in DragLeave.
        _previousFill = circleUI.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.ToString());
                circleUI.Fill = newFill;
            }
        }
    }
    
    Protected Overrides Sub OnDragEnter(ByVal e As System.Windows.DragEventArgs)
        MyBase.OnDragEnter(e)
        _previousFill = circleUI.Fill
    
        ' If the DataObject contains string data, extract it.
        If e.Data.GetDataPresent(DataFormats.StringFormat) Then
            Dim dataString As String = 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 = converter.ConvertFromString(dataString)
                circleUI.Fill = newFill
            End If
        End If
        e.Handled = True
    End Sub
    

    OnDragEnter 替代执行下列任务:

    • EllipseFill 属性保存在 _previousFill 变量中。

    • 执行在 OnDrop 方法中执行的相同检查,以确定数据是否可以转换为 Brush

    • 如果数据可转换为有效的 Brush,则将其应用于 EllipseFill

  4. 添加以下 OnDragLeave 替代,以便为 DragLeave 事件提供类处理。

    protected override void OnDragLeave(DragEventArgs e)
    {
        base.OnDragLeave(e);
        // Undo the preview that was applied in OnDragEnter.
        circleUI.Fill = _previousFill;
    }
    
    Protected Overrides Sub OnDragLeave(ByVal e As System.Windows.DragEventArgs)
        MyBase.OnDragLeave(e)
        ' Undo the preview that was applied in OnDragEnter.
        circleUI.Fill = _previousFill
    End Sub
    

    OnDragLeave 替代执行下列任务:

    • 将保存在 _previousFill 变量中的 Brush 应用于提供圆形用户控件 UI 的 EllipseFill
  5. F5 生成和运行应用程序。

  6. TextBox 中选择文本 green

  7. 将文本拖到圆形控件上,但不要松手。 圆形从蓝色更改为绿色。

    预览拖放操作效果

  8. 将文本从圆形控件拖离。 圆圈从绿色回到蓝色。

使面板能够接收放置的数据

在本节中,让承载圆形用户控件的面板充当已拖动圆形数据的拖放目标。 你将实现代码,使你能够将圆形从一个面板移动到另一个面板,或者通过在拖放圆时按住 Ctrl 键来创建圆形控件的副本。

  1. 打开 MainWindow.xaml。

  2. 如以下 XAML 所示,在每个 StackPanel 控件中,为 DragOverDrop 事件添加处理程序。 将 DragOver 事件处理程序命名为 panel_DragOver,并命名 Drop 事件处理程序,panel_Drop

    默认情况下,面板不是放置目标。 若要启用它们,请将 AllowDrop 属性添加到两个面板,并将值设置为 true

    <StackPanel Grid.Column="0"
                Background="Beige"
                AllowDrop="True"
                DragOver="panel_DragOver"
                Drop="panel_Drop">
        <TextBox Width="Auto" Margin="2"
                 Text="green"/>
        <local:Circle Margin="2" />
        <local:Circle Margin="2" />
    </StackPanel>
    <StackPanel Grid.Column="1"
                Background="Bisque"
                AllowDrop="True"
                DragOver="panel_DragOver"
                Drop="panel_Drop">
    </StackPanel>
    
  3. 打开MainWindows.xaml.cs或MainWindow.xaml.vb。

  4. DragOver 事件处理程序添加以下代码。

    private void panel_DragOver(object sender, DragEventArgs e)
    {
        if (e.Data.GetDataPresent("Object"))
        {
            // These Effects values are used in the drag source's
            // GiveFeedback event handler to determine which cursor to display.
            if (e.KeyStates == DragDropKeyStates.ControlKey)
            {
                e.Effects = DragDropEffects.Copy;
            }
            else
            {
                e.Effects = DragDropEffects.Move;
            }
        }
    }
    
    Private Sub panel_DragOver(ByVal sender As System.Object, ByVal e As System.Windows.DragEventArgs)
        If e.Data.GetDataPresent("Object") Then
            ' These Effects values are used in the drag source's
            ' GiveFeedback event handler to determine which cursor to display.
            If e.KeyStates = DragDropKeyStates.ControlKey Then
                e.Effects = DragDropEffects.Copy
            Else
                e.Effects = DragDropEffects.Move
            End If
        End If
    End Sub
    

    DragOver 事件处理程序执行以下任务:

    • 检查拖动的数据中是否包含Circle用户控件在DataObject中打包的“对象”数据,并在调用时传递至DoDragDrop

    • 如果存在“对象”数据,则检查是否按下 Ctrl 键。

    • 如果按下 Ctrl 键,将 Effects 属性设置为 Copy。 否则,请将 Effects 属性设置为 Move

  5. Drop 事件处理程序添加以下代码。

    private void panel_Drop(object sender, DragEventArgs e)
    {
        // If an element in the panel has already handled the drop,
        // the panel should not also handle it.
        if (e.Handled == false)
        {
            Panel _panel = (Panel)sender;
            UIElement _element = (UIElement)e.Data.GetData("Object");
    
            if (_panel != null && _element != null)
            {
                // Get the panel that the element currently belongs to,
                // then remove it from that panel and add it the Children of
                // the panel that its been dropped on.
                Panel _parent = (Panel)VisualTreeHelper.GetParent(_element);
    
                if (_parent != null)
                {
                    if (e.KeyStates == DragDropKeyStates.ControlKey &&
                        e.AllowedEffects.HasFlag(DragDropEffects.Copy))
                    {
                        Circle _circle = new Circle((Circle)_element);
                        _panel.Children.Add(_circle);
                        // set the value to return to the DoDragDrop call
                        e.Effects = DragDropEffects.Copy;
                    }
                    else if (e.AllowedEffects.HasFlag(DragDropEffects.Move))
                    {
                        _parent.Children.Remove(_element);
                        _panel.Children.Add(_element);
                        // set the value to return to the DoDragDrop call
                        e.Effects = DragDropEffects.Move;
                    }
                }
            }
        }
    }
    
    Private Sub panel_Drop(ByVal sender As System.Object, ByVal e As System.Windows.DragEventArgs)
        ' If an element in the panel has already handled the drop,
        ' the panel should not also handle it.
        If e.Handled = False Then
            Dim _panel As Panel = sender
            Dim _element As UIElement = e.Data.GetData("Object")
    
            If _panel IsNot Nothing And _element IsNot Nothing Then
                ' Get the panel that the element currently belongs to,
                ' then remove it from that panel and add it the Children of
                ' the panel that its been dropped on.
    
                Dim _parent As Panel = VisualTreeHelper.GetParent(_element)
                If _parent IsNot Nothing Then
                    If e.KeyStates = DragDropKeyStates.ControlKey And _
                        e.AllowedEffects.HasFlag(DragDropEffects.Copy) Then
                        Dim _circle As New Circle(_element)
                        _panel.Children.Add(_circle)
                        ' set the value to return to the DoDragDrop call
                        e.Effects = DragDropEffects.Copy
                    ElseIf e.AllowedEffects.HasFlag(DragDropEffects.Move) Then
                        _parent.Children.Remove(_element)
                        _panel.Children.Add(_element)
                        ' set the value to return to the DoDragDrop call
                        e.Effects = DragDropEffects.Move
                    End If
                End If
            End If
        End If
    End Sub
    

    Drop 事件处理程序执行以下任务:

    • 检查是否已处理 Drop 事件。 例如,如果将一个圆形放置在处理 Drop 事件的另一个圆形上,则无需让包含该圆形的面板也处理该事件。

    • 如果未处理 Drop 事件,请检查是否按下了 Ctrl 键

    • 如果在发生 Drop 时按下了 Ctrl 键,则会创建圆形控件的副本并将其添加到 StackPanelChildren 集合

    • 如果未按下 Ctrl 键,则将圆形从其父面板的 Children 集合移动到放置该圆形的面板的 Children 集合

    • 设置 Effects 属性,以通知 DoDragDrop 方法是执行移动还是复制操作。

  6. F5 生成和运行应用程序。

  7. TextBox 中选择文本 green

  8. 将文本拖放到一个圆形控件上。

  9. 将圆形控件从左面板拖动到右侧面板,然后将其拖放。 该圆形将从左面板的 Children 集合中移除,并添加到右面板的 Children 集合中。

  10. 将圆圈控件从其所在的面板拖动到另一个面板,并在释放时按住 Ctrl 键。 将复制该圆形并将副本添加到接收面板的 Children 集合中。

    按 Ctrl 键时拖动圆圈

另请参阅