Сделать отображаемый текст из кода

у меня есть простой 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>

вывод такой

alt text

это даст мне "TextBlock: отображаемый текст"

string text = myTextBlock.Text;

но есть ли способ, чтобы получить текст, который на самом деле отображается на экране?
Значение " TextBlock: Дисплей..."

спасибо

5 ответов


вы можете сделать это, извлекая Drawing объект, представляющий внешний вид TextBlock в визуальном дереве, а потом идти искать GlyphRunDrawing элементы-они будут содержать фактический отображаемый текст на экране. Вот очень грубая и готовая реализация:

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

это прямой отрывок из небольшой тестовой проводки, которую я только что написал - первый метод-обработчик нажатия кнопки для удобства экспериментов.

использует VisualTreeHelper чтобы получить рендеринг Drawing на TextBlock - это будет работать, только если вещь уже была оказана кстати. А потом ... --8--> метод делает фактическую работу - он просто обходит Drawing дерево ищет текст.

это не очень умно - это предполагает, что GlyphRunDrawing объекты отображаются в том порядке, в котором вы их хотите. Для вашего конкретного примера это так - мы получаем один GlyphRunDrawing содержащий усеченный текст, за которым следует второй, содержащий многоточие. (И кстати, это всего лишь один символ Юникода-codepoint 2026, и если этот редактор позволяет мне вставлять символы юникода, это "...". Это не три отдельных периода.)

если вы хотите сделать это более надежным, вам нужно будет разработать позиции всех этих GlyphRunDrawing объекты и сортировать их, чтобы обрабатывать их в том порядке, в котором они появляются, а не просто надеяться, что WPF случайно производит их в этом порядке.

обновлена добавить:

вот эскиз того, как может выглядеть пример с учетом позиции. Хотя это несколько ограниченно-он предполагает чтение текста слева направо. Вам нужно что-то более сложное для интернационализированного решения.

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

покопавшись некоторое время вокруг рефлектора, я обнаружил следующее:

System.Windows.Media.TextFormatting.TextCollapsedRange 

С Length свойство, содержащее количество символов, которые не отображаются (в свернутом/скрытая часть текста). Зная это значение, это просто вопрос вычитания, чтобы получить символы, которые отображаются.

это свойство не доступно напрямую из объекта TextBlock. Похоже, что это часть кода, который используется WPF для на самом деле нарисуйте текст на экране.

Это может оказаться довольно много дурачиться, чтобы на самом деле получить значение этого свойства для текстового строку в элемент TextBlock.


Ну, это немного конкретный запрос, поэтому я не уверен, что в рамках есть готовая функция для этого. Я бы сделал, чтобы вычислить логическую ширину каждого символа, разделить фактическую ширину TextBlock на это значение, и там у вас есть количество символов с начала строки, которые видны. Это, конечно, предполагает, что отсечение будет происходить только справа.


Если вам нужен текст для эффекта - может быть достаточно образа отображаемому тексту? Если это так, вы можете использовать VisualBrush или систему.Окна.Сми.Изображений.RenderTargetBitmap


также я воспроизвел на .Net framework со следующим 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>

если удалить TextOptions.TextFormattingMode= "Display"
TextOptions.TextRenderingMode= "Auto"

или удалить XML-код:Ланг="НЛ-НЛ" работает нормально