mrdecompilator
mrdecompilator

Reputation: 165

MFC CheckBox - retrieve accurate square size

The problem has been discussed here, but people settled for an inaccurate solution. I'm using a CButton class with a BS_AUTOCHECKBOX flag. Is there a precise way to determine the size of the square with a black border (which holds the checkmark) on Windows/MFC? And also the gap between this square and text?

enter image description here

I realized, the gap can be calculated like this (scale_factor depends on the client's current screen DPI - 100%dpi, scale_factor==1; 150%dpi, scale_factor==1.5, etc.)

std::ceil(3 * scale_factor)

I need my solution to return the true size based on the client's screen resolution/DPI settings. Windows creates this square 13x13px on 100% DPI, 16x16px on 125% DPI, but 20x20px on both 150% and 175% DPI! Therefore, it's impossible to just multiply 13 with the scale_factor.

This code

int x = GetSystemMetrics(SM_CXMENUCHECK);
int y = GetSystemMetrics(SM_CYMENUCHECK);

returns 15px on 100% DPI, 19px on 125% DPI, and if I subtract

int xInner = GetSystemMetrics( SM_CXEDGE );
int yInner = GetSystemMetrics( SM_CYEDGE );

which returns 2px (on every DPI setting (???) ), I get my 13px on 100% dpi, but 17px on 125% DPI (it should be 16) and the error only gets bigger with higher DPIs. The problem is that the SM_CXMENUCHECK doesn't represent the standard CButton checkbox.

I'd like to avoid custom drawing. Is there any way to do this with pixel-precision (if not, what's the best possible solution you came up with)? Many thanks in advance.

EDIT: Just to clarify, I need a function (or some algorithm) that will return 13px on 100% scaling, 16px on 125%, 20px on 150% AND 175% (and for any other possible scalings as well). That means I need a true size of the checkbox (which is drawn by Windows based on the resolution that is currently set on the client's screen).

Upvotes: 4

Views: 502

Answers (2)

Zeus
Zeus

Reputation: 3890

You can use GetThemePartSize function.

And some code :

HTHEME hTheme = ::OpenThemeData(hwnd, L"button");
SIZE szCheckBox;
HRESULT hr = GetThemePartSize(hTheme, NULL, BP_CHECKBOX, CBS_UNCHECKEDNORMAL, NULL, TS_TRUE, &szCheckBox);

Edit:

Answer related: Determine checkbox square size [MFC]

Upvotes: 2

Andrew Truckle
Andrew Truckle

Reputation: 19197

What about using GetSystemMetricsForDpi ?

To quote:

Retrieves the specified system metric or system configuration setting taking into account a provided DPI.

This function returns the same result as GetSystemMetrics but scales it according to an arbitrary DPI you provide if appropriate.

So:

int x = GetSystemMetricsForDpi(SM_CXMENUCHECK, dpi);
int y = GetSystemMetricsForDpi(SM_CYMENUCHECK, dpi);

int xInner = GetSystemMetricsForDpi(SM_CXEDGE, dpi);
int yInner = GetSystemMetricsForDpi(SM_CYEDGE, dpi);

These methods and others are documented (High DPI Desktop Application Development on Windows) as the correct way to handle DPI issues and that article states:

Also, in the case of Win32 programming, many Win32 APIs do not have any DPI or display context so they will only return values relative to the System DPI. It can be useful to grep through your code to look for some of these APIs and replace them with DPI-aware variants.

It even shows an example:

#define INITIALX_96DPI 50 
#define INITIALY_96DPI 50 
#define INITIALWIDTH_96DPI 100 
#define INITIALHEIGHT_96DPI 50 
 
 
// DPI scale the position and size of the button control 
void UpdateButtonLayoutForDpi(HWND hWnd) 
{ 
    int iDpi = GetDpiForWindow(hWnd); 
    int dpiScaledX = MulDiv(INITIALX_96DPI, iDpi, 96); 
    int dpiScaledY = MulDiv(INITIALY_96DPI, iDpi, 96); 
    int dpiScaledWidth = MulDiv(INITIALWIDTH_96DPI, iDpi, 96); 
    int dpiScaledHeight = MulDiv(INITIALHEIGHT_96DPI, iDpi, 96); 
    SetWindowPos(hWnd, hWnd, dpiScaledX, dpiScaledY, dpiScaledWidth, dpiScaledHeight, SWP_NOZORDER | SWP_NOACTIVATE); 
} 

Please note that according to the documentation this function is only supported on Windows 10.

Upvotes: 1

Related Questions