从 TextBlock 获取显示的文本

Posted

技术标签:

【中文标题】从 TextBlock 获取显示的文本【英文标题】:Get Displayed Text from TextBlock 【发布时间】:2011-05-18 04:04:26 【问题描述】:

我有一个这样定义的简单 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>

这样的输出

这会让我得到“TextBlock:显示的文本”

string text = myTextBlock.Text;

但是有没有办法获取实际显示在屏幕上的文本? 意思是“TextBlock:显示...”

谢谢

【问题讨论】:

我很想知道你为什么要这样做...... @Guy:呵呵,好问题 :) 我正在为 TextBlock 创建一个效果,但要做到这一点,我需要显示的文本 【参考方案1】:

您可以通过首先检索表示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);
            
        
    

这是我刚刚编写的一个小测试工具的直接摘录 - 第一个方法是一个按钮单击处理程序,只是为了便于实验。

它使用VisualTreeHelperTextBlock 获取渲染的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 的位置考虑在内,而不是简单地希望它们以正确的顺序出现。【参考方案2】:

在I Reflector周围扎根了一段时间,发现如下:

System.Windows.Media.TextFormatting.TextCollapsedRange 

它有一个Length 属性,其中包含不显示的字符数(在文本行的折叠/隐藏部分中)。知道了这个值,就可以得到 ARE 显示的字符了。

无法从 TextBlock 对象直接访问此属性。看起来它是 WPF 用来在屏幕上实际绘制文本的代码的一部分。

要真正获取 TextBlock 中文本行的此属性值,最终可能会花很多时间。

【讨论】:

我真的很想得到这个值,但你不是在开玩笑..:) TextBlock 有一个名为 _textBlockCache 的 TextBlockCache 字段,由此创建了一个 Line。此行使用来自 TextBlock 的大量内部字段和属性进行格式化。 Line 然后有一个从 TextBlock 和 Line 创建的 TextLine,最后在 TextLine 上调用 Collapse,然后我们得到 TextCollapsedRange。尝试用反射模拟所有这些,但我在 Format.. 中遇到异常【参考方案3】:

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

【讨论】:

感谢您的回答。是的,这是一种方法。我希望有更多类似反射解决方案的东西 据我所知,文本框的内容被渲染到 TextBoxView(它需要的宽度)并放置在 ScrollViewer 中,然后提供剪辑。不确定反射会提供什么,但祝你好运。 是的,对于一个文本框来说,这是真的。我在询问 TextTrimming 设置为 CharacterEllipsis 的 TextBlock。据我所知,VisualTree 中唯一的东西就是 TextBlock 本身。我也不确定反射能提供什么,但这只是我想到的一件事:) 哎呀,对不起,把这两个搞混了。我很想看看你的想法。【参考方案4】:

如果您需要文本来实现效果 - 那么渲染文本的图像是否就足够了? 如果是这样,您可以使用 VisualBrush 或 System.Windows.Media.Imaging.RenderTargetBitmap

【讨论】:

感谢您的回答!是的,也许,在最坏的情况下。我也在考虑将 TextBlock 保存为 Image,然后将其转换回 Text【参考方案5】:

我还使用以下 xaml 在 .Net 框架上进行了复制:

<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="nl-nl" 工作正常

【讨论】:

以上是关于从 TextBlock 获取显示的文本的主要内容,如果未能解决你的问题,请参考以下文章

如何在 AdornedElementPlaceholder 上使用 TextTrimming 获取 Textblock?

格式化 TextBlock 中的文本

教你玩转流程图控件GoJS:使用TextBlock类显示文本

如何让 WPF TextBlock 在多行上显示我的文本?

WPF TextBlock 根据搜索条件突出显示某些部分

UWP仅在TextBlock文本溢出时显示Tooltip