2010-11-30 12 views
22

Tengo un TextBlock sencilla definido como estoObtener texto que se muestran de TextBlock

<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 da salida como esta

alt text

Esto hará que me "TextBlock: Se muestra el texto"

string text = myTextBlock.Text; 

¿Pero hay alguna manera de obtener el texto que se muestra realmente en la pantalla?
Significado "TextBlock: Pantalla ..."

Gracias

+1

Me encantaría saber por qué querrías hacer esto ... – Guy

+0

@Guy: Hehe, buena pregunta :) Estoy creando un efecto para TextBlock, pero para hacerlo necesitaré el texto mostrado –

Respuesta

15

Usted puede hacer esto mediante la recuperación primero el objeto Drawing que representa el aspecto de la TextBlock en el árbol visual, y luego a pie que busca GlyphRunDrawing elementos - que contendrán el texto real presentado en la pantalla. He aquí una aplicación muy áspera y listo:

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 instrumento de prueba que acabo de escribir - el primer método es un clic de botón manejador simplemente para facilitar la experimentación.

Utiliza el VisualTreeHelper para obtener el Drawing renderizado para el TextBlock - eso solo funcionará si la cosa ya se ha procesado por cierto. Y luego el método WalkDrawingForText hace el trabajo real, simplemente atraviesa el árbol Drawing en busca de texto.

Esto no es terriblemente inteligente: asume que los objetos GlyphRunDrawing aparecen en el orden en que los quiere. Para su ejemplo particular, lo hace - obtenemos un GlyphRunDrawing que 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 quiere hacer esto más robusto , necesitaría 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 añadir:

Aquí hay un bosquejo de cómo un ejemplo de posición podría ser conscientes. 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); 
      } 
     } 
    } 
} 
+0

¡De nada! Acabo de agregar una ilustración de cómo podría abordar el enfoque más complejo que toma en cuenta la posición de cada GlyphRunDrawing, en lugar de simplemente esperar que salga en el orden correcto. –

1

Bueno, es un poco de una petición específica así que no estoy seguro de que hay una función ya hecha en el marco de 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. Eso es, por supuesto, asumiendo que el recorte solo ocurrirá desde la derecha.

+0

Gracias por tu respuesta. Sí, esa es una forma de hacerlo. Esperaba más de algo así como una solución Reflection –

+0

Por lo que puedo decir, el contenido de un cuadro de texto se procesa en un TextBoxView (que es todo lo que necesita) y se coloca dentro de un ScrollViewer que luego proporciona el recorte. No estoy seguro de qué reflejo tiene para ofrecer, pero buena suerte. – Moonshield

+0

Sí, para un TextBox que es verdad. Estoy preguntando sobre un TextBlock con TextTrimming establecido en CharacterEllipsis. En cuanto a lo que puedo decir, lo único en el VisualTree es el propio TextBlock. No estoy seguro de lo que el reflejo tiene que ofrecer tampoco, pero fue solo una cosa que me vino a la mente :) –

10

Después de hurgar I Reflector por un tiempo, me encontré con lo siguiente:

System.Windows.Media.TextFormatting.TextCollapsedRange 

que tiene una propiedad Length que contiene el número de caracteres que no se muestran (se encuentran en la parte/oculta colapsado 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 se puede acceder directamente desde el objeto TextBlock. Parece que es parte del código que utiliza WPF para pintar el texto en la pantalla.

Podría 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.

+1

Realmente traté de obtener el valor pero no estabas bromeando .. :) TextBlock tiene un campo TextBlockCache llamado _textBlockCache, a partir del cual se crea una línea. Esta línea está formateada con un montón de campos internos y propiedades de TextBlock. La línea tiene una línea de texto creada a partir del bloque de texto y de la línea, y finalmente se invoca colapso en la línea de texto y luego obtenemos TextCollapsedRange. Intenté simular todo esto con la reflexión, pero recibí una excepción en Formato ... –

1

Si necesita el texto para un efecto, ¿podría ser suficiente con la imagen del texto procesado? Si es así usted podría utilizar un VisualBrush o System.Windows.Media.Imaging.RenderTargetBitmap

+0

¡Gracias por tu respuesta! Sí, tal vez, en el peor de los casos. También estoy buscando guardar el TextBlock en una imagen y luego traducirlo a texto –

0

También he reproducido 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 se quita TextOptions.TextFormattingMode = "Display"
TextOptions.TextRenderingMode = "Auto"

o eliminar xml: lang = "nl-nl" está funcionando bien

Cuestiones relacionadas