Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find all windows beneath a point

Tags:

windows

winapi

I want to find all the top-level windows (children of the desktop) beneath a given point on the desktop. I can't find an API for this.

My scenario is that I'm dragging a window across the screen and want to drop it into another (known) window. I can hit test the bounds of the target window ok, but that doesn't tell me whether it's occluded by another (unknown) window. Using WindowFromPoint and friends won't work, because the window being dragged is necessarily directly under the mouse. So I'm wondering if I can obtain all windows at the mouse position, and review them to see whether one of the windows I'm tracking is directly beneath the window I'm dragging.

Is there a way to do this without resorting to EnumDesktopWindows/GetWindowRect on every mouse drag? Or perhaps there's another solution I'm missing.

like image 202
Drew Noakes Avatar asked May 25 '16 20:05

Drew Noakes


2 Answers

If you ask kindly, WindowFromPoint will ignore your window (the one currently being dragged) and return the next window. This is what Internet Explorer does when you drag a tab.

To do that:

  1. Handle WM_NCHITTEST in window being dragged
  2. Return HTTRANSPARENT during dragging. Call default window proc otherwise.
  3. WindowFromPoint will ignore HTTRANSPARENT windows, but only those belonging to the calling thread. This shouldn't be a problem for you, because you should be calling WindowFromPoint from the window owner thread anyway.
  4. Make sure there're no child windows at point passed to WindowFromPoint, or handle WM_NCHITTEST for these child windows as well.

Troubleshooting (if you still get your window from WindowFromPoint)

  1. Test GetCurrentThreadID() == GetWindowThreadProcessId(WindowFromPoint(), 0) to ensure you're calling from correct thread
  2. In WM_NCHITTEST, test that hwnd parameter equals what you get from WindowFromPoint()

Example (the area within rectangle returns the underlying window from WindowFromPoint):

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static const RECT s_TransparentRect = {100, 100, 200, 200};

    switch (message)
    {
    case WM_NCCREATE:
        SetTimer(hWnd, 1, 100, 0);
        break;
    case WM_TIMER:
        {
            POINT cursorPos;
            GetCursorPos(&cursorPos);

            TCHAR buffer[256];
            _snwprintf_s(buffer, _countof(buffer), _TRUNCATE, _T("WindowFromPoint: %08X\n"), (int)WindowFromPoint(cursorPos));
            SetWindowText(hWnd, buffer);
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            BeginPaint(hWnd, &ps);
            Rectangle(ps.hdc, s_TransparentRect.left, s_TransparentRect.top, s_TransparentRect.right, s_TransparentRect.bottom);
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_NCHITTEST:
        {
            POINT cursorPos;
            GetCursorPos(&cursorPos);
            MapWindowPoints(HWND_DESKTOP, hWnd, &cursorPos, 1);

            if (PtInRect(&s_TransparentRect, cursorPos))
                return HTTRANSPARENT;
        }
        break;
    }

    return DefWindowProc(hWnd, message, wParam, lParam);
}
like image 129
Codeguard Avatar answered Oct 25 '22 14:10

Codeguard


Right, you already know what WindowFromPoint() is going to return, should be the one you are dragging. Then use GetWindow() with uCmd = GW_HWNDNEXT to get the one below it in the Z-order. GetWindowRect() to get its bounds, IntersectRect() to compute the overlap.

Keep calling GetWindow() to find more windows that might be overlapped. Until it returns NULL or the overlap is good enough. If not then you'll normally favor the one that has the largest result rectangle from IntersectRect().

like image 39
Hans Passant Avatar answered Oct 25 '22 15:10

Hans Passant