不使用 Graphics 对象如何测量字符串长度?

44

我正在使用像素作为我的字体单位。在某个地方,我正在执行一个命中测试,以检查用户是否在屏幕上某些文本的边界矩形内单击了。我需要使用类似于MeasureString的东西来实现这一点。不幸的是,执行命中测试的代码深入到一个库中,该库没有访问Graphics对象甚至Control的权限。

如何在不使用Graphics类的情况下获取给定字体的字符串的边界框?当我的字体以像素为单位时,为什么我甚至需要Graphics对象?

Translated result:

我正在使用像素作为字体的单位。在某处,我正在进行命中测试,以检查用户是否单击了屏幕上某些文本的边界矩形内。为此,我需要使用类似于MeasureString的东西。不幸的是,执行命中测试的代码深入到一个没有访问Graphics对象或者Control的库中。

我怎样才能在不使用Graphics类的情况下获得给定字体的字符串边界框呢?既然我的字体是以像素为单位的,为什么我还需要一个Graphics对象呢?


如果您没有控件,您会拥有什么?我猜想是字体和字符串,但是还有其他的吗? - micahtan
我的库类似于场景图。我试图避免依赖于System.Drawing和System.Windows.Forms。 - Agnel Kurian
5个回答

52
如果您引用了System.Windows.Forms,请尝试使用TextRenderer类。该类有一个静态方法(MeasureText),它接受字符串和字体,返回大小。 MSDN Link

3
TextRenderer存在一些小问题,它使用整数作为返回值,如果需要亚像素精度可能会引起问题。除此之外,它是一个不错的选择。 - Raymond Martineau
1
@jp2code - 你有没有看过我的回答?他问如何在不使用Graphics类的情况下测量字符串。我建议使用TextRenderer类上的MeasureText方法。它回答了他的问题,我甚至提供了这个类的MSDN文档链接。再看一遍。 - Jarrod
3
不知道你是否还在关注,MeaureText使用了一个名为“WindowsGraphicsCacheManager”的内部类。该管理器具有静态的“MeasurementGraphics”属性,该属性会在第一次使用时被初始化。它通过使用PInvoke到gdi32.dll并使用this constructor从设备上下文中创建Graphics来完成此操作。NerdFury的答案最终使用了this constructor,当然,Graphics没有被缓存,但除此之外,它们是相同的。 - Conrad Frix
这个功能运行得很好,甚至不需要创建对象。 - bwoogie
1
@Jarrod 嗯,我喜欢将我的表单相关代码与绘图相关代码分开,并与除 C# 之外没有依赖关系的代码分开。也许我正在将文本渲染到图像中,或者在命令行程序或服务中进行某些操作。除了 TextRenderer 在 Windows Forms 中而不是在 System.Drawing 中,没有理由需要引用 Windows Forms。 - Dave Cousineau
显示剩余7条评论

26

你不需要使用你正在渲染的图形对象来进行测量。你可以创建一个静态的工具类:

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

根据您的情况,设置位图的dpi可能是值得的。


1
看起来不错,但我认为不允许使用 new Bitmap(0, 0),也许应该用 (1,1)。 - Alex Nolasco
1
@AlexanderN 我刚刚确认了Bitmap(0, 0)是无效的。你提出的Bitmap(1, 1)的解决方案是正确的。 - Mark Bouchard
图像对象可以是静态的,以避免每次重新创建对象。 - dlopezgonzalez
10
使用 Graphics.FromHwnd(IntPtr.Zero) 来避免创建位图。 - Raj
1
请注意使用StringFormat.GenericTypographic以获取正确的大小。还要在DrawString方法中使用相同的StringFormat。请参阅MeasureStringRemarks部分这个不错的StackOverflow答案 - Andre Kampling
还要注意所使用的Graphics对象的PageUnit属性,它用于定义度量单位。这可以是毫米或像素等单位。如果以像素为单位测量,则取决于输入单位,可能会考虑Graphics对象的DPI。输入单位在Font类型的GraphicsUnit对象中定义,就像我之前提到的PageUnit一样。请参阅Graphics.PageUnitFont.Unit - Andre Kampling

11

MeasureString方法在 @NerdFury的回答中会给出比预期更高的字符串宽度。你可以在这里找到更多信息。如果你只想测量实际长度,请添加以下两行:

g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
result = 
  g.MeasureString(measuredString, font, int.MaxValue, StringFormat.GenericTypographic);

1
这个例子很好地说明了FormattedText的使用。FormattedText为Windows Presentation Foundation(WPF)应用程序中绘制文本提供了低级别控制。您可以使用它来测量具有特定字体的字符串的宽度,而不需要使用Graphics对象。
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;
}

包括 WindowsBase 和 PresentationCore 库。


0
这可能不是别人的重复,但我的问题也是不需要的图形对象。在听了上面的抱怨后,我简单地尝试了以下方法:
Size proposedSize = new Size(int.MaxValue, int.MaxValue);
TextFormatFlags flags = TextFormatFlags.NoPadding;
Size ressize = TextRenderer.MeasureText(content, cardfont, proposedSize, flags);

(其中'content'是要测量的字符串,cardfont是它所在的字体)

... 幸运的是,我可以使用这个结果来设置我的VSTO列的宽度。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接