如何在Wpf RichTextBox中“恢复”插入符位置?

4

将RichTextBox的文本设置为字符串T后,RichTextBox中的插入符位置会“丢失”(跳到了开头)。以下是我尝试在其“丢失”后“恢复”它的方法:

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

然而,这段代码并没有起作用。 "恢复"的插入符号位置与"原始"位置不同(由于某种原因始终在"原始"位置之后)。

将RichTextBox的CaretPosition保存为TextPointer似乎也不起作用。

有人能提供另一种“恢复”插入符号的方法,或者修复上面的代码吗?


你检索一个索引并设置一个位置。根据文档,它们不是相同的。尝试保存插入符号位置而不是插入符号索引。你似乎正在替换整个内容 - 如果有新文本,恢复插入符号的意义是什么?特别是,如果插入符号在接近末尾的位置,而新文本较短,应该发生什么? - Cee McSharpface
@dlatikay 尝试将CaretPosition保存为TextPointer会使“恢复”的指针回到RichTextBox的开头。我正在替换整个内容以实现撤消/重做系统(请参见:https://dev59.com/PnDYa4cB1Zd3GeqPAG_L)。回答您的第二个问题,似乎没有发生任何“不同”的事情,插入符号只是移到了“原始”插入符号行上方的段落,或者向后移动了几个字符。 - Polygons
4个回答

4
我最近遇到了类似的问题,并想到了一个解决方案。在我的情况下,我正在创建一个新的RichTextBox.Document内容,当我这样做时,我想保持插入符号位置不变。
我的想法是,由于用于文本表示(段落、运行等)的数据结构对偏移位置也有所计算,因此插入符号偏移功能存在偏差。
TextRange是获取文本中确切插入符号位置的好方法。问题在于如何恢复它。但是,当我知道我的文档由哪些组件构成时,就很容易了。在我的情况下,只有段落和运行。
剩下的就是访问文档结构,找到插入符号应该在的确切运行位置,并将插入符号设置为找到的运行的正确位置。
代码:
// 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);

这段代码未经过测试,是我从应用程序的各个部分组装而成的。如果您发现任何问题,请告诉我。


4

似乎对我有用:

C.CaretPosition = C.Document.ContentStart; //将光标位置设置为文档开头 C.CaretPosition = C.CaretPosition.GetPositionAtOffset(CaretIndex, LogicalDirection.Forward); //将光标移动到指定偏移量处

(顺便说一下,我讨厌RichTextBox.)


1
那个(因为某种奇怪的原因)“效果更好”。当新文本更改文本大小时仍然存在一些问题,但我相信通过一些尝试我可以让它正常工作(一旦我让它正常工作,我会在这里发布)。“顺便说一下,我讨厌RichTextBox。” - 你不是唯一一个。 - Polygons
这很简短、明显且直接。只是希望CaretPosition不要设置两次,但我愿意接受这一点。 - Joel Rondeau

0

我发现最简单的解决方案就是比较变化前后的文本。

这是它的样子:

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;
}

然后在实践中,您只需将这两种方法夹在更新方法之间即可。

private void KeyUse_Editor(object sender, System.Windows.Input.KeyEventArgs e)
{
    SaveCursor();
    //Whatever your update method is
    UpdateText();
    RestoreCursor();
}

这样,如果您对底层结构进行更改,只要文本保持不变,就可以轻松找到新的FlowDocument位置。

0
在我的情况下,我有一个 RichTextBox,其中只有一个段落,允许输入文本和换行符。我更改 RichTextBox 的结构(通过创建不同颜色的 Run 实例),但不更改文本,并在更改后还原。
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;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接