C# 如何在WPF RichTextBox中跟踪文本指针?

C# 如何在WPF RichTextBox中跟踪文本指针?,c#,wpf,richtextbox,C#,Wpf,Richtextbox,我试图在WPF RichTextBox中了解TextPointer类 我希望能够跟踪它们,以便将信息与文本中的区域相关联 我目前正在使用一个非常简单的示例来尝试找出发生了什么。在PreviewKeyDown事件中,我存储插入符号位置,然后在PreviewKeyUp事件中,我基于插入符号位置之前和之后创建TextRange。下面是一个代码示例,说明了我正在尝试做的事情: // The caret position before typing private TextPointer caretBef

我试图在WPF RichTextBox中了解TextPointer类

我希望能够跟踪它们,以便将信息与文本中的区域相关联

我目前正在使用一个非常简单的示例来尝试找出发生了什么。在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);
}
问题是我得到的文本是空白的。例如,如果我键入字符“a”,那么我希望在TextRange中找到文本“a”

有人知道出了什么问题吗?这可能很简单,但我花了一个下午却一事无成


我试图接受新的WPF技术,但发现RichTextBox特别复杂,即使是做这样简单的事情也很困难。如果有人有任何能很好地解释文本指针的链接,如果您能让我知道,我将不胜感激。

当您从FlowDocument中添加和删除文本时,所有文本指针都会根据一些启发式方法调整其位置,这些启发式方法旨在使它们尽可能靠近相同的“位置”

对于删除,这很简单:如果文本指针位于已删除的文本中,它将在已删除文本周围的字符之间结束。但对于插入来说,它并不是那么简单:当文本或其他元素正好在现有的TextPointer处插入到FlowDocument中时,TextPointer应该在插入的文本之前还是之后结束?TextPointer有一个名为“LogicalDirection”的属性来控制此操作

在您的案例中,您捕获的“caretBefore”位置正是插入键入字符的TextPosition,而在您的测试案例中,您的LogicalDirection是LogicalDirection.Forward。因此,当插入字符时,您的“caretBefore”将在插入字符后结束,这与TextPosition一致,为您提供了一个空的TextRange

文本指针如何获得分配给它的逻辑方向?如果单击RichTextBox中的某个字符以设置插入符号位置,则单击将被解释为介于两个字符之间。如果单击的实际点位于第二个字符上,则LogicalDirection设置为前进,但如果单击的实际点位于第一个字符上,则LogicalDirection设置为后退

试试这个实验:

  • 设置FontSize=“40”并在构造函数中用文本“ABCD”预填充RichTextBox
  • 单击B的右侧,在B和C之间键入一个“X”。逻辑方向是向后的,因此您的“beforeCaret”在“X”之前结束,您的消息框显示“X”
  • 单击C的左侧,在B和C之间键入一个“X”。逻辑方向是向前的,因此“beforeCaret”在“X”之后结束,消息框为空
  • 这种行为是违反直觉的:当您不知道逻辑方向存在时,您会认为单击B的右侧或C的左侧会给出完全相同的插入符号位置

    注意:可视化正在发生的事情的一个简单方法是命令您的MessageBox.Show,而不是执行
    caretBefore.InsertTextInRun(“^”)

    你如何达到你需要的结果?逻辑方向是只读的。一种方法是使用TextRange强制构造逻辑方向为Backward的TextPointer:

    caretBefore = new TextRange(caretBefore, caretBefore.DocumentEnd).Start;
    
    在PreviewKeyDown中执行此操作。如果等到PreviewKeyUp,已经太晚了:caretBefore已移动。这是因为据我所知,非空TextRange的开头总是有一个向后的逻辑方向

    另一个选项是保存文档开头的符号偏移量(请注意,这不是字符偏移量!)。在这种情况下,可以将偏移量存储在PreviewKeyDown中:

    caretBeforeOffset = caretBefore.DocumentStart.OffsetToPosition(caretBefore);
    
    并在PreviewKeyUp中将caretBefore重置为相同的符号偏移:

    caretBefore = caretBefore.DocumentStart.GetPositionAtOffset(caretBeforeOffset,
                                                                LogicalDirection.Forward);
    
    尽管这样做有效,但它并不像强制文本指针具有向后的逻辑方向那样通用:在PreviewKeyDown和PreviewKeyUp之间文档中先前的任何文本更改都会导致符号偏移量计算找到错误的位置,这正是TextPointers最初设计用来修复的位置


    我不知道有什么好的资源可以用来学习TextPointer,除了阅读文档和使用它们,这正是您一直在做的事情。

    对于我来说
    TextPointer before=yourRichTextBox.CaretPosition.GetPositionAtOffset(-1,LogicalDirection.Backward)用于获取插入符号前的字符位置。然后,您可以使用
    TextRange range=new TextRange(之前为yourRichTextBox.CaretPosition)获取插入字符的
    TextRange
    (在使用插入符号之前的
    时,您应该检查
    null
    ,因为如果插入符号之前没有任何内容,那么它将是
    null

    嗨,Ray,非常感谢您提供了这样一个信息丰富的回复。这是非常感谢,当然有助于澄清我的逻辑方向-我尝试了你的实验。我最终设想的是逐行处理文档。我知道GetLineStartPosition方法,但不知道您是否知道如何从TextPointer获取行尾的位置?再次感谢,您可以通过更改传入的计数来控制GetLineStartPosition的移动方式。如果您想了解更多有关此操作的详细信息,以及如何使用此操作获得行尾位置,请在StackOverflow上发布另一个问题。有足够的理由解释,它需要一个单独的问题和答案。此外,这些评论中的格式化功能也非常重要