如何让WPF RichTextBox双击可选中含句号的完整缩写如i.a.
解决方案
方案1:自定义RichTextBox重写双击逻辑
直接接管双击选词流程,自定义词边界规则即可实现需求,核心逻辑是双击时从点击位置向前后遍历,将连续的「字母/缩写点」划分为同一个选中范围:
实现代码
public class CustomRichTextBox : RichTextBox { protected override void OnMouseDoubleClick(MouseButtonEventArgs e) { if (e.ChangedButton != MouseButton.Left) { base.OnMouseDoubleClick(e); return; } // 拿到点击位置的指针 TextPointer clickPos = GetPositionFromPoint(e.GetPosition(this), true); if (clickPos == null) { base.OnMouseDoubleClick(e); return; } // 向前找词的起始边界 TextPointer start = clickPos; while (start != null) { // 获取前一个字符 var prevChar = GetPreviousChar(start); if (!prevChar.HasValue || !IsAbbreviationChar(prevChar.Value)) { break; } start = start.GetNextContextPosition(LogicalDirection.Backward); } // 向后找词的结束边界 TextPointer end = clickPos; while (end != null) { var nextChar = GetNextChar(end); if (!nextChar.HasValue || !IsAbbreviationChar(nextChar.Value)) { break; } end = end.GetNextContextPosition(LogicalDirection.Forward); } // 选中范围 if (start != null && end != null) { Selection.Select(start, end); e.Handled = true; } else { base.OnMouseDoubleClick(e); } } // 判断是否属于缩写允许的字符:大小写字母、英文句号 private bool IsAbbreviationChar(char c) { return char.IsLetter(c) || c == '.'; } // 获取指针前一个字符 private char? GetPreviousChar(TextPointer pointer) { var backPos = pointer.GetNextContextPosition(LogicalDirection.Backward); if (backPos == null) return null; var text = backPos.GetTextInRun(LogicalDirection.Forward, 1); return text.Length > 0 ? text[0] : null; } // 获取指针后一个字符 private char? GetNextChar(TextPointer pointer) { var forwardPos = pointer.GetNextContextPosition(LogicalDirection.Forward); if (forwardPos == null) return null; var text = forwardPos.GetTextInRun(LogicalDirection.Forward, 1); return text.Length > 0 ? text[0] : null; } }
XAML使用方式
<local:CustomRichTextBox> <FlowDocument> <Paragraph>Reason for contact: i.a.</Paragraph> </FlowDocument> </local:CustomRichTextBox>
方案2:附加行为实现(无需自定义控件)
如果不想继承RichTextBox,可以用WPF附加行为将逻辑注入到现有控件,使用方式更灵活:
public static class RichTextBoxBehavior { public static readonly DependencyProperty EnableAbbreviationSelectProperty = DependencyProperty.RegisterAttached( "EnableAbbreviationSelect", typeof(bool), typeof(RichTextBoxBehavior), new PropertyMetadata(false, OnEnableAbbreviationSelectChanged)); public static void SetEnableAbbreviationSelect(DependencyObject element, bool value) { element.SetValue(EnableAbbreviationSelectProperty, value); } public static bool GetEnableAbbreviationSelect(DependencyObject element) { return (bool)element.GetValue(EnableAbbreviationSelectProperty); } private static void OnEnableAbbreviationSelectChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not RichTextBox rtb) return; if ((bool)e.NewValue) { rtb.PreviewMouseDoubleClick += Rtb_PreviewMouseDoubleClick; } else { rtb.PreviewMouseDoubleClick -= Rtb_PreviewMouseDoubleClick; } } private static void Rtb_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e) { // 此处逻辑和方案1的OnMouseDoubleClick逻辑完全一致 if (e.ChangedButton != MouseButton.Left) return; var rtb = (RichTextBox)sender; TextPointer clickPos = rtb.GetPositionFromPoint(e.GetPosition(rtb), true); if (clickPos == null) return; // 向前找起始 TextPointer start = clickPos; while (start != null) { var prevChar = GetPreviousChar(start); if (!prevChar.HasValue || !IsAbbreviationChar(prevChar.Value)) break; start = start.GetNextContextPosition(LogicalDirection.Backward); } // 向后找结束 TextPointer end = clickPos; while (end != null) { var nextChar = GetNextChar(end); if (!nextChar.HasValue || !IsAbbreviationChar(nextChar.Value)) break; end = end.GetNextContextPosition(LogicalDirection.Forward); } if (start != null && end != null) { rtb.Selection.Select(start, end); e.Handled = true; } } private static bool IsAbbreviationChar(char c) => char.IsLetter(c) || c == '.'; private static char? GetPreviousChar(TextPointer pointer) { var backPos = pointer.GetNextContextPosition(LogicalDirection.Backward); return backPos == null ? null : backPos.GetTextInRun(LogicalDirection.Forward, 1).FirstOrDefault(); } private static char? GetNextChar(TextPointer pointer) { var forwardPos = pointer.GetNextContextPosition(LogicalDirection.Forward); return forwardPos == null ? null : forwardPos.GetTextInRun(LogicalDirection.Forward, 1).FirstOrDefault(); } }
附加行为使用方式
<RichTextBox local:RichTextBoxBehavior.EnableAbbreviationSelect="True"> <FlowDocument> <Paragraph>Reason for contact: i.a.</Paragraph> </FlowDocument> </RichTextBox>
规则扩展说明
如果需要适配其他缩写场景,直接修改IsAbbreviationChar方法的判断逻辑即可:
- 要支持带数字的缩写(如
v2.0),可增加char.IsDigit(c)判断 - 要排除单独出现的句号不被选中,可额外增加边界校验逻辑,判断点前后是否为字母
内容的提问来源于stack exchange,提问作者Jesper
相关产品推荐
相关产品推荐

