Làm thế nào để tính toán chiều rộng WPF TextBlock cho kích thước phông chữ và ký tự đã biết của nó?


79

Giả sử tôi có TextBlockvăn bản "Một số Văn bản"kích thước phông chữ 10.0 .

Làm thế nào tôi có thể tính toán TextBlock chiều rộng thích hợp ?


Điều này đã được hỏi trước đây, điều đó cũng phụ thuộc vào phông chữ.
HB

Ngoài ra, bạn có thể chỉ lấy chiều rộng thực tế từ ActualWidth.
HB

2
Sử dụng TextRenderer cũng sẽ hoạt động cho WPF: stackoverflow.com/questions/721168/…
HB

Câu trả lời:


152

Sử dụng FormattedTextlớp học.

Tôi đã tạo một hàm trợ giúp trong mã của mình:

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

Nó trả về các pixel độc lập với thiết bị có thể được sử dụng trong bố cục WPF.


Điều này thực sự hữu ích
Rakesh

1
Cách làm tương tự trong UWP
Arun Prasad

6
@ArunPrasad Đó sẽ là một điều tuyệt vời nếu bạn hỏi một câu hỏi riêng biệt. Câu hỏi này rõ ràng là thuộc phạm vi WPF.
RandomEngy

Điều này trả về độ rộng bằng 0 nếu ứng viên được chuyển vào là khoảng trắng. Tôi nghĩ rằng điều này có liên quan đến việc gói từ?
Mark Miller

42

Đối với bản ghi ... Tôi giả sử op'er đang cố gắng xác định theo chương trình chiều rộng mà textBlock sẽ chiếm sau khi được thêm vào cây trực quan. IMO một giải pháp tốt hơn sau đó formattedText (làm cách nào để bạn xử lý một thứ gì đó như textWrapping?) Sẽ là sử dụng Measure và Sắp xếp trên một TextBlock mẫu. ví dụ

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
Với một vài sửa đổi nhỏ, điều này đã phù hợp với tôi khi viết ứng dụng Windows 8.1 Store (Windows Runtime). Đừng quên đặt thông tin phông chữ cho TextBlock này để nó tính toán chiều rộng chính xác. Giải pháp gọn gàng và đáng được ủng hộ.
David Hiệu trưởng

1
Điểm mấu chốt ở đây là tạo văn bản tạm thời tại chỗ. Tất cả các thao tác này với khối văn bản hiện có trên một trang không hoạt động: Độ rộng thực của nó chỉ được cập nhật sau khi vẽ lại.
Tertium

13

Giải pháp được cung cấp phù hợp với .Net Framework 4.5, tuy nhiên, với tính năng mở rộng DPI của Windows 10 và Framework 4.6.x bổ sung các mức độ hỗ trợ khác nhau cho nó, hàm tạo được sử dụng để đo lường văn bản hiện được đánh dấu [Obsolete], cùng với bất kỳ hàm tạo nào trên phương pháp đó không bao gồm pixelsPerDiptham số.

Thật không may, nó liên quan nhiều hơn một chút, nhưng nó sẽ dẫn đến độ chính xác cao hơn với khả năng mở rộng quy mô mới.

### PixelsPerDip

Theo MSDN, điều này thể hiện:

Giá trị Pixel độc lập trên mỗi mật độ, tương đương với hệ số tỷ lệ. Ví dụ: nếu DPI của màn hình là 120 (hoặc 1,25 vì 120/96 = 1,25), thì 1,25 pixel trên mỗi pixel độc lập với mật độ được vẽ. DIP là đơn vị đo lường được WPF sử dụng để độc lập với độ phân giải và DPI của thiết bị.

Đây là cách tôi triển khai câu trả lời đã chọn dựa trên hướng dẫn từ kho lưu trữ GitHub của Microsoft / WPF-Mẫu với nhận thức về tỷ lệ DPI.

Có một số cấu hình bổ sung được yêu cầu để hỗ trợ hoàn toàn tỷ lệ DPI kể từ Windows 10 Anniversary (bên dưới mã), điều này tôi không thể hoạt động, nhưng nếu không có nó, nó hoạt động trên một màn hình duy nhất có cấu hình tỷ lệ (và tôn trọng các thay đổi về tỷ lệ). Tài liệu Word trong repo ở trên là nguồn của thông tin đó vì ứng dụng của tôi sẽ không khởi chạy sau khi tôi thêm các giá trị đó. Mã mẫu này từ cùng một kho cũng đóng vai trò là một điểm tham chiếu tốt.

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

Những yêu cầu khác

Theo tài liệu tại Microsoft / WPF-Sa samples, bạn cần thêm một số cài đặt vào tệp kê khai của ứng dụng để bảo đảm khả năng của Windows 10 Anniversary có các cài đặt DPI khác nhau trên mỗi màn hình trong cấu hình nhiều màn hình. Dự đoán công bằng rằng nếu không có các cài đặt này, sự kiện OnDpiChanged có thể không được nâng lên khi một cửa sổ được chuyển từ màn hình này sang màn hình khác với các cài đặt khác nhau, điều này sẽ khiến các phép đo của bạn tiếp tục dựa vào trước đó DpiScale. Ứng dụng tôi đang viết chỉ dành riêng cho tôi và tôi không có kiểu thiết lập như vậy nên tôi không có gì để thử nghiệm và khi tôi làm theo hướng dẫn, tôi đã kết thúc với một ứng dụng không khởi động do tệp kê khai lỗi, vì vậy tôi đã từ bỏ, nhưng bạn nên xem qua và điều chỉnh tệp kê khai ứng dụng của mình để chứa:

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

Theo tài liệu:

Sự kết hợp của [những] hai thẻ này có tác dụng sau: 1) Per-Monitor for> = Windows 10 Anniversary Update 2) Hệ thống <Windows 10 Anniversary Update


5

Tôi đã tìm thấy một số phương pháp hoạt động tốt ...

/// <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
Tính tổng chiều rộng glyph sẽ không hoạt động đối với hầu hết các phông chữ vì nó không tính đến kerning .
Nicolas Repiquet,

4

Tôi đã giải quyết vấn đề này bằng cách thêm một đường dẫn liên kết vào phần tử trong mã phụ trợ:

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

Tôi thấy đây là một giải pháp gọn gàng hơn nhiều so với việc thêm tất cả chi phí của các tham chiếu ở trên như FormattedText vào mã của tôi.

Sau đó, tôi đã có thể làm điều này:

double d_width = MyText.Width;

2
Bạn có thể làm đơn giản d_width = MyText.ActualWidth;mà không cần ràng buộc. Vấn đề là khi cái TextBlockchưa có trong cây trực quan.
xmedeko

0

Tôi sử dụng cái này:

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

Tìm thấy cái này cho bạn:

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

24
Cách tiếp cận cụ thể này chỉ áp dụng cho WinForms.
HB
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.