43

I am using pixels as the unit for my font. In one place, I am performing a hit test to check if the user has clicked within the bounding rectangle of some text on screen. I need to use something like MeasureString for this. Unfortunately, the code doing the hit test is deep within a library which does not have access to a Graphics object or even a Control.

How do I get the bounding box of a string given the font without using the Graphics class? Why do I even need a Graphics object when my font is in pixels?

Agnel Kurian
  • 53,593
  • 39
  • 135
  • 210
  • What do you have if you don't have the Control? I'm assuming Font and the string, but is there anything else? – micahtan Jun 16 '09 at 19:09
  • Nothing else. My library is a kind of a Scenegraph. I am trying to avoid dependencies on System.Drawing and System.Windows.Forms – Agnel Kurian Jun 17 '09 at 04:29

5 Answers5

48

If you have a reference to System.Windows.Forms, try using the TextRenderer class. There is a static method (MeasureText) which takes the string and font and returns the size. MSDN Link

Jarrod
  • 1,325
  • 2
  • 13
  • 22
  • 3
    Slight problem with TextRenderer is that it uses integers for the return value, which can cause problems if you need sub-pixel precision. Otherwise, it's a good alternative. – Raymond Martineau Sep 01 '10 at 03:45
  • How is this the solution? The example in the link shows `TextRenderer.DrawText(e.Graphics, ` – jp2code Oct 03 '12 at 19:01
  • 1
    @jp2code - Did you even read my answer? He asked how to measure a string without using the Graphics class. I suggested the MeasureText method on the TextRenderer class. It answers his question and I even provided the link to the MSDN documentation for this class. Take another look. – Jarrod Oct 05 '12 at 04:02
  • But, the [TextRenderer](http://msdn.microsoft.com/en-us/library/system.windows.forms.textrenderer.aspx)'s `DrawText` method's first parameter is **Graphics**. Am I missing something? – jp2code Oct 05 '12 at 13:31
  • Hmmm... on closer inspection of the [MeasureText](http://msdn.microsoft.com/en-us/library/y4xdbe66.aspx) example, I see that static call you talk about. I wonder if that method is a wrapper for [NerdFury](http://stackoverflow.com/users/6146/nerdfury)'s answer below. – jp2code Oct 05 '12 at 13:38
  • 3
    @jp2code don't know if you still care but MeaureText uses an internal class `WindowsGraphicsCacheManager`. The manager has a static `MeasurementGraphics` property which gets inited on its first use. It does this by creating a graphics from the device context with a PInvoke to the gdi32.dll using [this constructor](http://msdn.microsoft.com/en-us/library/windows/desktop/ms536160(v=vs.85).aspx). NerdFury's answer eventually uses [this constructor](http://msdn.microsoft.com/en-us/library/windows/desktop/ms536159(v=vs.85).aspx) and of course the Graphics isn't cached but otherwise they're the same – Conrad Frix Feb 26 '13 at 20:58
  • This works splendidly and doesnt even require an object to be created. – bwoogie Dec 19 '15 at 03:29
  • I like this except that it requires a dependency on `System.Windows.Forms` just to measure a string, which might have nothing to do with forms. – Dave Cousineau Aug 23 '18 at 21:36
  • @DaveCousineau That wasn't much of a problem in 2011 when I wrote this answer. Nearly all scenarios where this question might come up, a person would have had a reference to Forms. – Jarrod Oct 26 '18 at 19:26
  • 1
    @Jarrod well, I like to keep my forms related code separate from my drawing related code, and separate from code that has no dependencies except C#, etc. maybe I'm rendering text to an image or something in a command-line program or service or something. no reason that I should need a reference to windows forms, except that TextRenderer is there for some reason instead of in System.Drawing. – Dave Cousineau Oct 26 '18 at 23:29
  • AFAIK TextRenderer uses GDI to render the text, whereas Graphics uses GDI+. So it might not return same results. – tigrou Aug 16 '20 at 13:58
  • one problem about this though is that it calls methods from GDI+ (tigrou also said something like this, only complementing) that basically are the same that the Graphics object wraps around (if you dial it down the code it will end on "DrawTextEx" from a WindowsGraphics class (as refered here: https://referencesource.microsoft.com/#System.Windows.Forms/misc/GDI/WindowsGraphics2.cs,5f8d74ff4fafe0de) – SammuelMiranda Mar 18 '21 at 16:45
25

You don't need to use the graphics object that you are using to render to do the measuring. You could create a static utility class:

public static class GraphicsHelper
{
    public static SizeF MeasureString(string s, Font font)
    {
        SizeF result;
        using (var image = new Bitmap(1, 1))
        {
            using (var g = Graphics.FromImage(image))
            {
                result = g.MeasureString(s, font);
            }
        }

        return result;
    }
}

It might be worthwile, depending on your situation to set the dpi of the bitmap as well.

Eric J.
  • 139,555
  • 58
  • 313
  • 529
NerdFury
  • 17,581
  • 5
  • 36
  • 41
11

MeasureString method in @NerdFury answer will give a higher string width than expected.Additional info you can find here. If you want to measure only the physical length please add this two lines :

g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
result = 
  g.MeasureString(measuredString, font, int.MaxValue, StringFormat.GenericTypographic);
Community
  • 1
  • 1
Anton Putov
  • 1,881
  • 7
  • 34
  • 61
1

This example does great job of illustrating the use of FormattedText. FormattedText provides low-level control for drawing text in Windows Presentation Foundation (WPF) applications. You can use it to measure the Width of a string with a particular Font without using a Graphics object.

public static float Measure(string text, string fontFamily, float emSize)
{
    FormattedText formatted = new FormattedText(
        item, 
        CultureInfo.CurrentCulture, 
        System.Windows.FlowDirection.LeftToRight, 
        new Typeface(fontFamily), 
        emSize, 
        Brushes.Black);

    return formatted.Width;
}

Include WindowsBase and PresentationCore libraries.

Jacob Raines
  • 116
  • 4
0

This may not be but a repeat of others, but my problem was the unwanted Graphics object as well. After listening to the above frustrations I simply tried:

Size proposedSize = new Size(int.MaxValue, int.MaxValue);
TextFormatFlags flags = TextFormatFlags.NoPadding;
Size ressize = TextRenderer.MeasureText(content, cardfont, proposedSize, flags);

(where 'content' is the string to measure and cardfont the font it is in)

... and was in luck. I could use the result to set the width of my column in VSTO.