如何高效地判断鼠标是否悬停在顶层窗口上?
所谓“悬停”,是指鼠标指针位于顶层窗口的客户矩形内且没有其他顶层窗口覆盖在鼠标指针位置的窗口上。换句话说,如果用户点击,事件将发送到我的顶层窗口(或其子窗口之一)。
我正在使用Windows Forms编写C#代码,但我不介意使用p/invoke来进行Win32调用。
WindowFromPoint
。它的C#签名是:[DllImport("user32.dll")]
static extern IntPtr WindowFromPoint(POINT Point);
POINT
与System.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.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);
GetWindowLongPtr()
来检查它是否为顶层窗口。将GWL_EXSTYLE
传递给nIndex
参数,然后对函数结果进行位运算以检查它是否包含WS_EX_TOPMOST
值。 - Vantomex
Control.MousePosition
获得;父窗口可以通过从user32.dll
中的单个调用GetAncestor(hwnd, 2)
找到(GetParent
不仅检查父级,还检查窗口所有者,这可能不太好)。 - max