Search code examples
winapisendmessagewindows-messages

When is the correct time to send the WM_SETFONT message to a static control child of a main window in win32?


I am trying to figure out the correct way to set the font of a static text box which is a child of a main window. After much googling, I find many results that explain essentially the following:

  1. Create a font object using CreateFont
  2. Send the WM_SETFONT message to the control, referencing the font handle.
  3. Destroy the font object when it is no longer needed.

What's missing to me in every possible explanation I've found is when to send the message. Do I send it in the WinProc while handling some other appropriate message? I can't find a good explanation for this.

At least one MSDN article discusses setting the font while handling a WM_INITDIALOG message, but this isn't a dialog. This is just a main window with a static child text box.

What I've done is send the WM_SETFONT message just before entering the message loop. Surprisingly to me, it works! My surprise arises from my understanding that SendMessage doesn't return until the message has been handled (i.e. "until the window procedure has processed the message." - DOCS). So, how the heck does the code even get to the message loop?

Here's my code:

#include <windows.h>
#include <windowsx.h>

HWND hMainWind;
HWND hStartButton;
HWND hStopButton;
HWND hStaticBox;
static HFONT hFont;
static LRESULT CALLBACK MainWinProc(HWND, UINT, WPARAM, LPARAM);

#define START_BUTTON 1001
#define STOP_BUTTON  1002

int WINAPI WinMain( HINSTANCE hMainInstance, HINSTANCE hPrevInstance,
                    LPSTR lpCmdLine, int nShowCmd )
{

  MSG Msg = {0};
  WNDCLASSEX MainWinClass;

  MainWinClass.cbSize         = sizeof(WNDCLASSEX);
  MainWinClass.style          = CS_VREDRAW|CS_HREDRAW;
  MainWinClass.lpfnWndProc    = MainWinProc;
  MainWinClass.cbClsExtra     = 0;
  MainWinClass.cbWndExtra     = 0;
  MainWinClass.hInstance      = hMainInstance;

  MainWinClass.hIcon          = LoadIcon(NULL, IDI_APPLICATION);
  MainWinClass.hCursor        = LoadCursor(NULL, IDC_ARROW);
  MainWinClass.hIconSm        = LoadIcon(NULL, IDI_APPLICATION);

  MainWinClass.hbrBackground  = (HBRUSH)COLOR_BACKGROUND;
  MainWinClass.lpszMenuName   = NULL; /*No menu*/
  MainWinClass.lpszClassName  = "TwoButtons"; /* class name to register*/

  if( !RegisterClassEx( &MainWinClass ) )
  {
      MessageBox( NULL, "Window Failed to Register!", "ERROR",
                  MB_ICONEXCLAMATION | MB_OK);
    return 0;
  }

  hMainWind = CreateWindowEx
  (
    WS_EX_LEFT,
    MainWinClass.lpszClassName,
    "TwoButtons",
    WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT,  /*Default x pos*/
    CW_USEDEFAULT,  /*Default y pos*/
    640,  /*Width*/
    480,  /*Height*/
    HWND_DESKTOP,
    NULL,
    hMainInstance,
    NULL
  );

  hStartButton = CreateWindow
  (
    "BUTTON",
    "START",
    WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
    270,  /*x pos*/
    200,  /*y pos*/
    100,
    50,
    hMainWind,
    (HMENU)START_BUTTON,
    hMainInstance,
    NULL
  );

  hStopButton = CreateWindow
  (
    "BUTTON",
    "STOP",
    WS_TABSTOP | WS_VISIBLE | WS_DISABLED | WS_CHILD | BS_PUSHBUTTON,
    270,
    300,
    100,
    50,
    hMainWind,
    (HMENU)STOP_BUTTON,
    hMainInstance,
    NULL
  );

  /*create a static text box*/
  hStaticBox = CreateWindow
  (
    "STATIC",                       /* lpClassName */
    "Box",                          /* lpWindowName */
    WS_BORDER|WS_CHILD|WS_VISIBLE,  /* dwStyle */
    270,                            /* x */
    100,                            /* y */
    100,                            /* nWidth */
    50,                             /* nHeight */
    hMainWind,                      /* hWndParent */
    NULL,                           /* hMenu */
    hMainInstance,                  /* hInstance */
    (LPVOID)1                       /* lpParam */
  );

  hFont = CreateFont
  (
    24,                   /* cHeight */
    0,                    /* cWidth use default*/
    0,                    /* cEscapement */
    0,                    /* cOrientation */
    FW_NORMAL,            /* cWeight */
    FALSE,                /* bItalic */
    FALSE,                /* bUnderline */
    FALSE,                /* bStrikeOut */
    DEFAULT_CHARSET,      /* iCharSet */
    OUT_DEFAULT_PRECIS,   /* iOutPrecision */
    CLIP_DEFAULT_PRECIS,  /* iClipPrecision */
    DEFAULT_QUALITY,      /* iQuality */
    DEFAULT_PITCH,        /* iPitchAndFamily */
    "Arial"               /* pszFacename */
  );

  /* Make the main window visible on the screen */
  ShowWindow(hMainWind, nShowCmd);
  UpdateWindow(hMainWind);

  /*Set up the font for the static text box*/
  SendMessage ( hStaticBox, WM_SETFONT, (WPARAM) hFont, TRUE );

  /* Run the message loop. It will run until GetMessage() returns 0 */
  while( GetMessage( &Msg, NULL, 0, 0 ) )
  {
    /* Translate virtual-key messages into character messages */
    TranslateMessage( &Msg );
    /* Send message to MainWinProc */
    DispatchMessage( &Msg );
  }

  /* The program return-value is 0 - The value that PostQuitMessage() gave */
  return Msg.wParam;
}

/* This function is called by DispatchMessage()  */
static LRESULT CALLBACK MainWinProc(  HWND hWind, UINT Message, WPARAM wParam,
                                      LPARAM lParam )
{
  switch(Message) /* handle the message */
  {
    case WM_DESTROY:
      DeleteObject(hFont);
      PostQuitMessage(0); /* sends a WM_QUIT to the message queue */
      break;

    case WM_COMMAND:
      if( HIWORD( wParam ) == BN_CLICKED )  /*A button was clicked*/
      {
        switch ( LOWORD(wParam) ) /*Which button?*/
        {
          case START_BUTTON:
            Button_Enable( hStartButton, FALSE );
            Button_Enable( hStopButton,  TRUE );
            SetWindowText( hStaticBox, "START" );
            break;

          case STOP_BUTTON:
            Button_Enable( hStartButton, TRUE );
            Button_Enable( hStopButton,  FALSE );
            SetWindowText( hStaticBox, "STOP" );
            break;
        }
      }
    default:  /* Pass on messages not handled here */
      return DefWindowProc (hWind, Message, wParam, lParam);
  }
  return 0;
}

Solution

  • What's missing to me in every possible explanation I've found is when to send the message. Do I send it in the WinProc while handling some other appropriate message?

    You can send it to the static control any time after you have created it.

    At least [one MSDN article][1] discusses setting the font while handling a WM_INITDIALOG message, but this isn't a dialog. This is just a main window with a static child text box.

    Since you are not creating a dialog, you can handle WM_CREATE instead.

    What I've done is send the WM_SETFONT message just before entering the message loop. Surprisingly to me, it works!

    Yes, because you sent it after you created the static control.

    My surprise arises from my understanding that SendMessage doesn't return until the message has been handled (i.e. "until the window procedure has processed the message."). So, how the heck does the code even get to the message loop?

    It doesn't. Per the same doc you quoted from:

    If the specified window was created by the calling thread, the window procedure is called immediately as a subroutine.

    In other words, SendMessage() will call the static control's own WndProc directly in your example. That is why it doesn't need to go through the message loop.

    [on WM_INITDIALOG or on WM_CREATE] doesn't work. I added the following snippet:

    case WM_INITDIALOG:
    case WM_CREATE:
        SendMessage ( hStaticBox, WM_SETFONT, (WPARAM) hFont, TRUE );
        break;
    

    to the switch statement in my WinProc and commented out the call to SendMessage before the message loop. This did not work - the font for the static text box didn't get updated.

    That is because WM_CREATE was sent to your WndProc before you created the static control, as WM_CREATE is sent by CreateWindowEx() itself. Had you debugged your code, you would have seen that hStaticBox and hFont were still NULL at the time you sent WM_SETFONT, and thus SendMessage() would have returned a failure due to being given an invalid HWND.

    You should have your MainWinProc handle WM_CREATE and create all of your child controls from there, they do not belong in WinMain() directly. You can then send WM_SETFONT inside of WM_CREATE as well, eg:

    ...
    
    int WINAPI WinMain( HINSTANCE hMainInstance, HINSTANCE hPrevInstance,
                        LPSTR lpCmdLine, int nShowCmd )
    {
        ...    
    
        hMainWind = CreateWindowEx(...);
    
        // get rid of all the subsequent creates here...
        
        ShowWindow(hMainWind, nShowCmd);
        UpdateWindow(hMainWind);
        
        ...
    
        return Msg.wParam;
    }
        
    static LRESULT CALLBACK MainWinProc(  HWND hWind, UINT Message, WPARAM wParam,
                                          LPARAM lParam )
    {
        switch(Message) /* handle the message */
        {
            case WM_CREATE:
                hStartButton = CreateWindow(..., hWind, ...);
                hStopButton = CreateWindow(..., hWind, ...);
                hStaticBox = CreateWindow(..., hWind, ...);
        
                hFont = CreateFont(...);
                SendMessage(hStaticBox, WM_SETFONT, (WPARAM) hFont, TRUE);
                break;
    
            case WM_DESTROY:
                DeleteObject(hFont);
                PostQuitMessage(0); /* sends a WM_QUIT to the message queue */
                break;
        
            ...
        }
    
        ...
    }