从TextBlock获取显示文本

24
我有一个简单的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: Displayed text"

string text = myTextBlock.Text;

但是是否有一种方法可以获取实际显示在屏幕上的文本呢?
例如 "TextBlock:Display..."

谢谢


1
我很想知道你为什么想这样做... - Guy
@Guy: 嘿嘿,好问题 :) 我正在为 TextBlock 创建一个效果,但是为了做到这一点,我需要显示的文本。 - Fredrik Hedblad
5个回答

19

您可以通过首先检索代表TextBlock在可视树中外观的Drawing对象,然后查找其中的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来获取TextBlock的渲染Drawing - 顺便说一下,如果该项已被渲染,则只会起作用。然后WalkDrawingForText方法执行实际操作 - 它只是遍历Drawing树查找文本。

这并不是非常智能 - 它假定您希望按照顺序出现GlyphRunDrawing对象。对于您的特定示例,它可以 - 我们得到包含截断文本的一个GlyphRunDrawing,后跟一个包含省略号字符的第二个GlyphRunDrawing。(顺便说一下,这只是一个Unicode字符 - 码点2026,并且如果此编辑器允许我粘贴Unicode字符,则它是“…”。它不是三个单独的句点。)

如果您想使其更加健壮,那么您需要计算所有这些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);
            }
        }
    }
}

不客气!我刚刚添加了一张图示,展示了如何采用更复杂的方法,考虑每个GlyphRunDrawing的位置,而不是简单地希望它们以正确的顺序出现。 - Ian Griffiths
很好。可能需要添加 sb.AppendLine() 来获取换行符,而不是一个长文本。 - stijn

10

在研究了一段时间的Reflector后,我找到了以下内容:

System.Windows.Media.TextFormatting.TextCollapsedRange 

文本块对象有一个 Length 属性,它包含未显示的字符数(即在文本行的折叠/隐藏部分中)。通过了解该值,只需减法即可得到已显示的字符。

这个属性不能直接从 TextBlock 对象访问。看起来它是 WPF 用于实际绘制屏幕上文本的代码的一部分。

要实际获取文本块中文本行的此属性值可能需要进行许多琐碎的操作。


1
我真的尝试获取该值,但你并没有开玩笑..:) TextBlock有一个名为_textBlockCache的TextBlockCache字段,从中创建了一条Line。这个Line使用来自TextBlock的大量内部字段和属性进行格式化。然后,从TextBlock和Line创建了一个TextLine,最后在TextLine上调用Collapse,然后我们获得了TextCollapsedRange。尝试使用反射模拟所有这些操作,但在Format中出现异常。 - Fredrik Hedblad

1

这是一个比较具体的请求,所以我不确定框架中是否有现成的函数可以做到这一点。我的做法是计算每个字符的逻辑宽度,将TextBlock的ActualWidth除以此值,这样就可以得到从字符串开头开始可见的字符数。当然,这是假定裁剪只会发生在右侧。


谢谢您的回答。是的,那是一种方法。我希望更多像反射解决方案的东西。 - Fredrik Hedblad
据我所知,文本框的内容被呈现到一个TextBoxView中(它的宽度与需要的一样宽),并放置在一个ScrollViewer中,然后提供剪切。不确定反射能提供什么帮助,但祝好运。 - Moonshield
是的,对于一个 TextBox 是这样的。我问的是 TextBlock 并设置了 TextTrimming 为 CharacterEllipsis。据我所知,VisualTree 中唯一的东西就是 TextBlock 本身。我不确定反射能提供什么,但它只是我想到的其中一件事 :) - Fredrik Hedblad
抱歉,我总是把这两个搞混了。我很想看看你会得出什么结果。 - Moonshield

1

如果您只需要文本的效果,那么使用渲染后的图像是否足够呢?如果是这样,您可以使用VisualBrush或System.Windows.Media.Imaging.RenderTargetBitmap。


谢谢您的回答!是的,也许会是最坏的情况。我还在研究将TextBlock保存为图像,然后再将其转换回文本。 - Fredrik Hedblad

0

我也在 .Net 框架上复现了以下 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:lang="nl-nl" 都可以正常工作


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接