user3010974
user3010974

Reputation: 71

Win32 Subclassing - About Messages

So I'm practicing subclassing a predefined window class in Win32 so I can define my own custom message proc for the predefined classes (e.g. making a custom WndProc for a button class) and I got it to work for the most part but I can't get the WM_COMMAND messages to be automatically sent to the new message proc. I can set up a case in the parent window for WM_COMMAND and check the wParam then call the respective new message proc for the child window that sent the WM_COMMAND message but I would prefer for this to be done automatically like all the other commands. As far as I can tell all the other WM_'s I have tried in the custom message proc I defined get passed to it automatically from the parent window's message proc, but the WM_COMMAND messages won't unless I explicitly redirect them. I have a feeling WM_COMMAND messages will always be sent to the parent window when subclassing the way I have it set up, but if someone could explain why this is the case or what I would need to do in order to direct all messages belonging to the button window to the custom WndProc I defined, it would be much appreciated.

Code:

#include <Windows.h>
#include <windowsx.h>

#define IDC_BUTTON   0


LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
LRESULT CALLBACK WndProcButton (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

HWND    g_hwndButton            = NULL;
WNDPROC g_wndProcButtonOrigianl = NULL;
BOOL    g_bSeeingMouse          = FALSE;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR szCmdLine, int iCmdShow)
{
     static TCHAR szClassName[] = TEXT ("HelloWin") ;
     HWND         hwnd ;
     MSG          msg ;
     WNDCLASS     wndclass ;

     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
     wndclass.lpfnWndProc   = WndProc ;
     wndclass.cbClsExtra    = 0 ;
     wndclass.cbWndExtra    = 0 ;
     wndclass.hInstance     = hInstance ;
     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     wndclass.lpszMenuName  = NULL ;
     wndclass.lpszClassName = szClassName ;

     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"), 
                      szClassName, MB_ICONERROR) ;
          return 0 ;
     }

     hwnd = CreateWindow (szClassName,                // window class name
                          TEXT ("The Hello Program"), // window caption
                          WS_OVERLAPPEDWINDOW,        // window style
                          CW_USEDEFAULT,              // initial x position
                          CW_USEDEFAULT,              // initial y position
                          CW_USEDEFAULT,              // initial x size
                          CW_USEDEFAULT,              // initial y size
                          NULL,                       // parent window handle
                          NULL,                       // window menu handle
                          hInstance,                  // program instance handle
                          NULL) ;                     // creation parameters

     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;

     while (GetMessage (&msg, NULL, 0, 0))
     {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
     }
     return msg.wParam ;
}


LRESULT CALLBACK WndProcButton (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch( message )
    {
    case WM_COMMAND:
        MessageBox( hwnd, TEXT( "Test box" ), TEXT( "Test box" ), MB_OK );
        SetFocus( g_hwndButton );
        break;
    default:
        if( !g_bSeeingMouse && GetCapture() == hwnd )
        {
            g_bSeeingMouse = TRUE;
            SetWindowText( hwnd, L"Ok +mouse" );
        }
        else if( g_bSeeingMouse && GetCapture() != hwnd )
        {
            g_bSeeingMouse = FALSE;
            SetWindowText( hwnd, L"Ok" );
        }
        break;
    }
    return CallWindowProc( g_wndProcButtonOrigianl, hwnd, message, wParam, lParam );
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     HDC         hdc ;
     PAINTSTRUCT ps ;
     RECT        rect ;

     switch (message)
     {
     case WM_CREATE:
         g_hwndButton = CreateWindow( L"Button",                                            // predefined class
                                      L"Ok",                                                // button text
                                      ( WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON ),         // styles
                                      // Size and poition values are given explicitly, because
                                      // the CW_USEDEFAULT constant gives zero values for buttons.
                                      10,                                                   // starting x position
                                      10,                                                   // starting y position
                                      100,                                                  // button width
                                      30,                                                   // button height
                                      hwnd,                                                 // parent window
                                      (HMENU)IDC_BUTTON,                                    // no menu
                                      (HINSTANCE)GetWindowLongPtr( hwnd, GWLP_HINSTANCE ),
                                      NULL );                                               // pointer not needed
         SetFocus( g_hwndButton );
         g_wndProcButtonOrigianl = (WNDPROC)SetWindowLongPtr( g_hwndButton, GWLP_WNDPROC, (LONG_PTR)WndProcButton );
          return 0 ;

     case WM_PAINT:
          hdc = BeginPaint (hwnd, &ps) ;

          GetClientRect (hwnd, &rect) ;

          DrawText (hdc, TEXT ("Hello, Windows 98!"), -1, &rect,
                    DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;

          EndPaint (hwnd, &ps) ;
          return 0 ;

     case WM_DESTROY:
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

Upvotes: 3

Views: 1897

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 596121

By design, WM_COMMAND and WM_NOTIFY messages are sent to a control's parent window. There is no way to make the API automatically send them to the control that generated them. The parent window must handle them, or forward them, as needed. There is no avoiding that.

In the VCL framework originally written by Borland and now owned by Embarcadero, it uses a simple system for forwarding such messages. When any parent window receives one of these messages, it increments the message ID by an offset that puts the message into the user-defined range, and then forwards the message to the child HWND specified in the message. So WM_COMMAND becomes CN_COMMAND and WM_NOTIFY becomes CN_NOTIFY, which the child control's window procedure can look for.

For example:

#define MY_COMMAND  (WM_COMMAND + some_offset)
#define MY_NOTIFY   (WM_NOTIFY + some_offset)

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     switch (message)
     {
     case WM_COMMAND:
         if (lParam != 0)
             SendMessage((HWND)lParam, MY_COMMAND, wParam, lParam);
         return 0;

     case WM_NOTIFY:
         SendMessage(((NMHDR*)lParam)->hwndFrom, MY_NOTIFY, wParam, lParam);
         return 0;

     ...
     }

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

LRESULT CALLBACK WndProcButton (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch( message )
    {
    case MY_COMMAND:
        ...
    case MY_NOTIFY:
        ...
    }

    return CallWindowProc( g_wndProcButtonOrigianl, hwnd, message, wParam, lParam );
} 

Upvotes: 6

Related Questions