How to calculate WPF TextBlock width for its known

2019-01-03 23:41发布

Let's say I have TextBlock with text "Some Text" and font size 10.0.

How I can calculate appropriate TextBlock width?

标签: c# wpf font-size
7条回答
我只想做你的唯一
2楼-- · 2019-01-04 00:02

Use the FormattedText class.

I made a helper function in my code:

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

It returns device-independent pixels that can be used in WPF layout.

查看更多
混吃等死
3楼-- · 2019-01-04 00:03

I found some methods which work fine...

/// <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);
}
查看更多
成全新的幸福
4楼-- · 2019-01-04 00:06

Found this for you:

Graphics g = control.CreateGraphics();
int width =(int)g.MeasureString(aString, control.Font).Width; 
g.dispose();
查看更多
\"骚年 ilove
5楼-- · 2019-01-04 00:09

The provided solution was appropriate for .Net Framework 4.5, however, with Windows 10 DPI scaling and Framework 4.6.x adding varying degrees of support for it, the constructor used to measure text is now marked [Obsolete], along with any constructors on that method that do not include the pixelsPerDip parameter.

Unfortunately, it's a little more involved, but it will result in greater accuracy with the new scaling capabilities.

PixelsPerDip

According to MSDN, this represents:

The Pixels Per Density Independent Pixel value, which is the equivalent of the scale factor. For example, if the DPI of a screen is 120 (or 1.25 because 120/96 = 1.25) , 1.25 pixel per density independent pixel is drawn. DIP is the unit of measurement used by WPF to be independent of device resolution and DPIs.

Here's my implementation of the selected answer based on guidance from the Microsoft/WPF-Samples GitHub repository with DPI scaling awareness.

There is some additional configuration required to completely support DPI scaling as of Windows 10 Anniversary (below the code), which I couldn't get to work, but without it this works on a single monitor with scaling configured (and respects scaling changes). The Word document in the above repo is the source of that information since my application wouldn't launch once I added those values. This sample code from the same repo also served as a good reference point.

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

Other Requirements

According to the documentation at Microsoft/WPF-Samples, you need to add some settings to the application's manifest to cover Windows 10 Anniversary's ability to have different DPI settings per display in multiple-monitor configurations. It's a fair guess that without these settings, the OnDpiChanged event might not be raised when a window is moved from one display to another with different settings, which would make your measurements continue to rely on the previous DpiScale. The application I was writing was for me, alone, and I don't have that kind of a setup so I had nothing to test with and when I followed the guidance, I ended up with an app that wouldn't start due to manifest errors, so I gave up, but it'd be a good idea to look that over and adjust your app manifest to contain:

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

According to the documentation:

The combination of [these] two tags have the following effect : 1) Per-Monitor for >= Windows 10 Anniversary Update 2) System < Windows 10 Anniversary Update

查看更多
Deceive 欺骗
6楼-- · 2019-01-04 00:10

I resolved this by adding a binding path to the element in the backend code:

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

I found this to be a much cleaner solution than adding all the overhead of the above references like FormattedText to my code.

After, I was able to do this:

double d_width = MyText.Width;
查看更多
地球回转人心会变
7楼-- · 2019-01-04 00:11

I use this one:

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)
查看更多
登录 后发表回答