【问题标题】:How to get size of check and gap in check box?如何获取复选框中的检查大小和间隙?
【发布时间】:2010-11-12 23:40:35
【问题描述】:

我有一个要精确测量的复选框,以便我可以在对话框中正确定位控件。我可以轻松测量控件上文本的大小 - 但我不知道计算复选框大小和文本之前(或之后)间隙的“官方”方法。

【问题讨论】:

    标签: c++ windows winapi checkbox


    【解决方案1】:

    我很确定复选框的宽度等于

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

    然后你可以通过减去以下来计算出里面的面积......

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

    我在我的代码中使用了它,到目前为止还没有遇到问题......

    【讨论】:

    • 对我来说似乎工作正常 :) (奇怪的是“菜单检查”并且没有常规复选框的选项?)
    • 我知道这是一个旧线程,我是通过 Google 找到的。不幸的是,这个答案并不完全正确。请参阅下面的解释...
    【解决方案2】:

    简答:

    加长版

    来自 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 个对话单元高:

    这意味着对话单元:

    • 用所选字体更改
    • 已随所选 DPI 设置更改
    • 不是方形的

    注意:任何发布到公共领域的代码。无需署名。

    【讨论】:

    • 因此,如果复选框的高度始终为 10 个单位,而间距始终为 12 个单位,那么为什么不直接使用从 GetTextExtentPoint32 获得的文本的高度,然后计算宽度那? checkboxSpacing = (12 * Height) / 10;
    • @DaedalusAlpha 因为1 horizontal dlu1 vertical dlu 的大小不同(以像素为单位)
    【解决方案3】:

    很抱歉复活了这个旧线程。我最近发现自己想知道完全相同的问题。目前,对于不同字体和字体大小,上述答案均未给出与 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 上使用各种字体和不同大小测试了结果,但我并未尝试验证它是否也适用于任何其他(旧)版本的操作系统。

    【讨论】:

    • hTheme 创建和 rcBackgroundContent 怎么样?这是一个无法编译的无法使用的代码!
    【解决方案4】:

    很遗憾,微软没有提供一种方法来确定这一点。我在同一个问题上苦苦挣扎,上面提供的答案并不完整。它的主要问题是,如果对话框窗口的字体设置为默认大小以外的字体,则该解决方案将不起作用,因为复选框将被调整大小。

    这是我解决这个问题的方法(这只是一个似乎对我有用的近似值)。该代码用于 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;
    }
    

    【讨论】:

      【解决方案5】:

      我想在这件事上给我 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 上使用不同的字体和大小进行了测试。

      【讨论】:

        【解决方案6】:

        此代码不适用于带有缩放 UI(字体大 125% 或 150%)的 Win7。唯一似乎有效的是:

        int WID = 13 * dc.GetDeviceCaps(LOGPIXELSX) / 96; 
        int HEI = 13 * dc.GetDeviceCaps(LOGPIXELSY) / 96;
        

        【讨论】:

          【解决方案7】:

          好吧,伙计们,我的方式可能不是在运行时使用的速度,但它在我迄今为止测试过的任何情况下都适用。 在我的 proggys 的开头,我输入了一个函数来获取大小并将其存储在一个全局变量中(是的,我听说这会很糟糕,但我不在乎这个)

          这里是解释:

          1. 创建一个树视图(如果你想不可见)
          2. 创建一个包含至少 1 张图片的图片列表(大小为 16x16)
          3. 将图像列表设置为树视图(“TVSIL_NORMAL”)
          4. 从树视图中获取“TVSIL_STATE”图像列表(您必须先创建“TVSIL_NORMAL”,否则这个会失败!)
          5. 使用 ImageList_GetIconSize(..) 并存储大小。哇,复选框和单选按钮的大小与树视图的状态图标相同。现在你有你想要的了!
          6. 销毁“TVSIL_NORMAL”图像列表
          7. 销毁树视图

          这段代码在我的 proggies 开始时只需要几微秒,我可以在每次需要时使用该值。

          【讨论】:

            【解决方案8】:

            序言:
            在尝试确定给定文本所需的复选框控件大小时,我遇到了同样的问题,并发现现有的答案对我来说并不适用,原因如下:

            • SM_CXMENUCHECK 不考虑差距。事实上,我不相信这甚至适用于常规复选框,尽管它可能具有相同的值。它还可能取决于启用的视觉样式。
            • 其他答案过于复杂,感觉有点老套(没有不尊重的意思,是 MS 没有让这件事变得简单)。
            • 所述的 12DLU 布局非常有用,尽管在没有系统指标可依赖的情况下再次让人感觉随意。
            • 我尝试的答案仍然没有产生足够高的像素值来阻止复选框文本换行。

            我的调查:
            我查看了 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);
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2020-07-05
              • 1970-01-01
              • 1970-01-01
              • 2015-03-28
              • 2014-05-05
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多