wpf - Obtener texto mostrado desde TextBlock
texttrimming (5)
Bueno, es un pedido un poco específico, así que no estoy seguro de que haya una función preparada en el marco para hacerlo. Lo que haría es calcular el ancho lógico de cada carácter, dividir el Ancho real del bloque de texto por este valor y allí tiene el número de caracteres desde el inicio de la cadena que son visibles. Por supuesto, eso supone que el recorte solo ocurrirá desde la derecha.
Tengo un TextBlock simple definido así
<StackPanel>
<Border Width="106"
Height="25"
Margin="6"
BorderBrush="Black"
BorderThickness="1"
HorizontalAlignment="Left">
<TextBlock Name="myTextBlock"
TextTrimming="CharacterEllipsis"
Text="TextBlock: Displayed text"/>
</Border>
</StackPanel>
Que salidas como esta
Esto me dará "TextBlock: texto en pantalla"
string text = myTextBlock.Text;
Pero, ¿hay alguna manera de obtener el texto que realmente se muestra en la pantalla?
Significado "TextBlock: Mostrar ..."
Gracias
Después de rootear alrededor de I Reflector por un tiempo, encontré lo siguiente:
System.Windows.Media.TextFormatting.TextCollapsedRange
que tiene una propiedad de Length
que contiene el número de caracteres que NO se muestran (están en la parte contraída / oculta de la línea de texto). Conociendo ese valor, es solo una cuestión de resta obtener los personajes que se muestran.
Esta propiedad no es accesible directamente desde el objeto TextBlock. Parece que es parte del código que utiliza WPF para pintar el texto en la pantalla.
Puede terminar siendo un montón de perder el tiempo para obtener realmente el valor de esta propiedad para la línea de texto en su TextBlock.
Para ello, primero recupere el objeto Drawing
que representa la apariencia del TextBlock
en el árbol visual, y luego camine en busca de elementos GlyphRunDrawing
, que contendrán el texto real renderizado en la pantalla. Aquí hay una implementación muy difícil y lista:
private void button1_Click(object sender, RoutedEventArgs e)
{
Drawing textBlockDrawing = VisualTreeHelper.GetDrawing(myTextBlock);
var sb = new StringBuilder();
WalkDrawingForText(sb, textBlockDrawing);
Debug.WriteLine(sb.ToString());
}
private static void WalkDrawingForText(StringBuilder sb, Drawing d)
{
var glyphs = d as GlyphRunDrawing;
if (glyphs != null)
{
sb.Append(glyphs.GlyphRun.Characters.ToArray());
}
else
{
var g = d as DrawingGroup;
if (g != null)
{
foreach (Drawing child in g.Children)
{
WalkDrawingForText(sb, child);
}
}
}
}
Este es un extracto directo de un pequeño arnés de prueba que acabo de escribir: el primer método es un manejador de clic de botón solo para facilitar la experimentación.
Utiliza el VisualTreeHelper
para obtener el Drawing
renderizado para TextBlock
, eso solo funcionará si la cosa ya se ha procesado. Y luego el método WalkDrawingForText
hace el trabajo real: simplemente atraviesa el árbol de Drawing
buscando texto.
Esto no es terriblemente inteligente: supone que los objetos GlyphRunDrawing
aparecen en el orden en que los desees. Para su ejemplo particular, lo hace: obtenemos un GlyphRunDrawing
contiene el texto truncado, seguido de un segundo que contiene el carácter de puntos suspensivos. (Y por cierto, es solo un carácter unicode - codepoint 2026, y si este editor me permite pegar en caracteres Unicode, es "...". No son tres períodos separados).
Si quisieras hacer esto más robusto, necesitarías calcular las posiciones de todos esos objetos GlyphRunDrawing
, y ordenarlos, para procesarlos en el orden en que aparecen, en lugar de simplemente esperar que WPF los produzca. en ese orden.
Actualizado para agregar:
Aquí hay un boceto de cómo podría verse un ejemplo de posición consciente. Aunque esto es un poco parroquial, supone texto de lectura de izquierda a derecha. Necesitarías algo más complejo para una solución internacionalizada.
private string GetTextFromVisual(Visual v)
{
Drawing textBlockDrawing = VisualTreeHelper.GetDrawing(v);
var glyphs = new List<PositionedGlyphs>();
WalkDrawingForGlyphRuns(glyphs, Transform.Identity, textBlockDrawing);
// Round vertical position, to provide some tolerance for rounding errors
// in position calculation. Not totally robust - would be better to
// identify lines, but that would complicate the example...
var glyphsOrderedByPosition = from glyph in glyphs
let roundedBaselineY = Math.Round(glyph.Position.Y, 1)
orderby roundedBaselineY ascending, glyph.Position.X ascending
select new string(glyph.Glyphs.GlyphRun.Characters.ToArray());
return string.Concat(glyphsOrderedByPosition);
}
[DebuggerDisplay("{Position}")]
public struct PositionedGlyphs
{
public PositionedGlyphs(Point position, GlyphRunDrawing grd)
{
this.Position = position;
this.Glyphs = grd;
}
public readonly Point Position;
public readonly GlyphRunDrawing Glyphs;
}
private static void WalkDrawingForGlyphRuns(List<PositionedGlyphs> glyphList, Transform tx, Drawing d)
{
var glyphs = d as GlyphRunDrawing;
if (glyphs != null)
{
var textOrigin = glyphs.GlyphRun.BaselineOrigin;
Point glyphPosition = tx.Transform(textOrigin);
glyphList.Add(new PositionedGlyphs(glyphPosition, glyphs));
}
else
{
var g = d as DrawingGroup;
if (g != null)
{
// Drawing groups are allowed to transform their children, so we need to
// keep a running accumulated transform for where we are in the tree.
Matrix current = tx.Value;
if (g.Transform != null)
{
// Note, Matrix is a struct, so this modifies our local copy without
// affecting the one in the ''tx'' Transforms.
current.Append(g.Transform.Value);
}
var accumulatedTransform = new MatrixTransform(current);
foreach (Drawing child in g.Children)
{
WalkDrawingForGlyphRuns(glyphList, accumulatedTransform, child);
}
}
}
}
Si necesita el texto para un efecto, ¿podría ser suficiente con la imagen del texto renderizado? Si es así, podría usar un VisualBrush o System.Windows.Media.Imaging.RenderTargetBitmap
También reproduje en .Net framework con el siguiente xaml:
<Window x:Class="TestC1Grid.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
TextOptions.TextFormattingMode="Display"
TextOptions.TextRenderingMode="Auto"
ResizeMode="CanResizeWithGrip"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock TextTrimming="CharacterEllipsis"
FontFamily="Tahoma"
FontSize="12"
HorizontalAlignment="Stretch"
TextAlignment="Left" xml:lang="nl-nl">My-Text</TextBlock>
<TextBlock Grid.Column="1" TextTrimming="CharacterEllipsis"
FontFamily="Tahoma"
FontSize="12"
IsHyphenationEnabled="True">My-Text</TextBlock>
<TextBlock Grid.Column="2" TextTrimming="CharacterEllipsis"
FontFamily="Tahoma"
FontSize="12"
IsHyphenationEnabled="True">My-Text</TextBlock>
</Grid>
</Grid>
</Window>
si elimina TextOptions.TextFormattingMode = "Mostrar"
TextOptions.TextRenderingMode = "Auto"
o eliminar xml: lang = "nl-nl" funciona bien