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

如何在WPF的WebView2组件中实现TextBox同款Edit->Find功能?

WebView2实现循环查找下一个匹配项功能

问题描述

需要在WPF/C#/JavaScript环境下为WebView2组件实现与TextBox(MainWindow1)完全一致的"Edit->Find"功能:点击FindForward按钮可多次查找下一个匹配字符串,所有匹配项查找完成后从头循环。

已实现的TextBox控件代码参考:

MainWindow1 GUI:

Mainwindow1

MainWindow1 XML:

<Window x:Class="WpfApp1.MainWindow1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Loaded="Window_Loaded"
        Title="MainWindow1" Height="450" Width="800">
    <DockPanel LastChildFill="True">
        <StackPanel Orientation="Horizontal"  
               DockPanel.Dock="Top" Background="Aqua">
            <TextBox Name="TboxFind" Width="80" Text="id"/>
            <Button Name="FindForward" Content="FindForward"
                    Click="FindForward_Click"/>
        </StackPanel>

        <TextBox Name="textbox1" VerticalScrollBarVisibility="Auto"/>

    </DockPanel>
</Window>

MainWindow1 C#:

using System.Text.RegularExpressions;
using System.Windows; using System.Windows.Controls;
namespace WpfApp1 {
  public partial class MainWindow1 : Window {
    public MainWindow1() {InitializeComponent();}
    private void Window_Loaded(object sender, RoutedEventArgs e) {
      string text1 = "";
      for (int i = 0; i < 10000; i++) {
        text1 = text1 + "id" + i.ToString() + "\n";}
      textbox1.Text = text1;textbox1.Focus();textbox1.CaretIndex = 0;
    }
    private void TextBoxGotoLine(TextBox textbox1, int linenum) {
      var target_cpos 
         = textbox1.GetCharacterIndexFromLineIndex(linenum);
      var target_char_rect 
         = textbox1.GetRectFromCharacterIndex(target_cpos);
      var first_char_rect = textbox1.GetRectFromCharacterIndex(0);
            textbox1.ScrollToVerticalOffset(target_char_rect.Top 
                  - first_char_rect.Top);
    }
    private void FindForward_Click(object sender, RoutedEventArgs e) {
      string pattern = @"(?i)(" + Regex.Escape(TboxFind.Text) + @")";
      string text1 = textbox1.Text.Substring(
          textbox1.CaretIndex + textbox1.SelectionLength);
      var match1 = Regex.Match(text1, pattern);
      if (match1.Success) {
        textbox1.Focus();
        textbox1.Select(textbox1.CaretIndex 
         + textbox1.SelectionLength 
         + match1.Index, match1.Groups[0].Length);
      } //if
    } //function
}/*class*/  }/*namespace*/ 

当前WebView2基础代码(MainWindow2)已完成环境配置,缺少核心查找逻辑:

MainWindow2 GUI:

Mainwindow2

MainWindow2 XML:

<Window x:Class="WpfApp1.MainWindow2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wv2
="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"        
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Loaded="Window_Loaded"
Title="MainWindow2" Height="450" Width="800" >
    <DockPanel LastChildFill="True">
        <StackPanel Orientation="Horizontal"  
                 DockPanel.Dock="Top" Background="Aqua">
            <TextBox Name="SearchStr" Width="80" Text="id"/>
            <Button Name="FindForward" 
                 Content="FindForward" Click="FindForward_Click"/>
        </StackPanel>

        <wv2:WebView2 Name="webview2" CoreWebView2InitializationCompleted
          ="webview2_CoreWebView2InitializationCompleted" />

    </DockPanel>
</Window>

MainWindow2 C#(未完成):

using System.Windows;  using System.Threading;
using Microsoft.Web.WebView2.Core;
namespace WpfApp1 {
  public partial class MainWindow2 : Window {
  public MainWindow2() {InitializeComponent(); SearchStr.Focus(); }
  private async void Window_Loaded(object sender, RoutedEventArgs e) {
    await webview2.EnsureCoreWebView2Async();   
  }
        
  private void webview2_CoreWebView2InitializationCompleted(
    object sender, CoreWebView2InitializationCompletedEventArgs e)
  {
    string html = "";
    for (int i = 0; i < 100; i++) {
      string id = "id" + i.ToString();
      html = html + "<b>" + id + "</b><br/>";
    }
    webview2.CoreWebView2.NavigateToString(html);
  }
  private async Tasks.Task<string> Find(string pattern) {
    string js = "";
    js = js + "var m1 = document.getElementById(\"body\")";
    js = js + "/*... ??? what goes here ??? */";
    // Find and highlight one at a time, and scroll into view ...
    // repeat find from beginning of html body when done ...
    // See MainWindow1 example with TextBox for desired behavior here.
    return await webview2.ExecuteScriptAsync(js);
  }
  private void async FindForward_Click(object s, RoutedEventArgs e) {
    await Find(SearchStr.Text);
  }
}/*class*/  }/*namespace*/

解决方案

要实现和TextBox一致的循环查找功能,需要在C#中维护查找状态,并编写JavaScript逻辑处理DOM内的查找、高亮和滚动,具体修改如下:

1. 修改MainWindow2的C#代码

添加变量记录当前查找状态,修正异步方法语法,注入全局查找逻辑:

using System.Windows;
using Microsoft.Web.WebView2.Core;
namespace WpfApp1 {
  public partial class MainWindow2 : Window {
    // 维护查找状态:当前搜索关键词、下一次查找的起始节点
    private string _currentSearchText = "";
    private int _currentMatchIndex = -1;

    public MainWindow2() {
      InitializeComponent(); 
      SearchStr.Focus(); 
    }

    private async void Window_Loaded(object sender, RoutedEventArgs e) {
      await webview2.EnsureCoreWebView2Async();   
      // 初始化时注入全局查找逻辑的JS
      await InjectFindScript();
    }
        
    private void webview2_CoreWebView2InitializationCompleted(
      object sender, CoreWebView2InitializationCompletedEventArgs e)
    {
      string html = "";
      for (int i = 0; i < 100; i++) {
        string id = "id" + i.ToString();
        html = html + "<b>" + id + "</b><br/>";
      }
      webview2.CoreWebView2.NavigateToString(html);
    }

    // 注入全局JavaScript查找函数
    private async System.Threading.Tasks.Task InjectFindScript() {
      string js = @"
        // 全局变量存储匹配结果和当前索引
        window.findMatches = [];
        window.currentMatchIdx = -1;
        window.lastSearchText = '';

        // 查找所有匹配项并返回结果
        function findAllMatches(searchText) {
          // 清除之前的高亮
          clearHighlights();
          if (!searchText.trim()) return [];

          const bodyText = document.body.innerText;
          const regex = new RegExp(searchText, 'gi');
          const matches = [];
          let match;
          
          // 遍历所有匹配项,记录位置和文本
          while ((match = regex.exec(bodyText)) !== null) {
            matches.push({
              start: match.index,
              length: match[0].length,
              text: match[0]
            });
            // 避免无限循环,当匹配空字符串时退出
            if (match[0].length === 0) break;
          }
          return matches;
        }

        // 高亮指定匹配项
        function highlightMatch(index) {
          if (window.findMatches.length === 0 || index < 0 || index >= window.findMatches.length) {
            clearHighlights();
            return false;
          }

          clearHighlights();
          const match = window.findMatches[index];
          const range = document.createRange();
          const selection = window.getSelection();
          
          // 定位到匹配文本的范围
          let charIndex = 0;
          const traverseTextNodes = (node) => {
            if (node.nodeType === Node.TEXT_NODE) {
              if (charIndex + node.length >= match.start) {
                const startOffset = match.start - charIndex;
                const endOffset = Math.min(node.length, startOffset + match.length);
                range.setStart(node, startOffset);
                range.setEnd(node, endOffset);
                return true;
              }
              charIndex += node.length;
            } else {
              for (let child of node.childNodes) {
                if (traverseTextNodes(child)) return true;
              }
            }
            return false;
          };
          
          traverseTextNodes(document.body);
          selection.removeAllRanges();
          selection.addRange(range);
          
          // 滚动到匹配位置
          range.startNode.parentElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
          return true;
        }

        // 清除所有高亮
        function clearHighlights() {
          const selection = window.getSelection();
          selection.removeAllRanges();
        }

        // 查找下一个匹配项,循环从头开始
        function findNext(searchText) {
          // 如果搜索关键词变化,重新查找所有匹配
          if (searchText !== window.lastSearchText) {
            window.findMatches = findAllMatches(searchText);
            window.currentMatchIdx = -1;
            window.lastSearchText = searchText;
          }

          if (window.findMatches.length === 0) return false;
          
          // 移动到下一个索引,超出则循环到0
          window.currentMatchIdx = (window.currentMatchIdx + 1) % window.findMatches.length;
          return highlightMatch(window.currentMatchIdx);
        }
      ";
      await webview2.ExecuteScriptAsync(js);
    }

    // 调用JS的查找函数
    private async System.Threading.Tasks.Task Find(string pattern) {
      if (string.IsNullOrWhiteSpace(pattern)) return;
      await webview2.ExecuteScriptAsync($"findNext('{EscapeJsString(pattern)}')");
    }

    // 转义JS字符串中的特殊字符
    private string EscapeJsString(string input) {
      return input.Replace("'", "\\'").Replace("\"", "\\\"").Replace("\n", "\\n").Replace("\r", "\\r");
    }

    private async void FindForward_Click(object s, RoutedEventArgs e) {
      await Find(SearchStr.Text);
    }
  }
}

2. 关键逻辑说明

  • C#端:通过InjectFindScript将查找逻辑注入WebView2全局上下文,调用findNextJS函数执行下一次查找,同时通过EscapeJsString处理搜索文本的特殊字符,避免语法错误。
  • JavaScript端:
    • findAllMatches:根据搜索关键词生成所有匹配项的位置信息,使用正则表达式全局匹配。
    • highlightMatch:递归遍历DOM文本节点定位匹配位置,创建选区并滚动到视图中心,模拟TextBox的选中效果。
    • findNext:处理循环逻辑,当所有匹配项查找完成后重置索引到0;若搜索关键词变化,重新生成匹配列表。

3. 注意事项

  • 文本节点遍历逻辑确保在复杂HTML结构中也能精确定位匹配内容。
  • 滚动使用scrollIntoView实现平滑定位,提升用户体验。
  • 每次搜索关键词变化时自动重置匹配列表,保证查找准确性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 06:24:22