Скажімо, у мене є TextBlock
текст "Трохи тексту" та розмір шрифту 10,0 .
Як я можу розрахувати відповідну TextBlock
ширину ?
Скажімо, у мене є TextBlock
текст "Трохи тексту" та розмір шрифту 10,0 .
Як я можу розрахувати відповідну TextBlock
ширину ?
ActualWidth
.
Відповіді:
Використовуйте 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 після додавання до візуального дерева. ІМО кращим рішенням тоді 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, однак, маючи масштабування 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
Я знайшов кілька методів, які добре працюють ...
/// <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;
d_width = MyText.ActualWidth;
без прив'язки. Проблема полягає в TextBlock
тому, що ще немає у візуальному дереві.
Я використовую цей:
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)