如何在WPF的WebView2组件中实现TextBox同款Edit->Find功能?
WebView2实现循环查找下一个匹配项功能
问题描述
需要在WPF/C#/JavaScript环境下为WebView2组件实现与TextBox(MainWindow1)完全一致的"Edit->Find"功能:点击FindForward按钮可多次查找下一个匹配字符串,所有匹配项查找完成后从头循环。
已实现的TextBox控件代码参考:
MainWindow1 GUI:

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 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
相关产品推荐
相关产品推荐

