Как рассчитать ширину WPF TextBlock для его известного размера шрифта и символов?
Допустим, у меня есть TextBlock
с текстом "Some Text" и размером шрифта 10,0.
Как я могу рассчитать соответствующий TextBlock
ширина?
7 ответов
Использовать 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?), - это использовать 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
Предоставленное решение было подходящим для.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, чтобы быть независимой от разрешения устройства и точек на дюйм.
Вот моя реализация выбранного ответа на основе руководства из репозитория 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)
{
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 for >= Windows 10 Anniversary Update 2) Система
Я решил это, добавив путь привязки к элементу в коде сервера:
<TextBlock x:Name="MyText" Width="{Binding Path=ActualWidth, ElementName=MyText}" />
Я обнаружил, что это намного более чистое решение, чем добавление всех накладных расходов из вышеуказанных ссылок, таких как FormattedText, в мой код.
После этого я смог сделать это:
double d_width = MyText.Width;
Я нашел несколько методов, которые работают нормально...
/// <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);
}
Я использую это:
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();