Reputation: 2929
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?
Upvotes: 1
Views: 973
Reputation: 2929
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:
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.TextRange
object that is bounded by the start of the document and the TextPointer
created previously.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.Upvotes: 1