How to get size of check and gap in check box?

前端 未结 7 1436
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-12-15 06:46

I have a check box that I want to accurately measure so I can position controls on a dialog correctly. I can easily measure the size of the text on the control - but I don\'

相关标签:
7条回答
  • 2020-12-15 06:51

    Short answer:

    enter image description here

    Long Version

    From MSDN Layout Specifications: Win32, we have the specifications of the dimensions of a checkbox.

    It is 12 dialog units from the left edge of the control to the start of the text:

    enter image description here

    And a checkbox control is 10 dialog units tall:

    Surfaces and Controls  Height (DLUs)  Width (DLUs)
    =====================  =============  ===========
    Check box              10             As wide as possible (usually to the margins) to accommodate localization requirements.
    

    First we calculate the size of a horizontal and a vertical dialog unit:

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

    Using the handy helper function:

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

    Now that we know how many pixels (checkboxSpacing) to add, we calculate the label size as normal:

    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;
    

    enter image description here

    Bonus Reading

    What's a dialog unit?

    A dialog is a unit of measure based on the user's preferred font size. A dialog unit is defined such that the average character is 4 dialog units wide by 8 dialog units high:

    This means that dialog units:

    • change with selected font
    • changed with selected DPI setting
    • are not square

    Note: Any code released into public domain. No attribution required.

    0 讨论(0)
  • 2020-12-15 06:59

    Sorry for resurrecting this old thread. I recently found myself wondering about the exact same question. Currently, none of the answers above give a result consistent with Windows 10 for different fonts and font sizes, especially in high-DPI environments.

    Instead, it seems that the correct result is obtained by

    SIZE szCheckBox;
    GetThemePartSize(hTheme, hDC, BP_CHECKBOX, CBS_UNCHECKEDNORMAL, &rcBackgroundContent, TS_TRUE, &szCheckBox);
    

    for the size of the checkbox itself. And

    SIZE szZeroCharacter;
    GetTextExtentPoint32(hDC, L"0", 1, &szZeroCharacter);
    int iGapWidth = szZeroCharacter.cx / 2;
    

    for the width of the gap. After trying a lot of different methods inspired by the posts above, I found L"0" in the dissembly of comctl32.dll. And while it looks like a joke to me (not necessarily a good one), I suspect it's a holdover from the old days when this might have been a good enough approximation of 2DLU.

    Disclaimer: While I tested the result with various fonts and different sizes on Windows 10, I have not attempted to verify that it also holds on any other (older) version of the operating system.

    0 讨论(0)
  • 2020-12-15 07:04

    This code doesn't work on Win7 with scaled UI (fonts 125% larger or 150% larger). The only thing that seems to work is:

    int WID = 13 * dc.GetDeviceCaps(LOGPIXELSX) / 96; 
    int HEI = 13 * dc.GetDeviceCaps(LOGPIXELSY) / 96;
    
    0 讨论(0)
  • 2020-12-15 07:06

    Preamble:
    I had the same question while trying to determine the needed size of the checkbox control for a given text and found that the existing answers didn't really work for me, for several reasons:

    • SM_CXMENUCHECK doesn't account for the gap. In fact, I'm not convinced this is even for regular checkboxes, although it may have the same value. It may also be dependent on visual styles being enabled.
    • The other answers were overly complicated and felt a bit hacky (no disrespect intended, it is MS that don't make this easy).
    • The stated 12DLU layout was very helpful, although again feels arbitrary without a system metric to rely on.
    • The answers I tried still didn't yield a high enough pixel value to stop the checkbox text from wrapping.

    My investigation:
    I looked at how Wine reproduces the behavior and found that it also gives the same results as simply assuming 12DLU. However, the text still wrapped unless I added an extra 3 pixels to the width (even though the text should fit fine without). I also noticed that GetTextExtentPoint32 yields a value of 3 for an empty string (hmmm...)
    Turning off the BS_MULTILINE style obviously stopped the text wrapping. My guess is that DrawTextW's word wrapping calculations are imperfect.
    At this point I decided that the simplest solution was to just add 1 extra space to GetTextExtentPoint32, so that there would definitely be enough pixels. The over-estimate of a couple of pixels was acceptable to me.

    Note that this all assumes your application is manifested as DPI aware. Otherwise I found the checkbox appeared much larger on some Windows 7 systems (not all though).

    My (mostly Wine's) solution:

    // 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);
    
    0 讨论(0)
  • 2020-12-15 07:09

    I'm pretty sure the width of the checkbox is equal to

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

    You can then work out the area inside by subtracting the following ...

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

    I use that in my code and haven't had a problem thus far ...

    0 讨论(0)
  • 2020-12-15 07:14

    It is a shame that Microsoft did not provide a way to know this for sure. I was struggling with the same question and the answer provided above is not complete. The main problem with it is that if the font of the dialog window is set to something other than the default size, that solution will not work because checkboxes will be resized.

    Here's how I solved this issue (it is just an approximation that seems to have worked for me). The code is for MFC project.

    1 - Create two test controls on your form, a checkbox and a radio box:

    enter image description here

    2 - Define the following custom struct:

    struct CHECKBOX_DIMS{
        int nWidthPx;
        int nHeightPx;
        int nSpacePx;       //Space between checkbox and text
    
        CHECKBOX_DIMS()
        {
            nWidthPx = 0;
            nHeightPx = 0;
            nSpacePx = 0;
        }
    };
    

    3 - Call the following code when form initializes for each of the test controls (that will measure them and remove them so that end-users don't seem them):

    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 - Now you can easily resize any checkbox or radio box by calling this:

    //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;
    }
    
    0 讨论(0)
提交回复
热议问题