WPF自定义Panel:让拖拽变得更简单

这篇具有很好参考价值的文章主要介绍了WPF自定义Panel:让拖拽变得更简单。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

   在 WPF 应用程序中,拖放操作是实现用户交互的重要组成部分。通过拖放操作,用户可以轻松地将数据从一个位置移动到另一个位置,或者将控件从一个容器移动到另一个容器。然而,WPF 中默认的拖放操作可能并不是那么好用。为了解决这个问题,我们可以自定义一个 Panel 来实现更简单的拖拽操作。

    自定义 Panel 的优点有很多。首先,我们可以根据自己的需求来设计 Panel 的外观和行为。其次,我们可以使用代码来控制拖放操作的细节,比如拖放的开始和结束位置、拖放过程中控件的显示方式等等。最后,我们可以将自定义 Panel 作为一个控件,方便地应用到不同的应用程序中。

    在本教程中,我们将一步一步地创建一个自定义 Panel 来实现更简单的拖拽操作。我们将学习如何定义 Panel 的布局、如何处理拖放事件,以及如何将自定义 Panel 应用到不同的应用程序中。按照本教程的步骤操作,您将能够创建一个功能强大且易于使用的自定义 Panel,从而使您的 WPF 应用程序更加友好和易用。

 1.定义一个继承自Panel的类。

public class DragStackPanel : Panel
{
    /// <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));
}

2.重写Panel类的MeasureOverride方法测量控件Size。

public class DragStackPanel : Panel
{
    protected override Size MeasureOverride(Size availableSize)
    {
        var panelDesiredSize = new Size();
        foreach (UIElement child in InternalChildren)
        {
            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;
    }
}

3.重写Panel类的ArrangeOverride方法排列控件位置。

public class DragStackPanel : Panel
{
    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;
            }
        }

        return finalSize;
    }
}

查看运行效果

<UniformGrid Rows="2">
    <local:DragStackPanel Orientation="Horizontal">
        <Button>test1</Button>
        <Button>test2</Button>
    </local:DragStackPanel>
    <local:DragStackPanel Orientation="Vertical">
        <Button>test3</Button>
        <Button>test4</Button>
    </local:DragStackPanel>
</UniformGrid>

WPF自定义Panel:让拖拽变得更简单

 文章来源地址https://www.toymoban.com/news/detail-843739.html

4.重写PreviewMouseLeftButtonDown方法。

    该方法在按下鼠标左键时触发,我们需要在该方法中获取第一次按下鼠标的坐标,并且通过命中测试找到我们要拖拽的控件,最后还要在装饰层中添加一个元素,该元素的背景用原控件的外观来填充(VisualBrush),这样就可以覆盖原来的控件,以便在拖拽控件时能跨越控件的边界。以下为参考代码:

public class DragStackPanel : Panel
{
    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);
    }
}

5.重写PreviewMouseMove方法。

    该方法在鼠标移动时触发,我们需要在鼠标被按下移动时,根据当前的坐标与第一次按下的坐标实时计算出被拖拽元素的偏移量,这样该元素就能跟随鼠标移动,实现拖拽效果。以下为参考代码:

public class DragStackPanel : Panel
{
    private FrameworkElement draggingElement;
    private Point mouseRelativePosition;
    private int draggingElementzIndex;
    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 = GetDraggingElementOriginalPosition(draggingElement);
                // 计算拖拽控件移动时的偏移量
                var offset = new Point(targetPosition.X - draggingElementOriginalPosition.X, targetPosition.Y - draggingElementOriginalPosition.Y);
                // 应用位移
                draggingElement.RenderTransform = new TranslateTransform(offset.X, offset.Y);
            }
            
             e.Handled = true;
        }
        base.OnPreviewMouseMove(e);
    }
}

6.重写PreviewMouseLeftButtonUp方法。

    该方法在鼠标左健抬起时触发,我们需要在该方法中将一些参数重置。

public class DragStackPanel : Panel
{
    private FrameworkElement draggingElement;
    private Point mouseRelativePosition;
    private int draggingElementzIndex;
    protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
    {
        mouseRelativePosition = default;
        RemoveOverlay(draggingElement);
        Panel.SetZIndex(draggingElement, draggingElementzIndex);
        draggingElement.IsHitTestVisible = true;
        draggingElement.RenderTransform = null;
        draggingElement = null;
        e.Handled = true;
        base.OnPreviewMouseLeftButtonUp(e);
    }
}

以下为运行效果:

WPF自定义Panel:让拖拽变得更简单

7.处理控件的拖拽换位。

    拖拽换位的思路就是将当前正在拖拽的元素放置到新的Index中,并把该Index后面的所有元素整体后移一位。该功能在PreviewMouseMove方法中实现。

public class DragStackPanel : Panel
{
    private FrameworkElement draggingElement;
    private FrameworkElement hitElement;
    private Point mouseRelativePosition;
    private int draggingElementzIndex;
    protected override void OnPreviewMouseMove(MouseButtonEventArgs e)
    {
        ...
        // 命中当前拖拽控件的下一层控件
        var hitTestResult = this.InputHitTest(mousePosition) as FrameworkElement;
        // 查找被命中的下一层换位控件
        hitElement = FindChild(hitTestResult);

        // 判断是否有效
        if (hitElement != null && this.InternalChildren.Contains(hitElement))
        {
            // 应用换位
            MoveChild(draggingElement, hitElement);
        }
    }

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

    在ArrangeOverride方法中处理重新排列时当前拖拽元素的坐标。

public class DragStackPanel : Panel
{
    private FrameworkElement draggingElement;
    private FrameworkElement hitElement;
    private Point mouseRelativePosition;
    private int draggingElementzIndex;
    protected override Size ArrangeOverride(Size finalSize)
    {
        double x = 0, y = 0;
        foreach (FrameworkElement child in InternalChildren)
        {
            ...

            // 获取当前正在拖拽元素的位置坐标
            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);
                SetDraggingElementMovingPosition(child, dragElementPosition);
            }

            ...
        }

        return finalSize;
    }
}

运行效果

WPF自定义Panel:让拖拽变得更简单

8.处理跨Panel拖拽。

    到目前为止已经实现了本Panel内的控件随意拖拽换位,处理从A控件拖到B控件也类似,这里需要用到一个静态变量来保存正在拖拽的控件,当B控件检测到鼠标进入时,只需要在A控件移除正在拖拽的控件,在B控件添加正在拖拽的控件就可以实现了。以下为核心代码:

public class DragStackPanel : Panel
{
    // 通过拖拽传递到下一个Panel的控件
    private static FrameworkElement draggingTransferElement;
    private void Control_MouseEnter(object sender, MouseEventArgs e)
    {
        panel.Children.Remove(draggingTransferElement);
        panel.DraggingElement = null;

        Panel.SetZIndex(draggingTransferElement, this.InternalChildren.Count + 1);
        this.Children.Add(draggingTransferElement);
        this.AddOverlay(draggingTransferElement);
    }
}

以下为运行效果:

WPF自定义Panel:让拖拽变得更简单

9.在ListBox、ListView、DataGrid等ItemsControl中使用拖拽功能。

    所有继承自ItemsControl的控件,都有一个ItemsPanel属性,该属性可以指定一个Panel类型的控件来对ItemsControl进行排列。理论上只要将ItemsControl.ItemsPanel设置为我们自己开发的Panel控件就可以实现排列及拖拽功能,但是这里直接使用的话并不会有效果。原因就是我们并没有对数据绑定的情况下做处理。它的处理逻辑也与上面的类似,首先找到ItemsControl控件,通过对ItemsSource进行操作就可以实现排列功能,由于代码大同小异这里就不再赘述。以下为ListBox控件拖拽的案例效果。

<ListBox ItemsSource="{Binding Items}">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <DragStackPanel AllowCrossBorderDrag="True" CanDragAndSort="True" IsItemsHost="True"/>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Property1}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

WPF自定义Panel:让拖拽变得更简单

10.添加动画效果。

    至此基本功能已经开发完成了,下面我们为它添加上动画效果,让它更具有观赏性。动画的核心思想就是记录每个元素旧位置的坐标,当元素移动到新位置时启动一个动画,从旧坐标过渡到新坐标,由于代码太过基础,这里就不展示了,直接上效果。

<DragStackPanel AllowCrossBorderDrag="True" CanDragAndSort="True" IsItemsHost="True">
    <DragStackPanel.ChildMoveBehavior>
        <ChildMoveBehavior Duration="0:0:0.5">
            <ChildMoveBehavior.EaseX>
                <QuinticEase EasingMode="EaseOut" />
            </ChildMoveBehavior.EaseX>
            <ChildMoveBehavior.EaseY>
                <QuinticEase EasingMode="EaseOut" />
            </ChildMoveBehavior.EaseY>
        </ChildMoveBehavior>
    </DragStackPanel.ChildMoveBehavior>
</DragStackPanel>

WPF自定义Panel:让拖拽变得更简单

 

到了这里,关于WPF自定义Panel:让拖拽变得更简单的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • WPF嵌入外部exe应用程序-使用Winfom控件承载外部程序

    首先要解决在WPF中如何使用Winfom控件的问题,官方对此有支持的方式。 在引用管理器中添加winfrom相关的程序集 System.Windows.Forms 和 WindowsFormsIntegration 。 然后使用winform的控件,得在外面套一层WindowsFormsHost(好像添加了WindowsFormsIntegration,不使用wfi:也能使用) 这样就可以在WPF中使

    2024年02月17日
    浏览(66)
  • 如何为WPF应用程序制作一个虚拟键盘?这里有答案(Part 1)

    Telerik UI for WPF拥有超过100个控件来创建美观、高性能的桌面应用程序,同时还能快速构建企业级办公WPF应用程序。UI for WPF支持MVVM、触摸等,创建的应用程序可靠且结构良好,非常容易维护,其直观的API将无缝地集成Visual Studio工具箱中。 点击获取Telerik UI for WPF最新版下载 T

    2024年02月09日
    浏览(95)
  • 【自动化】在WPF应用程序中使用MVVM框架实现Modbus协议通信

    Modbus是一种广泛应用于工业领域的通信协议,主要用于设备间的数据交换。在WPF应用程序中,我们可以使用MVVM(Model-View-ViewModel)框架来实现Modbus协议的通信。本文将详细介绍如何实现这一功能。 为了在WPF应用程序中实现Modbus协议通信,我们需要安装一些第三方库。可以使用

    2024年03月22日
    浏览(48)
  • C# WPF应用使用visual studio的安装程序类的一些坑

    否则会出现命名空间System.Configuration不存在Install的报错   var s = Context.Parameters[\\\"assemblypath\\\"].ToString() 这个里面是当前文件的路径,所以需要删除掉文件的名字才能获取目录路径 var dir = s.Substring(0, s.LastIndexOf(\\\"\\\\\\\") + 1)

    2024年02月12日
    浏览(40)
  • Wpf基础入门——容器Panel篇

    一个窗口中只能包含一个元素 不应显示设置元素尺寸 不应使用坐标设置元素的位置 可以嵌套布局容器 Grid:网格。可以自定义行和列并通过行列的数量、行高和列宽来调整控件的布局。近似于HTML中的Table。 StackPanel:栈式面板。可将包含的元素在竖直或水平方向上排成一条直线

    2024年02月06日
    浏览(72)
  • 虚拟桌宠模拟器:VPet-Simulator,一个开源的桌宠软件, 可以内置到任何WPF应用程序

    虚拟桌宠模拟器 一个开源的桌宠软件, 可以内置到任何WPF应用程序 获取虚拟桌宠模拟器 OnSteam(免费) 或 通过[Nuget]内置到你的WPF应用程序 虚拟桌宠模拟器是一款桌宠软件,支持各种互动投喂等. 开源免费并且支持创意工坊. 反正免费为啥不试试呢( 该游戏为 [虚拟主播模拟器] 内

    2024年02月08日
    浏览(42)
  • Flutter桌面应用程序定义系统托盘Tray

    系统托盘 :系统托盘是一种用户界面元素,通常出现在操作系统的任务栏或桌面顶部。它是一个水平的狭长区域,用于显示各种图标和通知,以提供快速访问和操作特定应用程序或系统功能。系统托盘通常包含操作系统或第三方应用程序的图标,这些图标可以显示有关应用程

    2024年02月04日
    浏览(54)
  • Windows应用程序基础(详细,简单易懂)

    大家好!这是我的 第一篇博客 ,首先来个自我介绍吧!我是来自一所双非院校的大一新生,所学的专业是 计算机科学与技术 ,纯纯小白一枚。 那我为什么要写博客呢 ?为什么?说实话,我一开始一直都没有写博客的想法,我都是看别人的博客,直到有一天,我的好同学

    2024年02月04日
    浏览(47)
  • WPF开发一个可以自适应排列的Panel控件

    一.控件介绍     初看标题可能无法理解,我们看看什么是自适应排列。 乍一看它有点像WrapPanel控件,都是从左至右排列,如果一行排列不下就换行继续排列,但是细看你就会发现不对,WrapPanel控件行尾是不会对齐的,也就是说只要WrapPanel的子控件的宽度不一致,每一行的

    2024年04月08日
    浏览(51)
  • 一个简单的web应用程序的创建

    实体、属性、关系 简单介绍 简单介绍

    2024年02月11日
    浏览(87)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包