【发布时间】:2010-11-12 23:40:35
【问题描述】:
我有一个要精确测量的复选框,以便我可以在对话框中正确定位控件。我可以轻松测量控件上文本的大小 - 但我不知道计算复选框大小和文本之前(或之后)间隙的“官方”方法。
【问题讨论】:
标签: c++ windows winapi checkbox
我有一个要精确测量的复选框,以便我可以在对话框中正确定位控件。我可以轻松测量控件上文本的大小 - 但我不知道计算复选框大小和文本之前(或之后)间隙的“官方”方法。
【问题讨论】:
标签: c++ windows winapi checkbox
我很确定复选框的宽度等于
int x = GetSystemMetrics( SM_CXMENUCHECK );
int y = GetSystemMetrics( SM_CYMENUCHECK );
然后你可以通过减去以下来计算出里面的面积......
int xInner = GetSystemMetrics( SM_CXEDGE );
int yInner = GetSystemMetrics( SM_CYEDGE );
我在我的代码中使用了它,到目前为止还没有遇到问题......
【讨论】:
简答:
来自 MSDN Layout Specifications: Win32,我们有一个复选框的尺寸规格。
从控件的左边缘到文本的开头是12个对话框单元:
一个复选框控件有 10 个对话框单元高:
Surfaces and Controls Height (DLUs) Width (DLUs)
===================== ============= ===========
Check box 10 As wide as possible (usually to the margins) to accommodate localization requirements.
首先我们计算水平和垂直对话单元的大小:
const dluCheckBoxInternalSpacing = 12; //12 horizontal dlus
const dluCheckboxHeight = 10; //10 vertical dlus
Size dialogUnits = GetAveCharSize(dc);
Integer checkboxSpacing = MulDiv(dluCheckboxSpacing, dialogUnits.Width, 4);
Integer checkboxHeight = MulDiv(dluCheckboxHeight, dialogUnits.Height, 8);
使用方便的辅助函数:
Size GetAveCharSize(HDC dc)
{
/*
How To Calculate Dialog Base Units with Non-System-Based Font
http://support.microsoft.com/kb/125681
*/
TEXTMETRIC tm;
GetTextMetrics(dc, ref tm);
String buffer = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
Size result;
GetTextExtentPoint32(dc, buffer, 52, out result);
result.Width = (result.X/26 + 1) / 2; //div uses trunc rounding; we want arithmetic rounding
result.Height = tm.tmHeight;
return result;
}
现在我们知道要添加多少像素 (checkboxSpacing),我们照常计算标签大小:
textRect = Rect(0,0,0,0);
DrawText(dc, Caption, -1, textRect, DT_CALCRECT or DT_LEFT or DT_SINGLELINE);
chkVerification.Width = checkboxSpacing+textRect.Right;
chkVerification.Height = checkboxHeight;
对话框是基于用户首选字体大小的度量单位。定义了一个对话单元,平均字符是 4 个对话单元宽 x 8 个对话单元高:
这意味着对话单元:
注意:任何发布到公共领域的代码。无需署名。
【讨论】:
GetTextExtentPoint32 获得的文本的高度,然后计算宽度那? checkboxSpacing = (12 * Height) / 10;
1 horizontal dlu 和1 vertical dlu 的大小不同(以像素为单位)
很抱歉复活了这个旧线程。我最近发现自己想知道完全相同的问题。目前,对于不同字体和字体大小,上述答案均未给出与 Windows 10 一致的结果,尤其是在高 DPI 环境中。
相反,似乎正确的结果是通过
SIZE szCheckBox;
GetThemePartSize(hTheme, hDC, BP_CHECKBOX, CBS_UNCHECKEDNORMAL, &rcBackgroundContent, TS_TRUE, &szCheckBox);
复选框本身的大小。和
SIZE szZeroCharacter;
GetTextExtentPoint32(hDC, L"0", 1, &szZeroCharacter);
int iGapWidth = szZeroCharacter.cx / 2;
为间隙的宽度。在尝试了很多受上述帖子启发的不同方法后,我在 comctl32.dll 的 dissembly 中找到了L"0"。虽然这对我来说看起来像是一个笑话(不一定是一个好笑话),但我怀疑它是过去的遗留物,当时这可能是 2DLU 的一个足够好的近似值。
免责声明:虽然我在 Windows 10 上使用各种字体和不同大小测试了结果,但我并未尝试验证它是否也适用于任何其他(旧)版本的操作系统。
【讨论】:
很遗憾,微软没有提供一种方法来确定这一点。我在同一个问题上苦苦挣扎,上面提供的答案并不完整。它的主要问题是,如果对话框窗口的字体设置为默认大小以外的字体,则该解决方案将不起作用,因为复选框将被调整大小。
这是我解决这个问题的方法(这只是一个似乎对我有用的近似值)。该代码用于 MFC 项目。
1 - 在表单上创建两个测试控件,一个复选框和一个单选框:
2 - 定义以下自定义结构:
struct CHECKBOX_DIMS{
int nWidthPx;
int nHeightPx;
int nSpacePx; //Space between checkbox and text
CHECKBOX_DIMS()
{
nWidthPx = 0;
nHeightPx = 0;
nSpacePx = 0;
}
};
3 - 在为每个测试控件初始化表单时调用以下代码(这将测量它们并删除它们,以便最终用户不会看到它们):
BOOL OnInitDialog()
{
CDialog::OnInitDialog();
//Calculate the size of a checkbox & radio box
VERIFY(GetInitialCheckBoxSize(IDC_CHECK_TEST, &dimsCheckBox, TRUE));
VERIFY(GetInitialCheckBoxSize(IDC_RADIO_TEST, &dimsRadioBox, TRUE));
//Continue with form initialization ...
}
BOOL GetInitialCheckBoxSize(UINT nCtrlID, CHECKBOX_DIMS* pOutCD, BOOL bRemoveCtrl)
{
//Must be called initially to calculate the size of a checkbox/radiobox
//'nCtrlID' = control ID to measure
//'pOutCD' = if not NULL, receives the dimensitions
//'bRemoveCtrl' = TRUE to delete control
//RETURN:
// = TRUE if success
BOOL bRes = FALSE;
//Get size of a check (not exactly what we need)
int nCheckW = GetSystemMetrics(SM_CXMENUCHECK);
int nCheckH = GetSystemMetrics(SM_CYMENUCHECK);
//3D border spacer (not exactly what we need either)
int nSpacerW = GetSystemMetrics(SM_CXEDGE);
//Get test checkbox
CButton* pChkWnd = (CButton*)GetDlgItem(nCtrlID);
ASSERT(pChkWnd);
if(pChkWnd)
{
CRect rcCheckBx;
pChkWnd->GetWindowRect(&rcCheckBx);
//We need only the height
//INFO: The reason why we can't use the width is because there's
// an arbitrary text followed by a spacer...
int h = rcCheckBx.Height();
CDC* pDc = pChkWnd->GetDC();
if(pDc)
{
//Get horizontal DPI setting
int dpiX = pDc->GetDeviceCaps(LOGPIXELSX);
//Calculate
if(pOutCD)
{
//Use height as-is
pOutCD->nHeightPx = h;
//Use height for the width
pOutCD->nWidthPx = (int)(h * ((double)nCheckW / nCheckH));
//Spacer is the hardest
//INFO: Assume twice and a half the size of 3D border &
// take into account DPI setting for the window
// (It will give some extra space, but it's better than less space.)
// (This number is purely experimental.)
// (96 is Windows DPI setting for 100% resolution setting.)
pOutCD->nSpacePx = (int)(nSpacerW * 2.5 * dpiX / 96.0);
}
//Release DC
pChkWnd->ReleaseDC(pDc);
if(bRemoveCtrl)
{
//Delete window
bRes = pChkWnd->DestroyWindow();
}
else
{
//Keep the window
bRes = TRUE;
}
}
}
return bRes;
}
4 - 现在您可以通过调用以下命令轻松调整任何复选框或单选框的大小:
//Set checkbox size & new text
VERIFY(SetCheckBoxTextAndSize(this, IDC_CHECK_ID, &dimsCheckBox, L"New text") > 0);
//Just resize radio box
VERIFY(SetCheckBoxTextAndSize(this, IDC_RADIO_ID, &dimsRadioBox, NULL) > 0);
int SetCheckBoxTextAndSize(CWnd* pParWnd, UINT nCheckBoxID, CHECKBOX_DIMS* pDims, LPCTSTR pNewText)
{
//Set size of the checkbox/radio to 'pNewText' and update its size according to its text
//'pParWnd' = parent dialog window
//'nCheckBoxID' = control ID to resize (checkbox or radio box)
//'pDims' = pointer to the struct with checkbox/radiobox dimensions
//'pNewText' = text to set, or NULL not to change the text
//RETURN:
// = New width of the control in pixels, or
// = 0 if error
int nRes = 0;
ASSERT(pParWnd);
ASSERT(pDims);
CButton* pChkWnd = (CButton*)pParWnd->GetDlgItem(nCheckBoxID);
ASSERT(pChkWnd);
if(pChkWnd)
{
CDC* pDc = pChkWnd->GetDC();
CFont* pFont = pChkWnd->GetFont();
if(pDc)
{
if(pFont)
{
//Make logfont
LOGFONT lf = {0};
if(pFont->GetLogFont(&lf))
{
//Make new font
CFont font;
if(font.CreateFontIndirect(&lf))
{
//Get font from control
CFont* pOldFont = pDc->SelectObject(&font);
//Get text to set
CString strCheck;
if(pNewText)
{
//Use new text
strCheck = pNewText;
}
else
{
//Keep old text
pChkWnd->GetWindowText(strCheck);
}
//Calculate size
RECT rc = {0, 0, 0, 0};
::DrawText(pDc->GetSafeHdc(), strCheck, strCheck.GetLength(), &rc, DT_CALCRECT | DT_NOPREFIX | DT_SINGLELINE);
//Get text width
int nTextWidth = abs(rc.right - rc.left);
//See if it's valid
if(nTextWidth > 0 ||
(nTextWidth == 0 && strCheck.GetLength() == 0))
{
//Get location of checkbox
CRect rcChk;
pChkWnd->GetWindowRect(&rcChk);
pParWnd->ScreenToClient(rcChk);
//Update its size
rcChk.right = rcChk.left + pDims->nWidthPx + pDims->nSpacePx + nTextWidth;
//Use this line if you want to change the height as well
//rcChk.bottom = rcChk.top + pDims->nHeightPx;
//Move the control
pChkWnd->MoveWindow(rcChk);
//Setting new text?
if(pNewText)
{
pChkWnd->SetWindowText(pNewText);
}
//Done
nRes = abs(rcChk.right - rcChk.left);
}
//Set font back
pDc->SelectObject(pOldFont);
}
}
}
//Release DC
pChkWnd->ReleaseDC(pDc);
}
}
return nRes;
}
【讨论】:
我想在这件事上给我 2 美分,因为 iv 花了一整天的时间为这个问题找到一个准确的解决方案,同时考虑了 DPI 意识和字体。
首先以单位定义复选框的大小。
#define CHECKBOX_INTERNAL_SIZE 12
然后我定义了一个将单位转换为像素的函数。注意:MulDiv 也可以正常工作。
double dpi_MulDiv(double nNumber, double nNumerator, double nDenominator)
{
return (nNumber * nNumerator) / nDenominator;
}
最后是具有魔力的函数。有关详细信息,请参见代码 cmets。
//
// Get the minimum size of the Checkbox.
// NOTE: The font of the control must be set before calling this function.
//
SIZE dpi_GetCheckBoxWidth(HWND hWnd, int monitorDpi)
{
HDC dc;
HFONT hFont;
HFONT oldFont;
TEXTMETRIC tm;
double checkboxSize;
double whiteSpace;
WCHAR sourceString[128];
RECT txtRect;
SIZE size;
dc = GetDC(hWnd);
// Note that GetDC returns an uninitialized DC, which has "System" (a bitmap font) as the default font; thus the need to select a font into the DC.
hFont = (HFONT)SendMessage(hWnd, WM_GETFONT, 0, 0);
oldFont = (HFONT)SelectObject(dc, hFont);
// Get the Checkbox width.
checkboxSize = round(dpi_MulDiv(CHECKBOX_INTERNAL_SIZE, monitorDpi, 96));
// Get the space between the Checkbox and text.
GetTextMetrics(dc, &tm);
whiteSpace = round((double)tm.tmAveCharWidth / 2.0f);
// Get the Text width.
txtRect = { 0, 0, 0, 0 };
if (GetWindowTextW(hWnd, sourceString, 128) != 0)
{
DrawTextW(dc, sourceString, -1, &txtRect, DT_CALCRECT | DT_LEFT | DT_SINGLELINE);
}
// Cleanup.
SelectObject(dc, oldFont);
ReleaseDC(hWnd, dc);
// Done.
size.cx = (LONG)checkboxSize + (LONG)whiteSpace + txtRect.right + 3;
size.cy = ((LONG)checkboxSize < txtRect.bottom) ? txtRect.bottom : (LONG)checkboxSize;
return size;
}
我在计算宽度的最后一行添加了+ 3,作为调整小不规则的一种方式。欢迎对此提供反馈。到目前为止,IV 仅在 Windows 10 上使用不同的字体和大小进行了测试。
【讨论】:
此代码不适用于带有缩放 UI(字体大 125% 或 150%)的 Win7。唯一似乎有效的是:
int WID = 13 * dc.GetDeviceCaps(LOGPIXELSX) / 96;
int HEI = 13 * dc.GetDeviceCaps(LOGPIXELSY) / 96;
【讨论】:
好吧,伙计们,我的方式可能不是在运行时使用的速度,但它在我迄今为止测试过的任何情况下都适用。 在我的 proggys 的开头,我输入了一个函数来获取大小并将其存储在一个全局变量中(是的,我听说这会很糟糕,但我不在乎这个)
这里是解释:
这段代码在我的 proggies 开始时只需要几微秒,我可以在每次需要时使用该值。
【讨论】:
序言:
在尝试确定给定文本所需的复选框控件大小时,我遇到了同样的问题,并发现现有的答案对我来说并不适用,原因如下:
SM_CXMENUCHECK 不考虑差距。事实上,我不相信这甚至适用于常规复选框,尽管它可能具有相同的值。它还可能取决于启用的视觉样式。我的调查:
我查看了 Wine 如何重现该行为,发现它也给出了与简单假设 12DLU 相同的结果。但是,除非我在宽度上添加了额外的 3 个像素,否则文本仍然会换行(即使没有文本应该可以很好地适应)。我还注意到,GetTextExtentPoint32 为空字符串生成值 3 (hmmm...)
关闭BS_MULTILINE 样式显然会停止文本换行。我的猜测是DrawTextW 的自动换行计算不完善。
此时我决定最简单的解决方案是在GetTextExtentPoint32 上增加 1 个额外的空间,这样肯定会有足够的像素。高估几个像素对我来说是可以接受的。
请注意,这一切都假设您的应用程序显示为 DPI 感知。否则我发现复选框在某些 Windows 7 系统上显得更大(虽然不是全部)。
我的(主要是 Wine 的)解决方案:
// This code gets the size of a piece of text and adds the size of a
// checkbox and gap. Note that this is very rough code with no error handling.
BOOL isCheckbox = TRUE;
HWND dialog = ... // Your control or dialog
HFONT font = ... // The font your control will use if it hasn't been set yet
PTCHAR text = ... // Your text
HFONT currentFont;
SIZE size;
HDC dc = GetDC(dialog);
if (!font) {
font = (HFONT)SendMessage(dialog, WM_GETFONT, 0, 0);
}
currentFont = (HFONT)SelectObject(dc, font); // NB: You should add error handling here
if (isCheckbox) {
// Or you can disable BS_MULTILINE
_tcscat(text, TEXT(" ")); // NB: This assumes text is allocated for +1 char
}
GetTextExtentPoint32(dc, text, _tcslen(text), &size); // NB: You should add error handling here
if (isCheckbox) {
int checkBoxWidth = 12 * GetDeviceCaps(dc, LOGPIXELSX ) / 96 + 1;
int checkBoxHeight = 12 * GetDeviceCaps(dc, LOGPIXELSY ) / 96 + 1;
int textOffset;
GetCharWidthW(dc, '0', '0', &textOffset);
textOffset /= 2;
size->cx += checkBoxWidth + textOffset;
if (size->cy < checkBoxHeight) {
size->cy = checkBoxHeight;
}
}
if (currentFont) {
SelectObject(dc, currentFont);
}
ReleaseDC(dialog, dc);
【讨论】: