Giả sử tôi có TextBlock
văn bản "Một số Văn bản" và kích thước phông chữ 10.0 .
Làm thế nào tôi có thể tính toán TextBlock
chiều rộng thích hợp ?
Giả sử tôi có TextBlock
văn bản "Một số Văn bản" và kích thước phông chữ 10.0 .
Làm thế nào tôi có thể tính toán TextBlock
chiều rộng thích hợp ?
ActualWidth
.
Câu trả lời:
Sử dụng FormattedText
lớp học.
Tôi đã tạo một hàm trợ giúp trong mã của mình:
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);
}
Nó trả về các pixel độc lập với thiết bị có thể được sử dụng trong bố cục WPF.
Đối với bản ghi ... Tôi giả sử op'er đang cố gắng xác định theo chương trình chiều rộng mà textBlock sẽ chiếm sau khi được thêm vào cây trực quan. IMO một giải pháp tốt hơn sau đó formattedText (làm cách nào để bạn xử lý một thứ gì đó như textWrapping?) Sẽ là sử dụng Measure và Sắp xếp trên một TextBlock mẫu. ví dụ
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
Giải pháp được cung cấp phù hợp với .Net Framework 4.5, tuy nhiên, với tính năng mở rộng DPI của Windows 10 và Framework 4.6.x bổ sung các mức độ hỗ trợ khác nhau cho nó, hàm tạo được sử dụng để đo lường văn bản hiện được đánh dấu [Obsolete]
, cùng với bất kỳ hàm tạo nào trên phương pháp đó không bao gồm pixelsPerDip
tham số.
Thật không may, nó liên quan nhiều hơn một chút, nhưng nó sẽ dẫn đến độ chính xác cao hơn với khả năng mở rộng quy mô mới.
### PixelsPerDip
Theo MSDN, điều này thể hiện:
Giá trị Pixel độc lập trên mỗi mật độ, tương đương với hệ số tỷ lệ. Ví dụ: nếu DPI của màn hình là 120 (hoặc 1,25 vì 120/96 = 1,25), thì 1,25 pixel trên mỗi pixel độc lập với mật độ được vẽ. DIP là đơn vị đo lường được WPF sử dụng để độc lập với độ phân giải và DPI của thiết bị.
Đây là cách tôi triển khai câu trả lời đã chọn dựa trên hướng dẫn từ kho lưu trữ GitHub của Microsoft / WPF-Mẫu với nhận thức về tỷ lệ DPI.
Có một số cấu hình bổ sung được yêu cầu để hỗ trợ hoàn toàn tỷ lệ DPI kể từ Windows 10 Anniversary (bên dưới mã), điều này tôi không thể hoạt động, nhưng nếu không có nó, nó hoạt động trên một màn hình duy nhất có cấu hình tỷ lệ (và tôn trọng các thay đổi về tỷ lệ). Tài liệu Word trong repo ở trên là nguồn của thông tin đó vì ứng dụng của tôi sẽ không khởi chạy sau khi tôi thêm các giá trị đó. Mã mẫu này từ cùng một kho cũng đóng vai trò là một điểm tham chiếu tốt.
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
}
}
Theo tài liệu tại Microsoft / WPF-Sa samples, bạn cần thêm một số cài đặt vào tệp kê khai của ứng dụng để bảo đảm khả năng của Windows 10 Anniversary có các cài đặt DPI khác nhau trên mỗi màn hình trong cấu hình nhiều màn hình. Dự đoán công bằng rằng nếu không có các cài đặt này, sự kiện OnDpiChanged có thể không được nâng lên khi một cửa sổ được chuyển từ màn hình này sang màn hình khác với các cài đặt khác nhau, điều này sẽ khiến các phép đo của bạn tiếp tục dựa vào trước đó DpiScale
. Ứng dụng tôi đang viết chỉ dành riêng cho tôi và tôi không có kiểu thiết lập như vậy nên tôi không có gì để thử nghiệm và khi tôi làm theo hướng dẫn, tôi đã kết thúc với một ứng dụng không khởi động do tệp kê khai lỗi, vì vậy tôi đã từ bỏ, nhưng bạn nên xem qua và điều chỉnh tệp kê khai ứng dụng của mình để chứa:
<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>
Theo tài liệu:
Sự kết hợp của [những] hai thẻ này có tác dụng sau: 1) Per-Monitor for> = Windows 10 Anniversary Update 2) Hệ thống <Windows 10 Anniversary Update
Tôi đã tìm thấy một số phương pháp hoạt động tốt ...
/// <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);
}
Tôi đã giải quyết vấn đề này bằng cách thêm một đường dẫn liên kết vào phần tử trong mã phụ trợ:
<TextBlock x:Name="MyText" Width="{Binding Path=ActualWidth, ElementName=MyText}" />
Tôi thấy đây là một giải pháp gọn gàng hơn nhiều so với việc thêm tất cả chi phí của các tham chiếu ở trên như FormattedText vào mã của tôi.
Sau đó, tôi đã có thể làm điều này:
double d_width = MyText.Width;
d_width = MyText.ActualWidth;
mà không cần ràng buộc. Vấn đề là khi cái TextBlock
chưa có trong cây trực quan.
Tôi sử dụng cái này:
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)