从 TextBlock 获取显示的文本

发布于 2024-10-04 21:44:57 字数 679 浏览 2 评论 0原文

我有一个像这样定义的简单 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:显示...”

谢谢

I have a simple TextBlock defined like this

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

Which outputs like this

alt text

This will get me "TextBlock: Displayed text"

string text = myTextBlock.Text;

But is there a way to get the text that's actually displayed on the screen?
Meaning "TextBlock: Display..."

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

寂寞陪衬 2024-10-11 21:44:57

您可以通过首先检索表示视觉树中 TextBlock 外观的 Drawing 对象,然后遍历该对象查找 GlyphRunDrawing items - 这些将包含屏幕上实际呈现的文本。这是一个非常粗略且现成的实现:

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

You can do this by first retrieving the Drawing object that represents the appearance of the TextBlock in the visual tree, and then walk that looking for GlyphRunDrawing items - those will contain the actual rendered text on the screen. Here's a very rough and ready implementation:

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

This is a direct excerpt from a little test harness I just wrote - the first method's a button click handler just for ease of experimentation.

It uses the VisualTreeHelper to get the rendered Drawing for the TextBlock - that'll only work if the thing has already been rendered by the way. And then the WalkDrawingForText method does the actual work - it just traverses the Drawing tree looking for text.

This isn't terribly smart - it assumes that the GlyphRunDrawing objects appear in the order you'll want them. For your particular example it does - we get one GlyphRunDrawing containing the truncated text, followed by a second one containing the ellipsis character. (And by the way, it's just one unicode character - codepoint 2026, and if this editor lets me paste in unicode characters, it's "…". It's not three separate periods.)

If you wanted to make this more robust, you would need to work out the positions of all those GlyphRunDrawing objects, and sort them, in order to process them in the order in which they appear, rather than merely hoping that WPF happens to produce them in that order.

Updated to add:

Here's a sketch of how a position-aware example might look. Although this is somewhat parochial - it assumes left-to-right reading text. You'd need something more complex for an internationalized solution.

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);
            }
        }
    }
}
如梦 2024-10-11 21:44:57

在围绕 I Reflector 研究了一段时间后,我发现了以下内容:

System.Windows.Media.TextFormatting.TextCollapsedRange 

它有一个 Length 属性,其中包含未显示的字符数(位于文本行的折叠/隐藏部分)。知道该值后,只需进行减法即可获得显示的字符。

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

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

After rooting around I Reflector for a while, I found the following:

System.Windows.Media.TextFormatting.TextCollapsedRange 

which has a Length property that contains the number of characters that are NOT displayed (are in the collapsed/hidden portion of the text line). Knowing that value, it's just a matter of subtraction to get the characters that ARE displayed.

This property is not directly accessible from the TextBlock object. It looks like it is part of the code that is used by WPF to actually paint the text on the screen.

It could end up being quite a lot of fooling around to actually get the value of this property for the text line in your TextBlock.

-黛色若梦 2024-10-11 21:44:57

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

Well, it's a bit of a specific request so I'm not sure there's a ready made function in the framework to do it. What I would do is to calculate the logical width of each character, divide the ActualWidth of the TextBlock by this value and there you have the number of characters from the start of the string that are visible. That is of course assuming that clipping will only occur from the right.

琉璃繁缕 2024-10-11 21:44:57

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

If you need the text for an effect - might it then be enough with the image of the rendered text?
If so you could use a VisualBrush or System.Windows.Media.Imaging.RenderTargetBitmap

溺渁∝ 2024-10-11 21:44:57

接受的答案确实是正确的解决方案。现在,如果您想检查给定的 TextBlock 是否溢出,这里有一个扩展方法可以完成此任务。

public static class TextBlockExtensions
{
    public static bool HasOverflowed(this TextBlock textBlock) =>
        GetGlyphRuns(textBlock).Any(IsEllipsisGlyphRun);
    
    private static bool IsEllipsisGlyphRun(GlyphRun run) =>
        run.Characters.Count == 1 && run.Characters[0] == '\u2026';

    private static IEnumerable<GlyphRun> GetGlyphRuns(Visual visual) =>
        GetGlyphRuns(VisualTreeHelper.GetDrawing(visual));

    private static IEnumerable<GlyphRun> GetGlyphRuns(Drawing drawing)
    {
        if (drawing is GlyphRunDrawing glyphRunDrawing)
        {
            yield return glyphRunDrawing.GlyphRun;
        }
        else if (drawing is DrawingGroup drawingGroup)
        {
            foreach (Drawing child in drawingGroup.Children)
            {
                foreach (var glyphRun in GetGlyphRuns(child))
                {
                    yield return glyphRun;
                }
            }
        }
    }
}

The accepted answer is indeed the right solution. Now, if you want to check if a given TextBlock has overflowed, here's an extension method that does exactly that.

public static class TextBlockExtensions
{
    public static bool HasOverflowed(this TextBlock textBlock) =>
        GetGlyphRuns(textBlock).Any(IsEllipsisGlyphRun);
    
    private static bool IsEllipsisGlyphRun(GlyphRun run) =>
        run.Characters.Count == 1 && run.Characters[0] == '\u2026';

    private static IEnumerable<GlyphRun> GetGlyphRuns(Visual visual) =>
        GetGlyphRuns(VisualTreeHelper.GetDrawing(visual));

    private static IEnumerable<GlyphRun> GetGlyphRuns(Drawing drawing)
    {
        if (drawing is GlyphRunDrawing glyphRunDrawing)
        {
            yield return glyphRunDrawing.GlyphRun;
        }
        else if (drawing is DrawingGroup drawingGroup)
        {
            foreach (Drawing child in drawingGroup.Children)
            {
                foreach (var glyphRun in GetGlyphRuns(child))
                {
                    yield return glyphRun;
                }
            }
        }
    }
}
ζ澈沫 2024-10-11 21:44:57

我还使用以下 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="Auto"

或删除
xml:lang="nl-nl"
工作正常

Also I reproduced on .Net framework with the following 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>

if you remove
TextOptions.TextFormattingMode="Display"
TextOptions.TextRenderingMode="Auto"

or remove
xml:lang="nl-nl"
is working ok

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文