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

news2025/1/18 19:01:02

背景

看到趋时软件的公众号文章(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事件的监控。

完结

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【随笔】Git 高级篇 -- 相对引用1(十二)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

腾讯云4核8G12M服务器和标准型S5服务器配置价格表

2024年腾讯云4核8G服务器租用优惠价格&#xff1a;轻量应用服务器4核8G12M带宽646元15个月&#xff0c;CVM云服务器S5实例优惠价格1437.24元买一年送3个月&#xff0c;腾讯云4核8G服务器活动页面 txybk.com/go/txy 活动链接打开如下图&#xff1a; 腾讯云4核8G服务器优惠价格 轻…

2024年干货分享!11个无版权免费可商用高清素材网站分享,含图片插画图标

在当今这个信息化的时代&#xff0c;图片已成为我们生活与工作中不可或缺的一部分。对于设计师、自媒体人&#xff0c;网站设计师&#xff0c;以及众多创作者而言&#xff0c;都迫切需要高品质的图片来提升作品的吸引力。 然而&#xff0c;寻觅无版权、免费且高清的图片资源实…

商品购买过程中,库存的抵扣过程是怎样的?如何防止超卖?

在商品购买的过程中&#xff0c;库存的抵扣过程&#xff0c;一般操作如下&#xff1a; 1、select根据商品id查询商品的库存。 2、根据下单的数量&#xff0c;计算库存是否足够&#xff0c;如果存库不足则抛出库存不足的异常&#xff0c;如果库存足够&#xff0c;则减去扣除的…

全栈的自我修养 ———— react中router入门+路由懒加载

router 下载router配置view创建目录配置index.js 下载router npm install react-router-dom配置view 如下将组件倒出 const Login () > {return <div>这是登陆</div> } export default Login创建目录 配置index.js React.lazy有路由懒加载的功能&#xff0…

全猎/暴漏攻击面搜索

推荐&#xff1a; FullHunt | Expose YourAttack Surface Discover, monitor, and secure your attack surface. FullHunt delivers the best platform in the market for attack surface security.https://fullhunt.io/

vue快速入门(五)v-show与v-if

注释很详细&#xff0c;直接上代码 上一篇 新增内容 v-if与v-show底层的区别v-if与v-show的效果 源码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice…

内部类(来自类和对象的补充)

❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; hellohello~&#xff0c;大家好&#x1f495;&#x1f495;&#xff0c;这里是E绵绵呀✋✋ &#xff0c;如果觉得这篇文章还不错的话还请点赞❤️❤️收藏&#x1f49e; &#x1f49e; 关注&#x1f4a5;&a…

一分钟了解MOS管基础知识

场效应管&#xff08;Field-Effect Transistor&#xff0c;简称FET&#xff09;是电子技术中广泛使用的一种半导体器件&#xff0c;具有高输入阻抗、噪声低和低功耗等优点。 简介 场效应管是一种电压控制器件&#xff0c;其工作原理是通过改变栅极&#xff08;Gate&#xff09;…

【Unity 实用工具篇】| Unity中 实现背景模糊效果,简单易用

前言【Unity 实用工具篇】| Unity 实现背景模糊效果,简单易用一、实现背景模糊效果1.1 介绍1.2 效果展示1.3 使用说明及下载二、插件资源简单介绍2.1 导入下载好的资源2.2 功能介绍2.2.1 捕获特效2.2.2 高级选项

在集群中使用deepspeed如果端口被占用可以使用deepspeed参数更改

在集群中使用deepspeed如果端口被占用可以使用deepspeed参数更改 这一次G老师不好使了 在集群中使用deepspeed默认的端口号29500被占用&#xff0c;显示更改居然不起作用 G老师给的方法也不好使 #!/bin/bash MASTER_ADDRlocalhost MASTER_PORT29501 # 选择一个未被占用的端…

Spoon Taking Problem(c++题解)

题目描述 &#xfffd;N 人が円卓に座っており&#xff0c;各人は反時計回りに順に 1, …, &#xfffd;1, …, N と番号付けられています&#xff0e;各人はそれぞれ左右どちらか一方の利き手を持っています&#xff0e; 円卓上には 1, …, &#xfffd;1, …, N と番号付け…

企微知识库优缺点解析:如何让其效益最大化

企业搭建企微知识库&#xff0c;作为企业内部知识的集中存储和共享平台&#xff0c;为企业带来了很多便利。但是&#xff0c;任何事物都有其两面性&#xff0c;企微知识库也不例外。今天我们就来详细探讨搭建企微知识库的优点和缺点&#xff0c;如何在使用企微知识库时使其发挥…

谷歌(Google)技术面试——在线评估问题(三)

谷歌&#xff08;Google&#xff09;面试过程的第一步&#xff0c;你可能会收到一个在线评估链接。 评估有效期为 7 天&#xff0c;包含两个编码问题&#xff0c;需要在一小时内完成。 以下是一些供你练习的在线评估问题。 在本章结尾处&#xff0c;还提供了有关 Google 面试不…

Python 爬虫基础——http请求和http响应

写本篇文章&#xff0c;我认为是能把自己所理解的内容分享出来&#xff0c;说不定就有和我一样有这样思维的共同者&#xff0c;希望本篇文章能帮助大家&#xff01;✨✨ 文章目录 一、 &#x1f308;python介绍和分析二、 &#x1f308;http请求三、 &#x1f308;http响应四、…

原型模式详解

原型模式简单的理解来说,就是复制品,用一个已经做好的成品作为原型,然后通过复制它得到新的产品。就好像细胞分裂一样。用Java来说,就是用对象创建对象,而不是通过类来创建对象。 原型模式的目的是从原型实例克隆出新的实例 ,对于那些有非常复杂的初始化过程的对象或者是…

核心API-Activiti7从入门到专家(3)

背景 今天的说的api&#xff0c;activiti7真有&#xff0c;但真不是这个&#xff1a; 这个是为了云服务&#xff0c;封装的一些api&#xff0c;以后我们还会逐步探讨&#xff0c;今天我们讨论的&#xff0c;是其真正的api&#xff0c;以前是这样的&#xff1a; 是的&#xff0…

【数据结构】——二叉树的递归实现,看完不再害怕递归

创作不易&#xff0c;感谢三连加支持&#xff1f;&#xff01; 一 递归理解 递归无非就是相信它&#xff0c;只有你相信它&#xff0c;你才能写好递归&#xff01;为什么&#xff1f;请往下看 在进入二叉树的实现之前&#xff0c;我们得先理解一遍递归&#xff0c;可能很多…

synchronized到底锁住的是谁?

我们使用synchronized关键字是用来实现线程同步的&#xff0c;当多个线程同时去争抢同一个资源的时候在资源上边加一个synchronized关键字&#xff0c;能够使得线程排队去完成操作。 synchronized到底锁定的是什么资源&#xff1f; 修饰方法非静态方法 &#xff0c;锁定的是方…

效果翻倍!如何巧用邮件营销提升ROI?

我们可以通过“优化邮件内容、优化发送策略、优化数据分析、提升用户体验”来提升邮件营销ROI。 邮件营销一直被认为是一种有效的数字营销策略&#xff0c;可以帮助企业与潜在客户和现有客户建立联系、推广产品和服务、提高品牌认知度并促进销售。但是&#xff0c;为了确保邮件…