views:

47

answers:

2

How can I efficiently tell if the mouse is over a top-level window?

By "over", I mean that the mouse pointer is within the client rectangle of the top-level window and there is no other top-level window over my window at the location of the mouse pointer. In other words, if the user clicked the event would be sent to my top-level window (or one of its child windows).

I am writing in C# using Windows Forms, but I don't mind using p/invoke to make Win32 calls.

A: 

You could use the WinAPI function WindowFromPoint. Its C# signature is:

[DllImport("user32.dll")]
static extern IntPtr WindowFromPoint(POINT Point);

Note that POINT here is not the same as System.Drawing.Point, but PInvoke provides a declaration for POINT that includes an implicit conversion between the two.

If you don’t already know the mouse cursor position, GetCursorPos finds it:

[DllImport("user32.dll")]
static extern bool GetCursorPos(out POINT lpPoint);

However, the WinAPI calls lots of things “windows”: controls inside a window are also “windows”. Therefore, you might not get a top-level window in the intuitive sense (you might get a radio button, panel, or something else). You could iteratively apply the GetParent function to walk up the GUI hierarchy:

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

Once you find a window with no parent, that window will be a top-level window. Since the point you originally passed in belongs to a control that is not covered by another window, the top-level window is necessarily the one the point belongs to.

Timwi
cursor position can be obtained by `Control.MousePosition`; parent window can be found by a single call `GetAncestor(hwnd, 2)` from `user32.dll` (`GetParent` checks not only parents but window owners too, which might be not good).
max
Coredll.dll is only appropriate on Windows Mobile.
Hans Passant
@Hans: Thanks, picked the wrong one I guess. Updated.
Timwi
+1  A: 

After you obtain the window handle you can use Control.FromHandle() to get a reference to the control. Then check the relative mouse position to see if its it is the client area of the form or control. Like this:

    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);
Hans Passant
Under what circumstances will the mouse not be within the window returned by WindowFromPoint?
Daniel Stutzbach
@Daniel - per the OP's request, when the mouse is located on the non-client area of the window. Like the border and caption of a form.
Hans Passant
@Daniel, just an additional supplement, you could use `GetWindowLongPtr()` directly to check whether it is a top-level window. Pass `GWL_EXSTYLE` to `nIndex` parameter, then use bitwise operation on the function result to examine whether it contains `WS_EX_TOPMOST` value.
Vantomex