Reputation: 165
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?
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
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
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