如何计算其已知字体大小和字符的WPF TextBlock宽度?

Dev*_*per 72 c# wpf font-size

假设我有TextBlock文字"Some Text"字体大小10.0.

我怎么能算出合适的TextBlock 宽度

Ran*_*ngy 142

使用该FormattedText课程.

我在我的代码中创建了一个辅助函数:

private Size MeasureString(string candidate)
{
    var formattedText = new FormattedText(
        candidate,
        CultureInfo.CurrentCulture,
        FlowDirection.LeftToRight,
        new Typeface(this.textBlock.FontFamily, this.textBlock.FontStyle, this.textBlock.FontWeight, this.textBlock.FontStretch),
        this.textBlock.FontSize,
        Brushes.Black,
        new NumberSubstitution(),
        1);

    return new Size(formattedText.Width, formattedText.Height);
}
Run Code Online (Sandbox Code Playgroud)

它返回可在WPF布局中使用的与设备无关的像素.

  • @ArunPrasad对于一个单独的问题,这是一件很棒的事情.这个问题显然是针对WPF的. (3认同)
  • 这真的很有帮助 (2认同)

use*_*008 40

为了记录...我假设op'er正在尝试以编程方式确定textBlock在添加到可视树后将占用的宽度.IMO是一个更好的解决方案,然后formattedText(你如何处理像textWrapping?)将使用测量和排列样本TextBlock.例如

var textBlock = new TextBlock { Text = "abc abd adfdfd", TextWrapping = TextWrapping.Wrap };
// auto sized
textBlock.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
textBlock.Arrange(new Rect(textBlock.DesiredSize));

Debug.WriteLine(textBlock.ActualWidth); // prints 80.323333333333
Debug.WriteLine(textBlock.ActualHeight);// prints 15.96

// constrain the width to 16
textBlock.Measure(new Size(16, Double.PositiveInfinity));
textBlock.Arrange(new Rect(textBlock.DesiredSize));

Debug.WriteLine(textBlock.ActualWidth); // prints 14.58
Debug.WriteLine(textBlock.ActualHeight);// prints 111.72
Run Code Online (Sandbox Code Playgroud)

  • 这里的关键点是就地创建这个临时文本块。对页面上现有文本块的所有这些操作都不起作用:它的 ActualWidth 仅在重绘后更新。 (2认同)

小智 10

所提供的解决方案适用于.Net Framework 4.5,但是,在Windows 10 DPI扩展和Framework 4.6.x中添加了不同程度的支持,现在标记了用于测量文本的构造函数[Obsolete],以及该方法上的任何构造函数.不包括pixelsPerDip参数.

不幸的是,它涉及的更多,但它将通过新的扩展功能提高准确性.

PixelsPerDip

根据MSDN,这代表:

每个密度独立像素的像素值,相当于比例因子.例如,如果屏幕的DPI是120(或1.25因为120/96 = 1.25),则绘制每个密度独立像素1.25像素.DIP是WPF用于独立于设备分辨率和DPI的测量单位.

这是我基于具有DPI扩展感知的Microsoft/WPF-Samples GitHub存储库的指导实现的所选答案.

从Windows 10 Anniversary(代码下方)开始,完全支持DPI扩展需要一些额外的配置,这是我无法工作的,但如果没有它,这可以在配置了扩展的单个监视器上工作(并且考虑扩展更改).上面的repo中的Word文档是该信息的来源,因为一旦我添加了这些值,我的应用程序就无法启动. 来自同一个repo的示例代码也是一个很好的参考点.

public partial class MainWindow : Window
{
    private DpiScale m_dpiInfo;
    private readonly object m_sync = new object();

    public MainWindow()
    {
        InitializeComponent();
        Loaded += OnLoaded;
    }

    private Size MeasureString(string candidate)
    {
        DpiInfo dpiInfo;
        lock (m_dpiInfo)
            dpiInfo = m_dpiInfo;

        if (dpiInfo == null)
            throw new InvalidOperationException("Window must be loaded before calling MeasureString");

        var formattedText = new FormattedText(candidate, CultureInfo.CurrentUICulture,
                                              FlowDirection.LeftToRight,
                                              new Typeface(this.textBlock.FontFamily, 
                                                           this.textBlock.FontStyle, 
                                                           this.textBlock.FontWeight, 
                                                           this.textBlock.FontStretch),
                                              this.textBlock.FontSize,
                                              Brushes.Black, 
                                              dpiInfo.PixelsPerDip);

        return new Size(formattedText.Width, formattedText.Height);
    }

// ... The Rest of Your Class ...

    /*
     * Event Handlers to get initial DPI information and to set new DPI information
     * when the window moves to a new display or DPI settings get changed
     */
    private void OnLoaded(object sender, RoutedEventArgs e)
    {            
        lock (m_sync)
            m_dpiInfo = VisualTreeHelper.GetDpi(this);
    }

    protected override void OnDpiChanged(DpiScale oldDpiScaleInfo, DpiScale newDpiScaleInfo)
    {
        lock (m_sync)
            m_dpiInfo = newDpiScaleInfo;

        // Probably also a good place to re-draw things that need to scale
    }
}
Run Code Online (Sandbox Code Playgroud)

其他需求

根据Microsoft/WPF-Samples的文档,您需要在应用程序的清单中添加一些设置,以涵盖Windows 10 Anniversary在多显示器配置中为每个显示器设置不同DPI设置的能力.可以合理地猜测,如果没有这些设置,当窗口从一个显示器移动到另一个显示器时,可能不会引发OnDpiChanged事件,这会使您的测量继续依赖于之前的测量DpiScale.我写的应用程序对我来说是独自的,而且我没有那种设置,所以我没有什么可以测试的,当我按照指导时,我最终得到了一个由于清单而无法启动的应用程序错误,所以我放弃了,但是看一遍并调整你的app清单包含以下内容是个好主意:

<application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
        <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
        <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>
    </windowsSettings>
</application>
Run Code Online (Sandbox Code Playgroud)

根据文件:

[这些]两个标签的组合具有以下效果:1)每个监视器> = Windows 10周年更新2)系统<Windows 10周年更新


Dev*_*per 5

我发现一些可以正常工作的方法...

/// <summary>
/// Get the required height and width of the specified text. Uses Glyph's
/// </summary>
public static Size MeasureText(string text, FontFamily fontFamily, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, double fontSize)
{
    Typeface typeface = new Typeface(fontFamily, fontStyle, fontWeight, fontStretch);
    GlyphTypeface glyphTypeface;

    if (!typeface.TryGetGlyphTypeface(out glyphTypeface))
    {
        return MeasureTextSize(text, fontFamily, fontStyle, fontWeight, fontStretch, fontSize);
    }

    double totalWidth = 0;
    double height = 0;

    for (int n = 0; n < text.Length; n++)
    {
        ushort glyphIndex = glyphTypeface.CharacterToGlyphMap[text[n]];

        double width = glyphTypeface.AdvanceWidths[glyphIndex] * fontSize;

        double glyphHeight = glyphTypeface.AdvanceHeights[glyphIndex] * fontSize;

        if (glyphHeight > height)
        {
            height = glyphHeight;
        }

        totalWidth += width;
    }

    return new Size(totalWidth, height);
}

/// <summary>
/// Get the required height and width of the specified text. Uses FortammedText
/// </summary>
public static Size MeasureTextSize(string text, FontFamily fontFamily, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, double fontSize)
{
    FormattedText ft = new FormattedText(text,
                                            CultureInfo.CurrentCulture,
                                            FlowDirection.LeftToRight,
                                            new Typeface(fontFamily, fontStyle, fontWeight, fontStretch),
                                            fontSize,
                                            Brushes.Black);
    return new Size(ft.Width, ft.Height);
}
Run Code Online (Sandbox Code Playgroud)

  • 字形宽度的总和不适用于大多数字体,因为它不能解决[字距调整](http://en.wikipedia.org/wiki/Kerning)。 (2认同)

Che*_*aoh 5

我通过在后端代码中添加到元素的绑定路径解决了这个问题:

<TextBlock x:Name="MyText" Width="{Binding Path=ActualWidth, ElementName=MyText}" />
Run Code Online (Sandbox Code Playgroud)

我发现这是一个比将上述引用(如 FormattedText)的所有开销添加到我的代码中更干净的解决方案。

之后,我能够做到这一点:

double d_width = MyText.Width;
Run Code Online (Sandbox Code Playgroud)

  • 您可以简单地执行“d_width = MyText.ActualWidth;”而无需绑定。问题是当“TextBlock”尚未出现在可视化树中时。 (4认同)