MFC复选框 - 获取准确的方块尺寸

4
这个问题已经在这里讨论过,但人们接受了一个不准确的解决方案。我正在使用带有BS_AUTOCHECKBOX标志的CButton类。有没有一种精确的方法来确定Windows/MFC上黑色边框的正方形大小(其中包含复选标记),以及该正方形与文本之间的间隙?

enter image description here

我意识到,可以通过以下方式计算间隔(scale_factor 取决于客户端当前的屏幕 DPI - 100%dpi 时,scale_factor==1;150%dpi 时,scale_factor==1.5,依此类推)。
std::ceil(3 * scale_factor)

我需要我的解决方案根据客户端的屏幕分辨率/DPI设置返回真实大小。在100% DPI下,Windows会创建一个正方形13x13像素,125% DPI下为16x16像素,但在150%和175% DPI下均为20x20像素!因此,不可能仅仅将13乘以scale_factor
这段代码
int x = GetSystemMetrics(SM_CXMENUCHECK);
int y = GetSystemMetrics(SM_CYMENUCHECK);

在100% DPI上返回15px,在125% DPI上返回19px,如果我减去

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

该函数返回2像素(在每个DPI设置上),我在100% DPI上得到了13像素,但在125% DPI上得到了17像素(应该是16),并且随着更高的DPI,误差只会变大。问题在于SM_CXMENUCHECK不代表标准的CButton复选框。
我想避免自定义绘图。有没有办法以像素精度来解决这个问题(如果没有,你提出的最佳解决方案是什么)?非常感谢您的帮助。
编辑: 为了澄清,我需要一个函数(或一些算法),它将在100%缩放时返回13px,在125%时返回16px,在150%和175%时返回20px(以及任何其他可能的缩放)。这意味着我需要复选框的真实大小(由Windows根据客户端屏幕上当前设置的分辨率绘制)。

旁边的问题:它必须是一个按钮吗?使用标准复选框也可以映射到单击事件。 - Andrew Truckle
1
这里有一些关于主题值得探究的答案:https://stackoverflow.com/questions/22689311/get-the-size-of-checkbox - Andrew Truckle
https://stackoverflow.com/a/22695333/2287576 - Andrew Truckle
我不知道如何在MFC中创建“标准复选框”,除了通过创建带有复选框标志的CButton之外。这些主题看起来很有前途,谢谢!我会尝试一下。 - mrdecompilator
您可以直接从资源编辑器中拖动复选框。 - Andrew Truckle
1
我需要从代码动态创建控件。无论如何,问题已经得到解答,感谢您的帮助。 - mrdecompilator
2个回答

2
您可以使用 GetThemePartSize 函数。
以下是一些代码:
HTHEME hTheme = ::OpenThemeData(hwnd, L"button");
SIZE szCheckBox;
HRESULT hr = GetThemePartSize(hTheme, NULL, BP_CHECKBOX, CBS_UNCHECKEDNORMAL, NULL, TS_TRUE, &szCheckBox);

编辑:

相关答案:如何确定复选框的正方形大小[MFC]


我在评论中链接了那段代码,回答他的问题。 - Andrew Truckle
@mrdecompilator 你好,这个答案解决了你的问题吗?如果你还有其他问题,请随时告诉我,并且如果它确实有帮助,请接受它。 - Zeus
1
是的,我也附上了给出间隙正确尺寸答案的链接 - https://dev59.com/D3M_5IYBdhLWcg3w8H82#59376905 - mrdecompilator

1

那么使用GetSystemMetricsForDpi怎么样?

引用一下:

检索指定的系统度量或系统配置设置,考虑到所提供的 DPI。

如果适当,此函数将返回与GetSystemMetrics相同的结果,但根据您提供的任意 DPI 进行缩放。

所以:

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

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

这些方法和其他方法都有记录(在Windows上开发高DPI桌面应用程序),作为处理DPI问题的正确方式。该文章指出:

此外,在Win32编程中,许多Win32 API没有任何DPI或显示上下文,因此它们只会返回相对于系统DPI的值。通过搜索代码查找其中一些API并将其替换为DPI感知变体可能是有用的。

甚至还展示了一个例子:

#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); 
} 

请注意,根据文档,该函数仅受支持于Windows 10操作系统。


感谢您的建议。请查看原帖的编辑。我不想指定 DPI,我想要一个函数根据客户端屏幕上当前设置的 DPI 返回复选框的真实大小。 - mrdecompilator
但这就是它的作用。这显然是微软提供的工具来完成这项工作。您有多个监视器和多个 DPI 可能性。您计算出正在使用的 DPI 值并将其传递给函数。它将返回正确的比例值。这回答了您所问的问题。 - Andrew Truckle
1
好的,那么我应该传递什么参数给这个函数,使其在150% DPI时返回20px,当DPI设置为175%时再次返回20px? 我认为这个函数对于标准复选框返回的值不正确,因为SM_CXMENUCHECK并不像我附加的屏幕截图中所示代表标准复选框。它是菜单中的勾选标记,是完全不同的元素。 - mrdecompilator
我没有测试和调试的能力。我的建议是先查看那篇微软文章,尝试使用他们用于计算 DPI 等的方法进行测试,并查看结果。如果这样做仍然无法解决问题,那么可能更多是关于 Visual Studio 的问题,您应该通过帮助菜单上的软件系统直接向微软提出查询。但请先尝试,因为他们只会要求您提供一个可以进行测试的示例项目。 - Andrew Truckle
我尝试了一个测试应用程序,似乎无论我传递什么DPI值,GetSystemMetricsForDpi仍然返回152。听起来不正确。我会向微软提问。 - Andrew Truckle
1
对我来说,这个函数至少在96、120、144 DPI下运行得很好。我已经提到了问题,这个函数以及我的原始函数都使用你提供的元素。在我们的情况下,它是SM_CXMENUCHECK,但这不是我感兴趣的元素。一定有另外的方法,不过还是谢谢你的建议。 - mrdecompilator

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接