WPF 程序鼠标在窗口之外的时候,控件拿到的鼠标位置在哪里?

2021-01-24 08:17

阅读:440

在 WPF 程序中,我们有 Mouse.GetPosition(IInputElement relativeTo) 方法可以拿到鼠标当前相对于某个 WPF 控件的位置,也可以通过在 MouseMove 事件中通过 e.GetPosition(IInputElement relativeTo) 方法拿到同样的信息。不过,在任意时刻去获取鼠标位置的时候,如果鼠标在窗口之外,将获取到什么点呢?

本文将介绍鼠标在窗口之外时获取到的鼠标位置。


本文内容

    • 可用于演示的 DEMO
    • 观察现象
    • 推断结论
    • 原理

可用于演示的 DEMO

直接使用 Visual Studio 2019 创建一个空的 WPF 应用程序。默认 .NET Core 版本的 WPF 会带一个文本框和一个按钮。我们现在就用这两个按钮来显示 Mouse.GetPosition 获取到的值。

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;

namespace Walterlv.Demo
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            CompositionTarget.Rendering += OnRendering; 
        }

        private void OnRendering(object sender, EventArgs e)
        {
            DebugTextBlock.Text = Mouse.GetPosition(DebugTextBlock).ToString();
            DebugButton.Content = Mouse.GetPosition(DebugButton).ToString();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

观察现象

我们运行这个最简单的 Demo,然后不断移动鼠标,可以观察到一旦鼠标脱离窗口客户区,获取到的坐标点将完全固定。

技术图片

如果不知道客户区是什么,可以阅读下面我的另一篇博客:

  • WPF 使用 WindowChrome,在自定义窗口标题栏的同时最大程度保留原生窗口样式(类似 UWP/Chrome)

在以上图中,我拖动改变了窗口的位置,这时将鼠标移动至离开客户区后,获取到的坐标点又被固定为另一个数值。

推断结论

从上面的动图中以及我实际的测量发现,当鼠标移出窗口的客户区之后,获取鼠标的坐标的时候始终拿到的是屏幕的 (0, 0) 点。如果有多个屏幕,是所有屏幕组合起来的虚拟屏幕的 (0, 0) 点。

验证这一点,我们把窗口移动到屏幕的左上角后,将鼠标移出客户区,左上角的控件其获取到的鼠标位置已经变成了 (0, 31),而这个是窗口标题栏非客户区的高度。

技术图片

原理

Mouse.GetPosition 获取鼠标相对于控件的坐标点的方法在内部的最终实现是 user32.dll 中的 ClientToScreen

[DllImport("user32.dll")]
static extern bool ClientToScreen(IntPtr hWnd, ref Point lpPoint);
  • 1
  • 2

此方法需要使用到一个窗口句柄参数,此参数的含义:

A handle to the window whose client area is used for the conversion.

用于转换坐标点的窗口句柄,坐标会被转换到窗口的客户区部分。

If the function succeeds, the return value is nonzero.
If the function fails, the return value is zero.

如果此方法成功,将返回非零的坐标值;如果失败,将返回 0。

而鼠标在窗口客户区之外的时候,此方法将返回 0,并且经过后面的 ToPoint() 方法转换到控件的坐标下。于是这才得到了我们刚刚观察到的坐标值。

[SecurityCritical, SecurityTreatAsSafe]
public static Point ClientToScreen(Point pointClient, PresentationSource presentationSource)
{
    // For now we only know how to use HwndSource.
    HwndSource inputSource = presentationSource as HwndSource;
    if(inputSource == null)
    {
        return pointClient;
    }
    HandleRef handleRef = new HandleRef(inputSource, inputSource.CriticalHandle);

    NativeMethods.POINT ptClient            = FromPoint(pointClient);
    NativeMethods.POINT ptClientRTLAdjusted = AdjustForRightToLeft(ptClient, handleRef);

    UnsafeNativeMethods.ClientToScreen(handleRef, ptClientRTLAdjusted);

    return ToPoint(ptClientRTLAdjusted);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

参考资料

  • How do I get the current mouse screen coordinates in WPF? - Stack Overflow
  • pinvoke.net: clienttoscreen (user32)
  • c# - ClientToScreen unexpected return values? - Stack Overflow
  • ClientToScreen function (winuser.h) - Microsoft Docs

我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

技术图片

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。


评论


亲,登录后才可以留言!