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

本文介绍了如何在WPF中使用自定义DragStackPanel实现类似ListBox的控件拖动功能,重点讨论了控件的坐标操作和可能遇到的边界跨越问题,以及对背景透明度的要求。作者还在文中分享了现有代码和存在的待解决问题,期待后续深入理解和改进。
摘要由CSDN通过智能技术生成

背景

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

完结

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Oneal5354

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值