Grigory
Grigory

Reputation: 159

Avoid WM_MOUSELEAVE when the cursor moves over a child window

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:

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:

Or, put it another way:

Upvotes: 4

Views: 2834

Answers (3)

catnip
catnip

Reputation: 25388

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.

Upvotes: 5

Grigory
Grigory

Reputation: 159

Solved by getting mouse's coords on mouse leave event. The solution is not as beauty as I wanted.

Upvotes: 2

rpress
rpress

Reputation: 462

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.

Upvotes: 3

Related Questions