Як розрахувати ширину WPF TextBlock для відомого розміру шрифту та символів?


81

Скажімо, у мене є TextBlockтекст "Трохи тексту" та розмір шрифту 10,0 .

Як я можу розрахувати відповідну TextBlock ширину ?


Це вже просилося раніше, також це залежить від шрифту.
HB

Також ви можете просто отримати фактичну ширину з ActualWidth.
HB

2
Використання TextRenderer також має працювати для WPF: stackoverflow.com/questions/721168/…
HB

Відповіді:


156

Використовуйте 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.


Це було дуже корисно
Ракеш

1
Як зробити те саме в UWP
Арун Прасад

6
@ArunPrasad Це було б чудово, щоб задати окреме питання. Це питання чітко поширюється на WPF.
RandomEngy

Це повертає нульову ширину, якщо зданий кандидат є пробілом. Я думаю, це пов’язано, можливо, із загортанням слів?
Mark Miller

43

Для запису ... Я припускаю, що оператор намагається програмно визначити ширину, яку займе textBlock після додавання до візуального дерева. ІМО кращим рішенням тоді formattedText (як ви обробляєте щось на зразок textWrapping?) Було б використовувати Measure and Arrange на зразку TextBlock. напр

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 Runtime). Не забудьте встановити інформацію про шрифт для цього TextBlock так, щоб він точно розрахував ширину. Акуратне рішення і варто підтримати його.
Девід Ректор,

1
Ключовим моментом тут є створення цього тимчасового блоку тексту на місці. Всі ці маніпуляції з існуючим текстовим блоком на сторінці не працюють: його фактична ширина оновлюється лише після перемальовування.
Терцій

13

Надане рішення підходило для .Net Framework 4.5, однак, маючи масштабування DPI для Windows 10 та Framework 4.6.x, додавши для нього різну ступінь підтримки, конструктор, що використовується для вимірювання тексту, тепер позначений [Obsolete]разом із будь-якими конструкторами цього методу, які не включати pixelsPerDipпараметр.

На жаль, це трохи більше задіяно, але це призведе до більшої точності завдяки новим можливостям масштабування.

### PixelsPerDip

За даними MSDN, це означає:

Значення незалежного пікселя від пікселів на щільність, що еквівалентно масштабному коефіцієнту. Наприклад, якщо DPI екрана дорівнює 120 (або 1,25, оскільки 120/96 = 1,25), малюється 1,25 пікселя на незалежний від щільності піксель. DIP - це одиниця виміру, що використовується WPF, щоб не залежати від роздільної здатності пристрою та DPI.

Ось моя реалізація обраної відповіді на основі вказівок зі сховища GitHub Microsoft / WPF-Samples із обізнаністю щодо масштабування DPI.

Існує деяка додаткова конфігурація, необхідна для повної підтримки масштабування DPI станом на Windows 10 Anniversary (нижче коду), і я не міг приступити до роботи, але без неї це працює на одному моніторі з налаштованим масштабуванням (і поважає зміни масштабу). Документ 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) Per-Monitor for> = Windows 10 Anniversary Update 2) System <Windows 10 Anniversary Update


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

3
Підсумовування ширини гліфів не буде працювати для більшості шрифтів, оскільки це не враховує кернінг .
Ніколас Репіке

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.