Angenommen, ich habe TextBlock
mit Text "Some Text" und Schriftgröße 10.0 .
Wie kann ich die entsprechende TextBlock
Breite berechnen ?
Angenommen, ich habe TextBlock
mit Text "Some Text" und Schriftgröße 10.0 .
Wie kann ich die entsprechende TextBlock
Breite berechnen ?
ActualWidth
.
Antworten:
Verwenden Sie die FormattedText
Klasse.
Ich habe in meinem Code eine Hilfsfunktion erstellt:
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);
}
Es gibt geräteunabhängige Pixel zurück, die im WPF-Layout verwendet werden können.
Für die Aufzeichnung ... Ich gehe davon aus, dass der Benutzer versucht, die Breite, die der textBlock nach dem Hinzufügen zum visuellen Baum einnimmt, programmgesteuert zu bestimmen. IMO wäre eine bessere Lösung als formatierter Text (wie geht man mit so etwas wie textWrapping um?) Die Verwendung von Measure and Arrange für einen Beispiel-TextBlock. z.B
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
Die bereitgestellte Lösung war für .NET Framework 4.5 geeignet. Da Windows 10 DPI-Skalierung und Framework 4.6.x jedoch unterschiedliche Unterstützungsgrade hinzufügen, wird der zum Messen von Text verwendete Konstruktor jetzt [Obsolete]
zusammen mit allen Konstruktoren dieser Methode markiert pixelsPerDip
Parameter nicht einschließen .
Leider ist es etwas komplizierter, aber mit den neuen Skalierungsfunktionen wird es zu einer höheren Genauigkeit führen.
### PixelsPerDip
Laut MSDN bedeutet dies:
Der unabhängige Pixelwert für Pixel pro Dichte, der dem Skalierungsfaktor entspricht. Wenn beispielsweise die DPI eines Bildschirms 120 beträgt (oder 1,25, weil 120/96 = 1,25), werden 1,25 Pixel pro dichteunabhängiges Pixel gezeichnet. DIP ist die Maßeinheit, die von WPF verwendet wird, um unabhängig von Geräteauflösung und DPIs zu sein.
Hier ist meine Implementierung der ausgewählten Antwort basierend auf den Anweisungen des GitHub-Repositorys von Microsoft / WPF-Samples mit DPI-Skalierungsbewusstsein.
Es ist eine zusätzliche Konfiguration erforderlich, um die DPI-Skalierung ab Windows 10 Anniversary (unter dem Code) vollständig zu unterstützen. Ich konnte nicht arbeiten. Ohne diese Konfiguration funktioniert dies jedoch auf einem einzelnen Monitor mit konfigurierter Skalierung (und berücksichtigt Änderungen bei der Skalierung). Das Word-Dokument im obigen Repo ist die Quelle dieser Informationen, da meine Anwendung nicht gestartet werden würde, wenn ich diese Werte hinzugefügt hätte. Dieser Beispielcode aus demselben Repo diente auch als guter Bezugspunkt.
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
}
}
Gemäß der Dokumentation unter Microsoft / WPF-Samples müssen Sie dem Anwendungsmanifest einige Einstellungen hinzufügen, um die Fähigkeit von Windows 10 Anniversary abzudecken, in Konfigurationen mit mehreren Monitoren unterschiedliche DPI-Einstellungen pro Anzeige vorzunehmen. Es ist eine gute Vermutung, dass das OnDpiChanged-Ereignis ohne diese Einstellungen möglicherweise nicht ausgelöst wird, wenn ein Fenster mit unterschiedlichen Einstellungen von einer Anzeige zur anderen verschoben wird, sodass Ihre Messungen weiterhin auf den vorherigen basieren DpiScale
. Die Anwendung, die ich schrieb, war für mich allein, und ich habe kein solches Setup, sodass ich nichts zu testen hatte. Als ich den Anweisungen folgte, erhielt ich eine App, die aufgrund eines Manifests nicht gestartet werden konnte Fehler, also habe ich aufgegeben, aber es wäre eine gute Idee, das zu überprüfen und Ihr App-Manifest so anzupassen, dass es Folgendes enthält:
<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>
Laut Dokumentation:
Die Kombination dieser beiden Tags hat den folgenden Effekt: 1) Pro Monitor für> = Windows 10 Anniversary Update 2) System <Windows 10 Anniversary Update
Ich habe einige Methoden gefunden, die gut funktionieren ...
/// <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);
}
Ich habe dieses Problem behoben, indem ich dem Element im Backend-Code einen Bindungspfad hinzugefügt habe:
<TextBlock x:Name="MyText" Width="{Binding Path=ActualWidth, ElementName=MyText}" />
Ich fand, dass dies eine viel sauberere Lösung ist, als den gesamten Aufwand der oben genannten Referenzen wie FormattedText zu meinem Code hinzuzufügen.
Danach konnte ich Folgendes tun:
double d_width = MyText.Width;
d_width = MyText.ActualWidth;
ohne Bindung tun . Das Problem ist, wenn das TextBlock
noch nicht im visuellen Baum ist.
Ich benutze dieses:
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)