Let's say I have TextBlock
with text "Some Text" and font size 10.0.
How I can calculate appropriate TextBlock
width?
Let's say I have TextBlock
with text "Some Text" and font size 10.0.
How I can calculate appropriate TextBlock
width?
Use the FormattedText
class.
I made a helper function in my code:
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(),
VisualTreeHelper.GetDpi(this.textBlock).PixelsPerDip);
return new Size(formattedText.Width, formattedText.Height);
}
It returns device-independent pixels that can be used in WPF layout.
c# formattedText.MaxTextHeight = textBlock.MaxHeight; formattedText.MaxTextWidth = textBlock.MaxWidth; formattedText.Trimming = TextTrimming.None; // Change this value in case you do have trimming
–
Proprietress For the record... I'm assuming the op'er is trying to programically determine the width that the textBlock will take up after being added to the visual tree. IMO a better solution then formattedText (how do you handle something like textWrapping?) would be to use Measure and Arrange on a sample TextBlock. e.g.
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
The provided solution was appropriate for .Net Framework 4.5, however, with Windows 10 DPI scaling and Framework 4.6.x adding varying degrees of support for it, the constructor used to measure text is now marked [Obsolete]
, along with any constructors on that method that do not include the pixelsPerDip
parameter.
Unfortunately, it's a little more involved, but it will result in greater accuracy with the new scaling capabilities.
###PixelsPerDip
According to MSDN, this represents:
The Pixels Per Density Independent Pixel value, which is the equivalent of the scale factor. For example, if the DPI of a screen is 120 (or 1.25 because 120/96 = 1.25) , 1.25 pixel per density independent pixel is drawn. DIP is the unit of measurement used by WPF to be independent of device resolution and DPIs.
Here's my implementation of the selected answer based on guidance from the Microsoft/WPF-Samples GitHub repository with DPI scaling awareness.
There is some additional configuration required to completely support DPI scaling as of Windows 10 Anniversary (below the code), which I couldn't get to work, but without it this works on a single monitor with scaling configured (and respects scaling changes). The Word document in the above repo is the source of that information since my application wouldn't launch once I added those values. This sample code from the same repo also served as a good reference point.
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_sync)
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
}
}
According to the documentation at Microsoft/WPF-Samples, you need to add some settings to the application's manifest to cover Windows 10 Anniversary's ability to have different DPI settings per display in multiple-monitor configurations. It's a fair guess that without these settings, the OnDpiChanged event might not be raised when a window is moved from one display to another with different settings, which would make your measurements continue to rely on the previous DpiScale
. The application I was writing was for me, alone, and I don't have that kind of a setup so I had nothing to test with and when I followed the guidance, I ended up with an app that wouldn't start due to manifest errors, so I gave up, but it'd be a good idea to look that over and adjust your app manifest to contain:
<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>
According to the documentation:
The combination of [these] two tags have the following effect : 1) Per-Monitor for >= Windows 10 Anniversary Update 2) System < Windows 10 Anniversary Update
lock
s are totally unnecessary - not only is assigning the DpiScale
already an atomic operation so there's no need to lock it, and not only is only one thing locking on m_dpiInfo
while the rest do m_sync
, but isn't OnDpiChanged
just executed on the UI thread like everything else anyway? Where even are the multiple threads here? –
Overstep DpiScale
information be stored in a field at all? Why not use var dpiInfo = VisualTreeHelper.GetDpi(this.textBlock)
? Performance? Threading? Can anyone help me understand? –
Tildatilde I found some methods which work fine...
/// <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);
}
I resolved this by adding a binding path to the element in the backend code:
<TextBlock x:Name="MyText" Width="{Binding Path=ActualWidth, ElementName=MyText}" />
I found this to be a much cleaner solution than adding all the overhead of the above references like FormattedText to my code.
After, I was able to do this:
double d_width = MyText.Width;
d_width = MyText.ActualWidth;
without binding. The problem is when the TextBlock
is not in the visual tree yet. –
Contradance I use this one:
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)
Found this for you:
Graphics g = control.CreateGraphics();
int width =(int)g.MeasureString(aString, control.Font).Width;
g.dispose();
© 2022 - 2024 — McMap. All rights reserved.
ActualWidth
. – Hadhramaut