Search code examples
winapicheckboxradio-buttoncontrols

Auto-calculate width for standard WinAPI controls


I am trying to write a general-purpose function to automatically calculate the width of several standard types of WinAPI controls based on the current caption/text content. This code works very well for getting the width of the text:

HWND hWnd = GetDlgItem(GetWindowHandle(), GetControlID());
int textLen = GetWindowTextLengthW(hWnd);
std::wstring ctrlText(textLen, 0);
GetWindowTextW(hWnd, ctrlText.data(), static_cast<int>(ctrlText.size()));
HDC hdc = GetDC(hWnd);
HFONT hFont = (HFONT)SendMessage(hWnd, WM_GETFONT, 0, 0);
if (!hFont)
    hFont = (HFONT)GetStockObject(SYSTEM_FONT);
HFONT hOldFont = (HFONT)SelectObject(hdc, hFont);
SIZE textSize{};
GetTextExtentPoint32W(hdc, ctrlText.data(), static_cast<int>(ctrlText.size()), &textSize);

Although the documentation says the result in textSize.cx is "logical units", the result appears to be window horizontal pixels. If I assume that scale I am able derive what appears to be the correct number of pixels in my dpi-aware app.

The question comes with what to do about the WC_BUTTON controls that I use for radio buttons and checkboxes. Is there any proper way to calculate the additional width of the radio button or checkbox? Currently I am hard-coding an additional value per control type. But that seems like a poor choice, notwithstanding that ChatGPT recommended hard-coding it as well. I am looking for a better solution.

EDIT: Based on the code above (which now includes assigning the font), I added the following code to try to use themes as suggested:

HTHEME hTheme = OpenThemeData(hWnd, L"BUTTON");
if (hTheme)
{
    RECT rcContent{}, rcBounds{};
    HRESULT rslt = GetThemeBackgroundContentRect(hTheme, hdc, partID, contentID, &rcBounds, &rcContent);
    if (rslt != S_OK)
    {
        LONG errCode = GetLastError();
    }
    MARGINS margins{};
    rslt = GetThemeMargins(hTheme, hdc, partID, contentID, TMT_CONTENTMARGINS, NULL, &margins);
    if (rslt != S_OK)
    {
        LONG errCode = GetLastError();
    }
    CloseThemeData(hTheme);
    LONG width = (rcBounds.right - rcBounds.left) + margins.cxLeftWidth + margins.cxRightWidth;
}
SelectObject(hdc, hOldFont);
ReleaseDC(hWnd, hdc); // release hdc moved here (compared to above)

I have verified in the debugger that no error is occurring. (That is, the != S_OK paths are not being taken.) But the rectangles (and margins) returned are all zeros. Does anyone see where I have gone astray?


Solution

  • GetThemePartSize can retrieve the size data referring to Parts and States, which also is pointed out by @JonathanPotter.

    HTHEME hTheme = OpenThemeData(NULL, L"BUTTON");
    if (hTheme)
    {
        SIZE size{};
        HRESULT hr = GetThemePartSize(hTheme,NULL, BP_RADIOBUTTON, RBS_UNCHECKEDNORMAL,NULL, TS_DRAW,&size);//13*13
        hr = GetThemePartSize(hTheme,NULL, BP_CHECKBOX, CBS_UNCHECKEDNORMAL,NULL, TS_DRAW,&size);//13*13
    }