WPF中通过自定义Panel实现控件拖动
背景
看到趋时软件的公众号文章(WPF自定义Panel:让拖拽变得更简单),发现可以不通过Drag的方法来实现ListBox控件的拖动,而是通过对控件的坐标相加减去实现控件的位移等判断,因此根据文章里面的代码,边理解边学习的写了这一篇博客,里面结合一定自己的理解,而且存在很多问题没能解决,仅实现了简单的流程,如有大佬可以指点,不慎感激!!
现代码实现效果

代码文件
MainWindow.xaml
<Windowx:Class="DragListDemo2.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:local="clr-namespace:DragListDemo2"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:p="clr-namespace:DragListDemo2.Panels"Title="MainWindow"Width="800"Height="450"mc:Ignorable="d"><Grid><p:DragStackPanel Background="White"><ButtonHeight="50"Margin="0"Content="ceshi1" /><ButtonHeight="50"Margin="0"Content="ceshi2" /><ButtonHeight="50"Margin="0"Content="ceshi3" /><TextBoxHeight="50"HorizontalContentAlignment="Center"VerticalContentAlignment="Center"Text="ceshikankan" /></p:DragStackPanel><!--<ItemsControl HorizontalAlignment="Stretch" VerticalAlignment="Stretch"><ItemsControl.ItemsPanel><ItemsPanelTemplate><p:DragStackPanel Background="Red" /></ItemsPanelTemplate></ItemsControl.ItemsPanel></ItemsControl>--></Grid>
</Window>
DragStackPanel.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;namespace DragListDemo2.Panels
{public class DragStackPanel : Panel{#region 自定义控件排序的方法/// <summary>/// 获取或设置方向/// </summary>public Orientation Orientation{get { return (Orientation)GetValue(OrientationProperty); }set { SetValue(OrientationProperty, value); }}public static readonly DependencyProperty OrientationProperty =DependencyProperty.Register("Orientation", typeof(Orientation), typeof(DragStackPanel), new PropertyMetadata(Orientation.Vertical));protected override Size MeasureOverride(Size availableSize){var panelDesiredSize = new Size();foreach (UIElement child in InternalChildren){//让内部控件去调用Measure,去计算轮廓child.Measure(availableSize);if (this.Orientation == Orientation.Horizontal){panelDesiredSize.Width += child.DesiredSize.Width;panelDesiredSize.Height = double.IsInfinity(availableSize.Height) ? child.DesiredSize.Height : availableSize.Height;}else{panelDesiredSize.Width = double.IsInfinity(availableSize.Width) ? child.DesiredSize.Width : availableSize.Width;panelDesiredSize.Height += child.DesiredSize.Height;}}return panelDesiredSize;}protected override Size ArrangeOverride(Size finalSize){double x = 0, y = 0;foreach (FrameworkElement child in InternalChildren){// 坐标var position = new Point(x, y);// 宽度var width = child.DesiredSize.Width;// 高度var height = child.DesiredSize.Height;// 通过排列方向计算宽度和高度if (this.Orientation == Orientation.Vertical){width = finalSize.Width;}else{height = finalSize.Height;}// 尺寸var size = new Size(width, height);// 排列位置及尺寸child.Arrange(new Rect(position, size));// 计算位置if (this.Orientation == Orientation.Horizontal){x += child.DesiredSize.Width;}else{y += child.DesiredSize.Height;}if (child.Equals(draggingElement)){// 获取当前正在拖拽元素的位置坐标var dragElementPosition = GetDraggingElementMovingPosition(child);if (dragElementPosition != default){// 处理拖拽元素坐标var offset = new Point(dragElementPosition.X - position.X, dragElementPosition.Y - position.Y);child.RenderTransform = new TranslateTransform(offset.X, offset.Y);}}}return finalSize;}#endregionprivate FrameworkElement hitElement;private FrameworkElement draggingElement;private Point mouseRelativePosition;private int draggingElementzIndex;protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e){// 获取鼠标相对于Panel的坐标var mousePosition = e.GetPosition(this);// 通过命中测试获取当前鼠标位置下的元素var hitTestResult = this.InputHitTest(mousePosition) as FrameworkElement;// 通过命中测试结果找到当前拖拽的控件子项draggingElement = FindChild(hitTestResult);if (draggingElement != null && this.InternalChildren.Contains(draggingElement)){// 记录鼠标相对位置,以供后续使用mouseRelativePosition = e.GetPosition(draggingElement);// 暂存ZIndexdraggingElementzIndex = Panel.GetZIndex(draggingElement);// 将ZIndex置顶Panel.SetZIndex(draggingElement, this.InternalChildren.Count);// 添加遮罩,防止拖拽时覆盖//AddOverlay(draggingElement);e.Handled = true;}base.OnPreviewMouseLeftButtonDown(e);}protected override void OnPreviewMouseMove(MouseEventArgs e){var mousePosition = e.GetPosition(this);if (e.LeftButton == MouseButtonState.Pressed && draggingElement != null){// 当前拖拽控件置为不可鼠标命中,以供命中下一层的换位控件draggingElement.IsHitTestVisible = false;// 判断当前拖拽的控件是否为顶层控件if (Panel.GetZIndex(draggingElement) == this.InternalChildren.Count){// 计算出当前拖拽控件相对于this的位置(控件左上角)var targetPosition = new Point(mousePosition.X - mouseRelativePosition.X - draggingElement.Margin.Left, mousePosition.Y - mouseRelativePosition.Y - draggingElement.Margin.Top);// 获取当前拖拽控件在this中的原始位置var draggingElementOriginalPosition = GetDraggingElementOriginPosition(draggingElement);// 计算拖拽控件移动时的偏移量var offset = new Point(targetPosition.X - draggingElementOriginalPosition.X, targetPosition.Y - draggingElementOriginalPosition.Y);// 应用位移draggingElement.RenderTransform = new TranslateTransform(offset.X, offset.Y);}// 命中当前拖拽控件的下一层控件var hitTestResult = this.InputHitTest(mousePosition) as FrameworkElement;// 查找被命中的下一层换位控件hitElement = FindChild(hitTestResult);// 判断是否有效if (hitElement != null && this.InternalChildren.Contains(hitElement)){// 应用换位MoveChild(draggingElement, hitElement);}e.Handled = true;}base.OnPreviewMouseMove(e);}protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e){if (draggingElement == null) return;mouseRelativePosition = default;//RemoveOverlay(draggingElement);Panel.SetZIndex(draggingElement, draggingElementzIndex);draggingElement.IsHitTestVisible = true;draggingElement.RenderTransform = null;draggingElement = null;e.Handled = true;base.OnPreviewMouseLeftButtonUp(e);}#region Method/// <summary>/// 找到拖拽的控件/// </summary>/// <param name="frameworkElement"></param>/// <returns></returns>private FrameworkElement FindChild(FrameworkElement frameworkElement){if (frameworkElement == null) return null;DependencyObject parent = VisualTreeHelper.GetParent(frameworkElement);if (parent != null && parent is FrameworkElement frameworkParent){if (frameworkParent != this){var result = FindChild(frameworkParent);return result;}else{return frameworkElement;}}return null;}/// <summary>/// 获得拖动控件在panel中的坐标点/// </summary>/// <param name="frameworkElement"></param>/// <returns></returns>private Point GetDraggingElementMovingPosition(FrameworkElement frameworkElement){Point position = frameworkElement.TranslatePoint(new Point(), this);return position;}/// <summary>/// 获得拖动控件的原始坐标/// </summary>/// <param name="frameworkElement"></param>/// <returns></returns>private Point GetDraggingElementOriginPosition(FrameworkElement frameworkElement){Point position = frameworkElement.TranslatePoint(new Point(), this);if(frameworkElement.RenderTransform==null) return position;return new Point(position.X - frameworkElement.RenderTransform.Value.OffsetX, position.Y - frameworkElement.RenderTransform.Value.OffsetY);}private void SetDraggingElementMovingPosition(FrameworkElement child, Point dragPosition){mouseRelativePosition = child.TranslatePoint(dragPosition, this);}private void MoveChild(FrameworkElement element1, FrameworkElement element2){var index1 = this.InternalChildren.IndexOf(element1);var index2 = this.InternalChildren.IndexOf(element2);if (index1 >= 0 && index2 >= 0){this.InternalChildren.RemoveAt(index1);this.InternalChildren.Insert(index2, element1);}}#endregion}
}
现存待解决问题
- 控件拖拽时跨越边界问题。
- 跨越控件拖拽问题。
- panel必须设置Background不透明,才能实现OnPreviewMouseMove事件的监控。
完结
研究暂时告一段落,等后面有时间时再深入去理解公众号中的代码,希望这一点经验可以给你带来帮助
相关文章:
WPF中通过自定义Panel实现控件拖动
背景 看到趋时软件的公众号文章(WPF自定义Panel:让拖拽变得更简单),发现可以不通过Drag的方法来实现ListBox控件的拖动,而是通过对控件的坐标相加减去实现控件的位移等判断,因此根据文章里面的代码,边理解边…...
Centos7安装Docker与Docker-compose【图文教程】
个人记录 查看一下系统是否已经安装了Docker yum list installed | grep docker如下图代表没有安装Docker 卸载已有Docker yum remove docker docker-common docker-selinux docker-engine切换目录 cd /etc/yum.repos.d/查看当前目录所有的镜像源 ll安装yum-util与devi…...
mac电脑maven配置环境变量
1、下载maven https://maven.apache.org 2、配置环境变量 vim .bash_profile JAVA_HOME/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home PATH$JAVA_HOME/bin:$PATH export JAVA_HOME export PATH#maven export MAVEN_HOME/Users/haines/desktop/work/java/a…...
后端返还二进制excl表格数据时候,如何实现在前端下载表格功能及出现表格打开失败的异常处理。
背景: 后端返还一个二进制流的excl表格数据,前端需要对其解析,然后可提供给客户进行下载。 思路:把二进制流数据转换给blob对象,然后利用a标签进行前端下载。 代码: 后端返还 类似如下的数据 前端代码…...
搞学术研究好用免费的学术版ChatGPT网站-学术AI
学术版ChatGPThttps://chat.uaskgpt.com/mobile/?user_sn88&channelcsdn&scenelogin 推荐一个非常适合中国本科硕士博士等学生老师使用的学术版ChatGPT, 对接了超大型学术模型,利用AI技术实现学术润色、中英文翻译,学术纠错&#…...
vue3从精通到入门9:计算属性computed
在 Vue 3 中,computed 是一个用于创建计算属性的工具,它基于组件的响应式依赖进行复杂的计算,并返回一个新的响应式引用。计算属性是 Vue 的一个核心概念,它提供了一种声明式的方式来执行基于其依赖的响应式数据的计算。 compute…...
kafka面试常见问题
1、如何判断kafka某个主题消息堆积? 要判断Kafka中某个主题的消息是否堆积,可以通过查看该主题的生产者和消费者的偏移量(offset)差异来实现。Kafka中的每条消息在主题的分区内都有一个唯一的偏移量,生产者每发送一条…...
深入解析Hadoop生态核心组件:HDFS、MapReduce和YARN
这里写目录标题 01HDFS02Yarn03Hive04HBase1.特点2.存储 05Spark及Spark Streaming关于作者:推荐理由:作者直播推荐: 一篇讲明白 Hadoop 生态的三大部件 进入大数据阶段就意味着进入NoSQL阶段,更多的是面向…...
【chatGPT】我:在Cadence Genus软件中,出现如下问题:......【1】
我 在Cadence Genus中,出现如下问题:Error:A command argument did not match any of the acceptable command option. [TUI-170] [set_db] :‘/’ is not a legal option for the command. 该如何解决 ChatGPT Cadence Genus的错误消息 “…...
面试题:JVM 调优
一、JVM 参数设置 1. tomcat 的设置 vm 参数 修改 TOMCAT_HOME/bin/catalina.sh 文件,如下图 JAVA_OPTS"-Xms512m -Xmx1024m" 2. springboot 项目 jar 文件启动 通常在linux系统下直接加参数启动springboot项目 nohup java -Xms512m -Xmx1024m -jar…...
PS从入门到精通视频各类教程整理全集,包含素材、作业等(8)
PS从入门到精通视频各类教程整理全集,包含素材、作业等 最新PS以及插件合集,可在我以往文章中找到 由于阿里云盘有分享次受限制和文件大小限制,今天先分享到这里,后续持续更新 B站-PS异闻录:萌新系统入门课课程视频 …...
VSCode安装及Python、Jupyter插件安装使用
VSCode 介绍 Visual Studio Code(简称VSCode)是一个由微软开发的免费、开源的代码编辑器。VSCode是一个轻量级但是非常强大的代码编辑器,它支持多种编程语言(如C,C#,Java,Python,PHP࿰…...
JMeter+Grafana+influxdb 配置出现transaction无数据情况解决办法
JMeterGrafanainfluxdb 配置出现transaction无数据情况解决办法 一、问题描述二、解决方法 一、问题描述 如下图所示出现application有数据但是transaction无数据情况 二、解决方法 需要做如下设置 打开变量设置如下图打开两个选项 然后再进行后端监听器的设置 如下图所…...
Acrobat Pro DC 2023 for Mac PDF编辑管理软件
Acrobat Pro DC 2023 for Mac是一款功能强大的PDF编辑和管理软件,旨在帮助用户轻松处理PDF文件。它提供了丰富的工具和功能,使用户可以创建、编辑、转换和注释PDF文件,以及填写和签署PDF表单。 软件下载:Acrobat Pro DC 2023 for …...
Python大型数据集(GPU)可视化和Pillow解释性视觉推理及材料粒子凝聚
🎯要点 Python图像处理Pillow库:🎯打开图像、保存图像、保存期间的压缩方式、读取方法、创建缩略图、创建图像查看器。🎯获取 RGB 值,从图像中获取颜色,更改像素颜色,转换为黑…...
1、快速上手Docker:入门指南
文章目录 Linux中安装docker防火墙端口配置web项目需要的环境安装yarn安装nodejs安装脚手架并准备项目 构建镜像启动镜像查看日志管理镜像推送镜像 发布项目准备服务器环境部署项目: PS:扩展一点小知识 这篇文章只是docker入门的第一个Docker项目&#x…...
通用开发技能系列:Authentication、OAuth、JWT 认证策略
云原生学习路线导航页(持续更新中) 本文是 通用开发技能系列 文章,主要对编程通用技能 Authentication、OAuth、JWT 认证策略 进行学习 1.Basic Authentication认证 每个请求都需要将 用户名密码 进行base64编码后,放在请求头的A…...
【Leetcode】【240404】1614. Maximum Nesting Depth of the Parentheses
BGM(?):圣堂之门-阿沁《梵谷的左耳》 Description A string is a valid parentheses string (denoted VPS) if it meets one of the following: It is an empty string “”, or a single character not equal to “(” or “)…...
联通iccid 19转20 使用luhn 算法的计算公式
联通iccid 19转20 使用luhn 算法的计算公式 第一次对接iccid 才知道 使用的是luhn 算法 19转20位 文章来源于 文章来源 当时也是一脸懵逼 的状态,然后各种chatgpt 寻找,怎么找都发现不对,最后看到这片java的文章实验是正确的,因…...
I.MX6ULL的MAC网络外设设备树实现说明一
一. 简介 IMX6ULL芯片内部集成了两个 10/100M 的网络 MAC 外设,所以,ALPHA开发板上的有线网络的硬件方案是: SOC内部集成网络MAC外设 PHY网络芯片方案。 本文来说明一下MAC网络外设的设备节点信息的实现。 因此, I.MX6ULL 网络…...
AI-Git-Narrator:基于LLM的Git提交历史自动化分析与文档生成工具
1. 项目概述与核心价值最近在折腾一个挺有意思的开源项目,叫AI-Git-Narrator。简单来说,它就像一个能“看懂”你代码提交历史的AI解说员。每次你往Git仓库里推送代码,它都能自动分析你这次提交到底改了啥,然后用自然语言生成一段清…...
点式玻璃幕墙及采光顶设计的一些想法
点式玻璃幕墙及采光顶设计的一些想法 点式玻璃幕墙是在主龙骨上面固定点支撑装置,由点支撑装置支撑玻璃面板的一种常用幕墙表现形式,他最早起源于国外。因为玻璃的通透性,建筑内外有效融合,空间感增强,开阔了视野,增加了建筑物的现代感。 点式玻璃幕墙最主要的组成部分是…...
工业多串口通信实战:基于EM9170的8串口方案设计与优化
1. 项目概述:为什么8串口在今天依然重要?在物联网、工业自动化、智能楼宇这些领域里摸爬滚打久了,你会发现一个有趣的现象:那些看似“古老”的通信接口,生命力往往比我们想象的要顽强得多。串口,或者说RS-2…...
Shell 相关基础入门,在 Ubuntu 与 CentOS Shell 中的语法差异总结(bash、dash、sh)
新建的test.sh文件,vim进去,每行开头都默认有一个~符号,是什么意思,而且在里面鼠标也失效了? 你问的这两个问题,恰好是初学者刚接触 vim 编辑器时最常遇到的两个困惑。它们完全正常,不是系统出错…...
实战配置:5个提升MPC-HC播放器性能的专业技巧
实战配置:5个提升MPC-HC播放器性能的专业技巧 【免费下载链接】mpc-hc MPC-HCs main repository. For support use our Trac: https://trac.mpc-hc.org/ 项目地址: https://gitcode.com/gh_mirrors/mpc/mpc-hc Media Player Classic - Home Cinema࿰…...
猫抓插件:三步轻松下载网页视频音频资源的终极指南
猫抓插件:三步轻松下载网页视频音频资源的终极指南 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 你是否曾经在网上看到一个精彩的视频…...
Python异步编程与Discord机器人开发:pincer库实战指南
1. 项目概述与核心价值最近在折腾一个游戏服务器的后端,发现处理实时通信和状态同步这块儿,用传统的HTTP轮询或者WebSocket裸写,代码很快就变得又臭又长,维护起来简直是噩梦。就在我头疼的时候,社区里一个叫pincer的项…...
高光谱数据处理避坑指南:从RAW文件到反射率,你的白板校正做对了吗?
高光谱数据处理避坑指南:从RAW文件到反射率,你的白板校正做对了吗? 在实验室里,一位研究员盯着屏幕上扭曲的反射率曲线皱起了眉头——明明按照标准流程采集了白板和暗电流数据,为什么最终结果会出现负值和异常波动&am…...
HttpOnly Cookie 深度解析
一、什么是 HttpOnly Cookie HttpOnly 是一个可以附加在 Set-Cookie 响应头上的标志位(flag)。当一个 Cookie 被标记为 HttpOnly 后,客户端脚本(如 JavaScript)将无法通过 document.cookie 等 API 访问该 Cookie&…...
3分钟掌握:163MusicLyrics终极免费歌词解决方案全攻略
3分钟掌握:163MusicLyrics终极免费歌词解决方案全攻略 【免费下载链接】163MusicLyrics 云音乐歌词获取处理工具【网易云、QQ音乐】 项目地址: https://gitcode.com/GitHub_Trending/16/163MusicLyrics 想要快速获取网易云音乐和QQ音乐的歌词吗?1…...
