wpfflowdocumenttextrange

WPF: get TextRange from ListItem content


I am trying to get a TextRange from a WPF FlowDocument ListItem:

var doc = new FlowDocument();
doc.Blocks.Add(new List(new ListItem(new Paragraph(new Run("first bullet")))));

If I now try to get a TextRange with

var range1 = new TextRange(doc.ContentStart, doc.ContentEnd);

or

var range2 = new TextRange(doc.ContentStart.GetNextInsertionPosition(LogicalDirection.Forward), doc.ContentEnd);

I get a range where the Text property returns

•   first bullet

And if I try

var range3 = new TextRange(doc.ContentStart.GetNextInsertionPosition(LogicalDirection.Forward).GetNextInsertionPosition(LogicalDirection.Forward), doc.ContentEnd);

the Text property returns

irst bullet

The debugger shows:

range1.Start.Offset == 4
range2.Start.Offset == 4
range3.Start.Offset == 5

How could I create a TextRange which points to "first bullet" only (without the bullet and the separating tab)?


Solution

  • The problem here is not in TextRange itself (range1 points exactly to the text you need), but rather in its Text property. Let's refer to the implementation of ITextRange.Text.

    It calls GetText(ITextRange thisRange) from TextRangeBase internal class, that intentionally moves starting TextPoiner backwards to include initial list marker (if any) and after that calls GetTextInternal(ITextPointer startPosition, ITextPointer endPosition) with updated starting TextPointer.

    This behavior can be reproduced with reflection:

    var textRangeBase  = typeof(TextRange).Assembly.GetType("System.Windows.Documents.TextRangeBase");
    var getTextInternal = textRangeBase.GetMethod("GetTextInternal"
        , BindingFlags.NonPublic | BindingFlags.Static, null
        , new Type[] { typeof (TextPointer), typeof(TextPointer) }, null);
    
    var text1 = getTextInternal.Invoke(null, new[] { range1.Start, range1.End });
    
    var text2 = getTextInternal.Invoke(null, new[] { range1.Start
        .GetNextContextPosition(LogicalDirection.Backward)
        .GetNextContextPosition(LogicalDirection.Backward)
        .GetNextContextPosition(LogicalDirection.Backward)
        , range1.End });
    
    
    //Results:
    //text1: "first bullet\r\n"    
    //text2: "•\tfirst bullet\r\n"