c++winapimfc

The curious problem of the missing WM_NCLBUTTONUP message when a window isn't maximised


I've got a window that I handle WM_NCLBUTTONUP messages, in order to handle clicks on custom buttons in the caption bar. This works great when the window is maximised, but when it's not, the WM_NCLBUTTONUP message never arrives! I do get a WM_NCLBUTTONDOWN message though. Strangely WM_NCLBUTTONUP does arrive if I click on the right of the menu bar, but anywhere along the caption bar / window frame, the message never arrives.

After a while of debugging I discovered that if I set a breakpoint on CMainFrame::OnNcLButtonDown(), clicked the caption bar, but keep the mouse button held down, let the debugger break in the function, hit F5 to continue debugging, then release the mouse button - magically WM_NCLBUTTONUP is sent!!

My question is two-fold, (1) what the hell is going on? (2) how do I get around this "problem".

I also note that there are several other people on the internet who have the same issue (a quick Google reveals lots of other people with the same issue, but no solution).

Edit
Thanks for the first two replies, I've tried calling ReleaseCapture in NCLButtonDown, but it has no effect (in fact, it returns NULL, indicating a capture is not in place). I can only assume that the base class (def window proc) functionality may set a capture. I shall investigate on Monday...


Solution

  • I've had this same problem. The issue is indeed that a left button click on the window caption starts a drag, and thus mouse capture, which prevents WM_NCLBUTTONUP from arriving.

    The solution is to override WM_NCHITTEST:

    LRESULT CALLBACK WndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (nMsg)
        {
            ...
            case WM_NCHITTEST:
                Point p(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam);
                ScreenToClient(p);
                if (myButtonRect.Contains(p))
                {
                    return HTBORDER;
                }
                break;
        }
        return DefWindowProc(hWnd, nMsg, wParam, lParam);
    }
    

    So essentially you inform Windows that the area occupied by your button is not part of the window caption, but a non-specific part of the non-client area (HTBORDER).

    Footnote: If you have called SetCapture() and not yet called ReleaseCapture() when you expect the WM_NCLBUTTONDOWN message to come in, it won't arrive even with the above change. This can be irritating since it's normal to capture the mouse during interaction with such custom buttons so that you can cancel the click/highlight if the mouse leaves the window. However, as an alternative to using capture, you might consider SetTimer()/KillTimer() with a short (eg. 100 ms) interval, which won't cause WM_NCLBUTTONUP messages to vanish.