Reputation: 124
After setting my RichTextBox's text to the string T, the Caret Position in the RichTextBox is "lost" (it goes to the start of it). Here's what I'm doing to try to "restore" it after it is "lost":
public static int GetCaretIndex(RichTextBox C)
{
return new TextRange(C.Document.ContentStart, C.CaretPosition).Text.Length;
}
...
int CaretIndex = GetCaretIndex(C); // Get the Caret position before setting the text of the RichTextBox
new TextRange(C.Document.ContentStart, C.Document.ContentEnd).Text = T; // Set the text of the RichTextBox
C.CaretPosition = C.Document.ContentStart.GetPositionAtOffset(CaretIndex, LogicalDirection.Forward); // Set the Caret Position based on the "Caret Index" variable
This code, however, does not work. The "restored" Caret is at a different position than the "original" one (always behind the "original" one for some reason).
"Saving" the RichTextBox's CaretPosition as a TextPointer doesn't seem to work either.
Can anyone provide me with an alternative way of "restoring" the Caret, or a way to fix the code above?
Upvotes: 5
Views: 2090
Reputation: 31
I found the simplest solution was just to compare the text before and after the change.
Here's what that looks like:
string _preText = "";
private void SaveCursor()
{
_preText = new TextRange(RTB.Document.ContentStart, RTB.CaretPosition).Text;
}
private void RestoreCursor()
{
var startPos = RTB.Document.ContentStart;
var newPos = RTB.Document.ContentStart;
string _postText = "";
while (newPos != null)
{
_postText = new TextRange(startPos, newPos).Text;
if (_preText == _postText)
break;
newPos = newPos.GetNextContextPosition(LogicalDirection.Forward);
}
RTB.CaretPosition = newPos;
}
Then in practice, you would just sandwich the two methods around your update.
private void KeyUse_Editor(object sender, System.Windows.Input.KeyEventArgs e)
{
SaveCursor();
//Whatever your update method is
UpdateText();
RestoreCursor();
}
This way if you make a change to the underlying structure, as long as the text is the same, it will be easy to find the new FlowDocument position.
Upvotes: 0
Reputation: 630
In my situation I have a RichTextBox with a single Paragraph that only allows entering text and line breaks. I change the structure of the RichTextBox ( by creating different coloured Run instances ) but not the text and restore after the change.
public static class CaretRestorer
{
public static void Restore(RichTextBox richTextBox, Action changer)
{
var caretPosition = GetCaretPosition(richTextBox);
changer();
Restore(richTextBox, caretPosition);
}
private static string GetFullText(RichTextBox richTextBox)
{
return new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd).Text;
}
private static int GetInlineTextLength(Inline inline)
{
if(inline is LineBreak)
{
return 2;
}
return new TextRange(inline.ContentStart, inline.ContentEnd).Text.Length;
}
private static void Restore(RichTextBox richTextBox,int caretPosition)
{
var inlines = GetInlines(richTextBox);
var accumulatedTextLength = 0;
foreach (var inline in inlines)
{
var inlineTextLength = GetInlineTextLength(inline);
var newAccumulatedTextLength = accumulatedTextLength + inlineTextLength;
if (newAccumulatedTextLength >= caretPosition)
{
TextPointer newCaretPosition = null;
if(inline is LineBreak)
{
newCaretPosition = inline.ContentEnd;
}
else
{
var diff = caretPosition - accumulatedTextLength;
newCaretPosition = inline.ContentStart.GetPositionAtOffset(diff);
}
richTextBox.CaretPosition = newCaretPosition;
break;
}
else
{
accumulatedTextLength = newAccumulatedTextLength;
}
}
}
private static int GetCaretPosition(RichTextBox richTextBox)
{
return new TextRange(richTextBox.Document.ContentStart, richTextBox.CaretPosition).Text.Length;
}
private static Paragraph GetParagraph(RichTextBox RichTextBox)
{
return RichTextBox.Document.Blocks.FirstBlock as Paragraph;
}
private static InlineCollection GetInlines(RichTextBox RichTextBox)
{
return GetParagraph(RichTextBox).Inlines;
}
}
Upvotes: 0
Reputation: 83
I was dealing with a similar issue recently and there is my solution. In my case, I'm creating a new RichTextBox.Document content and when I do this, I want to keep the caret position.
My idea was that caret offset functions are biased thanks to data structures used for text representation (Paragraphs, Runs, ...) which are also somehow calculated to offset position.
TextRange is a good approach to get exact caret position in the text. The problem lays in its restoration. But it gets easy when I know from which components my document is constructed. In my case, there are just Paragraphs and Runs.
What remains is to visit document structure, find an exact run where the caret should be and set the caret to correct position of found run.
Code:
// backup caret position in text
int backPosition =
new TextRange(RichTextBox.CaretPosition.DocumentStart, RichTextBox.CaretPosition).Text.Length;
// set new content (caret position is lost there)
RichTextBox.Document.Blocks.Clear();
SetNewDocumentContent(RichTextBox.Document);
// find position and run to which place caret
int pos = 0; Run caretRun = null;
foreach (var block in RichTextBox.Document.Blocks)
{
if (!(block is Paragraph para))
continue;
foreach (var inline in para.Inlines){
{
if (!(inline is Run run))
continue;
// find run to which place caret
if (caretRun == null && backPosition > 0)
{
pos += run.Text.Length;
if (pos >= backPosition){
caretRun = run;
break;
}
}
}
if (caretRun!=null)
break;
}
// restore caret position
if (caretRun != null)
RichTextBox.CaretPosition =
caretRun.ContentEnd.GetPositionAtOffset(backPosition - pos, LogicalDirection.Forward);
The code is not tested. I assembled it from various parts of my application. Let me know if you find any issue.
Upvotes: 4
Reputation: 1175
Seems to work (for me):
C.CaretPosition = C.Document.ContentStart;
C.CaretPosition = C.CaretPosition.GetPositionAtOffset(CaretIndex, LogicalDirection.Forward);
(I hate RichTextBox by the way.)
Upvotes: 5