当前位置: 首页 > news >正文

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实现控件拖动

背景 看到趋时软件的公众号文章&#xff08;WPF自定义Panel&#xff1a;让拖拽变得更简单&#xff09;&#xff0c;发现可以不通过Drag的方法来实现ListBox控件的拖动&#xff0c;而是通过对控件的坐标相加减去实现控件的位移等判断&#xff0c;因此根据文章里面的代码,边理解边…...

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表格数据时候,如何实现在前端下载表格功能及出现表格打开失败的异常处理。

背景&#xff1a; 后端返还一个二进制流的excl表格数据&#xff0c;前端需要对其解析&#xff0c;然后可提供给客户进行下载。 思路&#xff1a;把二进制流数据转换给blob对象&#xff0c;然后利用a标签进行前端下载。 代码&#xff1a; 后端返还 类似如下的数据 前端代码…...

搞学术研究好用免费的学术版ChatGPT网站-学术AI

学术版ChatGPThttps://chat.uaskgpt.com/mobile/?user_sn88&channelcsdn&scenelogin 推荐一个非常适合中国本科硕士博士等学生老师使用的学术版ChatGPT&#xff0c; 对接了超大型学术模型&#xff0c;利用AI技术实现学术润色、中英文翻译&#xff0c;学术纠错&#…...

vue3从精通到入门9:计算属性computed

在 Vue 3 中&#xff0c;computed 是一个用于创建计算属性的工具&#xff0c;它基于组件的响应式依赖进行复杂的计算&#xff0c;并返回一个新的响应式引用。计算属性是 Vue 的一个核心概念&#xff0c;它提供了一种声明式的方式来执行基于其依赖的响应式数据的计算。 compute…...

kafka面试常见问题

1、如何判断kafka某个主题消息堆积&#xff1f; 要判断Kafka中某个主题的消息是否堆积&#xff0c;可以通过查看该主题的生产者和消费者的偏移量&#xff08;offset&#xff09;差异来实现。Kafka中的每条消息在主题的分区内都有一个唯一的偏移量&#xff0c;生产者每发送一条…...

深入解析Hadoop生态核心组件:HDFS、MapReduce和YARN

这里写目录标题 01HDFS02Yarn03Hive04HBase1&#xff0e;特点2&#xff0e;存储 05Spark及Spark Streaming关于作者&#xff1a;推荐理由&#xff1a;作者直播推荐&#xff1a; 一篇讲明白 Hadoop 生态的三大部件 进入大数据阶段就意味着进入NoSQL阶段&#xff0c;更多的是面向…...

【chatGPT】我:在Cadence Genus软件中,出现如下问题:......【1】

我 在Cadence Genus中&#xff0c;出现如下问题&#xff1a;Error&#xff1a;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 文件&#xff0c;如下图 JAVA_OPTS"-Xms512m -Xmx1024m" 2. springboot 项目 jar 文件启动 通常在linux系统下直接加参数启动springboot项目 nohup java -Xms512m -Xmx1024m -jar…...

PS从入门到精通视频各类教程整理全集,包含素材、作业等(8)

PS从入门到精通视频各类教程整理全集&#xff0c;包含素材、作业等 最新PS以及插件合集&#xff0c;可在我以往文章中找到 由于阿里云盘有分享次受限制和文件大小限制&#xff0c;今天先分享到这里&#xff0c;后续持续更新 B站-PS异闻录&#xff1a;萌新系统入门课课程视频 …...

VSCode安装及Python、Jupyter插件安装使用

VSCode 介绍 Visual Studio Code&#xff08;简称VSCode&#xff09;是一个由微软开发的免费、开源的代码编辑器。VSCode是一个轻量级但是非常强大的代码编辑器&#xff0c;它支持多种编程语言&#xff08;如C,C#&#xff0c;Java&#xff0c;Python&#xff0c;PHP&#xff0…...

JMeter+Grafana+influxdb 配置出现transaction无数据情况解决办法

JMeterGrafanainfluxdb 配置出现transaction无数据情况解决办法 一、问题描述二、解决方法 一、问题描述 如下图所示出现application有数据但是transaction无数据情况 二、解决方法 需要做如下设置 打开变量设置如下图打开两个选项 然后再进行后端监听器的设置 如下图所…...

Acrobat Pro DC 2023 for Mac PDF编辑管理软件

Acrobat Pro DC 2023 for Mac是一款功能强大的PDF编辑和管理软件&#xff0c;旨在帮助用户轻松处理PDF文件。它提供了丰富的工具和功能&#xff0c;使用户可以创建、编辑、转换和注释PDF文件&#xff0c;以及填写和签署PDF表单。 软件下载&#xff1a;Acrobat Pro DC 2023 for …...

Python大型数据集(GPU)可视化和Pillow解释性视觉推理及材料粒子凝聚

&#x1f3af;要点 P​y​t​ho​n​图像​处理Pillow​库​&#xff1a;&#x1f3af;打开图像、保存图像、保存期间的压缩方式、读取方法、创建缩略图、创建图像查看器。&#x1f3af;获取 RGB 值&#xff0c;从图像中获取颜色&#xff0c;更改像素颜色&#xff0c;转换为黑…...

1、快速上手Docker:入门指南

文章目录 Linux中安装docker防火墙端口配置web项目需要的环境安装yarn安装nodejs安装脚手架并准备项目 构建镜像启动镜像查看日志管理镜像推送镜像 发布项目准备服务器环境部署项目&#xff1a; PS&#xff1a;扩展一点小知识 这篇文章只是docker入门的第一个Docker项目&#x…...

通用开发技能系列:Authentication、OAuth、JWT 认证策略

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 通用开发技能系列 文章&#xff0c;主要对编程通用技能 Authentication、OAuth、JWT 认证策略 进行学习 1.Basic Authentication认证 每个请求都需要将 用户名密码 进行base64编码后&#xff0c;放在请求头的A…...

【Leetcode】【240404】1614. Maximum Nesting Depth of the Parentheses

BGM&#xff08;&#xff1f;&#xff09;&#xff1a;圣堂之门-阿沁《梵谷的左耳》 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位 文章来源于 文章来源 当时也是一脸懵逼 的状态&#xff0c;然后各种chatgpt 寻找&#xff0c;怎么找都发现不对&#xff0c;最后看到这片java的文章实验是正确的&#xff0c;因…...

I.MX6ULL的MAC网络外设设备树实现说明一

一. 简介 IMX6ULL芯片内部集成了两个 10/100M 的网络 MAC 外设&#xff0c;所以&#xff0c;ALPHA开发板上的有线网络的硬件方案是&#xff1a; SOC内部集成网络MAC外设 PHY网络芯片方案。 本文来说明一下MAC网络外设的设备节点信息的实现。 因此&#xff0c; I.MX6ULL 网络…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合

强化学习&#xff08;Reinforcement Learning, RL&#xff09;是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程&#xff0c;然后使用强化学习的Actor-Critic机制&#xff08;中文译作“知行互动”机制&#xff09;&#xff0c;逐步迭代求解…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

Objective-C常用命名规范总结

【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名&#xff08;Class Name)2.协议名&#xff08;Protocol Name)3.方法名&#xff08;Method Name)4.属性名&#xff08;Property Name&#xff09;5.局部变量/实例变量&#xff08;Local / Instance Variables&…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解

JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用&#xff0c;结合SQLite数据库实现联系人管理功能&#xff0c;并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能&#xff0c;同时可以最小化到系统…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下&#xff0c;风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的

修改bug思路&#xff1a; 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑&#xff1a;async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)

前言&#xff1a; 在Java编程中&#xff0c;类的生命周期是指类从被加载到内存中开始&#xff0c;到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期&#xff0c;让读者对此有深刻印象。 目录 ​…...

DingDing机器人群消息推送

文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人&#xff0c;点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置&#xff0c;详见说明文档 成功后&#xff0c;记录Webhook 2 API文档说明 点击设置说明 查看自…...