Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF FlowDocument - Absolute Character Position

I have a WPF RichTextBox that I am typing some text into and then parsing the whole of the text to do processing on. During this parse, I have the absolute character positions of the start and end of each word.

I would like to use these character positions to apply formatting to certain words. However, I have discovered that the FlowDocument uses TextPointer instances to mark positions in the document.

I have found that I can create a TextRange by constructing it with start and end pointers. Once I have the TextRange I can easily apply formatting to the text within it. I have been using GetPositionAtOffset to get a TextPointer for my character offset but suspect that its offset is different from mine because the selected text is in a slightly different position from what I expect.

My question is, how can I accurately convert an absolute character position to a TextPointer?

like image 786
Alan Spark Avatar asked Apr 02 '10 08:04

Alan Spark


3 Answers

I have also had this problem and has ended up with the following RichTextBox extension method. In my context it work flawlessly!

/// <summary>
/// Gets the text pointer at the given character offset.
/// Each line break will count as 2 chars.
/// </summary>
/// <param name="richTextBox">The rich text box.</param>
/// <param name="offset">The offset.</param>
/// <returns>The TextPointer at the given character offset</returns>
public static TextPointer GetTextPointerAtOffset(this RichTextBox richTextBox, int offset)
{
    var navigator = richTextBox.Document.ContentStart;
    int cnt = 0;

    while (navigator.CompareTo(richTextBox.Document.ContentEnd) < 0)
    {
        switch (navigator.GetPointerContext(LogicalDirection.Forward))
        {
            case TextPointerContext.ElementStart:
                break;
            case TextPointerContext.ElementEnd:
                if (navigator.GetAdjacentElement(LogicalDirection.Forward) is Paragraph)
                    cnt += 2;
                break;
            case TextPointerContext.EmbeddedElement:
                // TODO: Find out what to do here?
                cnt++;
                break;
            case TextPointerContext.Text:
                int runLength = navigator.GetTextRunLength(LogicalDirection.Forward);

                if (runLength > 0 && runLength + cnt < offset)
                {
                    cnt += runLength;
                    navigator = navigator.GetPositionAtOffset(runLength);
                    if (cnt > offset)
                        break;
                    continue;
                }
                cnt++;
                break;
        }

        if (cnt > offset)
            break;

        navigator = navigator.GetPositionAtOffset(1, LogicalDirection.Forward);

    } // End while.

    return navigator;
}
like image 52
Jesper Gissel Avatar answered Oct 20 '22 19:10

Jesper Gissel


I didn't find a reliable way of converting absolute character positions into TextPosition instances.

My alternative solution was to modify the original parse to work on individual runs rather than capturing the whole text of the RichTextBox. Working with character positions that are relative to a specific Run instance has proved reliable for me. I think that moving my mindset more towards the WPF way of thinking has helped.

I took the following approach for navigating runs in the FlowDocument (inspired by http://blogs.msdn.com/prajakta/archive/2006/10/12/customize-richtextbox-to-allow-only-plain-text-input.aspx):

// Get starting pointer
TextPointer navigator = flowDocument.ContentStart;

// While we are not at end of document
while (navigator.CompareTo(flowDocument.ContentEnd) < 0)
{
    // Get text pointer context
    TextPointerContext context = navigator.GetPointerContext(LogicalDirection.Backward);

    // Get parent as run
    Run run = navigator.Parent as Run;

    // If start of text element within run
    if (context == TextPointerContext.ElementStart && run != null)
    {
        // Get text of run
        string runText = run.Text;

        // ToDo: Parse run text
    }

    // Get next text pointer
    navigator = navigator.GetNextContextPosition(LogicalDirection.Forward);
}
like image 43
Alan Spark Avatar answered Oct 20 '22 19:10

Alan Spark


I had exact similar problem, I found out that there is a bug in RichTextBox because it does not count "new line characters - \r\n", so as your line numbers increase, you will find that your offset is positioned wrong by line number count, and I had solved my problems by offsetting line number from offset.

like image 26
Akash Kava Avatar answered Oct 20 '22 18:10

Akash Kava