5

Hello I am creating an app in win32 that will display the x, y position(In screen coords) of the mouse whereever the mouse is (inside my app client/NC area & outside).

I am at the stage where I want to detect when the mouse leaves my application completely. I have written a simple win32 app that should detect & notify myself when the mouse leaves my app, BUT its not working, I never receive the messages WM_MOUSELEAVE & WM_NCMOUSELEAVE.

What do you think is wrong? Am I using the wrong win32 functions?

// Track Mouse.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include <windows.h>
#include <vector>
#include <string>
#include <cstdlib>

static HINSTANCE gInstance;


// Globals //
enum   MouseStatus { DEFAULT = 50001, LEFT_CLIENT, LEFT_NCLIENT }; 
static MouseStatus mouseState = DEFAULT;
static COLORREF    bkCol      = RGB(0,255,255);

// Functions List //

BOOL TrackMouse( HWND hwnd )
{
    // Post:

    TRACKMOUSEEVENT mouseEvt;
    ZeroMemory( &mouseEvt, sizeof(TRACKMOUSEEVENT) );

    mouseEvt.cbSize      = sizeof(TRACKMOUSEEVENT);
    mouseEvt.dwFlags     = TME_LEAVE | TME_NONCLIENT;
    //mouseEvt.dwHoverTime = HOVER_DEFAULT;
    mouseEvt.hwndTrack   = hwnd;

    return TrackMouseEvent( &mouseEvt );
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{

    switch(msg)
    {
    case WM_CREATE:
        {               
            // Track mouse so I can be notified when it leaves my application (Client & NC areas)
            BOOL trackSuccess = TrackMouse( hwnd ); // Returns successful, so I correctly track the mouse

            if ( trackSuccess == 0 )
            {
                MessageBoxW( hwnd, L"Failed to track mouse", L"Error", MB_OK|MB_ICONEXCLAMATION );
            }
            else MessageBoxW( hwnd, L"Tracking mouse", L"Success", MB_OK|MB_ICONEXCLAMATION );
        }    
        break;
    case WM_MOUSELEAVE:
        {
            // I never receive this message

            // Detect when the mouse leaves the client area
            mouseState = LEFT_CLIENT;
            bkCol = RGB(50,50,50);
            InvalidateRect( hwnd, NULL, true );
        }
        break;
    case WM_NCMOUSELEAVE :
        {
            // I never receive this message

            // If the mouse has left the client area & then leaves the NC area then I know
            // that the mouse has left my app
            if ( mouseState == LEFT_CLIENT )
            {
                mouseState = LEFT_NCLIENT;
                BOOL trackSuccess = TrackMouse( hwnd );

                if ( trackSuccess == 0 )
                {
                    bkCol = RGB(255,255,0);
                    MessageBoxW( hwnd, L"On WM_NCMOUSELEAVE: Failed to track mouse", L"Error", MB_OK|MB_ICONEXCLAMATION );
                }
                else MessageBoxW( hwnd, L"On WM_NCMOUSELEAVE: Tracking mouse", L"Success", MB_OK|MB_ICONEXCLAMATION );

                InvalidateRect( hwnd, NULL, true );
            }
        }
        break;
    case WM_ACTIVATE:
    case WM_MOUSEHOVER:
        {
            // The mouse is back in my app
            mouseState = DEFAULT;
            bkCol      = RGB(0,255,255);
            InvalidateRect( hwnd, NULL, true );
        }
        break;
    case WM_PAINT:
        {
            HDC hdc;
            PAINTSTRUCT ps;
            hdc = BeginPaint( hwnd, &ps );

            SetBkColor( hdc, bkCol );
            Rectangle( hdc, 10, 10, 200, 200 );

            EndPaint( hwnd, &ps );
        }
        break;
    case WM_CLOSE:
        DestroyWindow(hwnd);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default: 
        break;
    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}


int WINAPI WinMain(HINSTANCE gInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;


    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = gInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(DKGRAY_BRUSH);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = L"Custom Class";
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    // if registration of main class fails
    if(!RegisterClassEx(&wc))
    {
        MessageBoxW(NULL, L"Window Registration Failed!", L"Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        L"Custom Class",
        L"App Name",
        WS_CAPTION|WS_MINIMIZEBOX|WS_VISIBLE|WS_OVERLAPPED|WS_SYSMENU,
        CW_USEDEFAULT, CW_USEDEFAULT, 600, 500,
        NULL, NULL, gInstance, NULL);

    if(hwnd == NULL)
    {
        MessageBoxW(NULL, L"Window Creation Failed!", L"Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}
user593747
  • 1,484
  • 4
  • 32
  • 52

3 Answers3

4

The key component your missing is SetCapture(hwnd); which directs all mouse messages to that hwnd until you call ReleaseCapture();

HANDLE_DLGMSG(hwnd, WM_RBUTTONDOWN,     SKDemo_OnRButtonDown);
HANDLE_DLGMSG(hwnd, WM_MOUSEMOVE,       SKDemo_OnMouseMove);
HANDLE_DLGMSG(hwnd, WM_RBUTTONUP,       SKDemo_OnRButtonUp);

void SKDemo_OnRButtonDown (HWND hwnd, BOOL fDbClk, int x, int y, UINT keyFlags)
{
     // Force all mouse messages to come to this window.
     SetCapture(hwnd);

     // Change the mouse cursor to eyes. This provides a visual indication
     // to the user that Voyeur is "peering."
     SetCursor(LoadCursor(GetWindowInstance(hwnd),
        MAKEINTRESOURCE(IDC_POINTER)));
}

void SKDemo_OnMouseMove (HWND hwnd, short x, short y, UINT keyFlags) 
{
    if( GetCapture() == NULL ) {
        return;
    }
// do something with the message here
}

void SKDemo_OnRButtonUp (HWND hwnd, int x, int y, UINT keyFlags) 
{
   ReleaseCapture();
}
Greg Domjan
  • 13,943
  • 6
  • 43
  • 59
  • 1
    The OP may want to detect mouse entering and leaving his window without capturing the mouse, which is quite an invasive procedure. And of course you can only capture when your window is the foreground. – David Heffernan Feb 05 '11 at 11:46
  • @DavidHeffernan: Implementations of `TrackMouseEvent` vary by OS version, but some of them do exactly this: capture the mouse while it's in the windown and release it when it moves out. You don't need to be the foreground window to capture the mouse, the mouse just has to be over the window. This approach should work just fine, and if you're polite and re-post the mouse move that left the window, it's not very invasive at all. – Adrian McCarthy Jun 04 '12 at 23:29
  • @AdrianMcCarthy: I doubt that any of this is accurate. From the documentation of [SetCapture](https://msdn.microsoft.com/en-us/library/windows/desktop/ms646262.aspx): *"Only the foreground window can capture the mouse."* There's not a single OS that implements `TrackMouseEvent` by capturing the mouse. This can easily be verified, since `TrackMouseEvent` works across all OS', even for windows that aren't the foreground window. Logic has it that `SetCapture` can thus not be used to implement this. – IInspectable Dec 21 '16 at 20:23
  • @IInspectable: For older versions of Windows, you could call `_TrackMouseEvent` which would emulate the newer `TrackMouseEvent` by capturing the mouse. `_TrackMouseEvent` is typically called in response to a message, which is a perfectly legitimate time to call SetCapture. I recall a predecessor of `TrackMouseEvent` that was probably snarfed from a Microsoft sample; it was actually a blocking call. It would return once the hover or leave condition was satisfied rather than posting a message. That one captured the mouse and ran a private message loop. – Adrian McCarthy Dec 21 '16 at 21:41
  • @AdrianMcCarthy: Being called in response to a message does nowhere near indicate, that the calling thread is the foreground thread. You can (and do) receive `WM_MOUSEMOVE` messages all the time, for example, without being the foreground thread. I'm not convinced here. If `TrackMouseEvent` works for background windows and `SetCapture` doesn't, then clearly the latter cannot be used to implement the former. – IInspectable Dec 21 '16 at 21:50
  • @IInspectable: OK, maybe my memory is hazy. I know that the Win16 version everyone was using captured the mouse and ran a private message pump. I've never seen anyone use TrackMouseEvent on a background window; usually it was in response to a WM_LBUTTONDOWN (so the window was in the front) or it was to figure out if it was time to show a tooltip, which used to be done only for the active window. – Adrian McCarthy Dec 21 '16 at 22:09
  • @AdrianMcCarthy: Of course none of this applies to 16-bit Windows. Things were radically different back then, with a synchronous input model. There hadn't even been a need to invent the concept of a *"foreground window"*. And since the question is tagged [tag:winapi], I find concerns about 16-bit Windows surprisingly off-topic. Anyway, if you need to see a background window that tracks mouse leave events, open Windows' *File Explorer*, and observe how it highlights items on mouse hover. Spy++ helps you see them `WM_MOUSELEAVE` messages. – IInspectable Dec 22 '16 at 00:39
3

I think you need the mouse to be over your window before you all TrackMouseEvent. Try calling whilst you handle a mouse move message.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
1

As written in Win32 documentation, the request expires each time the mouse leaves the window. So you need to call TrackMouseEvent also in your WM_MOUSELEAVE handler. Note also that if the mouse isn't in your window at the time TrackMouseEvent is called, WM_MOUSELEAVE is generated immediately.

passo
  • 11
  • 1