MeasureString() doesn't return enough width - c#

I have the following code to create a Label on a PictureBox:
Label l = new Label();
l.Text = _name;
l.Size = CreateGraphics().MeasureString(_name, l.Font).ToSize();
l.BackColor = Color.White;
but the label is always dropping the last character. If I add a character to the call:
l.Size = CreateGraphics().MeasureString(_name+".", l.Font).ToSize();
it works fine, but that doesn't feel right.
There seems to be some white space just before the text in the label, but Padding is set to 0. How can I fix this the correct way?

Can't you use the AutoSize property?
MeasureString is notoriously inaccurate, though normally it returns a size bigger than you'd expect:
The MeasureString method is designed for use with individual strings and includes a small amount of extra space before and after the string to allow for overhanging glyphs. Also, the DrawString method adjusts glyph points to optimize display quality and might display a string narrower than reported by MeasureString. To obtain metrics suitable for adjacent strings in layout (for example, when implementing formatted text), use the MeasureCharacterRanges method or one of the MeasureString methods that takes a StringFormat, and pass GenericTypographic. Also, ensure the TextRenderingHint for the Graphics is AntiAlias.
http://msdn.microsoft.com/en-us/library/6xe5hazb.aspx

ToSize() truncates values of the SizeF to the next lower integer values.
So, to avoid losses you can do something like that:
l.Size = (CreateGraphics().MeasureString(_name, l.Font) + new SizeF(1, 0)).ToSize();

Is it something as simple as getting the size wrong when you declare your font compared to the UI font size?

Related

Draw a lot of single characters to a WinForms Control

I have a custom WinForms control which could be described as some kind of terminal control (like a control containing Putty). According to this, the control should display a lot of characters, each with a different color and background color (worst case).
Currently I'm using the (kind of obsolete) Graphics.MeasureString method to determine the size of a single character in my fixed-size font so I can calculate the position of a character at a specific row and column. Then I use Graphics.DrawString to draw the characters. To optimize the performance I create a BufferedGraphics and group characters by their properties to draw consecutive characters with the same color with just one DrawString call. (Because one DrawString call per character is really slow.)
Now the problem is that DrawString apparently calculates the width of a character slightly different from MeasureString. When I draw a complete line at once, the resulting text width is different from what I calculated using the width of a single character multiplied by the character count of the line. It's just one or two pixels (or maybe less), but you can see it clearly - especially because I'm using anti-alias so you can even see a difference of just half a pixel.
The following sample code draws a long string a on the form's graphics, followed by character 'B'. Then it draws just a 'B' on the position calculated by measuring a.
var f = new Form {
Width = 1200,
Height = 500,
Font = new Font("Consolas", 11, FontStyle.Regular)
};
f.Paint += delegate(object sender, PaintEventArgs e) {
var a = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
var size = e.Graphics.MeasureString(a, f.Font, new PointF(0, 0), StringFormat.GenericTypographic);
using (var black = new SolidBrush(Color.Black))
{
e.Graphics.DrawString(a + "B", f.Font, black, 0, 0, StringFormat.GenericTypographic);
e.Graphics.DrawString("B", f.Font, black, size.Width, 20, StringFormat.GenericTypographic);
}
};
f.Show();
And if you look closely, you will see that the second B is about one pixel more right - at least with my display settings (100% dpi scale, ClearType enabled). Although one pixel is not much, when you draw lines using unicode characters U+2500 through U+257F it looks pretty ugly if the characters aren't perfectly aligned.
Also I can't use the TextRenderer class because its MeasureString method returns integer values, but DrawString of course does not draw each character on a full pixel position (what would be required to align it with a position calculated using row/column and the measured integer character size).
So my question is: Is there any (efficient) method to draw strings which are perfectly aligned to their corresponding position calculated using the character size?
P.S.: MeasureString with a 200-character-string returns exactly 200 times the return value of MeasureString with a single-character-string.
I have had some similar issues with measuring the strings in Graphics. I have used SizeInPoints, a property from the class Font and multiplied it for the number of characters I have in the string... I dont't know if it helps.
If not it can be a "rounding" problem with the pixels... then I would try to scale up the font size (maybe 10 times), measure it and then divide it by 10 again when using the size to color the background.
I hope it helps.
Good luck! Regards!
I'm having same issue.
It seems that the behavior depends on the font size.
After changing font with following code, this issue didn't occur.
Font = new Font("Consolas", 20.0F, FontStyle.Regular, GraphicsUnit.Pixel)
However, with another font size as below this issue still occurs.
Font = new Font("Consolas", 20.3F, FontStyle.Regular, GraphicsUnit.Pixel)
My guess on background of this issue: MeasureString and DrawString uses different layout routine and they have different characteristic in rounding error of float number. Only with 'simple' font size they gives same results.
Also, this issue didn't occur with bitmap fonts.

Is it possible to calculate how many characters will fit in a fixed width label if font is provided in C#?

I have a fixed width (Height is not the problem) label component in C#. Now I want to calculate that approximately how many characters will fit in to that width if font size and font family is provided. I know that every character takes different pixels while rendering so it is not possible to get the exact number of characters. But, I think if we take the letter who takes more no of pixels and calculate considering that letter, we will able to get approximate number of characters that will fit into the fixed width according to font provided. So, if we consider the character 'W' as the widest one then how will I calculate the number of 'W's that will fit in particular width.
I can not use the GDI+ Graphics.MeasureString method since I want it before rendering the character.
When I use the GDI TextRenderer class
SizeF sizeOfW = TextRenderer.MeasureText("W", new Font("DejaVu Sans", 28.0F));
It returns {59.0, 44.0}, which I find is completely wrong, because if I took width of label as 80 px, according to above calculation It will have only one 'W' but it's not the case in reality.
So can anybody tell where I am going wrong?
You can use the following:
var g=Graphics.FromHwnd(label1.Handle);
int charFitted, linesFitted;
g.MeasureString(mystring, label1.Font, label1.Size, null,
out charFitted, out linesFitted);
After the execution you will have into charFitted the amout of chars that label1 can show.

Drawing text centered within a rectangle

I have code, similar to the following:
string myText = "This is a test";
Font myFont = new Font("Arial", 10);
Rectangle rect = new Rectangle(10,10,100,100);
Graphics g = e.Graphics;
g.DrawString(myText, myFont, rect.X, rect.Y);
Although this works, what I would like to do is have the text vertically and horizontally centered within the dimensions of the rectangle. In addition, if its possible, I'd like to wrap the text if its too big to fit on one line. How do I do this in C#/GDI+?
I would use one of the DrawString Overloads that takes a StringFormat You would have to check the Length of your Text using MeasureString to make sure it would fit and wrap it yourself. In looking further at the StringFormatFlags it has a NoWrap Flag implying that the default is to wrap:
from last link:
Text wrapping between lines when formatting within a rectangle is disabled. This flag is implied when a point is passed instead of a rectangle, or when the specified rectangle has a zero line length.
the simple, un-researched way to do it:
to word-wrap break your strings up into smaller sub-strings. I.e.
"this is"
"a test"
you can use the String.Split() method to split your strings up into words, and then append the words together until your string has reached a certain threshold. and then make a new string.
As for centering, you can pad your individual strings with spaces. for vertical centering, you can add some newlines to the beginning of your strings.
I'll leave the arithmetic involved to you. it shouldn't be that hard.

How to get the exact text margins used by TextRenderer

System.Windows.Forms.TextRenderer.DrawText method renders formatted text with or without left and right padding depending on the value of the flags parameter:
TextFormatFlags.NoPadding - fits the text tightly into the bounding box,
TextFormatFlags.GlyphOverhangPadding - adds some left and right margins,
TextFormatFlags.LeftAndRightPadding - adds even bigger margins.
Now, my question is how can I get the exact amount of padding (left and right) added by DrawText to the text for a given device context, string, font etc?
I've dug into .NET 4 with .NET Reflector and found that TextRenderer calculates "overhang padding" which is 1/6 of the font's height and then multiplies this value to calculate left and right margins using these coefficients:
left 1.0, right 1.5 for TextFormatFlags.GlyphOverhangPadding,
left 2.0, right 2.5 for TextFormatFlags.LeftAndRightPadding.
The resulting values are rounded up and passed to the DrawTextExA or DrawTextExW native API functions. It's difficult to recreate this process because font's height is taken not from System.Drawing.Font but from System.Windows.Forms.Internal.WindowsFont and these classes return different values for the same font. And a lot of other internal BCL classes from the System.Windows.Forms.Internal namespace are involved. Decompiling all of them and reusing their code in my app is not an option, because that would be a serious .NET implementation dependency. That's why I need to know if there is some public API in WinForms or at least which Windows functions I can use to get the values of left and right margins.
Note: I've tried to TextRenderer.MeasureText with and without padding and compare the results but that gave me only the sum of left and right margins and I need them separately.
Note 2: In case you wonder why I need this: I want to draw one string with multiple fonts/colors. That involves calling DrawText once for every uniformly formatted substring with NoPadding option (so that the text doesn't spread) but I also want to add manually normal GlyphOverhangPadding at the very beginning and very end of the whole multi-format text.
The value you need for computing left and right margins is TEXTMETRIC.tmHeight, which is possible to obtain using Win32 API.
However, I found that tmHeight is just a line height of a font in pixels, so these three approaches will give you the same value (you can use whichever you like in your code):
int apiHeight = GetTextMetrics(graphics, font).tmHeight;
int gdiHeight = TextRenderer.MeasureString(...).Height;
int gdipHeight = (int)Math.Ceiling(font.GetHeight(graphics));
To obtain left and right margins, we use the same code as TextRenderer does under the hood:
private const float ItalicPaddingFactor = 0.5f;
...
float overhangPadding = (gdiHeight / 6.0f);
//NOTE: proper margins for TextFormatFlags.LeftAndRightPadding flag
//int leftMargin = (int)Math.Ceiling(overhangPadding);
//int rightMargin = (int)Math.Ceiling(overhangPadding * (2 + ItalicPaddingFactor));
//NOTE: proper margins for TextFormatFlags.GlyphOverhangPadding flag
int leftMargin = (int)Math.Ceiling(overhangPadding);
int rightMargin = (int)Math.Ceiling(overhangPadding * (1 + ItalicPaddingFactor));
Size sizeOverhangPadding = TextRenderer.MeasureText(e.Graphics, "ABC", font, Size.Empty, TextFormatFlags.GlyphOverhangPadding);
Size sizeNoPadding = TextRenderer.MeasureText(e.Graphics, "ABC", font, Size.Empty, TextFormatFlags.NoPadding);
int overallPadding = (sizeOverhangPadding.Width - sizeNoPadding.Width);
Now you can easily check that
(leftMargin + rightMargin) == overallPadding
Just to note:
I needed to solve this problem in order to implement "Search Highlight" feature in a ListView-based control that uses GDI text rendering:
Works like a charm :)
This answer is an excerpt from here - http://www.techyv.com/questions/how-get-exact-text-margins-used-textrenderer#comment-35164
If you have ever wanted a Label or TextBox in Windows Forms that performs a little more like on the web, then you've probably figured out that there's no intuitive way to make a Label or TextBox automatically adjust its height to fit the text it contains. While it may not be intuitive, it's definitely not impossible.
In this example, I'll use a TextBox (you could just as easily use a Label) that is docked to the top of a form.To use this, add aTextBox called MyTextBox to the form, and set Dock to DockStyle.Top. Wire up the Resize event of the TextBox to this event handler.
private void MyTextBox_Resize( object sender, EventArgs e )
{
// Grab a reference to the TextBox
TextBox tb = sender as TextBox;
// Figure out how much space is used for borders, etc.
int chromeHeight = tb.Height - tb.ClientSize.Height;
// Create a proposed size that is very tall, but exact in width.
Size proposedSize = new Size( tb.ClientSize.Width, int.MaxValue );
// Measure the text using the TextRenderer
Size textSize = TextRenderer.MeasureText( tb.Text, tb.Font,
proposedSize, TextFormatFlags.TextBoxControl
| TextFormatFlags.WordBreak );
// Adjust the height to include the text height, chrome height,
// and vertical margin
tb.Height = chromeHeight + textSize.Height
+ tb.Margin.Vertical;
}
If you want to resize the a Label or TextBox that is not docked (for example, one that is in a FlowLayoutPanel or other Panel, or just placed on the form), then you can handle the Form's Resize even instead, and just modify the Control's properties directly.
This might seem (very) crude, but this is the only native implementation I can think of:
DrawText draws to an IDeviceContext, which is implemented by Graphics. Now, we can take advantage of that with the following code:
Bitmap bmp = new Bitmap(....);
Graphics graphic = Graphics.FromImage(bmp);
textRenderer.DrawText(graphic,....);
graphic.Dispose();
With the new Bitmap you can go pixel by pixel and count them by some condition.
Again, this method is very crude and wasteful, but at least it's native....
This is not tested but based on the following sources:
http://msdn.microsoft.com/en-us/library/4ftkekek.aspx
http://msdn.microsoft.com/en-us/library/system.drawing.idevicecontext.aspx
http://msdn.microsoft.com/en-us/library/system.drawing.graphics.aspx
http://www.pcreview.co.uk/forums/graphics-bitmap-t1399954.html
I've done something similar a few years ago, to highlight search results (search pattern appears in bold etc.). My implementation was in DevExpress, so the code might not be relevant. If you think it's of use I can copy it, just need to find that implementation.
In System.Windows.Forms, the class to use would be Graphics. It has a MeasureCharacterRanges() method which accepts a StringFormat (start with GenericTypographic and go from there). It is much more appropriate than TextRenderer for displaying a complete string by chaining parts with different styles, fonts or brushes.
You've gone way further than me with the actual padding measuring. DevExpress's controls gave you the text bounding rectangle to start with so that was done for me.
Here's an article by Pierre Arnaud that came up for me in Google, which touches on this area. Unfortunately the GDI+ "Gory details" link there is broken.
Cheers,
Jonno
The fix is to calculate what MeasureText is going to add:
var formatFlags FormatFlags =
TextFormatFlags.NoPadding |
TextFormatFlags.SingleLine;
int largeWidth = TextRenderer.MeasureText(
" ",
font,
new Size(int.MaxValue, int.MaxValue),
formatFlags
).Width;
int smallWidth = TextRenderer.MeasureText(
" ",
font,
new Size(int.MaxValue, int.MaxValue),
formatFlags
).Width;
int extra = smallWidth - (largeWidth - smallWidth);
We calculate the width of one space and the width of two spaces. Both have the extra width added, so we can extrapolate the extra width that is being added. The added width apparently is always the same, so subtracting extra from every width returned by MeasureText gives the expected results.

Determine Label Size based upon amount of text and font size in Winforms/C#

I'd like to know if there's a better approach to this problem. I want to resize a label (vertically) to accomodate certain amount of text. My label has a fixed width (about 60 chars wide before it must wrap), about 495 pixels. The font is also a fixed size (12points afaik), but the text is not.
What I want to do is increase the Label Height when there's a "NewLine" or the text must wrap; the idea is that the text is fully visible in the label. The AutoSize doesn't work because it will grow in width, not in height.
Of course I could count the number of NewLines and add: Newlines * LineHeight, and then -given that I manage to put 60 chars per line, just divide the number of chars and add as many LineHeight pixels as needed.
I was wondering if there was a more professional way to do it. Is my approach too "lame" ?
Thanks in advance.
How about Graphics.MeasureString, with the overload that accepts a string, the font, and the max width? This returns a SizeF, so you can round round-off the Height.
using(Graphics g = CreateGraphics()) {
SizeF size = g.MeasureString(text, lbl.Font, 495);
lbl.Height = (int) Math.Ceiling(size.Height);
lbl.Text = text;
}
System.Drawing.Graphics has a MeasureString method that you can use for this purpose. Use the overload that takes a string, a font, and an int "width" parameter; this last parameter specifies the maximum width allowed for the string - use the set width of your label for this parameter.
MeasureString returns a SizeF object. Use the Height property of this returned object to set the height of your label.
Note: to get a Graphics object for this purpose, you can call this.CreateGraphics.
Graphics.MeasureString() will probably help you.
This is also one of the only usecases for using the Control.CreateGraphics() call!
Size maxSize = new Size(495, int.MaxValue);
_label.Height = TextRenderer.MeasureText(_label.Text , _label.Font, maxSize).Height;
This "answer" is for future reference and to combat the initial assumption that AutoSize = true implies that it (a WinForms label) will never grow in height.
The following link shows the various effects of AutoSize = true with other properties such as MaximumSize. Depending upon the expected use of the question it may be appropriate to follow one of these approaches.
http://blogs.msdn.com/jfoscoding/articles/478299.aspx
I would like to propose an alternative since it's a label we are talking about and not just text rendering: GetPreferredSize. I tried Mark's answer and the problem is that it almost works: the label has some "padding" around the text and this needs to be taken into account in the width value of MeasureString. I couldn't find any apparent way to get this padding. While digging, I found this answer where the poster suggests to set the FlatStyle to System. That works, but unfortunately breaks the TextAlign which I wanted it to be MiddleLeft.
I poked into the Label code and I found out the GetPreferredSize which takes into account all the weird settings (FlatStyle, UseCompatibleTextRendering etc) and can give you the correct result for each case. So instead of g.MeasureString(text, lbl.Font, 495); you can do instead
lbl.GetPreferredSize(new Size(495, 0));
or even like this since the label size is already known:
lbl.GetPreferredSize(new Size(lbl.Width, 0));
In case you are wondering, 0 and 1 will be treated as int.MaxValue.
I don't know when GetPreferredSize was introduced, so back in 2008 when Mark wrote his answer, the above might not have been relevant. But if you still need something similar in 2021, GetPreferredSize might be a tiny bit handier -which returns a Size and not a SizeF.
I posted a user control which solves this problem in the accepted answer here:
Autoscale Font in a TextBox Control so that its as big as possible and still fits in text area bounds
The control extends RichTextBox. It has a method: ScaleFontToFit that will automatically resize the font to make all the text fit.
Neat thing is it respects the multiline property. If it's true it allows words to wrap, Otherwise it doesn't.
Well the 60 chars might be valid for your test text, but not all characters have the same width. For instance, compare
llllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
and
wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
They both have 60 characters, and yet have vastly differing widths.
Is there any downside to using the TextRenderer class to measure the string (like in Marc's response) instead of going through the work to create a Graphics object, etc?
According to this article you should use TextRenderer if you are going to use a Windows Form control for the final output. TextRenderer and Graphics.MeasureString will give different results, so use the one that matches your final mode of output.
In some cases where you must use compact framework, which does not have any override methods for MeasureString(), you might consider calculating the height by yourself.
private int YukseklikAyarla(string p, Font font, int maxWidth)
{
int iHeight = 0;
using (Graphics g = CreateGraphics())
{
var sizes = g.MeasureString(p, font); // THE ONLY METHOD WE ARE ALLOWED TO USE
iHeight = (int)Math.Round(sizes.Height);
var multiplier = (int)Math.Round((double)sizes.Width) / maxWidth; // DIVIDING THE TEXT WIDTH TO SEE HOW MANY LINES IT CAN HAS
if (multiplier > 0)
{
iHeight = (int)(iHeight * (multiplier + 1)); // WE ADD 1 HERE BECAUSE THE TEXT ALREADY HAS A LINE
}
}
return iHeight;
}
Although, its an old thread, thought it might help new visitors.
In C#, you could use control.width

Categories

Resources