WPF中通过自定义Panel实现控件拖动

背景

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

现代码实现效果

请添加图片描述

代码文件

MainWindow.xaml

<Window
    x: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">
            <Button
                Height="50"
                Margin="0"
                Content="ceshi1" />
            <Button
                Height="50"
                Margin="0"
                Content="ceshi2" />
            <Button
                Height="50"
                Margin="0"
                Content="ceshi3" />
            <TextBox
                Height="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;
        }
        #endregion

        private 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);

                // 暂存ZIndex
                draggingElementzIndex = 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事件的监控。

完结

研究暂时告一段落,等后面有时间时再深入去理解公众号中的代码,希望这一点经验可以给你带来帮助

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/516805.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

考题抄错会做也白搭--模版方法模式

1.1 选择题不会做&#xff0c;蒙呗&#xff01; "题目抄错了&#xff0c;那就不是考试题目了&#xff0c;而考试试卷最大的好处就是&#xff0c;大家都是一样的题目&#xff0c;特别是标准化的考试&#xff0c;比如全是选择或判断的题目&#xff0c;那就最大化地限制了答题…

整合Mybatis(Spring学习笔记十二)

一、导入相关的包 junit 包 Mybatis包 mysql数据库包 Spring相关的包 Aop相关的包 Mybatis-Spring包&#xff08;现在就来学这个&#xff09; 提示jdk版本不一致的朋友记得 jdk8只支持spring到5.x 所以如果导入的spring(spring-we…

Linux:进程终止和等待

一、进程终止 main函数的返回值也叫做进程的退出码&#xff0c;一般0表示成功&#xff0c;非零表示失败。我们也可以用不同的数字来表示不同失败的原因。 echo $?//打印最近一次进程执行的退出码 而作为程序猿&#xff0c;我们更需要知道的是错误码所代表的错误信息&#x…

【智能算法】磷虾群算法(KHA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2012年&#xff0c;Gandomi等人受到自然界中磷虾生存行为启发&#xff0c;提出了磷虾群算法&#xff08;Krill Herd Algorithm, KHA&#xff09;。 2.算法原理 2.1算法思想 KHA受南极鳞虾群觅食行…

Java | Leetcode Java题解之第8题字符串转换整数atoi

题目&#xff1a; 题解&#xff1a; class Solution {public int myAtoi(String str) {Automaton automaton new Automaton();int length str.length();for (int i 0; i < length; i) {automaton.get(str.charAt(i));}return (int) (automaton.sign * automaton.ans);} …

Matlab|储能辅助电力系统调峰的容量需求研究

目录 1 主要内容 目标函数 约束条件 2 部分代码 3 程序结果 4 下载链接 1 主要内容 该程序参考文献《储能辅助电力系统调峰的容量需求研究》&#xff0c;主要是对火电、风电和储能等电力设备主体进行优化调度&#xff0c;在调峰能力达不到时采用弃负荷&#xff0c;程序以…

无人售货奶柜:开启便捷生活的新篇章

无人售货奶柜&#xff1a;开启便捷生活的新篇章 在这个快节奏的现代生活中&#xff0c;科技的革新不仅为我们带来了前所未有的便利&#xff0c;更在不经意间改变着我们的日常。其中&#xff0c;无人售货技术的出现&#xff0c;尤其是无人售货奶柜&#xff0c;已经成为我们生活…

012:vue3使用vue-i18n实现国际化

文章目录 1. 安装 vue-i18n2. 创建文件存储翻译的语言3. 注册i18n实例4. 在main.ts中引入vue-i18n实例5. 在组件模板中使用6. 在js中使用7. locale.value 实现国际化语言切换8. vue3 中ref里面的国际化值没生效问题 1. 安装 vue-i18n cnpm i --save vue-i18n2. 创建文件存储翻…

树状数组-数据结构

树状数组 t[x] 节点的父节点为 t[x lowbit(x)] 整棵树的深度为 log2n 1 1 . add(x,k) 给指定的节点x加上k — 动态的维护前缀和 需要从x开始&#xff0c;向上找到所有父节点&#xff0c;值都加上k 2. ask(x) 求取节点x之前的前缀和 求取单点之前的前缀和只需要累加即可 …

【算法】单单单单单调栈,接接接接接雨水

【算法】单单单单单调栈&#xff0c;接接接接接雨水 今天没有小故事。 参考以及题单来源&#xff1a; 代码随想录 (programmercarl.com) Part 1 啥是单调栈&#xff1f; 1.啥啥啥是单调栈&#xff1f; 栈的特性想必各位再熟悉不过了&#xff1a;先进后出。栈仅仅有一个出口&a…

算法 day29 回溯5

491 非递减子序列 给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xff0c;递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 数组中可能含有重复元素&#xff0c;如出现两个整数相等&#xff0c;也可以视作递增序列的一种特殊情…

检测头篇 | 利用RT-DETR模型的检测头去替换YOLOv8中的检测头

前言:Hello大家好,我是小哥谈。RT-DETR号称是打败YOLO的检测模型,其作为一种基于Transformer的检测方法,相较于传统的基于卷积的检测方法,提供了更为全面和深入的特征理解,将RT-DETR检测头融入YOLOv8,我们可以结合YOLO的实时检测能力和RT-DETR的深度特征理解能力,打造出…

业务网关的设计与实践

在过去的两年里&#xff0c;主要在做业务网关的开发。今年春节后选择转岗去做更偏近业务的开发。公司的业务是金融相关&#xff0c;一直觉得金融相关的业务是有一定门槛并且是对职业生涯有帮助的&#xff0c;所以趁这个机会来深入了解这块业务。 仔细回想&#xff0c;在做业务…

【数据处理包Pandas】数据载入与预处理

目录 一、数据载入二、数据清洗&#xff08;一&#xff09;Pandas中缺失值的表示&#xff08;二&#xff09;与缺失值判断和处理相关的方法 三、连续特征离散化四、哑变量处理 准备工作 导入 NumPy 库和 Pandas 库。 import numpy as np import pandas as pd一、数据载入 对…

开机自启动

对win10,给一种开机自启动的设置方法: 1. winr 打开 2. 输入shell:startup打开 开始\程序\启动 3. 把想要自启动的应用的快捷方式放在这里即可 亲测有用

第十一届蓝桥杯物联网试题(省赛)

对于通信方面&#xff0c;还是终端A、B都保持接收状态&#xff0c;当要发送的数组不为空再发送数据&#xff0c;发送完后立即清除&#xff0c;接收数据的数组不为空则处理&#xff0c;处理完后立即清除&#xff0c;分工明确 继电器不亮一般可能是电压不够 将数据加空格再加\r…

RPA自动化小红书自动化写文以及发文!

1、视频演示 RPA自动化小红书自动写作发文 2、核心功能点 采集笔记&#xff1a;采集小红书上点赞量大于1000的爆款笔记 下载素材&#xff1a;下载爆款笔记的主图 爆款改写&#xff1a;根据爆款笔记的标题仿写新的标题以及新的文案 自动发布&#xff1a;将爆款笔记发布到小红…

Oracle RAC One Node,双胞胎变独生子?

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

LeetCode刷题实战2:两数相加

在日常我们学习数据结构和算法的知识&#xff0c;一定不能只停留在看书、看视频层面&#xff0c;一定要自己多练习&#xff0c;纸上得来终觉浅&#xff0c;绝知此事要躬行。 题目内容 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存…

Vue3:组件间通信-$attrs的使用

一、情景说明 我们之前学习了通过props实现&#xff0c;父给子传数据 那么&#xff0c;如果&#xff0c;父组件给子组件传递多个数据&#xff0c;但是&#xff0c;子组件只用props声明了一个数据 其他数据去哪里了呢&#xff1f; 二、案例 1、父组件 <Child :a"a&…