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)是个非常普遍的 ...
随机推荐
- DevOps落地实践点滴和踩坑记录-(1)
记录初衷 本人一直在从事企业内DevOps落地实践的工作,走了不少弯路,也努力在想办法解决面临的问题,期间也经历过不少人和事情,最近突然有想法把经历过的,不管好的不好的都记录下来,分享给和我一样的一线 ...
- C语言:如何给全局变量起一个别名?
作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++.嵌入式.Linux. 关注下方公众号,回复[书籍],获取 Linux.嵌入式领域经典书籍:回复[PDF],获取所有原创文章( PDF 格式). ...
- Windows系统重置用户登录密码
更新记录 2022年4月16日:本文迁移自Panda666原博客,原发布时间:2021年8月23日. 方法一.使用带有密码恢复功能的PE盘 买一张 PE光盘 或 自制PE启动盘,这里推荐微PE. 准备 ...
- 在.NET中计算文件的MD5值
更新记录 本文迁移自Panda666原博客,原发布时间:2021年7月2日. 直接上代码吧: using System; using System.IO; using System.Security. ...
- 纯css就能实现可点击切换的轮播图,feel起来很丝滑
前言 轮播图经常会在项目里用到,但是实际上用到的轮播图都是比较简单的,没有复杂的特效,这个时候如果去引入swiper那些库的话,未免就有点杀鸡焉用牛刀了. 所以不如自己手写一个,而今天我要分享的一种写 ...
- Spring jdbctemplate和事务管理器 全注解配置 不使用xml
/** * spring的配置类,相当于bean.xml */@Configuration//@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的<beans ...
- StringBuilder的构造方法和append方法
Java中StringBuilder的构造方法 StringBuilder构造方法: public StringBuilder(): 创建一个空白可变的字符串对象,不含有任何内容 public Str ...
- 数论之欧几里德gcd
序:这篇博客我最开始学的时候写的,后来又学了一遍,自我感觉这篇好像有问题,扩展欧几里得建议走这边 首先先说,欧几里德一共有俩,欧几里德和扩展欧几里德,前者非常简单,后者直接变态(因为我太菜) gcd ...
- Java学习dayo4
分支结构和循环语句 1.包的概念 包就是文件夹 包的命名规范:全小写,域名倒置,不能以点开头或结尾,可以包含点,每存在一个点表示一个子目录 举例:com.baidu.demo 定义包后,包中的java ...
- Kafka启动遇到ERROR Exiting Kafka due to fatal exception (kafka.Kafka$)
------------恢复内容开始------------ Kafka启动遇到ERROR Exiting Kafka due to fatal exception (kafka.Kafka$) 解决 ...