本文内容

  1. 创建应用程序项目
  2. 向项目添加新的用户控件
  3. 向主窗口添加用户控件
  4. 在用户控件中实现拖动源事件
  5. 向用户提供反馈
  6. 在用户控件中实现拖放目标事件
  7. 使面板能够接收放置的数据

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

本演练阐释了以下任务:

  • 创建自定义用户控件。

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

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

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

1、创建应用程序项目

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

  1. 使用 Visual Basic 或 Visual C# 创建名为DragDropExample的新 WPF 应用程序项目。

  2. 打开 MainWindow.xaml。

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

    此标记将创建用于测试应用程序的用户界面。

2、向项目添加新的用户控件

本节将介绍如何向项目添加新的用户控件。

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

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

    Circle.xaml 及其代码隐藏内容将添加到项目中。

  3. 打开 Circle.xaml。

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

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

  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;}

3、向主窗口添加用户控件

  1. 打开 MainWindow.xaml。

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

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

    此面板的完整 XAML 如下所示。

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

在本节中,将替代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);}}

    此OnMouseMove替代执行下列任务:

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

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

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

      • dragSource– 对此控件的引用。

      • data– 上一个示例中创建的DataObject。

      • allowedEffects– 允许的拖放操作,即Copy或Move。

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

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

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

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

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

5、向用户提供反馈

  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;}

    此OnGiveFeedback替代执行下列任务:

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

    • 基于Effects值设置自定义光标。 该光标旨在向用户提供关于放置数据所产生的效果的可视反馈。

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

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

  5. green中键入文本TextBox。

  6. 在TextBox中选择文本gre

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

  8. 从绿色圆形控件拖放到蓝色圆形控件。 该圆形会从蓝色变为绿色。 请注意,显示的光标取决于是TextBox还是圆形作为拖动源。

若要使某个元素成为拖放目标,只需将AllowDrop属性设置为true并处理放置的数据。 但是,若要提供更佳的用户体验,还应处理DragEnter、DragLeave和DragOver事件。 在这些事件中,你可以在放置数据前执行检查并向用户提供其他反馈。

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

6.3 验证是否允许数据放置

  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;}

    此OnDragOver替代执行下列任务:

    • 将Effects属性设置为None。

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

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

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

  4. 在TextBox中选择文本gre

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

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

6.4 预览拖放操作的效果

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

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

    public partial class Circle : UserControl{private Brush _previousFill = null;
  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;}}}

    此OnDragEnter替代执行下列任务:

    • 将Ellipse的Fill属性保存在_previousFill变量中。

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

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

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

    protected override void OnDragLeave(DragEventArgs e){base.OnDragLeave(e);// Undo the preview that was applied in OnDragEnter.circleUI.Fill = _previousFill;}

    此OnDragLeave替代执行下列任务:

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

  6. 在TextBox中选择文本green

  7. 将该文本拖到一个圆形控件上而不放置。 该圆形会从蓝色变为绿色。