Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't TextRenderer.MeasureText work properly?

I want to measure the height of the text given a certain width of available canvas. The text that I pass in is really long and I know will wrap. To that end, I call the following:

using System.Windows.Forms;
...
string text = "Really really long text that is sure to wrap...";
Font font = new Font("Arial", 14);
Size canvas = new Size(1100, 850);
Size size = TextRenderer.MeasureText(text, font, canvas);

No matter what I pass in for canvas, it always returns 14 for size.Height.

Am I missing something simple?

like image 821
AngryHacker Avatar asked Dec 02 '11 19:12

AngryHacker


2 Answers

Please, use the TextFormatFlags measure parameter as shown below:

Size size = TextRenderer.MeasureText(text, font, canvas, TextFormatFlags.WordBreak);
like image 59
DmitryG Avatar answered Nov 12 '22 18:11

DmitryG


DimitryG's solution seems to work great, but only when there is no word big enough to fill more than an entire row. If such word exists, the width will be bigger than the proposed width. There is the flag TextFormatFlags.EndEllipsis for this case, however I didn't manage to combine the flags in a way so the output is correct (if I use TextFormatFlags.WordEllipsis | TextFormatFlags.WordBreak the width is correct, but the height is not updated when Word Ellipsis takes place, which means that the big word will be trimmed, but the height will be the same as if it's not trimmed). I also tried the flag TextFormatFlags.EndEllipsis but with no results.

So until someone makes this clear, I propose using a TextBox for word wrap, and then multiply the number of lines in the TextBox with the Font's height.

Code:

int MeasureMultilineTextHeigh(string text, Font font, int proposedWidth)
{
    // Exception handling.

    TextBox textBox = new TextBox()
    {
        Multiline = true,
        BorderStyle = BorderStyle.None,
        Width = proposedWidth,
        Font = font,
        Text = text,
    };

    int lineCount = textBox.GetLineFromCharIndex(int.MaxValue) + 1;
    int fontHeight = TextRenderer.MeasureText("X", font).Height;

    return lineCount * fontHeight;
}

However this approach has one problem: If, and only if Multiline property of a TextBox is set to true, every Font will have its own left and right padding. See this stackoverflow.com question and this social.msdn.microsoft.com question for more details. So this means that in some situations the returned value might be bigger than expected. To solve this problem you can use the SetPadding function to remove the padding (you can find the method as an answer in the first question), code:

private const int EM_SETRECT = 0xB3;

[DllImport(@"User32.dll", EntryPoint = @"SendMessage", CharSet = CharSet.Auto)]
private static extern int SendMessageRefRect(IntPtr hWnd, uint msg, int wParam, ref RECT rect);

[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
    public readonly int Left;
    public readonly int Top;
    public readonly int Right;
    public readonly int Bottom;

    private RECT(int left, int top, int right, int bottom)
    {
        Left = left;
        Top = top;
        Right = right;
        Bottom = bottom;
    }

    public RECT(Rectangle r) : this(r.Left, r.Top, r.Right, r.Bottom) { }
}

public void SetPadding(TextBox textBox, Padding padding)
{
    var rect = new Rectangle(padding.Left, padding.Top, textBox.ClientSize.Width - padding.Left - padding.Right, textBox.ClientSize.Height - padding.Top - padding.Bottom);
    RECT rc = new RECT(rect);
    SendMessageRefRect(textBox.Handle, EM_SETRECT, 0, ref rc);
}

int MeasureMultilineTextHeigh(string text, Font font, int proposedWidth)
{
    // Exception handling.

    TextBox textBox = new TextBox()
    {
        Multiline = true,
        BorderStyle = BorderStyle.None,
        Width = proposedWidth,
        Font = font,
    };
    SetPadding(textBox, Padding.Empty);
    textBox.Text = text;

    int lineCount = textBox.GetLineFromCharIndex(int.MaxValue) + 1;
    int fontHeight = TextRenderer.MeasureText("X", font).Height;

    return lineCount * fontHeight;
}

Needed using statements:

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Runtime.InteropServices;

I hope this helps. Sorry for my English, if I made any mistake.

like image 1
dCake Avatar answered Nov 12 '22 18:11

dCake