简体   繁体   中英

WPF RichTextBox position caret to visible character with given index

I've been looking for a fast method in WPF to programmatically set the cursor to a specified visible symbol index.

The problem is that just by using Document.ContentStart.GetPositionAtOffset(cursorIndex, LogicalDirection.Forward) I didn't get the desired result as this method counts invisible symbols as well - such as Run start and Run end symbols. There are almost always some demarkation symbols in the document so this always ends up with the cursor being before the desired position.

So what is a fast, simple and elegant method to position the caret to the specified index by only accounting for the visible symbols ?

I have come up with the following solution:

public virtual void SetCursorIndex(Int32 cursorIndex)
{
    // If the specified index is less than or equal to 0, then we simply
    // position the caret to the start of the document.
    if (cursorIndex <= 0)
    {
        CaretPosition = Document.ContentStart;
        return;
    }

    // If the specified index is greater than or equal to the total length, we simply
    // position the caret to the end of the document.
    String fullText = new TextRange(Document.ContentStart, Document.ContentEnd).Text;
    Int32 totalTextLength = fullText.Length;
    if (cursorIndex >= totalTextLength)
    {
        CaretPosition = Document.ContentEnd;
        return;
    }

    // (*)
    TextPointer endPtr = Document.ContentStart
        .GetPositionAtOffset(cursorIndex, LogicalDirection.Forward);
    TextRange range = new TextRange(Document.ContentStart, endPtr);
    Int32 diff = cursorIndex - range.Text.Length;
    while (diff != 0)
    {
        endPtr = endPtr.GetPositionAtOffset(diff, LogicalDirection.Forward);
        range = new TextRange(Document.ContentStart, endPtr);
        diff = cursorIndex - range.Text.Length;

        // Overindexing, in this case we went over the document's length so we
        // position the caret to the end of the document as a safety measure.
        if (diff < 0)
        {
            endPtr = Document.ContentEnd;
            break;
        }
    }

    CaretPosition = endPtr;
}

The parts before // (*) are self-explanatory. From there, we do the following:

  • We use the build-in mechanism to get a text pointer at the cursorIndex logical position (relative to the start of the document) - again, this includes invisible symbols. But if it does, we can't further than the desired visible character index, only before it. If it does not include any invisible symbols, then this method gives us a TextPointer that is positioned right where we want it.
  • We create a TextRange object that is bounded by the start of the document and the TextPointer created previously.
  • We calculate the difference of the cursorIndex and the Length of the text in the TextRange object. This is the amount by which we iteratively advance our pointer until the difference is 0. This is a simple heuristic approach that is a little bit faster than iteratively advancing it by 1. It is based on the fact that if the TextRange object contains any invisible symbols, then the count of visible symbols is never greater than the number of positions we must advance our endPtr TextPointer so we do just this - we advance the endPtr by the difference of cursorIndex and range.Length . If there are any invisible symbols between the desired positon and the current positon pointed to by endPtr then we still won't completely reach the desired position - that's why we do the advancement of endPtr in a while -loop testing for the difference of the length of the text contained by range and cursorIndex being 0.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM