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

发布于 2025-01-05 06:32:52 字数 159 浏览 0 评论 0原文

假设我有 TextBlock,其中包含文本 “Some Text”字体大小 10.0

如何计算合适的 TextBlock 宽度

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

How I can calculate appropriate TextBlock width?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

宛菡 2025-01-12 06:32:52

使用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(),
        VisualTreeHelper.GetDpi(this.textBlock).PixelsPerDip);

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

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

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(),
        VisualTreeHelper.GetDpi(this.textBlock).PixelsPerDip);

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

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

装纯掩盖桑 2025-01-12 06:32:52

为了记录...
我假设操作员正在尝试以编程方式确定文本块在添加到可视化树后将占用的宽度。在我看来,比 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

For the record...
I'm assuming the op'er is trying to programically determine the width that the textBlock will take up after being added to the visual tree. IMO a better solution then formattedText (how do you handle something like textWrapping?) would be to use Measure and Arrange on a sample TextBlock. e.g.

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
少女净妖师 2025-01-12 06:32:52

提供的解决方案适用于 .Net Framework 4.5,但是,随着 Windows 10 DPI 缩放和 Framework 4.6.x 添加了不同程度的支持,用于测量文本的构造函数现在被标记为[已过时] ,以及该方法中不包含 pixelsPerDip 参数的任何构造函数。

不幸的是,它有点复杂,但新的缩放功能会带来更高的准确性。

###PixelsPerDip

根据 MSDN,这代表:

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

这是我根据 Microsoft/WPF-Samples 具有 DPI 缩放感知功能的 GitHub 存储库。

从 Windows 10 周年版开始,需要一些额外的配置来完全支持 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_sync)
            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 10 周年更新
2)系统< Windows 10 周年更新

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)
    {
        DpiScale dpiInfo;
        lock (m_sync)
            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

疑心病 2025-01-12 06:32:52

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

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

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

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

double d_width = MyText.Width;

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;
缘字诀 2025-01-12 06:32:52

我发现了一些效果很好的方法......

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

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);
}
说谎友 2025-01-12 06:32:52

我用这个:

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)

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)
多孤肩上扛 2025-01-12 06:32:52

为您找到了这个:

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

Found this for you:

Graphics g = control.CreateGraphics();
int width =(int)g.MeasureString(aString, control.Font).Width; 
g.dispose();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文