如何获取 TextRenderer 使用的确切文本边距

30

System.Windows.Forms.TextRenderer.DrawText方法根据flags参数的值呈现格式化文本,可以带有左右填充或不带有:

  • TextFormatFlags.NoPadding - 将文本紧密地适配到边界框中,
  • TextFormatFlags.GlyphOverhangPadding - 添加一些左右边距,
  • TextFormatFlags.LeftAndRightPadding - 添加更大的左右边距。

现在,我的问题是如何获取给定设备上下文、字符串、字体等添加到文本中的精确填充量(左右)

我使用 .NET Reflector 深入挖掘了 .NET 4,并发现 TextRenderer 计算了 "overhang padding",该值为字体高度的1/6倍,然后乘以以下系数来计算左右边距:

  • 对于 TextFormatFlags.GlyphOverhangPadding,左1.0,右1.5,
  • 对于 TextFormatFlags.LeftAndRightPadding,左2.0,右2.5。

计算结果四舍五入并传递给DrawTextExADrawTextExW本机API函数。很难重新创建此过程,因为字体高度不是从System.Drawing.Font获取的,而是从System.Windows.Forms.Internal.WindowsFont获取的,并且这些类为相同的字体返回不同的值。还涉及许多其他内部 BCL 类,来自System.Windows.Forms.Internal命名空间。反编译所有这些类并在应用程序中重新使用它们的代码不是一个选项,因为那将是一个严重的 .NET 实现依赖关系。这就是为什么我需要知道是否有一些公共 API 在 WinForms 中,或者至少可以使用哪些 Windows 函数来获取左右边距的值。


注意: 我已经尝试使用TextRenderer.MeasureText进行测量,包括带有和不带有填充的情况,并比较了结果,但是这仅给出了左右边距之和,而我需要它们分别。


注意2: 如果你想知道为什么我需要这个:我想用多种字体/颜色绘制一个字符串。这涉及到对均匀格式的子字符串每次调用DrawText选项为NoPadding(以使文本不扩展),但我还想在整个多格式文本的开头和结尾手动添加普通的GlyphOverhangPadding


+1 对于全面的分析赞一个 :-)。我也遇到了这个问题,你找到的左填充值解决了我的问题 - 谢谢。 - Fedearne
5个回答

5

计算左右边距所需的值为TEXTMETRIC.tmHeight,可以使用Win32 API获得。

然而,我发现tmHeight只是字体以像素为单位的行高,因此这三种方法将给你相同的值(您可以在代码中使用任何一种):

int apiHeight = GetTextMetrics(graphics, font).tmHeight;
int gdiHeight = TextRenderer.MeasureString(...).Height;
int gdipHeight = (int)Math.Ceiling(font.GetHeight(graphics));

为了获取左右边距,我们使用与TextRenderer在幕后使用的相同代码:
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);

现在您可以轻松检查这个。
(leftMargin + rightMargin) == overallPadding

请注意:

我需要解决这个问题,以便在使用GDI文本渲染的基于ListView控件中实现“搜索高亮”功能:

enter image description here

非常好用 :)


2
这个答案摘自这里 - http://www.techyv.com/questions/how-get-exact-text-margins-used-textrenderer#comment-35164 如果您曾经想要一个在Windows Forms中表现得更像网页的标签或文本框,那么您可能已经发现没有一种直观的方法可以使标签或文本框自动调整其高度以适应其包含的文本。虽然可能不直观,但绝对不是不可能。
在此示例中,我将使用一个TextBox(您也可以使用Label),该TextBox停靠在窗体的顶部。要使用此功能,请向窗体添加名为MyTextBox的TextBox,并将Dock设置为DockStyle.Top。将TextBox的Resize事件连接到此事件处理程序。
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; 
}

如果您想调整未停靠的Label或TextBox的大小(例如,在FlowLayoutPanel或其他Panel中,或仅放置在表单上),则可以处理Form的Resize事件,并直接修改控件的属性。

一个TextBox上没有Font属性。http://msdn.microsoft.com/en-us/library/system.windows.controls.textbox%28v=vs.110%29.aspx - user969153

1

这看起来可能有点粗糙,但这是我能想到的唯一的本地实现方式: DrawText 绘制在一个 IDeviceContext 上,它由 Graphics 实现。 现在,我们可以利用以下代码:

Bitmap bmp = new Bitmap(....);
Graphics graphic = Graphics.FromImage(bmp);
textRenderer.DrawText(graphic,....);
graphic.Dispose();

使用新的Bitmap,你可以逐像素地进行计数,并根据某些条件进行统计。

再次强调,这种方法非常粗糙且浪费资源,但至少它是原生的……

虽然此方法未经测试,但基于以下来源:

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


1

我几年前做过类似的事情,以突出搜索结果(搜索模式以粗体等方式显示)。我的实现是在DevExpress中进行的,所以代码可能不相关。如果您认为有用,我可以复制它,只需要找到那个实现。

System.Windows.Forms 中,要使用的类是 Graphics。它有一个 MeasureCharacterRanges() 方法,接受一个 StringFormat(从 GenericTypographic 开始)。与 TextRenderer 相比,它更适合通过链接具有不同样式、字体或笔刷的部分来显示完整字符串。

您已经比我更进一步地测量了实际填充量。DevExpress 的控件给了您文本边界矩形起点,因此这对我来说已经完成了。

这里是 Pierre Arnaud 的一篇文章,在 Google 上为我提供了这个领域的需求。不幸的是,那里的 GDI+“血腥细节”链接已经失效。

祝福您,
Jonno


0
解决方法是计算 MeasureText 将会添加的内容:
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);

我们计算一个空格和两个空格的宽度。两者都有额外的宽度添加,因此我们可以推断出正在添加的额外宽度。显然,添加的宽度始终相同,因此从MeasureText返回的每个宽度中减去extra即可得到预期结果。


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