How to get Words length and height, when drawing the images on a canvas in GDI+ - c++

I draw a string on a canvas, using GDI+ in C++.
Is there any API to get the out layer (width, height ) of a string in a certain font?
Thanks very much!
Many thanks to Windows programmer's solution.
I wrote the following code.
Bitmap bitmap(1000,1000);
Graphics graphics(&bitmap);
RectF rec;
RectF useless;
graphics.MeasureString(m_sWords, -1, m_pFont.get(), useless, &rec);
int WordWidth = rec.Width + 1;
int WordHeight Height = rec.Height + 1;
Need I use a real graphics to call MeasureString? Is there any way to get wordwidth,wordheight, without create a large Graphics Instance? I found it is resource comsuming.

Graphics::MeasureString computes an approximation.

To make the picture complete: it can simply be done with a GraphicsPath, which is better since it requires no DeviceContext:
Dim p As New GraphicsPath
Using stringFormat As New StringFormat()
stringFormat.Trimming = StringTrimming.EllipsisCharacter
stringFormat.LineAlignment = StringAlignment.Center
stringFormat.Alignment = StringAlignment.Near
p.AddString(text, font.FontFamily, font.Style, font.SizeInPoints, Point.Empty, stringFormat)
End Using
Return p.GetBounds.Size
Where text is a given string and font a given font. Returns a SizeF-Structure. I've found the results to be much more precise than Graphics.MeasureString aka GdipMeasureString-API.

Unfortunately you do need to use a Graphics object to do this.
The C# code I use (which returns a RectangleF, because I want to know both the width and the height) is as follows:
/// <summary> The text bounding box. </summary>
private static readonly RectangleF __boundingBox = new RectangleF(29, 25, 90, 40);
/// <summary>
/// Gets the width of a given string, in the given font, with the given
/// <see cref="StringFormat"/> options.
/// </summary>
/// <param name="text">The string to measure.</param>
/// <param name="font">The <see cref="Font"/> to use.</param>
/// <param name="fmt">The <see cref="StringFormat"/> to use.</param>
/// <returns> The floating-point width, in pixels. </returns>
private static RectangleF GetStringBounds(string text, Font font,
StringFormat fmt)
{
CharacterRange[] range = { new CharacterRange(0, text.Length) };
StringFormat myFormat = fmt.Clone() as StringFormat;
myFormat.SetMeasurableCharacterRanges(range);
using (Graphics g = Graphics.FromImage(
new Bitmap((int) __boundingBox.Width, (int) __boundingBox.Height)))
{
Region[] regions = g.MeasureCharacterRanges(text, font,
__boundingBox, myFormat);
return regions[0].GetBounds(g);
}
}
This will return a RectangleF of the size of the entire text string, word-wrapped as necessary, according to the bounding box specified as __boundingBox. On the plus side, the Graphics object is destroyed as soon as the using statement is complete…
As an aside, GDI+ seems to be pretty unreliable at this; I've found it to be quite buggy (see my question “Graphics.MeasureCharacterRanges giving wrong size calculations in C#.Net?”, for example). If you can use TextRenderer.DrawText from System.Windows.Forms, then do.

Related

Change the unit for setting position in TextOut, C++

I'm currently working on a printing plugin with C++, and starting working with TextOut to print the text I want. It works great, but apparently, the positions that TextOut uses as params are in pixels. Is there a way to set them to be in cm or mm? or any other?.
Well, it's pretty simple. The coordinates are not in pixels, but they are in the coordinates of your mapping mode. It just so happens that the default mapping mode of a DC is MM_TEXT which has each coordinate unit to be one pixel on the device.
Change your mapping mode using SetMapMode() to the coordinate system you prefer to use. You can also play with window extents, viewport extents, and origins to customize it however you want. You might want to look at the documentation for SetMapMode() and the MM_LOMETRIC (or MM_HIMETRIC) mapping mode.
There should be special handling implemented for printing. Basically, you need to perform conversion based on HIMETRIC units. The paper size is in HIMETRIC units.
Here is the code that will help you get started (MFC-based):
if (pDC->IsPrinting())
{
// printable area in millimeters
int nWidth = pDC->GetDeviceCaps(HORZSIZE);
int nHeight = pDC->GetDeviceCaps(VERTSIZE);
CDC ScreenDC;
ScreenDC.CreateIC(_T("DISPLAY"), NULL, NULL, NULL);
int nPixelsPerInchX = ScreenDC.GetDeviceCaps(LOGPIXELSX);
int nPixelsPerInchY = ScreenDC.GetDeviceCaps(LOGPIXELSY);
// paper size is in HIMETRIC units. we need to convert
CSize PaperSize(MulDiv(nWidth,nPixelsPerInchX*100,HIMETRIC_PER_INCH),
MulDiv(nHeight,nPixelsPerInchY*100,HIMETRIC_PER_INCH));
// now we need to calculate zoom ratio so the layer content fits on page
double fZoomX = (double)PaperSize.cx/(double)m_DocSize.cx;
double fZoomY = (double)PaperSize.cy/(double)m_DocSize.cy;
m_PrintZoom = min(fZoomX, fZoomY);
ResetViewSize(TRUE);
if (pDC->IsKindOf(RUNTIME_CLASS(CPreviewDC)))
{
pDC->SetMapMode(MM_ANISOTROPIC);
pDC->SetWindowExt(nPixelsPerInchX, nPixelsPerInchY);
pDC->SetViewportExt(pDC->GetDeviceCaps(LOGPIXELSX), pDC->GetDeviceCaps(LOGPIXELSY));
pDC->SetViewportOrg(0,0);
pDC->SetWindowOrg(0,0);
}
}

Firemonkey: Shrink text font to fit in TLabel

I am attempting to lower the font size of a TLabel if its text is to large to fit in the confines of the label. I didn't see any properties I could set on the label to achieve this, so I have tried writing my own method. My method works by using TCanvas.TextWidth to measure the width of the text in a label, and shrink the font until the width of the text fits within the width of the label.
void __fastcall ShrinkFontToFitLabel( TCanvas * Canvas, TLabel * Label )
{
float NewFontSize = Label->Font->Size;
Canvas->Font->Family = Label->Font->Family;
Canvas->Font->Size = NewFontSize;
while( Canvas->TextWidth( Label->Text ) > Label->Width && NewFontSize > MinimumFontSize )
{
NewFontSize -= FontSizeDecrement;
Canvas->Font->Size = NewFontSize;
}
Label->Font->Size = NewFontSize;
}
This works some of the time, however other times it does not shrink the font near enough. It seems as if the value I get from calling Canvas->TextWidth is a lot of times, much smaller than the number of pixels wide the label actually needs to be in order to fit the text.
Am I using Canvas->TextWidth incorrectly? Is there a better way to calculate the width of a string, or to re-size the font of a TLabel so its text fits within its demensions?
Edit:
In this case, I am passing in to my function, the TCanvas that my label is sitting in. I have tried using that TCanvas as well as Label->Canvas. Both give me the same number for text width, and both are short of the actual value in pixels needed to display the whole string.
The following code is taken from code that works in an FMX application, modified slightly to remove arrays that are being iterated through and declaring a variable locally to the function. It is being run in a TForm method. Canvas here is the Form's Canvas. You can see that I'm using "- 35" at one point - this might be because the numbers weren't quite right.
double InitialFontSize = 30;
Canvas->Font->Size = InitialFontSize;
StoryHeadlineLabel->Font->Size = InitialFontSize;
bool fits = false;
do
{
double widthA = Canvas->TextWidth (StoryHeadlineLabel->Text);
if (widthA > StoryHeadlineLabel->Width - 35)
{
StoryHeadlineLabel->Font->Size --;
Canvas->Font->Size --;
}
else
fits = true;
if (StoryHeadlineLabel->Font->Size < 6)
fits = true;
} while (!fits);

How to get text width (DirectX, C++)

I'm working on a GUI-Project with d3d9 & d3dx9. I create fonts using the D3DXCreateFont function (C++). Everything is working fine. But I need a function that determines the width in pixel for a specific text.
Something like this:
char text[64] = "Heyho - Blablabla";
GUIFont* hFont = gui->fonts->CreateFont("DefaultFont", "Arial", 18);
int width = hFont->GetTextWidth(text);
[...]
The part with gui->fonts->CreateFont is all working fine. It's my way of creating and storing fonts. Ignore that part, it's all about the GetTextWidth. My CreateFont function initalizes the GUIFont object. The actual D3D-Font is stored in a LPD3DXFONT.
I really hope you can help me with this one, I am pretty sure it's possible - I just don't know how. Thanks for reading, and I hope you have a clue.
You can use the DT_CALCRECT flag on the ID3DXFont function DrawText to return the required size of the rectangle enclosing the text.
So, if you want to get just the width, you might have a function something like this:
int GetTextWidth(const char *szText, LPD3DXFont pFont)
{
RECT rcRect = {0,0,0,0};
if (pFont)
{
// calculate required rect
pFont->DrawText(NULL, szText, strlen(szText), &rcRect, DT_CALCRECT,
D3DCOLOR_XRGB(0, 0, 0));
}
// return width
return rcRect.right - rcRect.left;
}
Obviously, you can also extract the height too if you need it.

Calculate QGraphicsTextItem font size based on scale

I have QGraphicsTextItem objects on a QGraphicsScene. The user can scale the QGraphicsTextItem objects by dragging the corners. (I am using a custom "transformation editor" to do this.) The user can also change the size of the QGraphicsTextItem by changing the font size from a property panel. What I would like to do is unify these so that when the user scales the object by dragging the corner with the mouse, behind the scenes it actually is calculating "What size font is necessary to make the resulting object fit the target size and keep the scale factor at 1.0?"
What I am doing now is letting the object scale as normal using QGraphicsItem::mouseMoveEvent and then triggering a FinalizeMapScale method in QGraphicsItem::mouseReleaseEvent once the mouse scale is complete. This method should then change the font to the appropriate size and set the scale back to 1.0.
I have a solution that appears to be working, but I'm not crazy about it. I'm relatively new to both Qt and C++, so would appreciate any comments or corrections.
Is there a better way to architect this whole thing?
Are there Qt methods that already do this?
Is my method on the right track but has some Qt or C++ errors?
Feel free to comment on my answer below on submit your own preferred solution. Thanks!
[EDIT] As requested in comment, here is the basics of the scaling code. We actually went a different direction with this, so this code (and the code below) is no longer being used. This code is in the mouseMoveEvent method, having previously set a "scaling_" flag to true in mousePressEvent if the mouse was clicked in the bottom-right "hot spot". Note that this code is in a decorator QGraphicsItem that holds a pointer to the target it is scaling. This abstraction was necessary for our project, but is probably overkill for most uses.
void TransformDecorator::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
...
if (scaling_) {
QGraphicsItem *target_item = target_->AsQGraphicsItem();
target_item->setTransformOriginPoint(0.0, 0.0);
QPointF origin_scene = mapToScene(target_item->transformOriginPoint());
QPointF scale_position_scene = mapToScene(event->pos());
qreal unscaled_width = target_item->boundingRect().width();
qreal scale_x = (scale_position_scene.x() - origin_scene.x()) / unscaled_width;
if (scale_x * unscaled_width < kMinimumSize) {
scale_x = kMinimumSize / unscaled_width;
}
target_item->setScale(scale_x);
} else {
QGraphicsObject::mouseMoveEvent(event);
}
}
Please no holy wars about the loop-with-exit construct. We're comfortable with it.
void MapTextElement::FinalizeMapScale() {
// scene_document_width is the width of the text document as it appears in
// the scene after scaling. After we are finished with this method, we want
// the document to be as close as possible to this width with a scale of 1.0.
qreal scene_document_width = document()->size().width() * scale();
QString text = toPlainText();
// Once the difference between scene_document_width and the calculated width
// is below this value, we accept the new font size.
const qreal acceptable_delta = 1.0;
// If the difference between scene_document_width and the calculated width is
// more than this value, we guess at the new font size by calculating a new
// scale factor. Once it is beneath this value, we creep up (or down) by tiny
// increments. Without this, we would sometimes incur long "back and forth"
// loops when using the scale factor.
const qreal creep_delta = 8.0;
const qreal creep_increment = 0.1;
QScopedPointer<QTextDocument> test_document(document()->clone());
QFont new_font = this->font();
qreal delta = 0.0;
// To prevent infinite loops, we store the font size values that we try.
// Because of the unpredictable (at least to me) relationship between font
// point size and rendering size, this was the only way I could get it to
// work reliably.
QList<qreal> attempted_font_sizes;
while (true) {
test_document->setDefaultFont(new_font);
delta = scene_document_width - test_document->size().width();
if (std::abs(delta) <= acceptable_delta ||
attempted_font_sizes.contains(new_font.pointSizeF())) {
break;
}
attempted_font_sizes.append(new_font.pointSizeF());
qreal new_font_size = 0.0;
if (std::abs(delta) <= creep_delta) {
new_font_size = delta > 0.0 ? new_font.pointSizeF() + creep_increment
: new_font.pointSizeF() - creep_increment;
} else {
new_font_size = new_font.pointSizeF()
* scene_document_width
/ test_document->size().width();
}
new_font.setPointSizeF(new_font_size);
}
this->setFont(new_font);
this->setScale(1.0);
}
Another way to look at the problem is: Qt has scaled the font, what is the effective font size (as it appears to the user, not the font size set in the text item) that I need to display to the user as their choice of new font size? This is just an alternative, you still need a calculation similar to yours.
I have a similar problem. I have a text item that I want to be unit size (one pixel size) like my other unit graphic items (and then the user can scale them.) What font (setPointSize) needs to be set? (Also what setTextWidth and what setDocumentMargin?) The advantage of this design is that you don't need to treat the scaling of text items different than the scaling of any other shape of graphics item. (But I don't have it working yet.)
Also, a user interface issue: if the user changes the font size, does the item change size? Or does it stay the same size and the text wrap differently, leaving more or less blank space at the end of the text? When the user appends new text, does the font size change so all the text fits in the size of the shape, or does the shape size grow to accommodate more text? In other words, is it more like a flowchart app (where the shape size is fixed and the font shrinks), or like a word processor app (where the font size is constant and the shape (number of pages) grows?

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