1
\$\begingroup\$

If you aren't aware, if you hand-code a GUI with the Windows API you will find your controls look quite ugly by default due to their font. Running this code:

#include <Windows.h>
#include <windowsx.h>
#include <CommCtrl.h>
#include <strsafe.h>
#include <sal.h>

#pragma comment(lib, "comctl32.lib")

#if defined _M_IX86
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")
#elif defined _M_IA64
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"")
#elif defined _M_X64
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"")
#else
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#endif

CONST WCHAR g_wszClassName[] = L"FONT_DEMO_WINDOW";

VOID WINAPI OnClose(
    HWND hWnd
)
{
    DestroyWindow(hWnd);
}

VOID WINAPI OnCommand(
    HWND hWnd,
    INT nID,
    HWND hwSource,
    UINT uNotify
)
{
    HWND hButton = FindWindowExW(hWnd, NULL, L"Button", NULL);
    

    if (hButton == hwSource)
    {
        WCHAR *wszBuffer = NULL;
        HWND hEdit = FindWindowExW(hWnd, NULL, L"Edit", NULL);
        HWND hStatic = FindWindowExW(hWnd, NULL, L"Static", NULL);
        HANDLE hHeap = GetProcessHeap();
        INT nLen = GetWindowTextLengthW(hEdit) + 1;

        wszBuffer = (WCHAR *) HeapAlloc(hHeap, HEAP_ZERO_MEMORY, nLen * sizeof(WCHAR));
        if (NULL == wszBuffer)
        {
            MessageBoxW(NULL, L"Out of memory", L"Error", MB_OK | MB_ICONSTOP);
            ExitProcess(ERROR_OUTOFMEMORY);
        }

        GetWindowTextW(hEdit, wszBuffer, nLen);
        SetWindowTextW(hStatic, wszBuffer);

        HeapFree(hHeap, 0, wszBuffer);
        wszBuffer = NULL;
    }
}
    
BOOL WINAPI OnCreate(
    HWND hWnd,
    LPCREATESTRUCTW lpCreateStruct
)
{
    INITCOMMONCONTROLSEX iccx;
    HINSTANCE hInstance = lpCreateStruct->hInstance;
    iccx.dwICC = ICC_STANDARD_CLASSES;
    iccx.dwSize = sizeof(INITCOMMONCONTROLSEX);

    InitCommonControlsEx(&iccx);
    CreateWindowW(L"Button", L"Click Me", WS_VISIBLE | WS_CHILD, 205, 210, 90, 30, hWnd, NULL, hInstance, NULL);
    CreateWindowW(L"Static", L"", WS_VISIBLE | WS_CHILD, 10, 100, 490, 20, hWnd, NULL, hInstance, NULL);
    CreateWindowW(L"Edit", L"Type something in here", WS_VISIBLE | WS_CHILD, 10, 10, 470, 20, hWnd, NULL, hInstance, NULL);

    return TRUE;
}

VOID WINAPI OnDestroy(
    HWND hWnd
)
{
    PostQuitMessage(ERROR_SUCCESS);
}

VOID WINAPI OnPaint(
    HWND hWnd
)
{
    PAINTSTRUCT ps;

    BeginPaint(hWnd, &ps);
    EndPaint(hWnd, &ps);
}



LRESULT CALLBACK WindowProc(
    HWND hWnd,
    UINT Msg,
    WPARAM wParam,
    LPARAM lParam
)
{
    switch (Msg)
    {
        HANDLE_MSG(hWnd, WM_CLOSE, OnClose);
        HANDLE_MSG(hWnd, WM_CREATE, OnCreate);
        HANDLE_MSG(hWnd, WM_COMMAND, OnCommand);
        HANDLE_MSG(hWnd, WM_DESTROY, OnDestroy);
        HANDLE_MSG(hWnd, WM_PAINT, OnPaint);
    default:
        return DefWindowProcW(hWnd, Msg, wParam, lParam);
    }

    return 0;
}

ATOM WINAPI RegisterWCEX(
    HINSTANCE hInstance
)
{
    WNDCLASSEXW wcex;
    ZeroMemory(&wcex, sizeof(WNDCLASSEXW));

    wcex.cbSize = sizeof(WNDCLASSEXW);
    wcex.hInstance = hInstance;
    wcex.lpszClassName = g_wszClassName;
    wcex.hbrBackground = (HBRUSH) COLOR_WINDOW;
    wcex.hCursor = LoadCursorW(NULL, IDC_ARROW);
    wcex.hIcon = wcex.hIconSm = LoadIconW(NULL, IDI_APPLICATION);
    wcex.lpfnWndProc = WindowProc;

    return RegisterClassExW(&wcex);
}

INT APIENTRY wWinMain(
    _In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR lpCmdLine,
    _In_ INT nShowCmd
)
{
    HWND hWnd;
    MSG Msg;

    if (RegisterWCEX(hInstance) == 0)
    {
        MessageBoxW(NULL, L"Window registration failed", L"Error", MB_OK | MB_ICONSTOP);
        return -1;
    }

    hWnd = CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, g_wszClassName, L"Windows Font Demo", WS_VISIBLE | WS_SYSMENU, 100, 100, 500, 350, NULL, NULL, hInstance, NULL);
    if (NULL == hWnd)
    {
        MessageBoxW(NULL, L"Window creation failed", L"Error", MB_OK | MB_ICONSTOP);
        return -1;
    }

    ShowWindow(hWnd, nShowCmd);
    UpdateWindow(hWnd);

    while (GetMessageW(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessageW(&Msg);
    }

    return Msg.wParam;
}

Will give us the following window:

Ugly window with unset control fonts

I therefore make the following addition to any GUI project I code (key changes: the EnumChildProc function, the SystemsParameterInfoW and EnumChildWindows calls in wWinMain):

#include <Windows.h>
#include <windowsx.h>
#include <CommCtrl.h>
#include <strsafe.h>
#include <sal.h>

#pragma comment(lib, "comctl32.lib")

#if defined _M_IX86
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")
#elif defined _M_IA64
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"")
#elif defined _M_X64
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"")
#else
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#endif

CONST WCHAR g_wszClassName[] = L"FONT_DEMO_WINDOW";

BOOL CALLBACK EnumChildProc(
    HWND hWnd,
    LPARAM lParam
)
{
    HFONT hfDefault = *(HFONT *) lParam;
    SendMessageW(hWnd, WM_SETFONT, (WPARAM) hfDefault, MAKELPARAM(TRUE, 0));
    return TRUE;
}

VOID WINAPI OnClose(
    HWND hWnd
)
{
    DestroyWindow(hWnd);
}

VOID WINAPI OnCommand(
    HWND hWnd,
    INT nID,
    HWND hwSource,
    UINT uNotify
)
{
    HWND hButton = FindWindowExW(hWnd, NULL, L"Button", NULL);
    

    if (hButton == hwSource)
    {
        WCHAR *wszBuffer = NULL;
        HWND hEdit = FindWindowExW(hWnd, NULL, L"Edit", NULL);
        HWND hStatic = FindWindowExW(hWnd, NULL, L"Static", NULL);
        HANDLE hHeap = GetProcessHeap();
        INT nLen = GetWindowTextLengthW(hEdit) + 1;

        wszBuffer = (WCHAR *) HeapAlloc(hHeap, HEAP_ZERO_MEMORY, nLen * sizeof(WCHAR));
        if (NULL == wszBuffer)
        {
            MessageBoxW(NULL, L"Out of memory", L"Error", MB_OK | MB_ICONSTOP);
            ExitProcess(ERROR_OUTOFMEMORY);
        }

        GetWindowTextW(hEdit, wszBuffer, nLen);
        SetWindowTextW(hStatic, wszBuffer);

        HeapFree(hHeap, 0, wszBuffer);
        wszBuffer = NULL;
    }
}
    
BOOL WINAPI OnCreate(
    HWND hWnd,
    LPCREATESTRUCTW lpCreateStruct
)
{
    INITCOMMONCONTROLSEX iccx;
    HINSTANCE hInstance = lpCreateStruct->hInstance;
    iccx.dwICC = ICC_STANDARD_CLASSES;
    iccx.dwSize = sizeof(INITCOMMONCONTROLSEX);

    InitCommonControlsEx(&iccx);
    CreateWindowW(L"Button", L"Click Me", WS_VISIBLE | WS_CHILD, 205, 210, 90, 30, hWnd, NULL, hInstance, NULL);
    CreateWindowW(L"Static", L"", WS_VISIBLE | WS_CHILD, 10, 100, 490, 20, hWnd, NULL, hInstance, NULL);
    CreateWindowW(L"Edit", L"Type something in here", WS_VISIBLE | WS_CHILD, 10, 10, 470, 20, hWnd, NULL, hInstance, NULL);

    return TRUE;
}

VOID WINAPI OnDestroy(
    HWND hWnd
)
{
    PostQuitMessage(ERROR_SUCCESS);
}

VOID WINAPI OnPaint(
     HWND hWnd
)
{
    PAINTSTRUCT ps;

    BeginPaint(hWnd, &ps);
    EndPaint(hWnd, &ps);
}



LRESULT CALLBACK WindowProc(
    HWND hWnd,
    UINT Msg,
    WPARAM wParam,
    LPARAM lParam
)
{
    switch (Msg)
    {
        HANDLE_MSG(hWnd, WM_CLOSE, OnClose);
        HANDLE_MSG(hWnd, WM_CREATE, OnCreate);
        HANDLE_MSG(hWnd, WM_COMMAND, OnCommand);
        HANDLE_MSG(hWnd, WM_DESTROY, OnDestroy);
        HANDLE_MSG(hWnd, WM_PAINT, OnPaint);
    default:
        return DefWindowProcW(hWnd, Msg, wParam, lParam);
    }

    return 0;
}

ATOM WINAPI RegisterWCEX(
     HINSTANCE hInstance
)
{
    WNDCLASSEXW wcex;
    ZeroMemory(&wcex, sizeof(WNDCLASSEXW));

    wcex.cbSize = sizeof(WNDCLASSEXW);
    wcex.hInstance = hInstance;
    wcex.lpszClassName = g_wszClassName;
    wcex.hbrBackground = (HBRUSH) COLOR_WINDOW;
    wcex.hCursor = LoadCursorW(NULL, IDC_ARROW);
    wcex.hIcon = wcex.hIconSm = LoadIconW(NULL, IDI_APPLICATION);
    wcex.lpfnWndProc = WindowProc;

    return RegisterClassExW(&wcex);
}

INT APIENTRY wWinMain(
    _In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR lpCmdLine,
    _In_ INT nShowCmd
)
{
    HWND hWnd;
    MSG Msg;
    NONCLIENTMETRICSW ncm;
    HFONT hfDefault;

    ZeroMemory(&ncm, sizeof(NONCLIENTMETRICSW));
    ncm.cbSize = sizeof(NONCLIENTMETRICSW);

    SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICSW), &ncm, FALSE);
    hfDefault = CreateFontIndirectW(&ncm.lfMessageFont);

    if (RegisterWCEX(hInstance) == 0)
    {
        MessageBoxW(NULL, L"Window registration failed", L"Error", MB_OK | MB_ICONSTOP);
        return -1;
    }

    hWnd = CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, g_wszClassName, L"Windows Font Demo", WS_VISIBLE | WS_SYSMENU, 100, 100, 500, 350, NULL, NULL, hInstance, NULL);
    if (NULL == hWnd)
    {
        MessageBoxW(NULL, L"Window creation failed", L"Error", MB_OK | MB_ICONSTOP);
        return -1;
    }

    ShowWindow(hWnd, nShowCmd);
    EnumChildWindows(hWnd, EnumChildProc, (LPARAM) &hfDefault);
    UpdateWindow(hWnd);

    while (GetMessageW(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessageW(&Msg);
    }

    return Msg.wParam;
}

Which gives us the much more aesthetically pleasing window:

Properly set fonts now

Is this an advisable, efficient, and correct solution to this problem?

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Came over from the SO question because admittedly I never look in here.

Firstly, it's not DPI aware. This is fine if you didn't intend it to be.

Not applicable here, but if it were a dialog, MSDN suggests doing it in WM_INITDIALOG.

What you programmed is what needs to happen in the end to change the font (as in it would be free of bugs as far as I can tell), but I would note that the way you programmed it increases the API surface area of the window class.

I'm not exactly sure what kind of answer you're looking for. If you want to know whether this correct and efficient in the context of the application that you posted, then yes, and I have nothing to add.

Otherwise, under specific circumstances (which might totally not be relevant to you), this approach might not scale well with growing complexity and use cases.

This depends a little on how you view things. Either you believe that the window's font is inherent to the window (that's most windows I would say), or you believe that the window is completely agnostic to which font is used, and expects the application to set it.

In the former case, there is no reason why it would be necessary for the application code (i.e. what you've written in wWinMain) to bother with the font - it could happen "automatically". This can for example be done by putting it in WM_CREATE. This is closer to what you would expect from a typical dialog created from a typical dialog template.

In the latter case, it would be possible to reduce the API surface to a single action that the application must take in addition to creating the window. This could be implemented in many different ways, such as implementing the child window enumeration in WM_SETFONT recursively. This is closer to having a reusable child control that integrates well with existing windows.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.