WM_NCLBUTTONUP message patch

 Environment: MFC, Windows 95, 98, Me, NT 3.51, 4.0, 5.0

If you spend time investigating what happens when you click and release the left button over the title bar, you will find out that instead of getting "non-client left button up" message, you just get "left button up". One could actually work with it, if the message found it's way into the application. It does not.

I was in a middle of writing an application when I discovered this. I looked all over the internet for articles regarding WM_NCLBUTTONUP problem, but the only thing I could find were questions about the problem. After some more investigating I have come up with a patch that could be adopted by each application requiring such notification.

The patch consists of installing a "windows hook" that will intercept all mouse messages for this application before they enter into the message pump. To do that you need to call SetWindowsHookEx(...) function, soon after the main window is created. Here is the call:
hMHook = SetWindowsHookEx( 
                     // hook type: 
                WH_MOUSE,
                     // hook procedure: 
                (HOOKPROC) MouseHookProc,
                     // handle to application instance: 
                AfxGetInstanceHandle(),
                     // thread identifier: 
                AfxGetThread()->m_nThreadID
            );

It is very important that you supply handle to application instance and thread identifier, otherwise every application running on your computer will attempt to hook it's mouse messages through your program and it could be disastrous. By supplying these two parameters you will insure that only messages from your application will end up in your callback function.

Equally important is a call to remove the hook before your application terminates. The UnhookWindowsHookEx(...) function removes a hook procedure installed in a hook chain. Most likely you will call it somewhere in OnDestroy(), like this:

   if(hMHook != NULL)
      UnhookWindowsHookEx(hMHook);

The callback function is where you will receive WM_NCLBUTTONDOWN message and the next time you receive WM_LBUTTONUP message you will post WM_NCLBUTTONUP directly into the application message pump. Therefore, no special handling will be required to service these messages. You will simply write your code inside of OnNcLButtonUp(...), just like you would for any other message.

Here is the callback code:

// ////////////////////////////////////////////////////
// handle to the mouse hook
HHOOK hMHook = NULL;

// status of non-client left button down
BOOL bNcLButtonDown = FALSE;  

// /////////////////////////////////////////////////////
// Mouse hook process

LRESULT CALLBACK MouseHookProc( int nCode, 
                                WPARAM wParam,
                                LPARAM lParam) 
{ 
  if(nCode == HC_ACTION) 
  { 
    // get a pointer to the mouse hook struct. 
    PMOUSEHOOKSTRUCT mhs = (PMOUSEHOOKSTRUCT) lParam; 
    
    // intercept messages for left button down and up
    switch(wParam) 
    { 
        case WM_NCLBUTTONDOWN: 
         {
             // get the pointer to the main window 
             CWnd *pWnd =  AfxGetMainWnd(); 

             // if the message is from your window and 
             // the hit test indicates title bar 
             if((mhs->hwnd == pWnd->GetSafeHwnd()) 
                 && (mhs->wHitTestCode == HTCAPTION))
             { 
                 // then indicate non-client left button down 
                 bNcLButtonDown = TRUE; 
                
                //  there is no problem with this message 
                // so you don't have to do anything else 
             } 
        } 
        break; 
        
        case WM_NCLBUTTONUP: 
            // you will get this message if you double-click 
            //     on the title bar 
            // reset the status 
            bNcLButtonDown = FALSE;  
            break;

        case WM_LBUTTONUP: 
          {
              //  get the pointer to the main window 
              CWnd *pWnd = AfxGetMainWnd(); 

              // if the message is from your window and 
              // non-client left button is down
              if((mhs->hwnd == pWnd->GetSafeHwnd()) 
                  && (bNcLButtonDown == TRUE)) 
              {
                 // then post WM_NCLBUTTONUP message directly 
                 // into your window message pump 
                 // Note: I'm hardcoding HTCAPTION because the 
                 // left button was down, and while it is down,
                 // the mouse does not move in respect to the 
                 // window, but it does in respect to the screen,
                 // so the mouse should still be over the caption
                 // bar of your window when you release the button.
                 pWnd->PostMessage(WM_NCLBUTTONUP, HTCAPTION, 
                                MAKELONG(mhs->pt.x,mhs->pt.y));

                 // reset non-client left button down 
                 bNcLButtonDown = FALSE;
              }
          } 
          break; 

        default: 
            break; 
    } 
  } 
  // let the messages through to the next hook 
 return CallNextHookEx(hMHook, nCode, wParam, lParam); 
}

I am including two sample projects. The "nclbxExample" is technical, and the "AlphaDialogExample" is more practical. The "nclbxExample" is better documented so you can see how and were I have implemented the code.

NOTE: If you are going to use mousepatch.cpp the way I'm using it, DO NOT add it to your project.

Downloads

Download nclbxExample project - 38 Kb
Download AlphaDialogExample project - 118 Kb (will only work in Win2K)
Download source - 2 Kb


Comments

  • ^_^

    Posted by Sanbrother on 10/22/2005 06:49am

    We simply process the "WM_NCLBUTTONDOWN" message, and return TRUE; For example, when LEFT BUTTON is down, check it whether it is in certain RECT, the code: CRect rect; rect.left = 0; rect.top = 0; rect.right = 20; rect.bottom = 20; if(rect.PtInRect(pt)) return TRUE;(pt is the cursor's current position) That is it, it works fine, but very simple.

    Reply
  • This works in delphi, but it might also apply...

    Posted by Legacy on 01/15/2004 12:00am

    Originally posted by: David

    In delphi you get the WM_NCLMOUSEUP message if when you sub class the wndproc you do not pass on the WM_NCLMOUSEUP / WM_NCLMOUSEDOWN messages to the original wndproc.

    You have to be really careful with all sorts of messages with this because many will act upon the message even if you have processed it.

    I have two case statements one which handles messages completely and another for "peeking" which needs further processing by the original wndproc.


    Hope that helps.

    Dave

    Reply
  • Handle WM_EXITSIZEMOVE as substitute

    Posted by Legacy on 04/19/2003 12:00am

    Originally posted by: Alan Mason

    Hello,
    
    I too have been plagued by this problem. The MSDN documentation is clearly wrong concerning WM_NCLBUTTONUP. It is received only when button is released over extreme edge of window. Spy++ shows that a WM_LBUTTONUP message is generated instead when button is released over top nonclient area (title bar). BUT THIS MESSAGE DOESN"T GET THROUGH TO YOUR APP!

    Instead, Windows has hijacked it in a modal Move/Size loop.
    Fortunately, Microsoft did provide a lesser-known message WM_EXITSIZEMOVE which you can use as a substitute for the desired behavior of WM_NCLBUTTONUP. This is the only solution I know of, short of installing a mouse message hook (ugh).

    In your message handler, include cases like:


    case WM_NCLBUTTONDOWN:
    pFlash->m_MouseState.bNcLButtonDown = true;
    break;
    case WM_LBUTTONDOWN:
    pFlash->m_MouseState.bLButtonDown = true;
    pFlash->m_MouseState.bNcLButtonDown = false;
    return DefWindowProc(hWnd, uMsg, wp, lp);
    case WM_LBUTTONUP:
    pFlash->m_MouseState.bLButtonDown = false;
    return DefWindowProc(hWnd, uMsg, wp, lp);
    break;
    case WM_NCLBUTTONUP: // turkey no worky
    pFlash->m_MouseState.bNcLButtonDown = false;
    pFlash->m_MouseState.bLButtonDown = false;
    break;
    case WM_EXITSIZEMOVE:// Use as substitute
    // for WM_NCLBUTTONUP
    pFlash->m_MouseState.bNcLButtonDown = false;
    pFlash->m_MouseState.bLButtonDown = false;
    break;

    Reply
  • running w2k but still -

    Posted by Legacy on 08/29/2002 12:00am

    Originally posted by: dwiz

    AlphaDialogExampleDlg.cpp
    G:\code\Samples\codeguru\NonClientLBUpSim\AlphaDialogExample\AlphaDialogExampleDlg.cpp(150) : error C2065: 'WS_EX_LAYERED' : undeclared identifier
    G:\code\Samples\codeguru\NonClientLBUpSim\AlphaDialogExample\AlphaDialogExampleDlg.cpp(152) : error C2065: 'SetLayeredWindowAttributes' : undeclared identifier
    G:\code\Samples\codeguru\NonClientLBUpSim\AlphaDialogExample\AlphaDialogExampleDlg.cpp(152) : error C2065: 'LWA_ALPHA' : undeclared identifier

    Reply
  • Easier solution.

    Posted by Legacy on 08/17/2002 12:00am

    Originally posted by: duckie

    // add BOOL bNcLButtonDown to class and don't forget to initialize as FALSE in the constructor.
    
    

    BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
    {
    if (pMsg->message == WM_NCLBUTTONDOWN)
    bNcLButtonDown = TRUE;
    if ((pMsg->message == WM_LBUTTONUP) && (bNcLButtonDown)) {
    bNcLButtonDown = FALSE;
    pMsg->message = WM_NCLBUTTONUP;
    }
    return CFrameWnd::PreTranslateMessage(pMsg);
    }

    void CMainFrame::OnNcLButtonDown(UINT nHitTest, CPoint point)
    {
    SetCapture();
    CFrameWnd::OnNcLButtonDown(nHitTest, point);
    }

    void CMainFrame::OnNcLButtonUp(UINT nHitTest, CPoint point)
    {
    ReleaseCapture();
    CFrameWnd::OnNcLButtonUp(nHitTest, point);
    }

    // btw, this example works if you want, for example, control the resize of the window (winamp 2 style). other purpose might not work with this solution, or may take extra code. regards.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • With the average hard drive now averaging one terabyte in size, the fallout from the explosion of user-created data has become an overwhelming volume of potential evidence that law-enforcement and corporate investigators spend countless hours examining. Join Us and SANS' Rob Lee for our 45-minute webinar, A Triage and Collection Strategy for Time-Sensitive Investigations, will demonstrate how to: Identify the folders and files that often contain key insights Reduce the time spent sifting through content by …

Most Popular Programming Stories

More for Developers

RSS Feeds