簡體   English   中英

如何計算其已知字體大小和字符的 WPF TextBlock 寬度?

[英]How to calculate WPF TextBlock width for its known font size and characters?

假設我有TextBlock文本"Some Text"字體大小 10.0

如何計算合適的TextBlock寬度

使用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 布局的與設備無關的像素。

作為記錄......我假設操作員正在嘗試以編程方式確定 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

提供的解決方案適用於 .Net Framework 4.5,但是,隨着 Windows 10 DPI 縮放和 Framework 4.6.x 為其添加了不同程度的支持,用於測量文本的構造函數現在被標記為[Obsolete] ,以及任何構造函數不包含pixelsPerDip參數的方法。

不幸的是,它涉及的更多一些,但它會通過新的縮放功能帶來更高的准確性。

###PixelsPerDip

根據 MSDN,這代表:

Pixels Per Density Independent Pixel 值,相當於比例因子。 例如,如果屏幕的 DPI 為 120(或 1.25,因為 120/96 = 1.25),則每個密度獨立像素繪制 1.25 個像素。 DIP 是 WPF 用於獨立於設備分辨率和 DPI 的測量單位。

這是我根據Microsoft/WPF-Samples GitHub 存儲庫中的指導對所選答案的實現,並具有 DPI 縮放意識。

從 Windows 10 周年起,需要一些額外的配置來完全支持 DPI 縮放(在代碼下方),我無法開始工作,但沒有它,它可以在配置了縮放的單個顯示器上運行(並尊重縮放更改)。 上述存儲庫中的 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)
    {
        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) Per-Monitor for >= Windows 10 Anniversary Update 2) System < Windows 10 Anniversary Update

我發現了一些工作正常的方法......

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

我通過向后端代碼中的元素添加綁定路徑來解決此問題:

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

我發現這是一個比將上述引用(如 FormattedText)的所有開銷添加到我的代碼中更簡潔的解決方案。

之后,我能夠做到這一點:

double d_width = MyText.Width;

我用這個:

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)

為你找到了這個:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM