如何判断鼠标是否悬停在顶层窗口上?

6

如何高效地判断鼠标是否悬停在顶层窗口上?

所谓“悬停”,是指鼠标指针位于顶层窗口的客户矩形内没有其他顶层窗口覆盖在鼠标指针位置的窗口上。换句话说,如果用户点击,事件将发送到我的顶层窗口(或其子窗口之一)。

我正在使用Windows Forms编写C#代码,但我不介意使用p/invoke来进行Win32调用。

2个回答

9
您可以使用WinAPI函数 WindowFromPoint。它的C#签名是:
[DllImport("user32.dll")]
static extern IntPtr WindowFromPoint(POINT Point);

请注意,这里的POINTSystem.Drawing.Point不同,但是PInvoke提供了一个包含两者之间隐式转换的POINT声明
如果您还不知道鼠标光标位置,可以使用GetCursorPos来查找。
[DllImport("user32.dll")]
static extern bool GetCursorPos(out POINT lpPoint);

然而,WinAPI调用很多东西都被称为“窗口”:在窗口内部的控件也是“窗口”。因此,您可能无法以直观的方式获取顶级窗口(您可能会得到单选按钮、面板或其他内容)。您可以迭代地应用GetParent函数来遍历GUI层次结构:

[DllImport("user32.dll", ExactSpelling=true, CharSet=CharSet.Auto)]
public static extern IntPtr GetParent(IntPtr hWnd);

一旦您找到一个没有父级的窗口,该窗口将成为顶级窗口。由于您最初传递的点属于未被其他窗口覆盖的控件,因此顶级窗口必然是该点所属的窗口。


光标位置可以通过 Control.MousePosition 获得;父窗口可以通过从 user32.dll 中的单个调用 GetAncestor(hwnd, 2) 找到(GetParent 不仅检查父级,还检查窗口所有者,这可能不太好)。 - max
Coredll.dll 只适用于 Windows Mobile。 - Hans Passant
我不知道这是否有所改变,但在win10上,使用System.Drawing.Point完全没有问题。 - Maslow

2

获取窗口句柄后,您可以使用Control.FromHandle()来获取对控件的引用。然后检查相对鼠标位置,以查看它是否在窗体或控件的客户区域内。像这样:

    private void timer1_Tick(object sender, EventArgs e) {
        var hdl = WindowFromPoint(Control.MousePosition);
        var ctl = Control.FromHandle(hdl);
        if (ctl != null) {
            var rel = ctl.PointToClient(Control.MousePosition);
            if (ctl.ClientRectangle.Contains(rel)) {
                Console.WriteLine("Found {0}", ctl.Name);
                return;
            }
        }
        Console.WriteLine("No match");
    }

    [System.Runtime.InteropServices.DllImport("user32.dll")]
    private static extern IntPtr WindowFromPoint(Point loc);

在什么情况下,鼠标不会在WindowFromPoint返回的窗口内? - Daniel Stutzbach
@Daniel - 根据原帖的要求,在鼠标位于窗口的非客户区域时触发,例如窗体的边框和标题栏。 - Hans Passant
1
@Daniel,额外补充一下,你可以直接使用GetWindowLongPtr()来检查它是否为顶层窗口。将GWL_EXSTYLE传递给nIndex参数,然后对函数结果进行位运算以检查它是否包含WS_EX_TOPMOST值。 - Vantomex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接