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)是个非常普遍的 ...
随机推荐
- conda cheat sheet可直接百度这个名字
- kruskal 及其应用
kruskal 最小生成树 kruskal 是一种常见且好理解的最小生成树(MST)算法. 前置知识 并查集和路径压缩 生成树 在有 n 的顶点的无向图中,取其中 n-1 条边相连,所得到的树即为生成 ...
- vue基本原理
当一个Vue实例创建时,Vue会遍历data中的属性,用Object.defineProperty(vue3.0使用proxy)将它们转为getter/setter,并且在内部追踪相关依赖,在属性被访 ...
- 关于使用koa实现线上 https服务
var https=require("https");//https服务var fs= require("fs");var Koa = require('koa ...
- python基础知识-day9(库学习)
1.os学习 1 print(os.name) #获取操作系统 2 print(os.path.exists("D:\soft\python")) #判断路径是否存在 3 prin ...
- 引入gitlab仓库代码到npm包的教程
背景介绍 随着人类地发展,社会地进步,计算机技术地更新迭代,每一片码海里都有它宝贵的财富,每一座码山里都有着各自的秘密.怎么守住财富,隐藏一些秘密,成了一些开发人员所关心的事情. 需求分析 简单地说, ...
- Freeswitch使用originate转dialplan
概述 Freeswitch是一款非常好用的开源VOIP软交换平台. 最近在对fs做一些功能测试,测试的过程中产生的一个需求,如何从fs发起呼叫并把后续的呼叫流程转到某一个dialplan上,这样在测试 ...
- Note -「数论 定理及结论整合」
数学素养 low,表达可能存在不严谨,见谅.我准备慢慢补上证明? Theorems. 裴蜀定理:关于 \(x, y\) 的线性方程 \(ax + by = c\) 有解,当且仅当 \(\gcd (a, ...
- 如何使用API接口批量查询图书信息?
之前小编讲过在Excel表格中根据ISBN查询图书信息可以使用我们的图书查询公式,但偶然间发现少部分书籍由于年份久远导致查不出来,今天小编就教给大家另一种查询图书信息的方式,即通过API接口返回的JS ...
- 一张图进阶 RocketMQ - 消息存储
前言 三此君看了好几本书,看了很多遍源码整理的 一张图进阶 RocketMQ 图片,关于 RocketMQ 你只需要记住这张图!觉得不错的话,记得点赞关注哦. [重要]视频在 B 站同步更新,欢迎围观 ...