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


Answers:


152

使用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);
}

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


这真的很有帮助
Rakesh 2015年

1
如何在UWP中做同样的事情
Arun Prasad

6
@ArunPrasad在一个单独的问题上问这将是一件很了不起的事情。这个问题显然是WPF的范围。
RandomEngy

如果传入的候选对象是空格,则返回零宽度。我认为这可能与自动换行有关?
Mark Miller

42

作为记录...我假设操作者正在尝试以编程方式确定将textBlock添加到可视树后将占用的宽度。IMO比formattedText(如何处理textWrapping等更好的解决方案)是在示例TextBlock上使用Measure和Arrange。例如

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

1
经过一些小的修改,这对我编写Windows 8.1 Store应用程序(Windows运行时)很有用。不要忘记为此TextBlock设置字体信息,以便它准确计算宽度。整洁的解决方案,值得推荐。
David Rector

1
这里的关键是就地创建此临时文本块。页面上所有现有文本块的所有这些操作均不起作用:仅在重绘后才会更新其ActualWidth。
Tertium

13

所提供的解决方案适用于.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的度量单位。

这是我根据Microsoft / WPF-Samples GitHub存储库中具有DPI缩放感知的指导对所选答案的实现。

从Windows 10 Anniversary开始,还需要一些其他配置来完全支持DPI缩放(在代码下方),我无法使用它,但是如果没有它,它就不能在配置了缩放(并遵守缩放更改)的单个监视器上工作。上述回购中的Word文档就是该信息的来源,因为一旦添加这些值,我的应用程序将无法启动。 来自同一仓库的此示例代码也可以作为参考。

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)
    {
        DpiScale 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
    }
}

其他需求

根据Microsoft / WPF-Samples上的文档,您需要在应用程序清单中添加一些设置,以涵盖Windows 10 Anniversary在多显示器配置中每个显示器具有不同DPI设置的能力。可以合理地猜想,如果没有这些设置,则当窗口从一个显示切换到另一个具有不同设置的显示时,可能不会引发OnDpiChanged事件,这将使您的测量继续依赖先前的DpiScale。我正在编写的应用程序是我一个人的,并且我没有那种设置,因此我没有什么可以测试的,而且当我按照指南进行操作时,由于出现清单错误,最终导致该应用程序无法启动错误,因此我放弃了,但是最好仔细检查一下并调整您的应用清单以使其包含以下内容:

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

根据文档:

[这两个]标签的组合具有以下效果:1)每个显示器的Windows NT 10周年更新2)系统<Windows 10周年更新


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

2
字形宽度的总和不适用于大多数字体,因为它不考虑字距调整
Nicolas Repiquet 2012年

4

我通过在后端代码中为元素添加绑定路径来解决此问题:

<TextBlock x:Name="MyText" Width="{Binding Path=ActualWidth, ElementName=MyText}" />

我发现这比将上述引用(如FormattedText)的所有开销添加到我的代码中更为干净。

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

double d_width = MyText.Width;

2
d_width = MyText.ActualWidth;无需绑定即可简单地进行操作。问题是当TextBlock不在可视树中时。
xmedeko

0

我用这个:

var typeface = new Typeface(textBlock.FontFamily, textBlock.FontStyle, textBlock.FontWeight, textBlock.FontStretch);
var formattedText = new FormattedText(textBlock.Text, Thread.CurrentThread.CurrentCulture, textBlock.FlowDirection, typeface, textBlock.FontSize, textBlock.Foreground);

var size = new Size(formattedText.Width, formattedText.Height)

-3

为您找到了这个:

Graphics g = control.CreateGraphics();
int width =(int)g.MeasureString(aString, control.Font).Width; 
g.dispose();

24
此特定方法仅适用于WinForms。
HB
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.