c# - not - wpf padding
¿Cómo se calcula el ancho WPF TextBlock para su tamaño de letra y caracteres conocidos? (6)
Digamos que tengo TextBlock
con texto "Some Text" y tamaño de letra 10.0 .
¿Cómo puedo calcular el ancho adecuado de TextBlock
?
Encontré algunos métodos que funcionan bien ...
/// <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);
}
Encontrado esto para ti:
Graphics g = control.CreateGraphics();
int width =(int)g.MeasureString(aString, control.Font).Width;
g.dispose();
La solución provista era apropiada para .Net Framework 4.5, sin embargo, con escalamiento de Windows 10 DPI y Framework 4.6.x añadiendo varios grados de soporte para ello, el constructor utilizado para medir texto ahora está marcado [Obsolete]
, junto con cualquier constructor en ese método que no incluye el parámetro pixelsPerDip
.
Desafortunadamente, es un poco más complicado, pero dará como resultado una mayor precisión con las nuevas capacidades de escalamiento.
PixelsPerDip
Según MSDN, esto representa:
El valor Pixels Per Density Independent Pixel, que es el equivalente del factor de escala. Por ejemplo, si el DPI de una pantalla es 120 (o 1.25 porque 120/96 = 1.25), se dibuja 1.25 píxeles por píxel independiente de la densidad. DIP es la unidad de medida utilizada por WPF para ser independiente de la resolución del dispositivo y de los DPI.
Aquí está mi implementación de la respuesta seleccionada basada en la guía del repositorio GitHub de Microsoft/WPF-Samples con conciencia de escalamiento DPI.
Se necesita alguna configuración adicional para admitir por completo la escala de DPI a partir del Aniversario de Windows 10 (debajo del código), lo cual no pude lograr, pero sin esto funciona en un solo monitor con escalamiento configurado (y respeta los cambios de escala). El documento de Word en el repositorio anterior es la fuente de esa información ya que mi aplicación no se lanzaría una vez que agregué esos valores. Microsoft/WPF-Samples del mismo repos también sirvió como un buen punto de referencia.
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
}
}
Otros requerimientos
De acuerdo con la documentación de Microsoft / WPF-Samples, debe agregar algunas configuraciones al manifiesto de la aplicación para cubrir la capacidad de Windows 10 Anniversary de tener diferentes configuraciones de DPI por pantalla en configuraciones de monitores múltiples. Es razonable suponer que sin estas configuraciones, el evento OnDpiChanged podría no aumentarse cuando una ventana se mueva de una pantalla a otra con diferentes configuraciones, lo que haría que sus mediciones continúen dependiendo del DpiScale
anterior. La aplicación que estaba escribiendo era para mí, solo, y no tengo ese tipo de configuración, así que no tenía nada con qué probar y cuando seguí la guía, terminé con una aplicación que no se iniciaba debido a manifiesto errores, así que me di por vencido, pero sería una buena idea mirar eso y ajustar el manifiesto de tu aplicación para que contenga:
<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>
De acuerdo con la documentación:
La combinación de [estas] dos etiquetas tiene el siguiente efecto: 1) Per-Monitor para> = Windows 10 Anniversary Update 2) Sistema <Windows 10 Anniversary Update
Para el registro ... supongo que el operador está tratando de determinar de manera programática el ancho que tomará textBlock luego de ser agregado al árbol visual. IMO una mejor solución que formattedText (¿cómo manejas algo como textWrapping?) Sería usar Measure and Organizar en un TextBlock de muestra. p.ej
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
Resolví esto agregando una ruta de enlace al elemento en el código back-end:
<TextBlock x:Name="MyText" Width="{Binding Path=ActualWidth, ElementName=MyText}" />
Encontré que esta es una solución mucho más limpia que agregar todos los gastos generales de las referencias anteriores, como FormattedText a mi código.
Después, pude hacer esto:
double d_width = MyText.Width;
Usa la clase FormattedText
.
Hice una función auxiliar en mi código:
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);
}
Devuelve píxeles independientes del dispositivo que se pueden usar en el diseño de WPF.