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

WPF(.NET6 C#)基于字符串索引的RichTextBox搜索实现优化问询

WPF(.NET 6) RichTextBox搜索功能标准实现方案

现有实现的问题

你当前的变通方案可以满足基础场景需求,但存在三个明显缺陷:

  • 全量提取TextRange.Text做字符串匹配,大文档场景下会产生不必要的内存开销
  • 通过循环调用GetNextInsertionPosition定位匹配终点,时间复杂度和关键词长度正相关,长文本+长关键词场景下卡顿明显
  • 文档存在超链接、内联UI元素、段落格式标记等非纯文本内容时,容易出现匹配位置错位——TextRange.Text返回的纯文本会过滤部分内部元素标记,和FlowDocument内部的符号计数规则不匹配。

标准实现方案

WPF FlowDocument的文本内容是按TextRun分块存储的,最规范高效的搜索实现是直接沿TextPointer遍历文本块,在遍历过程中完成匹配,从根源上避免纯文本索引和文档内部偏移不对应的问题。
可直接复用的实现代码如下:

/// <summary>
/// 在FlowDocument中搜索指定文本
/// </summary>
/// <param name="doc">待搜索的FlowDocument实例</param>
/// <param name="searchPattern">搜索关键词</param>
/// <param name="comparison">字符串匹配规则</param>
/// <param name="startPosition">搜索起始位置,不传默认从文档开头开始</param>
/// <returns>搜索结果</returns>
public static SearchResultValue FindText(FlowDocument doc, string searchPattern, StringComparison comparison, TextPointer startPosition = null)
{
    if (doc == null || string.IsNullOrEmpty(searchPattern))
        return new SearchResultValue(false, null);

    TextPointer currentPointer = startPosition ?? doc.ContentStart;
    int patternLength = searchPattern.Length;

    while (currentPointer != null && currentPointer.CompareTo(doc.ContentEnd) < 0)
    {
        // 跳过非文本类型的元素边界
        if (currentPointer.GetPointerContext(LogicalDirection.Forward) != TextPointerContext.Text)
        {
            currentPointer = currentPointer.GetNextContextPosition(LogicalDirection.Forward);
            continue;
        }

        // 读取当前文本块的内容
        string currentRunText = currentPointer.GetTextInRun(LogicalDirection.Forward);
        if (string.IsNullOrEmpty(currentRunText))
        {
            currentPointer = currentPointer.GetNextContextPosition(LogicalDirection.Forward);
            continue;
        }

        // 单文本块内匹配
        int matchIndex = currentRunText.IndexOf(searchPattern, comparison);
        if (matchIndex >= 0)
        {
            TextPointer matchStart = currentPointer.GetPositionAtOffset(matchIndex, LogicalDirection.Forward);
            TextPointer matchEnd = matchStart.GetPositionAtOffset(patternLength, LogicalDirection.Forward);
            return new SearchResultValue(true, new TextRange(matchStart, matchEnd));
        }

        // 处理跨文本块匹配(关键词被格式标记拆分到两个相邻Run的场景)
        int preCheckLength = Math.Min(patternLength - 1, currentRunText.Length);
        TextPointer crossCheckStart = currentPointer.GetPositionAtOffset(currentRunText.Length - preCheckLength, LogicalDirection.Forward);
        string crossCheckText = new TextRange(crossCheckStart, doc.ContentEnd).Text;
        int crossMatchIndex = crossCheckText.IndexOf(searchPattern, comparison);
        if (crossMatchIndex >= 0 && crossMatchIndex < preCheckLength)
        {
            TextPointer matchStart = crossCheckStart.GetPositionAtOffset(crossMatchIndex, LogicalDirection.Forward);
            TextPointer matchEnd = matchStart.GetPositionAtOffset(patternLength, LogicalDirection.Forward);
            return new SearchResultValue(true, new TextRange(matchStart, matchEnd));
        }

        // 移动到下一个文本块
        currentPointer = currentPointer.GetPositionAtOffset(currentRunText.Length, LogicalDirection.Forward);
    }

    return new SearchResultValue(false, null);
}

/// <summary>
/// 搜索操作返回结果
/// </summary>
/// <param name="Successful">是否匹配到内容</param>
/// <param name="Selection">匹配到的文本范围,无匹配时为null</param>
internal record struct SearchResultValue(bool Successful, TextRange? Selection);

实现优势

  • 不需要提前提取全量文档文本,大文档下内存占用更低
  • 不需要逐字符循环跳转定位终点,匹配到内容后直接通过偏移拿到起止位置,性能远高于逐位遍历的实现
  • 自动处理带格式文本、跨文本块关键词、内嵌元素等场景,不会出现位置错位
  • 完全兼容原有业务逻辑,支持从任意指定位置向后搜索。

内容的提问来源于stack exchange,提问作者Bjoern

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 16:36:43