WPF 多指触摸拖拽窗口 拖动修改窗口坐标

本文介绍了如何在WPF中实现多指触摸拖动窗口,通过Win32方法解决Window.DragMove()在触摸时的问题。文中提供了一个DragMoveWindowHelper类,详细讲解了触摸拖动窗口的核心逻辑,包括监听PreviewMouseMove和PreviewMouseUp事件,以及使用GetCursorPos和SetWindowPos等Win32 API设置窗口坐标。
摘要由CSDN通过智能技术生成

在 WPF 中,如果是鼠标点击拖动窗口坐标,可以调用 Window 的 DragMove 方法,但是如果是触摸,就需要自己调用 Win32 的方法实现

在 WPF 中,调用 Window 的 DragMove 方法要求鼠标的左键(主键)按下,否则将会抛出如下代码

System.InvalidOperationException:“只能在按下主鼠标按钮时调用 DragMove。”

或英文版的代码

System.InvalidOperationException:"Can only call DragMove when primary mouse button is down"

因此想要在 WPF 中使用手指 finger 进行 Touch 触摸拖拽窗口,拖动修改窗口坐标就需要用到 Win32 的方法了。相信大家都知道,在修改某个容器的坐标的时候,不能使用这个容器内的坐标做参考,所以在 Touch 拖动修改窗口坐标的时候,就不能使用监听窗口的事件拿到的坐标来作为参考

想要能平滑的移动窗口,就需要获取相对于屏幕的坐标,而如果此时处理多指的 Manipulation 的动作,那么整个逻辑将会非常复杂。本文仅仅支持使用一个手指的移动,因为使用了 GetCursorPos 的方法

当然了,此时假装是支持多指拖动也是可以的,只需要在进行多指触摸的时候开启拖动就可以了,此时用户的交互上不会有很大的差别

在开始之前,咱来封装一个类 DragMoveWindowHelper 用来在触摸下拖动窗口

    public static class DragMoveWindowHelper
    {
        public static void DragMove(Window window)
        {
        	// 这里的 DragMoveMode 在下文实现
            var dragMoveMode = new DragMoveMode(window);
            dragMoveMode.Start();
        }
    }

上面代码的 DragMoveMode 类放在下文实现。在封装完成了 DragMoveWindowHelper 类就可以尝试在拖动的时候使用,如下面代码

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            TouchDown += MainWindow_TouchDown;
            TouchUp += MainWindow_TouchUp;
        }

        private void MainWindow_TouchUp(object sender, TouchEventArgs e)
        {
            _currentTouchCount--;
        }

        private void MainWindow_TouchDown(object sender, TouchEventArgs e)
        {
            CaptureTouch(e.TouchDevice);

            if (_currentTouchCount == 0)
            {
                DragMoveWindowHelper.DragMove(this);
            }

            _currentTouchCount++;
        }

        private uint _currentTouchCount;
    }

上面代码有一点需要小心就是 CaptureTouch 是必备的,否则你会发现拖动的时候,拖动太快了,就丢失触摸设备了,触摸设备被你窗口后面的其他软件抓了

下面开始实现 DragMoveMode 也就是核心的通过触摸拖动窗口的逻辑

大概对外的接口方法实现请看代码

        class DragMoveMode
        {
            public DragMoveMode(Window window)
            {
                _window = window;
            }

            public void Start()
            {
                var window = _window;

                window.PreviewMouseMove += Window_PreviewMouseMove;
                window.PreviewMouseUp += Window_PreviewMouseUp;
                window.LostMouseCapture += Window_LostMouseCapture;
            }

            public void Stop()
            {
                Window window = _window;

                window.PreviewMouseMove -= Window_PreviewMouseMove;
                window.PreviewMouseUp -= Window_PreviewMouseUp;
                window.LostMouseCapture -= Window_LostMouseCapture;
            }

            private readonly Window _window;
        }

在上面代码里面监听 PreviewMouseMove 是为了获取移动的时机,而不是为了获取相对的坐标。而 PreviewMouseUp 可以用来了解啥时候结束。当然了 LostMouseCapture 也需要监听,和 PreviewMouseUp 一样用来了解啥时候结束

在 Window_PreviewMouseMove 方法需要先判断是否第一次进入移动,因此咱没有监听 MouseDown 方法。为什么没有监听 MouseDown 方法,是因为在上层业务此时业务调用 MoseDown 完成

判断是否第一次进入移动需要一个辅助的字段,咱定义一个叫上一次点击的坐标字段

            private Win32.User32.Point? _lastPoint;

上面代码的 Win32.User32 是我定义的代码,这些定义将会放在本文最后

判断是第一次进入移动可以使用下面代码

            private void Window_PreviewMouseMove(object sender, MouseEventArgs e)
            {
                Win32.User32.GetCursorPos(out var lpPoint);

                if (_lastPoint == null)
                {
                    _lastPoint = lpPoint;
                    _window.CaptureMouse();
                }
            }

通过 GetCursorPos 的 Win32 方法可以拿到相对于屏幕坐标的鼠标坐标,而触摸默认会将第一个触摸点转换为鼠标坐标,因此拿到的坐标点不是相对于窗口内的,这样就能做到在移动的时候不会抖

接下来判断相对上一次的移动距离,如下面代码

                var dx = lpPoint.X - _lastPoint.Value.X;
                var dy = lpPoint.Y - _lastPoint.Value.Y;

                Debug.WriteLine($"dx={dx} dy={dy}");

拿到的 dx 和 dy 就可以用来设置窗口的左上角坐标了。而此时不能通过 Window 的 Top 和 Left 属性获取,这两个属性的值使用的是 WPF 单位和坐标,而咱计算的 dx 和 dy 是相对于屏幕的坐标,因此需要调用 GetWindowRect 这个 win32 方法获取窗口所在屏幕的坐标

设置窗口坐标也需要使用屏幕坐标来设置,需要调用 SetWindowPos 方法,代码如下

     var handle = new WindowInteropHelper(_window).Handle;

     Win32.User32.GetWindowRect(handle, out var lpRect);

     Win32.User32.SetWindowPos(handle, IntPtr.Zero, lpRect.Left + dx, lpRect.Top + dy, 0, 0,
                        (int) (Win32.User32.WindowPositionFlags.SWP_NOSIZE |
                               Win32.User32.WindowPositionFlags.SWP_NOZORDER));

这个 Window_PreviewMouseMove 方法代码如下

            private void Window_PreviewMouseMove(object sender, MouseEventArgs e)
            {
                Win32.User32.GetCursorPos(out var lpPoint);

                if (_lastPoint == null)
                {
                    _lastPoint = lpPoint;
                    _window.CaptureMouse();
                }

                var dx = lpPoint.X - _lastPoint.Value.X;
                var dy = lpPoint.Y - _lastPoint.Value.Y;

                Debug.WriteLine($"dx={dx} dy&
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WPF(Windows Presentation Foundation)是Microsoft开发的用于创建富客户端应用程序的技术。基于WPF开发的应用程序可以使用图形、2D和3D视觉效果、文本、媒体以及许多其他元素来提供用户友好的界面。 在WPF图片管理器中,圈选和拖动是两个常见的操作。圈选是指通过鼠标在图片上拖动,形成一个矩形区域,选择想要操作的一部分图片。这个功能非常实用,可以方便地对多张图片进行批量操作,例如批量删除、拷贝或移动。 拖动是指通过鼠标点击并按住不放,然后拖动图片到指定位置。拖动功能使得图片管理器可以实现简单的图像排列和整理。用户可以将一张图片拖动到另一张图片旁边,从而实现图片的排序。拖动还可以用于将图片从一个文件夹或一个应用程序中拖动到另一个位置,方便地进行整理和分享。 在WPF中实现圈选和拖动功能相对简单。可以通过鼠标事件和相应的事件处理方法来实现。例如,在鼠标按下事件处理方法中,可以记录鼠标按下时的位置坐标,然后在鼠标抬起事件处理方法中获取鼠标抬起时的位置坐标,计算出圈选的矩形区域。在拖动过程中,只需要更新图片的位置坐标即可。WPF还提供了内置控件,如Canvas和ListBox,它们可以方便地实现圈选和拖动功能。 总而言之,WPF图片管理器通过实现圈选和拖动功能,可以方便地对图片进行选择和整理。具有良好的用户体验和交互性,提高了图片管理的效率和便捷性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值