You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 11:06:04