概述

本文描述WPF的拖放功能(Drag and Drop)。

拖放功能涉及到两个功能,一个就是拖,一个是放。拖放可以发生在两个控件之间,也可以在一个控件自己内部拖放。假设界面上有两个控件,一个TreeView,一个ListView,那么可能发生的拖动有以下几种情况:

1、TreeView -> ListView

2、ListView -> TreeView

3、TreeView -> TreeView

4、ListView -> ListView

对于拖的控件需要在鼠标移动事件中检测左键按下并启动拖动操作;对于放的控件需要处理Drop等事件来接收数据。如果是在控件内部拖动,则以上两个动作都要处理。

为简便起见,本文就以ListView拖动到TreeView为例进行讲解。

在拖与放的控件之间一定会有数据传递,我们可以设计一个类型来进行数据传输,由于ListView本身就是绑定到一个对象列表的,我就把选中的对象字节拿来传递了,没有额外定义类型。

  1. public class ListViewAdvNodeItem
  2. {
  3. public string Title {get;set;}
  4.  
  5. }

listView.ItemsSource的数据类型为:BindableCollection<ListViewAdvNodeItem> ListViewAdvNodeItems,通过this.listView.SelectedItem可以得到的数据类型即为:ListViewAdvNodeItem

设计代码如下:

  1. <ListView x:Name="listView"
  2. Mouse.MouseMove="listView_MouseMove" >
  3. </ListView>

在listView_MouseMove事件中,我们将启动拖动功能。

  1. private void listView_MouseMove(object sender, MouseEventArgs e)
  2. {
    if (sender is ListView listview
    && e.LeftButton == MouseButtonState.Pressed
    && listview.SelectedItem != null)
  3. {
  4. DragDrop.DoDragDrop(listview, listview.SelectedItem, DragDropEffects.Move);
  5. }
  6. }

通过DragDrop.DoDragDrop方法启动拖动,该方法有三个参数:

1、发起拖动的控件

2、传输的数据(这里是一个ListViewAdvNodeItem类型的对象)

3、拖动的类型,一般为Move或Copy

下面就要在TreeView控件中处理放的事件了

设计代码:

  1. <TreeView x:Name="treeView"
  2. AllowDrop="True"
  3. DragDrop.Drop="treeView_Drop"
  4. DragDrop.DragOver="treeView_DragOver"
  5. DragDrop.DragEnter="treeView_DragEnter"
  6. DragDrop.DragLeave="treeView_DragLeave" >
  7. </TreeView>

首先要设置AllowDrop="True",然后重点处理DragDrop.Drop事件:

  1. private void treeView_Drop(object sender, DragEventArgs e)
  2. {
  3. if (e.Data.GetData(typeof(ListViewAdvNodeItem)) is ListViewAdvNodeItem fromListNode)
  4. {
  5. if (e.OriginalSource is TextBlock txtTitle)
  6. { if (txtTitle.Tag is Excerpt toExcerpt)
  7. {
  8. //处理业务
  9. }
  10. }
  11. }
  12. }

在处理Drop事件时,我们需要知道两件事情,1:拖来的是什么数据?2、放哪里了?

首先,通过e.Data.GetData(typeof(ListViewAdvNodeItem))就可以获得数据来源,这里GetData得到的对象就是上面的 listview.SelectedItem;

其次,通过e.OriginalSource 我们将获得数据放在哪里的问题。这段代码很难理解,要回头看一下TreeView的ItemTemplate定义

  1. <TreeView.ItemTemplate>
  2. <HierarchicalDataTemplate DataType="{x:Type local:TreeViewAdvNodeItem}" ItemsSource="{Binding Children}">
  3. <Border x:Name="itemBorder" Margin="2" BorderBrush="White" BorderThickness="1" >
  4. <StackPanel Orientation="Horizontal">
  5. <Image x:Name="nodeImage" Source="../Images/FolderClose.png" Width="20" Height="20"/>
  6. <TextBlock Text="{Binding Title}" Tag="{Binding Excerpt}" VerticalAlignment="Center" FontSize="13" Margin="2"/>
  7. </StackPanel>
  8. </Border>
  9. </HierarchicalDataTemplate>
  10. </TreeView.ItemTemplate>

从这个模板定义可以看出,TreeView中用来显示Title的控件是一个TextBlock,然后这个TextBlock的Tag属性上还绑定了一个业务对象。

再回头看上面一段代码,就可以看出具体的逻辑:当鼠标放开时,其所指的对象是一个TextBlock,然后取到这个TextBlock的Tag对象,里面包含了我想要的业务数据。

到此拖放功能就完成了。

为了更好的展现效果,我们可以对拖放的目标进行判断,对于一些不能放的位置显示禁止拖放的图标,这时就需要处理DragOver事件了

  1. private void treeView_DragOver(object sender, DragEventArgs e)
  2. { //判断是否允许拖动
  3. e.Effects = DragDropEffects.None;
  4. if (e.Data.GetData(typeof(ListViewAdvNodeItem)) is ListViewAdvNodeItem fromListNode)
  5. {
  6. if (e.OriginalSource is TextBlock txtTitle)
  7. {
  8. if (txtTitle.Tag is Excerpt toExcerpt)
  9. {
  10. if (CanDrop(fromListNode.Excerpt, toExcerpt))  //业务判断
  11. {
  12. e.Effects = DragDropEffects.Move;
  13. }
  14. }
  15. }
  16. }
  17. e.Handled = true;
  18. }

装饰器

如果拖动时,有下面这样的一个标签跟随鼠标移动,其显示内容是拖动对象的Title,效果就更好了。

这个就需要通过装饰器来实现。

关于装饰器的介绍:装饰器概述 - WPF .NET Framework | Microsoft Docs

首先我们建一个装饰器对象DragTitleAdorner

  1. public class DragTitleAdorner : Adorner
  2. {
  3. private readonly ContentPresenter _contentPresenter;
  4. private Control Control
  5. {
  6. get
  7. {
  8. return (Control)this.AdornedElement;
  9. }
  10. }
  11.  
  12. public DragTitleAdorner(UIElement adornedElement, Point pos, string? Title = "") : base(adornedElement)
  13. {
  14. IsHitTestVisible = false;
  15.  
  16. int width = 22;
  17. if (Title != null)
  18. {
  19. width += (int)MeasureTextWidth(Title, 14, "宋体");
  20. }
  21.  
  22. this._contentPresenter = new ContentPresenter
  23. {
  24. Content = new Border
  25. {
  26. Background = Brushes.SteelBlue,
  27. Width = width,
  28. Height = 28,
  29. BorderBrush = Brushes.Gray,
  30. BorderThickness = new Thickness(1),
  31. HorizontalAlignment = HorizontalAlignment.Left,
  32. VerticalAlignment = VerticalAlignment.Top,
  33. CornerRadius= new CornerRadius(5),
  34. Child = new TextBlock
  35. {
  36. Text = Title,
  37. FontSize = 14,
  38. FontFamily= new FontFamily("宋体"),
  39. Foreground = Brushes.White,
  40. HorizontalAlignment = HorizontalAlignment.Left,
  41. VerticalAlignment = VerticalAlignment.Center,
  42. Margin = new Thickness(10, 0, 0, 0),
  43. },
  44. },
  45. };
  46.  
  47. double left = pos.X;
  48. double top = pos.Y;
  49. this.Margin = new Thickness(left + 5, top + 10, 0, 0);
  50. }
  51.  
  52. #region Override
  53.  
  54. protected override int VisualChildrenCount
  55. {
  56. get
  57. {
  58. return 1;
  59. }
  60. }
  61.  
  62. protected override Visual GetVisualChild(int index) // replace the Visual of the TextBox with the visual of the _contentPresenter;
  63. {
  64. return this._contentPresenter;
  65. }
  66.  
  67. protected override Size MeasureOverride(Size constraint)
  68. {
  69. this._contentPresenter.Measure(this.Control.RenderSize); // delegate the measure override to the ContentPresenter's Measure
  70. return this.Control.RenderSize;
  71. }
  72.  
  73. protected override Size ArrangeOverride(Size finalSize)
  74. {
  75. this._contentPresenter.Arrange(new Rect(finalSize));
  76. return finalSize;
  77. }
  78.  
  79. #endregion Override
  80.  
  81. private double MeasureTextWidth(string text, double fontSize, string fontFamily)
  82. {
  83. FormattedText formattedText = new FormattedText(
  84. text,
  85. System.Globalization.CultureInfo.InvariantCulture,
  86. FlowDirection.LeftToRight,
  87. new Typeface(fontFamily.ToString()),
  88. fontSize,
  89. Brushes.Black
  90. );
  91. return formattedText.WidthIncludingTrailingWhitespace;
  92. }
  93.  
  94. }

在构造这个对象时,我们将传入两个重要的参数:Point pos 和 string Title ,这两个参数决定了它在何处显示什么内容。

程序用代码构建了一个Border,其内有一个TextBlock,并通过pos参数来控制了它的位置。

下面,在treeView_DragOver事件中显示这个装饰器即可。

  1. private void treeView_DragOver(object sender, DragEventArgs e)
  2. {
  3. if (e.Data.GetData(typeof(ListViewAdvNodeItem)) is ListViewAdvNodeItem fromListNode)
  4. {
  5. //显示装饰器
  6. AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this.treeView);
  7. if (adornerLayer != null)
  8. {
  9. Adorner[] adorners = adornerLayer.GetAdorners(this.treeView);
  10. if (adorners != null)
  11. {
  12. foreach (var adorner in adorners)
  13. {
  14. adornerLayer.Remove(adorner);
  15. }
  16. }
  17.  
  18. DragTitleAdorner _adorner = new DragTitleAdorner(this.treeView, pos, fromListNode.Excerpt?.Title);
  19. adornerLayer.Add(_adorner);
  20. }
  21. }
  22. e.Handled = true;
  23. }

更多信息请参考文末源码。

资源

系列目录:WPF开发快速入门【0】前言与目录

代码下载:Learn WPF: WPF学习笔记 (gitee.com)

WPF开发快速入门【7】WPF的拖放功能(Drag and Drop)的更多相关文章

  1. WPF/MVVM Quick Start Tutorial - WPF/MVVM 快速入门教程 -原文,翻译及一点自己的补充

    转载自 https://www.codeproject.com/articles/165368/wpf-mvvm-quick-start-tutorial WPF/MVVM Quick Start T ...

  2. Transform组件C#游戏开发快速入门

    Transform组件C#游戏开发快速入门大学霸 组件(Component)可以看作是一类属性的总称.而属性是指游戏对象上一切可设置.调节的选项,如图2-8所示.本文选自C#游戏开发快速入门大学霸   ...

  3. HealthKit开发快速入门教程之HealthKit数据的操作

    HealthKit开发快速入门教程之HealthKit数据的操作 数据的表示 在HealthKit中,数据是最核心的元素.通过分析数据,人们可以看到相关的健康信息.例如,通过统计步数数据,人们可以知道 ...

  4. HealthKit开发快速入门教程之HealthKit框架体系创建健康AppID

    HealthKit开发快速入门教程之HealthKit框架体系创建健康AppID HealthKit开发准备工作 在开发一款HealthKit应用程序时,首先需要讲解HealthKit中有哪些类,在i ...

  5. HealthKit开发快速入门教程之HealthKit开发概述简介

    HealthKit开发快速入门教程之HealthKit开发概述简介 2014年6月2日召开的年度开发者大会上,苹果发布了一款新的移动应用平台,可以收集和分析用户的健康数据.该移动应用平台被命名为“He ...

  6. Apple Watch开发快速入门教程

     Apple Watch开发快速入门教程  试读下载地址:http://pan.baidu.com/s/1eQ8JdR0 介绍:苹果为Watch提供全新的开发框架WatchKit.本教程是国内第一本A ...

  7. 游戏控制杆OUYA游戏开发快速入门教程

    游戏控制杆OUYA游戏开发快速入门教程 1.2.2  游戏控制杆 游戏控制杆各个角度的视图,如图1-4所示,它的硬件规格是本文选自OUYA游戏开发快速入门教程大学霸: 图1-4  游戏控制杆各个角度的 ...

  8. SpringBoot开发快速入门

    SpringBoot开发快速入门 目录 一.Spring Boot 入门 1.Spring Boot 简介 2.微服务 3.环境准备 1.maven设置: 2.IDEA设置 4.Spring Boot ...

  9. HTML5 拖放(Drag 和 Drop)功能开发——基础实战

    随着HTML5的普及度越来越高,现在写代码也遇到一些了,经过同事的点播开展了一次Dojo活动用以技术交流,我也乘此机会将HTML5的拖放功能整理了一下. 简介 拖拽(Drag/Drop)是个非常普遍的 ...

随机推荐

  1. ebook下载 | 灵雀云发布《 企业高管IT战略指南——为何选择容器与Kubernetes》

    发送关键词[高管指南]至灵雀云公众号,立即下载完整版电子书 "本书将提供企业领导者/IT高管应该了解的,所有关于容器技术和Kubernetes的基础认知和关键概念,突破技术语言屏障,全面梳理 ...

  2. 【Redis】skiplist跳跃表

    有序集合Sorted Set zadd zadd用于向集合中添加元素并且可以设置分值,比如添加三门编程语言,分值分别为1.2.3: 127.0.0.1:6379> zadd language 1 ...

  3. SAP LUW 实现提交数据库更新

    CALL FUNCTION 'TRANSACTION_BEGIN' IMPORTING transaction_id = lv_transaction_id. * 更新日志表 MODIFY zfit0 ...

  4. RPA微信机器人汇总

    一.微信广告PDF对账单数据提取机器人 [机器人详情] 微信广告对账结算单为PDF文件,从每一期对账单文件中提取结算数据,统计成excel表格,便于与腾讯广告业务结算审核 [机器人步骤] 1.启动机器 ...

  5. 论HashMap、Hashtable、TreeMap、LinkedHashMap的内部排序

    参考文章 论HashMap.Hashtable.TreeMap.LinkedHashMap的内部排序

  6. 查询效率提升10倍!3种优化方案,帮你解决MySQL深分页问题

    开发经常遇到分页查询的需求,但是当翻页过多的时候,就会产生深分页,导致查询效率急剧下降. 有没有什么办法,能解决深分页的问题呢? 本文总结了三种优化方案,查询效率直接提升10倍,一起学习一下. 1. ...

  7. 螣龙安科携手51CTO:网络安全实战课程最新发布

    一年一度的双十一狂欢节即将来临了,相信各大电商平台也正摩拳擦掌跃跃欲试中.回顾2019年,阿里巴巴双十一狂欢节的单日交易额就达到了2684亿人民币,创造了电商交易历史上新的记录. 当人们愉快地购买着自 ...

  8. Solution -「COCI 2016-2017」 Mag 结论证明

    结论:最多包含一个 \(2\),并且不在链的两端点. 证明:我们问题分成两个 \(\texttt{pass}\). \(\texttt{pass 1}\):\(\forall u,s.t.x_{u}\ ...

  9. 多人共用一个Linux用户, 实现Bash配置文件独立

    本文中提到的 账户, 用户 均表示同一概念. 例如 ssh wbourne@192.168.xxx.101, 账户, 用户 指的均是 wbourne. 背景 在工作中, 我们经常会连接Linux服务器 ...

  10. 2022-07-10 第五小组 pan小堂 css学习笔记

    css学习笔记 什么是 CSS? CSS 指的是层叠样式表* (Cascading Style Sheets) CSS 描述了如何在屏幕.纸张或其他媒体上显示 HTML 元素 CSS 节省了大量工作. ...