概述

本文描述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)的更多相关文章

  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. 『忘了再学』Shell基础 — 27、AWK编程的介绍和基本使用

    目录 1.AWK介绍 (1)AWK概述 (2)printf格式化输出 (3)printf命令说明 2.AWK的基本使用 (1)AWK命令说明 (2)AWK命令使用 1.AWK介绍 (1)AWK概述 A ...

  2. Docker容器手动安装mysql(国内镜像)

    Docker手动安装mysql 1.创建centos镜像的容器 [root@localhost Tools]# docker run -i -d -h zabbix --name zabbix -p ...

  3. (数据科学学习手札138)使用sklearnex大幅加速scikit-learn运算

    本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 大家好我是费老师,scikit-learn作为经 ...

  4. 驱动开发实战之TcpClient

    场景模拟 假设你有一批非标设备需要对接,对方提供了如下协议文档: 协议概述 设备作为TCPServer,端口6666 字节序:Little-Endian,即低地址存放低位 请求回复 需要你主动发起读取 ...

  5. JavaScript与Node.js一起打造一款聊天App

    聊天是我们人与人交流最直接的方式,互联网的加入使我们交流更加便捷.我们手机上的微信.QQ是我们手机必不可少的应用软件.那么,我们是否可以做一款聊天应用呢? 之前我自己闲着没事,研究过一些技术,做了一款 ...

  6. Spring框架系列(5) - 深入浅出SpringMVC请求流程和案例

    前文我们介绍了Spring框架和Spring框架中最为重要的两个技术点(IOC和AOP),那我们如何更好的构建上层的应用呢(比如web 应用),这便是SpringMVC:Spring MVC是Spri ...

  7. PowerDesigner安装

    1.双击安装包进行安装 2.选择PRC 3.一路往下就行 4.将下图文件夹中的内容覆盖安装的内容 ----------------------------------------分割线 5.安装成功 ...

  8. Tapdata 实时数据融合平台解决方案(五):落地

    作者介绍:TJ,唐建法,Tapdata 钛铂数据 CTO,MongoDB中文社区主席,原MongoDB大中华区首席架构师,极客时间MongoDB视频课程讲师. 通过前面几篇文章,我们从企业数据整合与分 ...

  9. 比起网易有数BI,也许这款数据可视化软件更适合你!

    有数BI是网易推出的面向企业客户的可视化敏捷BI产品.拥有数据填报和自助式商业智能分析产品,提供网页端和手机端应用,帮助客户快速实现数据填报.多维分析.大数据探索.实时大数据展示和成员分享. 山海鲸可 ...

  10. springboot中配置skywalking请求日志

    pom.xml配置 <dependency> <groupId>org.apache.skywalking</groupId> <artifactId> ...