Ответ 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);
}
}
}
}