Как отслеживать TextPointer в WPF RichTextBox? - PullRequest
8 голосов
/ 15 июня 2010

Я пытаюсь разобраться с классом TextPointer в WPF RichTextBox.

Я хотел бы иметь возможность отслеживать их, чтобы связать информацию с областями в тексте.

В настоящее время я работаю с очень простым примером, чтобы попытаться выяснить, что происходит.В событии PreviewKeyDown я сохраняю позицию каретки, а затем в событии PreviewKeyUp я создаю TextRange на основе позиций каретки до и после.Вот пример кода, который иллюстрирует то, что я пытаюсь сделать:

// The caret position before typing
private TextPointer caretBefore = null;

private void rtbTest_PreviewKeyDown(object sender, KeyEventArgs e)
{
    // Store caret position
    caretBefore = rtbTest.CaretPosition;
}

private void rtbTest_PreviewKeyUp(object sender, KeyEventArgs e)
{
    // Get text between before and after caret positions
    TextRange tr = new TextRange(caretBefore, rtbTest.CaretPosition);
    MessageBox.Show(tr.Text);
}

Проблема в том, что текст, который я получаю, является пустым.Например, если я наберу символ «а», то я ожидаю найти текст «а» в TextRange.

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

Я пытаюсь использовать новую технологию WPF, но обнаружил, что RichTextBox, в частности, настолько сложен, что делает даже такие простые вещи такимисложно.Если у кого-нибудь есть ссылки, которые хорошо объясняют TextPointer, я был бы признателен, если бы вы дали мне знать.

Ответы [ 2 ]

20 голосов
/ 15 июня 2010

Когда вы добавляете и удаляете текст из FlowDocument, все TextPointers корректируют свое положение на основе ряда эвристик, разработанных так, чтобы они оставались как можно ближе к одному и тому же «месту».

Для удалений это просто: если TextPointer находится в удаленном тексте, он заканчивается между символами, которые окружали удаленный текст. Но для вставок это не так просто: когда текст или другие элементы вставляются в FlowDocument точно в существующий TextPointer, должен ли TextPointer заканчиваться до или после вставленного текста? TextPointer имеет свойство под названием «LogicalDirection», которое управляет этим.

Что происходит в вашем случае, так это то, что позиция "caretBefore", которую вы захватываете, - это в точности TextPosition, в который вставляется напечатанный символ, а в ваших тестовых примерах ваш LogicalDirection имеет значение LogicalDirection.Forward. Таким образом, когда символ вставлен, ваш «caretBefore» заканчивается после вставленного символа, что совпадает с TextPosition, давая вам пустой TextRange.

Как TextPointer получает назначенное ему логическое направление? Если вы щелкнете по RichTextBox, чтобы установить позицию каретки, щелчок интерпретируется как наличие между двумя символами. Если фактическая точка, по которой вы щелкнули, была на втором символе, для LogicalDirection задано значение «Вперед», но если фактическая точка, на которую вы щелкнули, была первым символом, для LogicalDirection будет задано значение «Назад».

Попробуйте этот эксперимент:

  1. Установите свой FontSize = "40" и предварительно заполните RichTextBox с текстом "ABCD" в конструкторе
  2. Щелкните по правой стороне B и введите «X» между B и C. LogicalDirection имеет значение Backward, поэтому ваш beforeCaret заканчивается перед «X», а в MessageBox отображается «X».
  3. Нажмите на левую сторону 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, кроме чтения документации и игры с ними, а это именно то, что вы уже делали.

0 голосов
/ 02 апреля 2019

Для меня TextPointer before = yourRichTextBox.CaretPosition.GetPositionAtOffset(-1, LogicalDirection.Backward); работает для того, чтобы получить позицию персонажа, которая находится непосредственно перед кареткой.Затем вы можете получить TextRange для вставленного символа с помощью TextRange range = new TextRange(before, yourRichTextBox.CaretPosition); (Вы должны проверить null при использовании before, потому что если перед кареткой ничего нет, это будет null)

...