我可以提供一个相对简单的方法来解决这个问题。
首先,需要注意到可以将UI元素放入 FlowDocument
中。这使得以下操作成为可能:
<RichTextBox>
<FlowDocument>
<Paragraph>
<InlineUIContainer>
<TextBlock>This is your label: </TextBlock>
</InlineUIContainer>
<Run>And this is the editable text.</Run>
</Paragraph>
</FlowDocument>
</RichTextBox>
现在的问题是如何防止用户编辑InlineUIContainer
,这实际上是两个问题。
第一个问题是防止用户选择它。为了做到这一点,你必须处理SelectionChanged
事件。在事件中,找到RTB文档中第一个InlineUIContainer
,如果Selection.Start
在它之前,就将其更改。
private void RichTextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
RichTextBox rtb = (RichTextBox) sender;
if (rtb == null) return;
InlineUIContainer c = rtb.Document
.Blocks
.Where(x => x is Paragraph)
.Cast<Paragraph>()
.SelectMany(x => x.Inlines)
.Where(x => x is InlineUIContainer)
.Cast<InlineUIContainer>()
.FirstOrDefault();
if (c == null) return;
if (rtb.Selection.Start.CompareTo(c.ElementEnd) < 0)
{
rtb.Selection.Select(c.ElementEnd, rtb.Selection.End);
}
}
也许有更简单的方法来构建LINQ查询,但我喜欢这种方式。虽然这不是百分之百完美的;如果你在文本中选择并向左拖动TextBlock
,它将失去选中状态。我相信这可以修复。但它运行得相当不错。它甚至处理了用户使用箭头键导航的情况。
只需要这么多就可以接近成功了。但另一个可能会引起问题的地方是,如果用户将光标放在文本开头并按下BACKSPACE键。
处理这个需要类似的方法:将插入点位置与第一个InlineUIElement
的末尾位置进行比较,并且如果插入点位于该位置,则取消BACKSPACE操作(通过标记事件为已处理):
private void RichTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key != Key.Back)
{
return;
}
RichTextBox rtb = (RichTextBox)sender;
if (rtb == null) return;
InlineUIContainer c = rtb.Document
.Blocks
.Where(x => x is Paragraph)
.Cast<Paragraph>()
.SelectMany(x => x.Inlines)
.Where(x => x is InlineUIContainer)
.Cast<InlineUIContainer>()
.FirstOrDefault();
if (c == null) return;
if (rtb.CaretPosition.CompareTo(c.ElementEnd.GetInsertionPosition(LogicalDirection.Forward)) <= 0)
{
e.Handled = true;
}
}