简体   繁体   English

如何在WPF RichTextBox中跟踪TextPointer?

[英]How to keep track of TextPointer in WPF RichTextBox?

I'm trying to get my head around the TextPointer class in a WPF RichTextBox. 我试图让我了解WPF RichTextBox中的TextPointer类。

I would like to be able to keep track of them so that I can associate information with areas in the text. 我希望能够跟踪它们,以便可以将信息与文本中的区域相关联。

I am currently working with a very simple example to try and figure out what is going on. 我目前正在使用一个非常简单的示例来尝试弄清楚发生了什么。 In the PreviewKeyDown event I am storing the caret position and then in the PreviewKeyUp event I am creating a TextRange based on the before and after caret positions. 在PreviewKeyDown事件中,我存储插入符位置,然后在PreviewKeyUp事件中,基于插入符位置之前和之后创建TextRange。 Here is a code sample that illustrates what I am trying to do: 这是一个代码示例,它说明了我正在尝试执行的操作:

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

The problem is that the text that I get is blank. 问题是我得到的文本为空白。 For example, if I type the character 'a' then I would expect to find the text "a" in the TextRange. 例如,如果我键入字符“ a”,那么我希望在TextRange中找到文本“ a”。

Does anyone know what is going wrong? 有人知道出什么事了吗? It could be something very simple but I've spent an afternoon getting nowhere. 这可能很简单,但是我花了一个下午无所事事。

I am trying to embrace the new WPF technology but find that the RichTextBox in particular is so complicated that it makes even doing simple things like this difficult. 我试图拥抱新的WPF技术,但是发现RichTextBox特别复杂,以至于即使做这样的简单事情也很困难。 If anyone has any links that do a good job of explaining the TextPointer, I would appreciate it if you can let me know. 如果有人有任何链接可以很好地解释TextPointer,请告诉我,我们将不胜感激。

When you add and remove text from a FlowDocument, all the TextPointers adjust their position based on a number of heuristics designed to make them stay in as close to the same "place" as possible. 当您在FlowDocument中添加和删除文本时,所有TextPointer都会根据多种启发式方法来调整其位置,这些启发式方法旨在使它们停留在尽可能靠近同一“位置”的位置。

For deletions this is simple: If the TextPointer is in the deleted text, it ends up betweem the characters that had been surrounding the deleted text. 对于删除而言,这很简单:如果TextPointer在删除的文本中,则它将终止于删除的文本周围的字符之间。 But for insertions it is not so simple: When text or other elements are inserted into a FlowDocument exactly at an existing TextPointer, should the TextPointer end up before or after the inserted text? 但是对于插入而言,并不是那么简单:当将文本或其他元素恰好在现有TextPointer处插入到FlowDocument中时,TextPointer应该在插入的文本之前还是之后结束? TextPointer has a property called "LogicalDirection" that controls this. TextPointer具有一个称为“ LogicalDirection”的属性来控制此属性。

What is happening in your case is that the "caretBefore" position you are capturing is exactly the TextPosition where the typed character is inserted, and in your test cases your LogicalDirection is LogicalDirection.Forward. 在您的情况下,正在捕获的“ caretBefore”位置正好是插入键入字符的TextPosition,在您的测试案例中,您的LogicalDirection是LogicalDirection.Forward。 Thus when the character is inserted, your "caretBefore" is ending up after the inserted character, which coincides with TextPosition giving you an empty TextRange. 因此,当插入字符时,您的“ caretBefore”在插入的字符之后结束,这与TextPosition一致,为您提供了一个空的TextRange。

How does a TextPointer get a LogicalDirection assigned to it? TextPointer如何获得分配给它的LogicalDirection? If you click on a the RichTextBox to set the caret position, the click is interpreted as being between two characters. 如果单击RichTextBox来设置插入标记的位置,则单击将被解释为介于两个字符之间。 If the actual point you clicked was on the second character, LogicalDirection is set to Forward, but if the actual point you clicked on was the first character, LogicalDirection is set to Backward. 如果您单击的实际点在第二个字符上,则将LogicalDirection设置为“前进”,但是如果您单击的实际点在第一个字符上,则将LogicalDirection设置为“后退”。

Try this experiment: 试试这个实验:

  1. Set your FontSize="40" and prepopulate the RichTextBox with the text "ABCD" in the constructor 设置您的FontSize =“ 40”并在构造函数中用文本“ ABCD”预先填充RichTextBox
  2. Click on the right side of the B and type an "X" between the B and the C. LogicalDirection is Backward, so your "beforeCaret" ends up before the "X" and your MessageBox shows the "X". 单击B的右侧,然后在B和C之间键入“ X”。LogicalDirection是Backward,因此您的“ beforeCaret”在“ X”之前结束,并且MessageBox显示“ X”。
  3. Click on the left side of the C and type an "X" between the B and the C. LogicalDirection is Forward, so your "beforeCaret" ends up after the "X" and your MessageBox is empty. 单击C的左侧,然后在B和C之间键入“ X”。LogicalDirection是Forward,因此您的“ beforeCaret”在“ X”之后结束,并且MessageBox为空。

This behaviour is counterintuitive: When you don't know that LogicalDirection exists, you would think that clicking on the right-hand side of the B or the left-hand side of the C would give you exactly the same caret position. 此行为是违反直觉的:当您不知道存在LogicalDirection时,您会认为单击B的右侧或C的左侧将获得完全相同的插入符位置。

Note: An easy way to visualize what is going on is to command out your MessageBox.Show and instead do a caretBefore.InsertTextInRun("^"); 注意:可视化发生情况的一种简单方法是发出MessageBox.Show,然后执行caretBefore.InsertTextInRun("^");

How do you achieve the result you need? 您如何获得所需的结果? LogicalDirection is read-only. LogicalDirection是只读的。 One way is to use TextRange to force the construction of a TextPointer with a LogicalDirection of Backward: 一种方法是使用TextRange来强制构造LogicalDirection为Backward的TextPointer:

caretBefore = new TextRange(caretBefore, caretBefore.DocumentEnd).Start;

Do this in PreviewKeyDown. 在PreviewKeyDown中执行此操作。 If you wait until PreviewKeyUp it is already too late: caretBefore has moved. 如果您等到PreviewKeyUp,则为时已晚:caretBefore已移动。 This works because as far as I can tell, the Start of a non-empty TextRange always has a LogicalDirection of Backward. 之所以可行,是因为据我所知,非空TextRange的Start始终具有Backward的LogicalDirection。

Another option is to save the symbol offset from the beginning of the document (note that this is not a character offset!). 另一种选择是从文档的开头保存符号偏移量(请注意,这不是字符偏移量!)。 In this case you could store the offset in PreviewKeyDown: 在这种情况下,您可以将偏移量存储在PreviewKeyDown中:

caretBeforeOffset = caretBefore.DocumentStart.OffsetToPosition(caretBefore);

and reset caretBefore to the same symbol offset in PreviewKeyUp: 并将caretBefore重置为PreviewKeyUp中的相同符号偏移量:

caretBefore = caretBefore.DocumentStart.GetPositionAtOffset(caretBeforeOffset,
                                                            LogicalDirection.Forward);

Although this works it is not as general as forcing your TextPointer to have a LogicalDirection of Backward: Any text changes earlier in the document between the PreviewKeyDown and PreviewKeyUp will cause the symbol offset calculation to find the wrong location, which is what TextPointers were designed to fix in the first place. 尽管此方法有效,但不像强迫TextPointer具有向后的LogicalDirection那样通用:在文档中的PreviewKeyDown和PreviewKeyUp之间的任何较早文本更改都会导致符号偏移量计算找到错误的位置,这是TextPointers设计的目的首先解决。

I don't know of any good resources for learning about TextPointers except for reading the documentation and playing with them, which is exactly what you have already been doing. 除了阅读文档并与他们一起玩之外,我不知道用于学习TextPointers的任何有用资源,而这正是您已经在做的事情。

For me TextPointer before = yourRichTextBox.CaretPosition.GetPositionAtOffset(-1, LogicalDirection.Backward); 对我TextPointer before = yourRichTextBox.CaretPosition.GetPositionAtOffset(-1, LogicalDirection.Backward); works in order to obtain the position of the character that is just before the Caret. 为了获得字符的位置,该字符位于插入符号之前。 You can then obtain the TextRange for the inserted character with TextRange range = new TextRange(before, yourRichTextBox.CaretPosition); 然后,可以得到TextRange用于与插入的字符TextRange range = new TextRange(before, yourRichTextBox.CaretPosition); (You should be checking for null when using before because if there's nothing before the caret it'll be null ) (使用before时,您应该检查null ,因为如果插入符号前没有任何内容,它将为null

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM