Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoid WM_MOUSELEAVE when the cursor moves over a child window

Tags:

c++

winapi

I am handling mouse hover/leave events with TrackMouseEvent, WM_MOUSEHOVER and WM_MOUSELEAVE just fine.

The only problem is that when the mouse is hovering over any of the window's children it sends the window that is tracking the mouse a WM_MOUSELEAVE message.

I actually understand why Windows is doing that, but have no idea how to fix it. Googling didn't help me. I believe the solution is pretty simple and I have just missed something. I am developing Visual C++ Win32 application. (No MFC and so on)

My code:

void TrackMouse(HWND hwnd)
{
    TRACKMOUSEEVENT tme;
    tme.cbSize = sizeof(TRACKMOUSEEVENT);
    tme.dwFlags = TME_HOVER | TME_LEAVE;
    tme.dwHoverTime = 1; //How long the mouse has to be in the window to trigger a hover event.
    tme.hwndTrack = hwnd;
    TrackMouseEvent(&tme);
}

WndProc:

case WM_MOUSEMOVE:
{
    if (!isTracking)
    {
        TrackMouse(hWnd);
        isTracking = true;
    }
    break;
}
case WM_MOUSEHOVER:
    ShowWindow(MouseIsOver, TRUE);
    break;
case WM_MOUSELEAVE:
    ShowWindow(MouseIsOver, FALSE);
    isTracking = false;
    break;

Analogy

Think of it this way:

  • I want a notification when the mouse enters the house
  • And i want a notifification when the mouse leaves the house

enter image description here

The mouse hasn't left the house just because it entered the Bathroom. And we know (Windows knows) that House is the hwndOwner of Bathroom; so it can guarantee:

  • that by entering the Bathroom
  • that it hasn't left the House

Or, put it another way:

  • the mouse hasn't WM_MouseLeft the hwndOnwer control
  • simply because the mouse entered an child control
like image 629
Grigory Avatar asked Oct 15 '25 22:10

Grigory


2 Answers

OK, so, on the basis that the OP (probably) wants to, in effect, ignore WM_MOUSELEAVE when the cursor passes over a child window of the window tracking the mouse, I think he is probably now doing something like this:

BOOL DidMouseLeaveWindow (HWND hWnd)
{
    DWORD msgpos = GetMessagePos ();
    POINT pt = { GET_X_LPARAM (msgpos), GET_Y_LPARAM (msgpos) };
    ScreenToClient (hWnd, &pt);
    RECT cr;
    GetClientRect (hWnd, &cr);
    return !PtInRect (&cr, pt);
}

which seems just fine to me.

If you don't like that for whatever reason, you could also do:

BOOL DidMouseLeaveWindow (HWND hWnd)
{
    DWORD msgpos = GetMessagePos ();
    POINT pt = { GET_X_LPARAM (msgpos), GET_Y_LPARAM (msgpos) };
    HWND hWndUnderCursor = WindowFromPoint (pt);
    return !IsChild (hWnd, hWndUnderCursor);
}

which is perhaps a bit more elegant.

There are a couple of issues with both of these approaches though. If (say) the right-hand edge of a child window aligns exactly with the right-hand edge of the parent and you exit via that edge then the parent will never get its WM_MOUSELEAVE. You might also possibly miss one if you position the cursor over a child window and then move it out of the parent very quickly,

To solve these issues, I would recommend setting up a timer as a backstop. So, you would do something like:

void TrackMouse(HWND hwnd)
{
    // ...
    SetTimer (hWnd, 1, 250, 0);
}

And in the WndProc:

case WM_TIMER:
case WM_MOUSELEAVE:
    if (DidMouseLeaveWindow (hWnd))
    {
        // ...
        isTracking = false;
        KillTimer (hWnd, 1);
    }
    break;

It would be nice if TrackMouseEvent had a TME_IGNORE_CHILDREN flag, but sadly it doesn't.

like image 117
Paul Sanders Avatar answered Oct 18 '25 13:10

Paul Sanders


Your message handler gets a WM_MOUSELEAVE message to tell it that tracking is done. You have to call TrackMouseEvent() again to continue tracking. There's nothing to fix. Your message handler can act accordingly.

Without that message your program would have no knowledge about the situation.

like image 22
rpress Avatar answered Oct 18 '25 15:10

rpress