Когда вы добавляете и удаляете текст из FlowDocument, все TextPointers корректируют свое положение на основе ряда эвристик, разработанных так, чтобы они оставались как можно ближе к одному и тому же «месту».
Для удалений это просто: если TextPointer находится в удаленном тексте, он заканчивается между символами, которые окружали удаленный текст. Но для вставок это не так просто: когда текст или другие элементы вставляются в FlowDocument точно в существующий TextPointer, должен ли TextPointer заканчиваться до или после вставленного текста? TextPointer имеет свойство под названием «LogicalDirection», которое управляет этим.
Что происходит в вашем случае, так это то, что позиция "caretBefore", которую вы захватываете, - это в точности TextPosition, в который вставляется напечатанный символ, а в ваших тестовых примерах ваш LogicalDirection имеет значение LogicalDirection.Forward. Таким образом, когда символ вставлен, ваш «caretBefore» заканчивается после вставленного символа, что совпадает с TextPosition, давая вам пустой TextRange.
Как TextPointer получает назначенное ему логическое направление? Если вы щелкнете по RichTextBox, чтобы установить позицию каретки, щелчок интерпретируется как наличие между двумя символами. Если фактическая точка, по которой вы щелкнули, была на втором символе, для LogicalDirection задано значение «Вперед», но если фактическая точка, на которую вы щелкнули, была первым символом, для LogicalDirection будет задано значение «Назад».
Попробуйте этот эксперимент:
- Установите свой FontSize = "40" и предварительно заполните RichTextBox с текстом "ABCD" в конструкторе
- Щелкните по правой стороне B и введите «X» между B и C. LogicalDirection имеет значение Backward, поэтому ваш beforeCaret заканчивается перед «X», а в MessageBox отображается «X».
- Нажмите на левую сторону C и введите «X» между B и C. LogicalDirection - Forward, поэтому ваш beforeCaret заканчивается после «X», а ваш MessageBox пуст.
Это поведение нелогично: если вы не знаете, что существует LogicalDirection, вы можете подумать, что нажатие на правую сторону B или левую сторону C даст вам точно такую же позицию каретки.
Примечание. Простой способ визуализировать происходящее состоит в том, чтобы указать свой MessageBox.Show и вместо этого выполнить caretBefore.InsertTextInRun("^");
Как вы добились нужного вам результата? LogicalDirection только для чтения. Одним из способов является использование TextRange для принудительного создания TextPointer с LogicalDirection of Backward:
caretBefore = new TextRange(caretBefore, caretBefore.DocumentEnd).Start;
Сделайте это в PreviewKeyDown. Если вы дождетесь PreviewKeyUp, то уже слишком поздно: caretBefore переместился. Это работает, потому что, насколько я могу судить, начало непустого TextRange всегда имеет LogicalDirection of Backward.
Другой вариант - сохранить смещение символа от начала документа (обратите внимание, что это не смещение символа!). В этом случае вы можете сохранить смещение в PreviewKeyDown:
caretBeforeOffset = caretBefore.DocumentStart.OffsetToPosition(caretBefore);
и сбросьте значение caretBefore до того же смещения символа в PreviewKeyUp:
caretBefore = caretBefore.DocumentStart.GetPositionAtOffset(caretBeforeOffset,
LogicalDirection.Forward);
Несмотря на то, что это работает, оно не является настолько общим, как принуждение вашего TextPointer иметь LogicalDirection of Backward: Любой текст, ранее измененный в документе между PreviewKeyDown и PreviewKeyUp, приведет к тому, что вычисление смещения символа найдет неправильное расположение, как и для TextPointers. были разработаны, чтобы исправить в первую очередь.
Я не знаю каких-либо хороших ресурсов для изучения TextPointers, кроме чтения документации и игры с ними, а это именно то, что вы уже делали.