Как рассчитать ширину WPF TextBlock для его известного размера шрифта и символов? - PullRequest
68 голосов
/ 13 февраля 2012

Допустим, у меня есть TextBlock с текстом "Some Text" и размер шрифта 10.0 .

Как я могу вычислить соответствующие TextBlock ширина

Ответы [ 7 ]

137 голосов
/ 13 февраля 2012

Используйте класс 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.

34 голосов
/ 31 октября 2014

Для записи ... Я предполагаю, что оператор пытается программно определить ширину, которую textBlock займет после добавления в визуальное дерево.IMO лучшее решение, чем 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
9 голосов
/ 09 апреля 2017

Предоставленное решение подходило для .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 для независимости от разрешения устройства и точек на дюйм.

Вот моя реализация выбранного ответа, основанная на указаниях Microsoft / WPF-Samples GitHub репозиторий с поддержкой масштабирования DPI.

Для полной поддержки 1020 * поддержки масштабирования 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)
    {
        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
    }
}

Другие требования

Согласно документации на 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 для> = Windows 10 Anniversary Update 2) Система

4 голосов
/ 22 июня 2013

Я решил эту проблему, добавив путь привязки к элементу в коде бэкэнда:

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

Я обнаружил, что это гораздо более чистое решение, чем добавление всех накладных расходов из вышеуказанных ссылок, таких как FormattedText, в мой код.

После того, как я смог сделать это:

double d_width = MyText.Width;
4 голосов
/ 13 февраля 2012

Я нашел несколько методов, которые прекрасно работают ...

/// <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);
}
0 голосов
/ 24 октября 2018

Я использую это:

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)
0 голосов
/ 13 февраля 2012

Нашел это для вас:

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