Как восстановить позицию каретки в Wpf RichTextBox?

После установки моего текста 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

Однако этот код не работает. «Восстановленный» Карет находится в другом положении, чем «оригинальный» (по какой-то причине всегда позади «оригинального»).

«Сохранение» CaretPosition RichTextBox в качестве TextPointer, похоже, тоже не работает.

Может ли кто-нибудь предоставить мне альтернативный способ «восстановления» каретки или способ исправить приведенный выше код?


person Polygons    schedule 28.05.2017    source источник
comment
вы получаете индекс и устанавливаете позицию. согласно документации, они не совпадают. попробуйте сохранить позицию каретки вместо индекса каретки. вы как бы подменяете весь контент - какой смысл восстанавливать каретку, если там новый текст? особенно, что должно произойти, если знак вставки был где-то ближе к концу, а новый текст короче?   -  person Cee McSharpface    schedule 29.05.2017
comment
@dlatikay Попытка сохранить CaretPosition в качестве TextPointer приводит к тому, что восстановленный указатель переходит в начало RichTextBox. Я заменяю весь контент для системы отмены/повтора (см.: stackoverflow.com/questions/15772602/). Чтобы ответить на ваш второй вопрос, похоже, ничего другого не происходит, курсор просто переходит к абзацу над исходной строкой курсора или возвращается на несколько символов назад.   -  person Polygons    schedule 29.05.2017


Ответы (3)


Кажется, работает (для меня): C.CaretPosition = C.Document.ContentStart; C.CaretPosition = C.CaretPosition.GetPositionAtOffset(CaretIndex, LogicalDirection.Forward);

(Кстати, я ненавижу RichTextBox.)

person Maciek Świszczowski    schedule 28.05.2017
comment
Это (по какой-то странной причине) работает лучше. Все еще возникают некоторые проблемы, когда новый текст меняет размер текста, но я почти уверен, что, немного поигравшись, я смогу заставить его работать (опубликую его здесь, как только он заработает). Между прочим, я ненавижу RichTextBox. - вы не единственный. - person Polygons; 29.05.2017

Недавно я имел дело с подобной проблемой, и есть мое решение. В моем случае я создаю новый контент 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);

Код не проверен. Я собрал его из разных частей своего приложения. Дайте мне знать, если вы обнаружите какие-либо проблемы.

person Dave    schedule 16.05.2018

В моей ситуации у меня есть 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;
    }
}
person user487779    schedule 16.07.2020