C# 如何计算其已知字体大小和字符的 WPF TextBlock 宽度?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9264398/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to calculate WPF TextBlock width for its known font size and characters?
提问by Developer
Let's say I have TextBlockwith text "Some Text"and font size 10.0.
假设我有TextBlock文本"Some Text"和字体大小 10.0。
How I can calculate appropriate TextBlockwidth?
我如何计算合适的TextBlock宽度?
采纳答案by RandomEngy
Use the FormattedTextclass.
使用FormattedText类。
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(),
1);
return new Size(formattedText.Width, formattedText.Height);
}
It returns device-independent pixels that can be used in WPF layout.
它返回可用于 WPF 布局的与设备无关的像素。
回答by Setheron
Found this for you:
为你找到了这个:
Graphics g = control.CreateGraphics();
int width =(int)g.MeasureString(aString, control.Font).Width;
g.dispose();
回答by Developer
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);
}
回答by Chef Pharaoh
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.
我发现这是一个比将上述引用(如 FormattedText)的所有开销添加到我的代码中更简洁的解决方案。
After, I was able to do this:
之后,我能够做到这一点:
double d_width = MyText.Width;
回答by user1604008
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.
作为记录......我假设操作员正在尝试以编程方式确定 textBlock 在添加到可视化树后将占用的宽度。IMO 比 formattedText 更好的解决方案(你如何处理 textWrapping 之类的东西?)将是在示例 TextBlock 上使用 Measure 和Arrange。例如
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
回答by mdip
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 pixelsPerDipparameter.
提供的解决方案适用于 .Net Framework 4.5,但是,随着 Windows 10 DPI 缩放和 Framework 4.6.x 为其添加了不同程度的支持,用于测量文本的构造函数现在被标记为[Obsolete],以及该方法上的任何构造函数不包括pixelsPerDip参数。
Unfortunately, it's a little more involved, but it will result in greater accuracy with the new scaling capabilities.
不幸的是,它涉及的更多一些,但它会通过新的缩放功能带来更高的准确性。
PixelsPerDip
像素每Dip
According to MSDN, this represents:
根据 MSDN,这代表:
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.
Pixels Per Density Independent Pixel 值,相当于比例因子。例如,如果屏幕的 DPI 为 120(或 1.25,因为 120/96 = 1.25),则每个密度独立像素绘制 1.25 个像素。DIP 是 WPF 用于独立于设备分辨率和 DPI 的测量单位。
Here's my implementation of the selected answer based on guidance from the Microsoft/WPF-SamplesGitHub repository with DPI scaling awareness.
这是我根据Microsoft/WPF-SamplesGitHub 存储库中的指导对所选答案的实现,并具有 DPI 缩放意识。
There is some additional configuration required to completelysupport 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 codefrom the same repo also served as a good reference point.
从 Windows 10 周年起,需要一些额外的配置来完全支持 DPI 缩放(在代码下方),我无法开始工作,但没有它,它可以在配置了缩放的单个显示器上运行(并尊重缩放更改)。上述存储库中的 Word 文档是该信息的来源,因为一旦我添加了这些值,我的应用程序就不会启动。 来自同一个 repo 的这个示例代码也可以作为一个很好的参考点。
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
}
}
Other Requirements
其他需求
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:
根据 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>
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
[这些]两个标签的组合具有以下效果:1) Per-Monitor for >= Windows 10 Anniversary Update 2) System < Windows 10 Anniversary Update
回答by isxaker
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)

