WPF开发快速入门【7】WPF的拖放功能(Drag and Drop)
概述
本文描述WPF的拖放功能(Drag and Drop)。
拖放功能涉及到两个功能,一个就是拖,一个是放。拖放可以发生在两个控件之间,也可以在一个控件自己内部拖放。假设界面上有两个控件,一个TreeView,一个ListView,那么可能发生的拖动有以下几种情况:
1、TreeView -> ListView
2、ListView -> TreeView
3、TreeView -> TreeView
4、ListView -> ListView
对于拖的控件需要在鼠标移动事件中检测左键按下并启动拖动操作;对于放的控件需要处理Drop等事件来接收数据。如果是在控件内部拖动,则以上两个动作都要处理。
为简便起见,本文就以ListView拖动到TreeView为例进行讲解。
拖
在拖与放的控件之间一定会有数据传递,我们可以设计一个类型来进行数据传输,由于ListView本身就是绑定到一个对象列表的,我就把选中的对象字节拿来传递了,没有额外定义类型。
- public class ListViewAdvNodeItem
- {
- public string Title {get;set;}
- }
listView.ItemsSource的数据类型为:BindableCollection<ListViewAdvNodeItem> ListViewAdvNodeItems,通过this.listView.SelectedItem可以得到的数据类型即为:ListViewAdvNodeItem
设计代码如下:
- <ListView x:Name="listView"
- Mouse.MouseMove="listView_MouseMove" >
- </ListView>
在listView_MouseMove事件中,我们将启动拖动功能。
- private void listView_MouseMove(object sender, MouseEventArgs e)
- {
if (sender is ListView listview
&& e.LeftButton == MouseButtonState.Pressed
&& listview.SelectedItem != null)- {
- DragDrop.DoDragDrop(listview, listview.SelectedItem, DragDropEffects.Move);
- }
- }
通过DragDrop.DoDragDrop方法启动拖动,该方法有三个参数:
1、发起拖动的控件
2、传输的数据(这里是一个ListViewAdvNodeItem类型的对象)
3、拖动的类型,一般为Move或Copy
放
下面就要在TreeView控件中处理放的事件了
设计代码:
- <TreeView x:Name="treeView"
- AllowDrop="True"
- DragDrop.Drop="treeView_Drop"
- DragDrop.DragOver="treeView_DragOver"
- DragDrop.DragEnter="treeView_DragEnter"
- DragDrop.DragLeave="treeView_DragLeave" >
- </TreeView>
首先要设置AllowDrop="True",然后重点处理DragDrop.Drop事件:
- private void treeView_Drop(object sender, DragEventArgs e)
- {
- if (e.Data.GetData(typeof(ListViewAdvNodeItem)) is ListViewAdvNodeItem fromListNode)
- {
- if (e.OriginalSource is TextBlock txtTitle)
- { if (txtTitle.Tag is Excerpt toExcerpt)
- {
- //处理业务
- }
- }
- }
- }
在处理Drop事件时,我们需要知道两件事情,1:拖来的是什么数据?2、放哪里了?
首先,通过e.Data.GetData(typeof(ListViewAdvNodeItem))就可以获得数据来源,这里GetData得到的对象就是上面的 listview.SelectedItem;
其次,通过e.OriginalSource 我们将获得数据放在哪里的问题。这段代码很难理解,要回头看一下TreeView的ItemTemplate定义
- <TreeView.ItemTemplate>
- <HierarchicalDataTemplate DataType="{x:Type local:TreeViewAdvNodeItem}" ItemsSource="{Binding Children}">
- <Border x:Name="itemBorder" Margin="2" BorderBrush="White" BorderThickness="1" >
- <StackPanel Orientation="Horizontal">
- <Image x:Name="nodeImage" Source="../Images/FolderClose.png" Width="20" Height="20"/>
- <TextBlock Text="{Binding Title}" Tag="{Binding Excerpt}" VerticalAlignment="Center" FontSize="13" Margin="2"/>
- </StackPanel>
- </Border>
- </HierarchicalDataTemplate>
- </TreeView.ItemTemplate>
从这个模板定义可以看出,TreeView中用来显示Title的控件是一个TextBlock,然后这个TextBlock的Tag属性上还绑定了一个业务对象。
再回头看上面一段代码,就可以看出具体的逻辑:当鼠标放开时,其所指的对象是一个TextBlock,然后取到这个TextBlock的Tag对象,里面包含了我想要的业务数据。
到此拖放功能就完成了。
为了更好的展现效果,我们可以对拖放的目标进行判断,对于一些不能放的位置显示禁止拖放的图标,这时就需要处理DragOver事件了
- private void treeView_DragOver(object sender, DragEventArgs e)
- { //判断是否允许拖动
- e.Effects = DragDropEffects.None;
- if (e.Data.GetData(typeof(ListViewAdvNodeItem)) is ListViewAdvNodeItem fromListNode)
- {
- if (e.OriginalSource is TextBlock txtTitle)
- {
- if (txtTitle.Tag is Excerpt toExcerpt)
- {
- if (CanDrop(fromListNode.Excerpt, toExcerpt)) //业务判断
- {
- e.Effects = DragDropEffects.Move;
- }
- }
- }
- }
- e.Handled = true;
- }
装饰器
如果拖动时,有下面这样的一个标签跟随鼠标移动,其显示内容是拖动对象的Title,效果就更好了。
这个就需要通过装饰器来实现。
关于装饰器的介绍:装饰器概述 - WPF .NET Framework | Microsoft Docs
首先我们建一个装饰器对象DragTitleAdorner


- public class DragTitleAdorner : Adorner
- {
- private readonly ContentPresenter _contentPresenter;
- private Control Control
- {
- get
- {
- return (Control)this.AdornedElement;
- }
- }
- public DragTitleAdorner(UIElement adornedElement, Point pos, string? Title = "") : base(adornedElement)
- {
- IsHitTestVisible = false;
- int width = 22;
- if (Title != null)
- {
- width += (int)MeasureTextWidth(Title, 14, "宋体");
- }
- this._contentPresenter = new ContentPresenter
- {
- Content = new Border
- {
- Background = Brushes.SteelBlue,
- Width = width,
- Height = 28,
- BorderBrush = Brushes.Gray,
- BorderThickness = new Thickness(1),
- HorizontalAlignment = HorizontalAlignment.Left,
- VerticalAlignment = VerticalAlignment.Top,
- CornerRadius= new CornerRadius(5),
- Child = new TextBlock
- {
- Text = Title,
- FontSize = 14,
- FontFamily= new FontFamily("宋体"),
- Foreground = Brushes.White,
- HorizontalAlignment = HorizontalAlignment.Left,
- VerticalAlignment = VerticalAlignment.Center,
- Margin = new Thickness(10, 0, 0, 0),
- },
- },
- };
- double left = pos.X;
- double top = pos.Y;
- this.Margin = new Thickness(left + 5, top + 10, 0, 0);
- }
- #region Override
- protected override int VisualChildrenCount
- {
- get
- {
- return 1;
- }
- }
- protected override Visual GetVisualChild(int index) // replace the Visual of the TextBox with the visual of the _contentPresenter;
- {
- return this._contentPresenter;
- }
- protected override Size MeasureOverride(Size constraint)
- {
- this._contentPresenter.Measure(this.Control.RenderSize); // delegate the measure override to the ContentPresenter's Measure
- return this.Control.RenderSize;
- }
- protected override Size ArrangeOverride(Size finalSize)
- {
- this._contentPresenter.Arrange(new Rect(finalSize));
- return finalSize;
- }
- #endregion Override
- private double MeasureTextWidth(string text, double fontSize, string fontFamily)
- {
- FormattedText formattedText = new FormattedText(
- text,
- System.Globalization.CultureInfo.InvariantCulture,
- FlowDirection.LeftToRight,
- new Typeface(fontFamily.ToString()),
- fontSize,
- Brushes.Black
- );
- return formattedText.WidthIncludingTrailingWhitespace;
- }
- }
在构造这个对象时,我们将传入两个重要的参数:Point pos 和 string Title ,这两个参数决定了它在何处显示什么内容。
程序用代码构建了一个Border,其内有一个TextBlock,并通过pos参数来控制了它的位置。
下面,在treeView_DragOver事件中显示这个装饰器即可。
- private void treeView_DragOver(object sender, DragEventArgs e)
- {
- if (e.Data.GetData(typeof(ListViewAdvNodeItem)) is ListViewAdvNodeItem fromListNode)
- {
- //显示装饰器
- AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this.treeView);
- if (adornerLayer != null)
- {
- Adorner[] adorners = adornerLayer.GetAdorners(this.treeView);
- if (adorners != null)
- {
- foreach (var adorner in adorners)
- {
- adornerLayer.Remove(adorner);
- }
- }
- DragTitleAdorner _adorner = new DragTitleAdorner(this.treeView, pos, fromListNode.Excerpt?.Title);
- adornerLayer.Add(_adorner);
- }
- }
- e.Handled = true;
- }
更多信息请参考文末源码。
资源
系列目录:WPF开发快速入门【0】前言与目录
代码下载:Learn WPF: WPF学习笔记 (gitee.com)
WPF开发快速入门【7】WPF的拖放功能(Drag and Drop)的更多相关文章
- WPF/MVVM Quick Start Tutorial - WPF/MVVM 快速入门教程 -原文,翻译及一点自己的补充
转载自 https://www.codeproject.com/articles/165368/wpf-mvvm-quick-start-tutorial WPF/MVVM Quick Start T ...
- Transform组件C#游戏开发快速入门
Transform组件C#游戏开发快速入门大学霸 组件(Component)可以看作是一类属性的总称.而属性是指游戏对象上一切可设置.调节的选项,如图2-8所示.本文选自C#游戏开发快速入门大学霸 ...
- HealthKit开发快速入门教程之HealthKit数据的操作
HealthKit开发快速入门教程之HealthKit数据的操作 数据的表示 在HealthKit中,数据是最核心的元素.通过分析数据,人们可以看到相关的健康信息.例如,通过统计步数数据,人们可以知道 ...
- HealthKit开发快速入门教程之HealthKit框架体系创建健康AppID
HealthKit开发快速入门教程之HealthKit框架体系创建健康AppID HealthKit开发准备工作 在开发一款HealthKit应用程序时,首先需要讲解HealthKit中有哪些类,在i ...
- HealthKit开发快速入门教程之HealthKit开发概述简介
HealthKit开发快速入门教程之HealthKit开发概述简介 2014年6月2日召开的年度开发者大会上,苹果发布了一款新的移动应用平台,可以收集和分析用户的健康数据.该移动应用平台被命名为“He ...
- Apple Watch开发快速入门教程
Apple Watch开发快速入门教程 试读下载地址:http://pan.baidu.com/s/1eQ8JdR0 介绍:苹果为Watch提供全新的开发框架WatchKit.本教程是国内第一本A ...
- 游戏控制杆OUYA游戏开发快速入门教程
游戏控制杆OUYA游戏开发快速入门教程 1.2.2 游戏控制杆 游戏控制杆各个角度的视图,如图1-4所示,它的硬件规格是本文选自OUYA游戏开发快速入门教程大学霸: 图1-4 游戏控制杆各个角度的 ...
- SpringBoot开发快速入门
SpringBoot开发快速入门 目录 一.Spring Boot 入门 1.Spring Boot 简介 2.微服务 3.环境准备 1.maven设置: 2.IDEA设置 4.Spring Boot ...
- HTML5 拖放(Drag 和 Drop)功能开发——基础实战
随着HTML5的普及度越来越高,现在写代码也遇到一些了,经过同事的点播开展了一次Dojo活动用以技术交流,我也乘此机会将HTML5的拖放功能整理了一下. 简介 拖拽(Drag/Drop)是个非常普遍的 ...
随机推荐
- ebook下载 | 灵雀云发布《 企业高管IT战略指南——为何选择容器与Kubernetes》
发送关键词[高管指南]至灵雀云公众号,立即下载完整版电子书 "本书将提供企业领导者/IT高管应该了解的,所有关于容器技术和Kubernetes的基础认知和关键概念,突破技术语言屏障,全面梳理 ...
- 【Redis】skiplist跳跃表
有序集合Sorted Set zadd zadd用于向集合中添加元素并且可以设置分值,比如添加三门编程语言,分值分别为1.2.3: 127.0.0.1:6379> zadd language 1 ...
- SAP LUW 实现提交数据库更新
CALL FUNCTION 'TRANSACTION_BEGIN' IMPORTING transaction_id = lv_transaction_id. * 更新日志表 MODIFY zfit0 ...
- RPA微信机器人汇总
一.微信广告PDF对账单数据提取机器人 [机器人详情] 微信广告对账结算单为PDF文件,从每一期对账单文件中提取结算数据,统计成excel表格,便于与腾讯广告业务结算审核 [机器人步骤] 1.启动机器 ...
- 论HashMap、Hashtable、TreeMap、LinkedHashMap的内部排序
参考文章 论HashMap.Hashtable.TreeMap.LinkedHashMap的内部排序
- 查询效率提升10倍!3种优化方案,帮你解决MySQL深分页问题
开发经常遇到分页查询的需求,但是当翻页过多的时候,就会产生深分页,导致查询效率急剧下降. 有没有什么办法,能解决深分页的问题呢? 本文总结了三种优化方案,查询效率直接提升10倍,一起学习一下. 1. ...
- 螣龙安科携手51CTO:网络安全实战课程最新发布
一年一度的双十一狂欢节即将来临了,相信各大电商平台也正摩拳擦掌跃跃欲试中.回顾2019年,阿里巴巴双十一狂欢节的单日交易额就达到了2684亿人民币,创造了电商交易历史上新的记录. 当人们愉快地购买着自 ...
- Solution -「COCI 2016-2017」 Mag 结论证明
结论:最多包含一个 \(2\),并且不在链的两端点. 证明:我们问题分成两个 \(\texttt{pass}\). \(\texttt{pass 1}\):\(\forall u,s.t.x_{u}\ ...
- 多人共用一个Linux用户, 实现Bash配置文件独立
本文中提到的 账户, 用户 均表示同一概念. 例如 ssh wbourne@192.168.xxx.101, 账户, 用户 指的均是 wbourne. 背景 在工作中, 我们经常会连接Linux服务器 ...
- 2022-07-10 第五小组 pan小堂 css学习笔记
css学习笔记 什么是 CSS? CSS 指的是层叠样式表* (Cascading Style Sheets) CSS 描述了如何在屏幕.纸张或其他媒体上显示 HTML 元素 CSS 节省了大量工作. ...