Подтвердить что ты не робот

Получить отображаемый текст из TextBlock

У меня есть простой 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: Display..."

Спасибо

4b9b3361

Ответ 1

Вы можете сделать это, сначала извлекая объект 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 -, который будет работать, только если вещь уже была обработана. И тогда метод WalkDrawingForText выполняет фактическую работу - он просто пересекает дерево Drawing, ищущее текст.

Это не очень умно - предполагается, что объекты GlyphRunDrawing отображаются в том порядке, в котором вы захотите. Для вашего конкретного примера он делает - мы получаем один GlyphRunDrawing содержащий усеченный текст, а затем второй, содержащий символ многоточия. (И, кстати, это всего лишь один символ Юникода - код 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);
            }
        }
    }
}

Ответ 2

После некоторого обхода вокруг я Reflector я обнаружил следующее:

System.Windows.Media.TextFormatting.TextCollapsedRange 

у которого есть свойство Length, которое содержит количество символов, которые НЕ отображаются (находятся в сложенной/скрытой части текстовой строки). Зная эту ценность, это просто вопрос вычитания, чтобы получить символы, которые ARE отображаются.

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

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

Ответ 3

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

Ответ 4

Если вам нужен текст для эффекта - возможно, этого будет достаточно с изображением визуализированного текста? Если это так, вы можете использовать VisualBrush или System.Windows.Media.Imaging.RenderTargetBitmap

Ответ 5

Также я воспроизвел на .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 = "Дисплей"
      TextOptions.TextRenderingMode = "Авто"

или удалить XML: LANG = "п-п" работает нормально