2010-11-30 13 views
22

Ich habe einen einfachen Textblock wie dieseGet angezeigt Text von TextBlock-

definierte
<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> 

Welche diese Ausgänge wie

alt text

Dies erhält mich "TextBlock-: Angezeigter Text"

string text = myTextBlock.Text; 

Aber gibt es eine Möglichkeit, den Text, der tatsächlich auf dem Bildschirm angezeigt wird, zu erhalten?
Bedeutung „TextBlock-: Display ...“

Dank

+1

Ich würde gerne wissen, warum Sie das tun möchten ... – Guy

+0

@Guy: Hehe, gute Frage :) Ich erstelle einen Effekt für den TextBlock, aber dazu brauche ich den angezeigten Text –

Antwort

15

Sie dies, indem man zuerst das Abrufen das Drawing Objekts tun kann, die das Aussehen der TextBlock in der visuellen Struktur darstellt, und dann zu Fuß, dass die Suche nach GlyphRunDrawing Elemente - diese enthalten den tatsächlich gerenderten Text auf dem Bildschirm. Hier ist eine sehr grobe und bereit Umsetzung:

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); 
      } 
     } 
    } 
} 

Dies ist ein direkter Auszug aus einem kleinen Test-Harnisch ich gerade geschrieben hätte - die erste Methode ist, einen Button-Klick-Handler nur für einfache Experimente.

Es verwendet die VisualTreeHelper, um das gerenderte Drawing für die TextBlock - das wird nur funktionieren, wenn das Ding übrigens schon gerendert wurde. Und dann die WalkDrawingForText Methode tut die eigentliche Arbeit - es durchquert nur die Drawing Baum auf der Suche nach Text.

Das ist nicht besonders schlau - es geht davon aus, dass die GlyphRunDrawing Objekte in der Reihenfolge erscheinen, in der Sie sie wollen. Für Ihr spezielles Beispiel - wir erhalten einen GlyphRunDrawing, der den abgeschnittenen Text enthält, gefolgt von einem zweiten, der das Ellipsenzeichen enthält.(Und übrigens, es ist nur ein Unicode-Zeichen - Codepunkt 2026, und wenn dieser Editor mich Unicode-Zeichen einfügen lässt, ist es "...". Es ist nicht drei separate Perioden.)

Wenn Sie dies robuster machen wollten Sie müssten die Positionen all dieser GlyphRunDrawing Objekte ausarbeiten und sortieren, um sie in der Reihenfolge zu verarbeiten, in der sie erscheinen, anstatt nur zu hoffen, dass WPF sie in dieser Reihenfolge erzeugt.

hinzufügen Aktualisiert:

Hier ist eine Skizze, wie ein Positions bewusst Beispiel aussehen könnte. Obwohl dies etwas engstirnig ist - es wird von links nach rechts gelesener Text angenommen. Sie würden etwas Komplexeres für eine internationalisierte Lösung benötigen.

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

zu übersetzen. Gern geschehen! Ich fügte nur eine Illustration hinzu, wie man den komplexeren Ansatz, der die Position jedes GlyphRunDrawings berücksichtigt, anstatt einfach zu hoffen, dass sie in der richtigen Reihenfolge kommen, nachvollziehen kann. –

1

Nun, es ist ein bisschen von einer bestimmten Anforderung so bin ich nicht sicher, dass es im Rahmen, es zu tun eine fertige Funktion ist. Was ich tun würde, ist die logische Breite jedes Zeichens zu berechnen, dividiere die ActualWidth des TextBlocks durch diesen Wert und dort hast du die Anzahl der Zeichen vom Anfang des Strings, die sichtbar sind. Das setzt natürlich voraus, dass das Clipping nur von rechts erfolgt.

+0

Danke für Deine Antwort. Ja, das ist ein Weg, es zu tun. Ich hatte gehofft mehr von etwas wie eine Reflection-Lösung –

+0

Soweit ich das beurteilen kann, wird der Inhalt einer Textbox auf eine TextBoxView gerendert (die so breit wie nötig ist) und in einem ScrollViewer platziert, der dann den Clipping bereitstellt. Nicht sicher, was Reflektion zu bieten hat, aber viel Glück. – Moonshield

+0

Ja, für eine TextBox, die wahr ist. Ich frage nach einem TextBlock mit TextTrimming, der auf CharacterEllipsis gesetzt ist. Soweit ich sagen kann, ist das einzige Ding im VisualTree der TextBlock selbst. Ich bin nicht sicher, was Reflektion zu bieten hat, aber es war nur eine Sache, die mir in den Sinn kam :) –

10

Nach einer Weile ich Reflector Verwurzelung, fand ich folgendes:

System.Windows.Media.TextFormatting.TextCollapsedRange 

, die eine Length Eigenschaft hat, dass die Anzahl der Zeichen enthält, die nicht angezeigt werden (sind in der kollabierten/verdeckten Teil der Textzeile). Wenn man diesen Wert kennt, ist es nur eine Frage der Subtraktion, um die angezeigten Zeichen zu erhalten.

Auf diese Eigenschaft kann vom TextBlock-Objekt nicht direkt zugegriffen werden. Es scheint, dass es Teil des Codes ist, der von WPF verwendet wird, um den Text tatsächlich auf dem Bildschirm zu zeichnen.

Es könnte am Ende ziemlich viel herumalbern, um tatsächlich den Wert dieser Eigenschaft für die Textzeile in Ihrem TextBlock zu erhalten.

+1

Ich habe wirklich versucht, den Wert zu bekommen, aber Sie waren kein Scherz .. :) TextBlock hat ein Feld TextBlockCache namens _textBlockCache, daraus wird eine Zeile erstellt. Diese Zeile ist mit vielen internen Feldern und Eigenschaften aus dem TextBlock formatiert. Die Zeile enthält dann eine TextLine, die sowohl aus dem TextBlock als auch aus der Zeile erstellt wird, und schließlich wird Collapse in der TextLine aufgerufen, und dann erhalten wir den TextCollapsedRange. Versucht, dies alles mit Reflexion zu simulieren, aber ich bekomme eine Ausnahme im Format .. –

1

Wenn Sie den Text für einen Effekt brauchen - reicht es dann vielleicht mit dem Bild des gerenderten Textes? Wenn dies der Fall könnten Sie eine VisualBrush verwenden oder System.Windows.Media.Imaging.RenderTargetBitmap

+0

Danke für Ihre Antwort! Ja vielleicht, im schlimmsten Fall. Ich bin auch dabei, den TextBlock in ein Bild zu speichern und ihn dann zurück in Text –

0

Auch reproduziert ich auf .Net-Framework mit dem folgenden 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> 

wenn Sie TextOptions.TextFormattingMode = "Anzeige"
TextOptions.TextRenderingMode = "Auto"

entfernen oder entfernen xml: lang = "nl-nl" funktioniert ok