WPF TreeView拖动排序拖拽排列

article/2025/9/14 8:06:01

底部附有Demo示例。需要的朋友可以去下载参考

一、图示

先上图,不知为啥,GIF总看起来特别卡,实际却很流畅。

由于录制问题,GIF动画只会播放一次,需要重复观看的,请将网页关闭后重新打开再观看

 

WPF的资料太少了,找些动画什么的都没有,最近工作中要用Treeview进行拖动排序,但是呢,网上几乎没有相关的Demo示例等,只能自己手撸,在这过程中,也学到了一些知识,我在此记录下,防止下次用到,或者需要的小伙伴用到。

二、 原理

我是利用控件的平移实现拖动效果的。当平移到边界的时候,会触发TreeView的上下滚动(滚动的时候也会平移);脱离边界则停止滚动;滚到最上和最下时,停止平移;当手指抬起来时,捕获当前鼠标的位置,将拖动的Item插入到当前鼠标位置的Item前面,同时删除数据集合旧的数据,添加新的数据到添加的位置

技术点:

1. TreeView代码控制滚动

2. 控件拖动,平移

3. 获取控件在其他父控件中的相对位置

4. 利用鼠标事件做平移,和拖动结束后的排序

三、代码解析

首先我们将布局写好,布局就是一个普通的TreeView。不过我们需要给控件添加一个MouseMove事件,该事件用来判断是否鼠标移动到了边界,然后做响应的自动滚动动作。

<Window x:Class="WpfAppTest.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WpfAppTest"mc:Ignorable="d"Loaded="OnViewLoaded"Title="MainWindow" Height="450" Width="800"><Grid><TreeView x:Name="mTreeView" Width="300" HorizontalAlignment="Left" MouseMove="ParentMove"/></Grid>
</Window>

接下来在初始化的时候,我们将TreeView的数据添加到Dictionary里面,再根据Dictionary的数据初始化TreeView的Item。

private void initData()
{datas["A"] = new List<string>();datas["A"].Add("阿轲");datas["A"].Add("艾琳");datas["A"].Add("安其拉");datas["B"] = new List<string>();datas["B"].Add("白起");datas["B"].Add("百里守约");datas["B"].Add("扁鹊");datas["B"].Add("百里玄策");datas["B"].Add("不知火舞");datas["C"] = new List<string>();datas["C"].Add("蔡文姬");datas["C"].Add("曹操");datas["C"].Add("陈咬金");datas["C"].Add("嫦娥");datas["C"].Add("成吉思汗");datas["D"] = new List<string>();datas["D"].Add("狄仁杰");datas["D"].Add("典韦");datas["D"].Add("貂蝉");datas["D"].Add("盾山");datas["D"].Add("东皇太一");datas["D"].Add("妲己");datas["D"].Add("大乔");datas["D"].Add("达摩");datas["G"] = new List<string>();datas["G"].Add("宫本武藏");datas["G"].Add("高渐离");datas["G"].Add("鬼谷子");datas["G"].Add("干将莫邪");datas["G"].Add("关羽");datas["G"].Add("公孙离");datas["H"] = new List<string>();datas["H"].Add("后裔");datas["H"].Add("韩信");datas["H"].Add("花木兰");datas["H"].Add("黄忠");
}private void initViews()
{foreach (KeyValuePair<string, List<string>> kv in datas){TreeViewItem parentItem = new TreeViewItem();parentItem.Header = kv.Key;parentItem.Tag = kv.Value;mTreeView.Items.Add(parentItem);foreach (string info in kv.Value)parentItem.Items.Add(creatItem(info));}
}private TreeViewItem creatItem(object header)
{TreeViewItem newItem = new TreeViewItem();newItem.Header = header;newItem.PreviewMouseDown += ItemMouseDown;newItem.PreviewMouseMove += ItemMove;newItem.PreviewMouseUp += ItemMouseUp;newItem.Height = 30;return newItem;
}

视图和数据准备好后,旧开始准备做拖动的动作,我上面给Item添加了3个事件,分别是PreviewMouseDown,PreviewMouseMove,PreviewMouseUp。这3个事件就是我们拖动Item的关键了

PreviewMouseDown:获取鼠标点击位置,作为移动的初始位置。初始化平移对象transform

PreviewMouseMove:根据鼠标的移动,对Item进行平移

PreviewMouseUp:鼠标抬起时捕获要插入的位置的Item,并做Item插入

上面我们不是还添加了TreeView的MouseMove事件吗,这个事件主要是边缘检测,也会在这里开启自动滚动

PreviewMouseDown事件的代码比较简单,lastPosition记录了上次移动的坐标,用来计算鼠标移动了多远,我们就将Item移动多远。_isMouseDown是拖动的标记,只有按下时,才会计算鼠标移动的距离,transform是我们用来拖动Item的对象

private void ItemMouseDown(object sender, MouseButtonEventArgs e)
{lastPosition = 0;var c = sender as UIElement;_isMouseDown = true;TreeViewItem treeViewItem = (TreeViewItem)sender;mouseDownOffset = e.GetPosition(treeViewItem).Y + 5;_mouseDownPosition = e.GetPosition(this);var transform = c.RenderTransform as TranslateTransform;if (transform == null){transform = new TranslateTransform();c.RenderTransform = transform;}c.CaptureMouse();
}

PreviewMouseMove事件计算鼠标移动的距离,然后将Item也拖动响应的距离。然后将本次的Y轴位置服给lastPosition,以便下次计算使用。

e.GetPosition(UIElement) 是获取鼠标在控件中的位置,要要获取哪个控件的相对位置,就控件作为参数传给GetPosition,就可以获取位置。Position和Position之间可以做加减法,计算两个坐标之间的直线距离

if (_isMouseDown && !isRollingItem[0])
{TreeViewItem viewItem = (TreeViewItem)sender;dragingItemHeight = viewItem.ActualHeight;var c = sender as UIElement;var pos = e.GetPosition(this);var dp = pos - _mouseDownPosition;transform = c.RenderTransform as TranslateTransform;//transform.X = _mouseDownControlPosition.X + dp.X;//transform.Y = _mouseDownControlPosition.Y + dp.Y;transform.Y = transform.Y + (dp.Y - lastPosition);lastPosition = dp.Y;
}

PreviewMouseUp事件就直接计算当前位置,将拖动的Item插入到当前位置。

我在项目中使用控件时,发现向上拖动会在其他Item上面,向下拖动会在Item底下(被遮挡)。当然我的Item是我自定义的布局。 当鼠标弹起时,并不能及时捕获鼠标在哪个Item上停留,需要等待大约100毫秒后才能获取到。所以遇到问题还得变通下

下面我们看下TreeView自动滚动的代码,这个比较重要,需要TreeView滚动的同时,拖动的Item也要平移

private void ScrollingOnMainThread(object obj)
{bool isDownScroll = (bool)obj;if (isDownScroll){if (scrollViewer.VerticalOffset >= (scrollViewer.ExtentHeight - mTreeView.ActualHeight)) return;scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + 6);    //向下调节垂直滚动条的位置;transform.Y += 6;}else{if (scrollViewer.VerticalOffset <= 0) return;scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - 6);    //向上调节垂直滚动条的位置;transform.Y -= 6;}
}

重要的也就这些,现在你的控件应该也能动起来了。

如果你还需要demo示例参考或查看效果。请查看链接

WPFTreeView拖动排序拖拽排列-桌面系统文档类资源-CSDN下载

本文代码实现简单,扩展性强,Demo示例总共也就267行,代码还包含了数据初始化,View控件初始化等非逻辑代码,代码简单以维护。 

如果在使用中有不懂的,您还可以通过私信询问,我经常在线的,帮您解答使用中的困难。 Demo示例有可能不太完善,如果您发现问题,可以提出来大家共同学习,将项目完善起来,本文试我手撸的代码,如果您喜欢,还请点赞支持下。


http://chatgpt.dhexx.cn/article/kYOuHEkd.shtml

相关文章

js原生拖拽的两种方法

一.mousedown、mousemove和mouseup 拖着目标元素在页面任意位置 如果要设置物体拖拽&#xff0c;那么必须使用三个事件&#xff0c;并且这三个事件的使用顺序不能颠倒。 1.onmousedown&#xff1a;鼠标按下事件 2.onmousemove&#xff1a;鼠标移动事件 3.onmouseup&#xff…

前端原生拖拽(drag drop)的一点小总结

新工作中&#xff0c;第一个手生的功能&#xff0c;遇到了很多诡异的问题&#xff0c;今天终于解惑了。最终原因还是对代码没有透彻的了解&#xff0c;jquery的运用也不熟练导致的。稍稍的记录一下。 原始功能 对项目列表中的元素进行拖拽&#xff0c;拖拽到一定的位置&#xf…

Vue2 _ 实现拖拽功能

老项目重构&#xff0c;其中有一些拖拽功能&#xff0c;不过用的是两个开源 JS 拖拽文件实现的效果&#xff0c;版本太老了&#xff0c;所以需要换代了&#xff0c;然后就查阅了能够用 Vue 来简单快速实现拖拽的功能实现方法 &#xff1a; 目录 一、HTML 拖放 二、Vue.Dragg…

vue2 使用 Sortable 库进行拖拽操作

一、vue 项目使用 文档地址&#xff1a; https://www.itxst.com/sortablejs/neuinffi.html 1、安装依赖 npm i -S vuedraggable2、.vue 文件引入组件 import draggable from "vuedraggable"; components: { draggable },3、.使用 查看文档中的示例即可&#xff…

空指针、悬空指针、野指针

文章目录 前言一、指针&#xff1f;二、指针的应用场景三、 空指针四、 悬空指针五、 野指针正确用法 总结 前言 相信很多小伙伴对指针的使用都有一定的了解了。但更多的人可能对指针又爱又恨。这次我们谈点重要的&#xff0c;进一步加深对指针的理解 一、指针&#xff1f; 指…

【C语言】指针(野指针)

目录 1&#xff1a;什么是野指针&#xff1f; 2&#xff1a;如何规避野指针 1.1&#xff1a;指针变量的初始化 2.2&#xff1a;指针越界访问 3.3&#xff1a;指针指向的空间如果我们还回去的话&#xff0c;就把指针指针置为NULL 4.4&#xff1a;指针使用之前检查有效性…

C语言的野指针

1.野指针 指针变量中的值是非法的内存地址&#xff0c;进而形成野指针野指针不是NULL指针&#xff0c;是指向不可用内存地址的指针NULL指针并无危害&#xff0c;很好判断&#xff0c;也很好调试C语言中无法判断一个指针所保存的地址是否合法&#xff0c;合法的地址是通过变量或…

初识C语言---野指针

野指针概念&#xff1a; 野指针就是指针指向的位置是不可知的&#xff08;随机的、不正确的、没有明确限制的&#xff09;。 一、野指针成因 1、指针未初始化就使用 #include<stdio.h> int main() {int* p; *p 10; return 0; }此段代码中&#…

野指针(概念,产生原因,危害,避免方法)

思维导图: 1.野指针与垂悬指针的区别: 野指针:访问一个已销毁或者访问受限的内存区域的指针,野指针不能判断是否为NULL来避免 垂悬指针:指针正常初始化,曾指向一个对象,该对象被销毁了,但是指针未制空,那么就成了悬空指针。 2.概念 指针指向了一块随机的空间,不受…

野指针概念、定义、及如何规避野指针

野指针 野指针的概念&#xff1a; 野指针就是指针指向的位置不可知的。&#xff08;随机的、不正确的、没有明确限制的&#xff09; 野指针的三种情况 1、指针未定义 #include <stdio.h> int main() {int* p; //局部变量指针未初始化&#xff0c;默认就是随机值*p10;r…

使用 OKTA 作为 SAML IdP 为 FortiClient配置 SAML SSO 登录

描述 随着用于 FortiGate 和 FortiClient 6.4 的 FortiOS 6.4 的发布,现在可以创建SSL SSO 单点登录解决方案,该解决方案可以集成第三方 SAML SSO 身份提供商 (IdP) 并利用其 MFA 功能。 前置条件 FortiGate 运行 FortiOS 6.4.0 或更高版本FortiClient 6.4.0 或更高版本OK…

SAML 流程讲解

SAML&#xff08;Security Assert Mark Language&#xff09;常用来实现SSO。 本文主要梳理一下SAML的代码逻辑 术语讲解&#xff1a; IDP: Identity provider 在单点登陆中是指统一身份认证平台。 SP&#xff1a;Service Provider 在单点登陆中是指需要被认证的服务方。 A…

SAML单点登录-spring-security-saml 整合使用

本文链接&#xff1a;http://t.csdn.cn/BIGKc SAML单点登录-spring-security-saml客户端SP 使用spring-security-saml搭建SAML协议的客户端&#xff0c;该依赖是spring框架的官方库&#xff0c;配置方便、文档详细。提供了包括单点登录、单点登出、获取sq元数据文件等接口&…

盘点认证协议 : 普及篇之SAML

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> &#x1f61c;&#x1f61c;&#x1f61c; 文章合集 : &#x1f381; https://juejin.cn/post/6941642435189538824 Github : &#x1f449; https://github.com/black-ant CASE 备份 : &#x1f449…

SAML2.0 笔记(二)

文章目录 零、示例代码参考一、前言二、共通内容1.1、引入依赖1.2、初始化SAML部分1.2.1、检查JCE环境1.2.2、初始化服务 1.3、拦截器部分1.3.1、构建AuthnRequest1.3.2、AuthRequest解析1.3.3、SP模式选择1.3.4、IDP模式选择 1.4、涉及的工具类1.4.1、OpenSAMLUtils工具类1.4.…

SAML单点登录-spring-security-saml客户端SP

SAML单点登录-spring-security-saml客户端SP 使用spring-security-saml搭建SAML协议的客户端&#xff0c;该依赖是spring框架的官方库&#xff0c;配置方便、文档详细。提供了包括单点登录、单点登出、获取sq元数据文件等接口&#xff0c;无需自己实现&#xff0c;参考&#x…

SAML入门

SAML (Security Assertion Markup Language)入门 提到SAML (Security Assertion Markup Language), 很多人都会联想到单点登录SSO。那么Saml到底是什么&#xff0c;它跟sso到底有什么联系&#xff1f;这里给大家分享一下我在读完了saml差不多全部规范之后的一些心得。希望给sa…

SAML

SAML SAML&#xff08;Security Assertion Markup Language&#xff09;是一个基于XML的开源标准数据格式&#xff0c;它在当事方之间交换身份验证和授权数据&#xff0c;尤其是在身份提供者和服务提供者之间交换。SAML2.0可以实现基于网络跨域的单点登录&#xff08;SSO&…

基于SAML的单点登录介绍

一、背景知识&#xff1a; SAML即安全断言标记语言&#xff0c;英文全称是Security Assertion Markup Language。它是一个基于XML的标准&#xff0c;用于在不同的安全域(security domain)之间交换认证和授权数据。在SAML标准定义了身份提供者(identity provider)和服务提供者(s…

走进SAML——基础篇

SAML的全称是Security Assertion Markup Language。提到SAML&#xff0c;我们主要想到的是其在各种单点登录场景中大行其道。单点登录我们通常叫做SSO&#xff0c;那么SAML到底是如何实现SSO的呢&#xff1f;在这个系列的文章中&#xff0c;我将为大家阐释清楚。不过&#xff0…