从 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);
这是我刚刚编写的一个小测试工具的直接摘录 - 第一个方法是一个按钮单击处理程序,只是为了便于实验。
它使用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 的位置考虑在内,而不是简单地希望它们以正确的顺序出现。【参考方案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?