Why does GetSystemMetrics() return these values? - c++

I am having some issues creating a client area of a set size. AdjustWindowRect() won't work properly so I decided to try manually calculating the width and height of the window.
That didn't work either and I wondered why so I checked up the values I used to take in account the borders etc.
#include <iostream>
#include <Windows.h>
int main(void)
{
std::cout << "GetSystemMetrics(SM_CYEDGE) = " << GetSystemMetrics(SM_CYEDGE) << std::endl;
std::cout << "GetSystemMetrics(SM_CXEDGE) = " << GetSystemMetrics(SM_CXEDGE) << std::endl;
std::cout << "GetSystemMetrics(SM_CYBORDER) = " << GetSystemMetrics(SM_CYBORDER) << std::endl;
std::cout << "GetSystemMetrics(SM_CXBORDER) = " << GetSystemMetrics(SM_CXBORDER) << std::endl;
std::cout << "GetSystemMetrics(SM_CYCAPTION) = " << GetSystemMetrics(SM_CYCAPTION);
std::cin.get();
}
This gives me:
GetSystemMetrics(SM_CYEDGE) = 2
GetSystemMetrics(SM_CXEDGE) = 2
GetSystemMetrics(SM_CYBORDER) = 1
GetSystemMetrics(SM_CXBORDER) = 1
GetSystemMetrics(SM_CYCAPTION) = 22
I am PRETTY sure that the borders of my window aren't that thin. What am I doing wrong?
EDIT 1:
Initially my window used the WS_OVERLAPPED style. Since the AdjustWindowRect does not allow that style to be used alongside it I constructed the same type of window I wanted with: (WS_BORDER | WS_CAPTION | WS_SYSMENU). This is the same style I use during the call to AdjustWindowRect and AdjustWindowRectEx(this one with NULL as extended style since I do not use any). This gives me the correct width but the height is missing a few pixels.
RECT rect = { 0, 0, 800, 600};
AdjustWindowRectEx( &rect, (WS_BORDER | WS_CAPTION | WS_SYSMENU), FALSE, NULL);
CreateWindowEx( ..., rect.right - rect.left, rect.bottom - rect.top, ...);
This gives me 800 pixels wide client-area but only 582 pixels in height.
EDIT 2:
CURIOUS, I used GetClientRect(); and it gave me that the width is 800 and the height IS 600. How come it doesn't display properly?
It seems that when I painted the whole window it all measured up. The reason? I don't know.
Maybe someone else can shed some light over this.

The first issue is that you use the wrong metric. You'll need to use SM_CXSIZEFRAME to get the width of a resizable border.
The second issue is that Windows won't give you the correct value. The fat borders of a window on Aero are a serious appcompat problem. Windows intentionally lies about the window rectangle and border size. Required to allow old programs to still work correctly, they specify the size of the window in the CreateWindow() call. But that's the size of the frame, including the borders. Without the lie, the window would end up with a client area that's too small.
To turn off the lie, you have to tell Windows that you know about the Aero behavior and don't need to be lied to. Project + Properties, Linker, Command Line, Additional options box and add:
/SUBSYSTEM:CONSOLE,6.0
Version 6.0 is the Vista version number, the first version of Windows that had Aero. Beware that your program won't run on XP anymore when you do this.

Have you tried AdjustWindowRectEx() instead of AdjustWindowRect() ?
As long as you are passing the correct styles (and extended styles) there's no reason I know of for that not to work properly.

Related

Setting new console window size (in characters) Windows API

I'm trying to set the size of the Windows terminal based on characters per row and the number of columns, not pixels. I'm a bit lost, but I figured out a few things so far.
To get the current number of dimensions of the console window in the units I want, I can do:
const auto hout = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo(hout, &csbi);
// Bind x and y
const auto& [wx, wy] = csbi.dwMaximumWindowSize;
std::cout << " { " << wx << ", " << wy << " } " << std::endl;
Which, for me, outputs { 120, 56 }. This means 120 chars/row and 56 visible rows.
My problem seems is that, to change this, I seem to have to convert these units to pixels and completely redraw the window. I know how to do this as well, assuming I knew the size I wanted for the window in terms of pixels:
HWND console = GetConsoleWindow();
RECT console_rect;
GetWindowRect(console, &console_rect);
MoveWindow(console, console_rect.left, console_rect.top, /* nWidth */, /* nHeight */, TRUE);
So, I need 1 of 2 solutions.
Is there a way to skip the conversion and change the size of the window in terms of chars/row and number of visible rows? If not,
How do I accurately convert from my preferred format to pixels? I'm assuming I have to take font sizes into account, which I'm also not completely sure how to do.
Thanks in advance for anyone who's willing to help! I'm using C++20 and the Windows API.

Why is QFontMetrics::lineSpacing() smaller than the height of the characters' bounding boxes?

This happens on Linux.
The problem shows when executing this code (I'm drawing in the paint event of a widget):
painter.drawText(0, 0, 1000, 1000, 0, QString("0"), &charBoundingBox);
qDebug() << "bounding box height" << charBoundingBox.height();
qDebug() << "lineSpacing" << painter.fontMetrics().lineSpacing();
qDebug() << "leading" << painter.fontMetrics().leading();
qDebug() << "height" << painter.fontMetrics().height();
qDebug() << painter.font();
It outputs:
bounding box height 11
lineSpacing 7
leading -1
height 8
QFont( "Digital-7,10,-1,5,50,0,0,0,0,0" )
Notice the height of the bounding box for the character is 11, while the line spacing is 7. This is partly because the leading is negative, but even without that, the font height is still smaller.
How does Qt calculate the character bounding box it returns? Its height obviously is not derived from fontMetrics::height and fontMetrics::leading.
There's no guarantee that the height of the bounding box returned by the painter will be the same as the font height. Also, the line spacing is the sum of the height and leading of the font. If the leading for a font is negative, the line spacing will be smaller than the height.
Furthermore, to make sure you're using the correct metrics, you should probably use painter.fontMetrics() instead of constructing a QFontMetrics(font).
In any event, to make sure which of the above is happening in your situation, you should print out the font leading and font height separately (using painter.fontMetrics()).

Finding screen width and height dynamically in OpenGL GLUT program

In my OpenGL program, in order to run it in fullscreen mode, I'm using the GLUT function glutGameModeString(const char *string). (where 'string' specifies the screen width,height,pixelDepth and refresh rate) To make it working in any system,i need to dynamically determine the screen width and screen height of the host system, prior to making the call to 'glutGameModeString' function.How do I do that?
I think you are wrong, there are the following constants:
GLUT_SCREEN_HEIGHT
GLUT_SCREEN_WIDTH
I've tested it, it works.
Just tested on Linux Mint 18
std::cout << "glut screen width(DEFUALT): " << GLUT_SCREEN_WIDTH << std::endl;
std::cout << "glut screen height(DEFAULT): " << GLUT_SCREEN_HEIGHT << std::endl;
std::cout << "glut screen width: " << glutGet(GLUT_SCREEN_WIDTH) << std::endl;
std::cout << "glut screen height: " << glutGet(GLUT_SCREEN_HEIGHT) << std::endl;
outputs:
glut screen width(DEFAULT): 200
glut screen height(DEFAULT): 201
glut screen width: 1366
glut screen height: 768
setting your window size to glutGet(GLUT_SCREEN_WIDTH/HEIGHT); will maximize your window, still has top bar and border.
Just calling glutFullScreen(); after initializing your window will make it full screen with no borders or anything, no need for the above stuff of getting your screen size.

Qt Resizing a QMainWindow with a Splitter

I have a QMainWindow with:
Two widgets in a horizontal splitter. "m_liner" is on the right side
Both widgets have a minimum size of say, 300 pixels.
A checkbox to hide/show the right-side widget m_liner.
I want the overall QMainWindow to expand when showing the widget, and shrink when hiding. The code below does this except:
If both widgets are shown, the minimum window size is 600 pixels.
Shrink the window to this smallest size.
Uncheck the box to hide the right-side widget.
Program hides the right-side widget.
Program calls this->resize(300, height);
The window ends up being 600 pixels wide (the minimum size with both widgets visible), instead of around 300 (the minimum size with only the left widget).
Later, I can resize the window down to 300 pixels with the mouse or another button. But it won't resize to 300 in the checkbox event, even if I call resize several times.
Does anyone have an idea how to solve this?
Critical bit of code follows, I have a full project available if you need it:
void MainWindow::on_checkBox_stateChanged(int val)
{
std::cout << "-------------------- Checkbox clicked " << val << std::endl;
bool visible = val;
QWidget * m_liner = ui->textEdit_2;
QSplitter * m_splitter = ui->splitter;
int linerWidth = m_liner->width();
if (linerWidth <= 0) linerWidth = m_lastLinerWidth;
if (linerWidth <= 0) linerWidth = m_liner->sizeHint().width();
// Account for the splitter handle
linerWidth += m_splitter->handleWidth() - 4;
std::cout << "Frame width starts at " << this->width() << std::endl;
std::cout << "Right Panel width is " << m_liner->width() << std::endl;
// this->setUpdatesEnabled(false);
if (visible && !m_liner->isVisible())
{
// Expand the window to include the Right Panel
int w = this->width() + linerWidth;
m_liner->setVisible(true);
QList<int> sizes = m_splitter->sizes();
if (sizes[1] == 0)
{
sizes[1] = linerWidth;
m_splitter->setSizes(sizes);
}
this->resize(w, this->height());
}
else if (!visible && m_liner->isVisible())
{
// Shrink the window to exclude the Right Panel
int w = this->width() - linerWidth;
std::cout << "Shrinking to " << w << std::endl;
m_lastLinerWidth = m_liner->width();
m_liner->setVisible(false);
m_splitter->setStretchFactor(1, 0);
this->resize(w, this->height());
m_splitter->resize(w, this->height());
this->update();
this->resize(w, this->height());
}
else
{
// Toggle the visibility of the liner
m_liner->setVisible(visible);
}
this->setUpdatesEnabled(true);
std::cout << "Frame width of " << this->width() << std::endl;
}
Sounds to me like there are some internal Qt events that need to get propagated before it recognizes that you can resize the main window. If this is the case then I can think of two potential solutions:
Use a queued single shot timer to call the code that resizes your window down to 300px:
m_liner->hide();
QTimer::singleShot( 0, this, SLOT(resizeTo300px()) );
or, after you hide your widget you can try a call to processEvents() (this function has potentially dangerous side effects, so use with caution):
m_liner->hide();
QApplication::processEvents();
resize( w, height() );
Another potential solution would be to set the horizontal size policy of your widget to ignored when hiding it:
m_liner->hide();
m_liner->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Preferred );
resize( w, height() );
When showing your widget again, you'd need to adjust the size policy again.

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

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't know the "official" way of calculating the size of the check box and the gap before (or after) the text.
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 ...
Short answer:
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:
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;
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.
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.
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:
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;
}
I'd like to give my 2 cents on the matter since iv spent an entire day working on an accurate solution for this problem that takes DPI awareness and fonts into consideration.
First define the checkbox's size in units.
#define CHECKBOX_INTERNAL_SIZE 12
Then i defined a function for converting units to pixels. NOTE: MulDiv may work just as good.
double dpi_MulDiv(double nNumber, double nNumerator, double nDenominator)
{
return (nNumber * nNumerator) / nDenominator;
}
Finally the function that does the magic. SEE the code comments for details.
//
// 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;
}
I added + 3 on the last line that computes the width as a way to adjust for little irregularities. Feed back on this is welcomed. Iv only tested on Windows 10 thus far with different fonts and sizes.
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;
Ok dudes my way is maybe not the fastes to use in runtime, but it works for me in any case i have tested so far.
In the beginnin of my proggys i put in a function to get the size and store it in a global variable (yeah i have heard this would be bad, but i dont care about this)
here the explanation:
Create a treeview (invisible if u want)
Create an imagelist with atleast 1 image inside (size 16x16)
Set the imagelist to the treeview ("TVSIL_NORMAL")
Get the "TVSIL_STATE" imagelist from the treeview (u have to create "TVSIL_NORMAL" before, otherwise this one will fail!)
Use ImageList_GetIconSize(..) and store the size. Wow, the checkboxs and the radio-buttons have the same size as the state icons of the treeview. Now u have what u want!
Destroy the "TVSIL_NORMAL" imagelist
Destroy the treeview
this code needs only a few microseconds at the beginning of my proggies and i can use the value everytime i need it.
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);