最近在开发一个需要嵌入浏览器内核的桌面应用时,遇到了一个经典难题:如何平衡功能、性能和部署复杂度。传统的WebBrowser控件(即 IE 内核)早已过时且功能受限,而直接集成完整版 Chromium 又会让应用体积变得异常臃肿。这时,一个基于Microsoft Edge WebView2的“轻量级 Edge”方案进入了视野——它并非官方产品,而是一种利用 WebView2 运行时重建经典 Edge (EdgeHTML) 界面与交互模式的设计思路。本文将详细拆解如何利用 WebView2 控件,从零开始构建一个具备现代化浏览器核心、同时保留经典交互逻辑的桌面应用,涵盖环境搭建、核心功能实现、常见问题排查到生产级优化建议的全流程。
1. 背景与核心概念:为什么是 WebView2?
在深入代码之前,我们首先要厘清几个关键概念以及为什么这个组合方案有实际价值。
1.1 什么是 Microsoft Edge WebView2?WebView2 是微软提供的一个控件,允许开发者在 Windows 桌面应用(WinForms, WPF, WinUI, 甚至原生 C++)中嵌入基于 Chromium 的 Web 渲染引擎。它与应用共享同一个浏览器进程,可以访问完整的现代 Web API(如 WebGL、WebRTC、ES6+),并且支持与宿主应用进行双向通信。
1.2 什么是 “Edge-Legacy” 风格?“Edge-Legacy” 通常指的是采用 EdgeHTML 渲染引擎的旧版 Microsoft Edge 浏览器(2015-2020)。其界面风格(如标签页设计、设置菜单布局)和部分交互逻辑(如阅读视图、Web 笔记)给用户留下了深刻印象。虽然其内核已停止更新,但许多用户或企业应用仍偏爱其特定的 UI/UX 设计语言。
1.3 项目核心目标:WebView2 内核 + Legacy 交互层本项目的目标不是重新编译一个浏览器,而是:
- 内核:使用 WebView2 作为底层渲染与执行引擎,获得最佳的现代 Web 兼容性和性能。
- 外壳:在宿主应用(如 WPF 应用)中,自定义实现一套模仿 Edge-Legacy 外观和部分核心交互逻辑(如地址栏、导航按钮、收藏夹管理)的用户界面。
- 价值:为需要内嵌浏览器且对 UI 有特定要求的企业应用、工具软件或教育产品,提供一个功能强大、可深度定制、且部署相对轻量的解决方案。
2. 环境准备与版本说明
开始编码前,请确保你的开发环境已就绪。
2.1 开发环境要求
- 操作系统: Windows 10 版本 1803 或更高,或 Windows 11。WebView2 对系统版本有要求。
- 开发工具: Visual Studio 2019 或更高版本(推荐 2022),并安装
.NET 桌面开发工作负载。 - .NET 框架: 本项目以 WPF 为例,目标框架为
.NET 6.0或.NET Framework 4.6.2+。.NET Core/5+ 是更推荐的选择。 - WebView2 运行时: 这是关键依赖。有两种模式:
- 固定版本运行时: 将特定版本的 WebView2 运行时随应用分发。体积可控,版本稳定。
- 常青版运行时: 依赖用户机器上全局安装的 WebView2 Evergreen Runtime。部署简单,但版本不可控。
- 建议:开发阶段确保机器已安装 WebView2 常青版运行时 。生产环境可根据需要选择分发模式。
2.2 创建项目与添加 NuGet 包
- 打开 Visual Studio,新建一个
WPF 应用项目,命名为EdgeLegacyWebView2。 - 右键点击项目,选择
管理 NuGet 程序包。 - 在浏览选项卡中,搜索
Microsoft.Web.WebView2,选择并安装稳定版本(如 1.0.2000+)。这个包提供了核心的WebView2控件。
安装完成后,你的csproj文件应包含类似引用:
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2000.0" />3. 核心组件与界面设计
我们将首先搭建起模仿 Edge-Legacy 主窗口的基础界面。
3.1 主窗口布局设计打开MainWindow.xaml,我们将使用 Grid 和 DockPanel 进行布局。核心区域分为:标题栏、导航栏、WebView2 主体、状态栏。
<!-- MainWindow.xaml --> <Window x:Class="EdgeLegacyWebView2.MainWindow" 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.microsoft.com/expression/blend/2008" xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf" mc:Ignorable="d" Title="Edge-Legacy (WebView2)" Height="768" Width="1024" WindowStartupLocation="CenterScreen"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <!-- 自定义标题栏 --> <RowDefinition Height="Auto"/> <!-- 导航工具栏 --> <RowDefinition Height="*"/> <!-- WebView2 内容区 --> <RowDefinition Height="Auto"/> <!-- 状态栏 --> </Grid.RowDefinitions> <!-- 自定义标题栏 (模仿Edge样式) --> <Border Grid.Row="0" Background="#0078D7" Height="32"> <Grid> <TextBlock Text="Edge-Legacy (WebView2)" Foreground="White" VerticalAlignment="Center" Margin="10,0"/> <StackPanel Orientation="Horizontal" HorizontalAlignment="Right"> <Button x:Name="MinimizeButton" Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" Content="─" Width="45" Click="MinimizeButton_Click"/> <Button x:Name="MaximizeButton" Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" Content="❐" Width="45" Click="MaximizeButton_Click"/> <Button x:Name="CloseButton" Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" Content="✕" Width="45" Click="CloseButton_Click" Background="Transparent" Foreground="White"/> </StackPanel> </Grid> </Border> <!-- 导航工具栏 --> <ToolBar Grid.Row="1" Background="#F3F3F3"> <Button x:Name="BackButton" Content="←" ToolTip="后退" Click="BackButton_Click" IsEnabled="False"/> <Button x:Name="ForwardButton" Content="→" ToolTip="前进" Click="ForwardButton_Click" IsEnabled="False"/> <Button x:Name="RefreshButton" Content="↻" ToolTip="刷新" Click="RefreshButton_Click"/> <Separator/> <TextBox x:Name="AddressBar" MinWidth="400" KeyDown="AddressBar_KeyDown"/> <Button x:Name="GoButton" Content="前往" Click="GoButton_Click"/> <Separator/> <Button x:Name="FavoritesButton" Content="★" ToolTip="收藏夹" Click="FavoritesButton_Click"/> <Button x:Name="SettingsButton" Content="⚙" ToolTip="设置" Click="SettingsButton_Click"/> </ToolBar> <!-- WebView2 核心区域 --> <wv2:WebView2 x:Name="webView" Grid.Row="2" Source="https://www.bing.com" NavigationStarting="WebView_NavigationStarting" NavigationCompleted="WebView_NavigationCompleted" CoreWebView2InitializationCompleted="WebView_CoreWebView2InitializationCompleted"/> <!-- 状态栏 --> <StatusBar Grid.Row="3" Background="#E1E1E1"> <StatusBarItem> <TextBlock x:Name="StatusText" Text="就绪"/> </StatusBarItem> <StatusBarItem HorizontalAlignment="Right"> <ProgressBar x:Name="PageLoadProgress" Width="100" Height="16" IsIndeterminate="False" Visibility="Collapsed"/> </StatusBarItem> </StatusBar> </Grid> </Window>3.2 初始化 WebView2 环境在MainWindow.xaml.cs中,我们需要异步初始化 WebView2 控件。这是最关键的一步,必须在窗口加载时完成。
// MainWindow.xaml.cs using Microsoft.Web.WebView2.Core; using System; using System.IO; using System.Windows; namespace EdgeLegacyWebView2 { public partial class MainWindow : Window { // 指定WebView2用户数据文件夹,用于缓存、Cookie等 private readonly string _webView2UserDataFolder = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "EdgeLegacyWebView2", "WebView2Data" ); public MainWindow() { InitializeComponent(); // 在构造函数中开始异步初始化,但核心初始化逻辑在Loaded事件中更可靠 this.Loaded += MainWindow_Loaded; } private async void MainWindow_Loaded(object sender, RoutedEventArgs e) { await InitializeWebView2Async(); } private async System.Threading.Tasks.Task InitializeWebView2Async() { try { // 1. 创建环境选项,可以在此配置缓存路径、语言等 var envOptions = new CoreWebView2EnvironmentOptions(); // 可以在此处添加额外的命令行参数,例如: // envOptions.AdditionalBrowserArguments = "--disable-web-security"; // 仅用于测试,生产环境慎用! // 2. 创建WebView2环境 var env = await CoreWebView2Environment.CreateAsync( userDataFolder: _webView2UserDataFolder, options: envOptions ); // 3. 确保WebView2控件已初始化环境 await webView.EnsureCoreWebView2Async(env); // 4. 环境创建完成后,可以配置更多CoreWebView2设置 // 例如:启用开发者工具(F12) webView.CoreWebView2.Settings.AreDevToolsEnabled = true; // 例如:禁用默认的上下文菜单 // webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false; StatusText.Text = "WebView2 初始化成功"; } catch (Exception ex) { MessageBox.Show($"WebView2 初始化失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); StatusText.Text = "初始化失败"; // 可以考虑在此处提供下载运行时引导 } } } }4. 实现核心浏览器交互功能
现在,我们将为界面上的按钮和地址栏绑定功能,实现基本的浏览操作。
4.1 导航控制(前进、后退、刷新、前往)在MainWindow.xaml.cs中继续添加以下事件处理程序:
// 后退 private void BackButton_Click(object sender, RoutedEventArgs e) { if (webView.CoreWebView2?.CanGoBack == true) { webView.CoreWebView2.GoBack(); } } // 前进 private void ForwardButton_Click(object sender, RoutedEventArgs e) { if (webView.CoreWebView2?.CanGoForward == true) { webView.CoreWebView2.GoForward(); } } // 刷新 private void RefreshButton_Click(object sender, RoutedEventArgs e) { webView.CoreWebView2?.Reload(); } // 地址栏输入后按回车导航 private void AddressBar_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) { if (e.Key == System.Windows.Input.Key.Enter) { NavigateToAddress(); } } // 点击“前往”按钮导航 private void GoButton_Click(object sender, RoutedEventArgs e) { NavigateToAddress(); } private void NavigateToAddress() { string address = AddressBar.Text.Trim(); if (!string.IsNullOrEmpty(address)) { // 如果用户没有输入协议头,默认添加 https:// if (!address.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && !address.StartsWith("https://", StringComparison.OrdinalIgnoreCase) && !address.StartsWith("file://", StringComparison.OrdinalIgnoreCase) && !address.StartsWith("about:", StringComparison.OrdinalIgnoreCase)) { address = "https://" + address; } try { webView.CoreWebView2?.Navigate(address); } catch (Exception ex) { StatusText.Text = $"导航出错: {ex.Message}"; } } }4.2 响应导航事件,更新UI状态我们需要在导航开始和完成时,更新按钮状态、地址栏和进度条。
// 导航开始时触发 private void WebView_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e) { // 显示进度条 PageLoadProgress.Visibility = Visibility.Visible; PageLoadProgress.IsIndeterminate = true; StatusText.Text = $"正在加载: {e.Uri}"; // 禁用导航按钮直到完成 BackButton.IsEnabled = false; ForwardButton.IsEnabled = false; } // 导航完成时触发 private void WebView_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e) { // 隐藏进度条 PageLoadProgress.Visibility = Visibility.Collapsed; PageLoadProgress.IsIndeterminate = false; if (e.IsSuccess) { StatusText.Text = "加载完成"; // 更新地址栏为当前实际URL AddressBar.Text = webView.CoreWebView2.Source; // 根据历史记录更新导航按钮状态 UpdateNavigationButtons(); } else { StatusText.Text = $"加载失败: {e.WebErrorStatus}"; } } // WebView2核心初始化完成时触发 private void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e) { if (e.IsSuccess) { // 监听历史状态变化,以更新前进/后退按钮 webView.CoreWebView2.HistoryChanged += (s, args) => UpdateNavigationButtons(); // 监听页面加载进度 webView.CoreWebView2.NavigationStarting += WebView_NavigationStarting; webView.CoreWebView2.NavigationCompleted += WebView_NavigationCompleted; // 监听新窗口请求(例如 target="_blank" 的链接) webView.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested; UpdateNavigationButtons(); StatusText.Text = "核心初始化完成"; } else { StatusText.Text = "核心初始化失败"; } } private void UpdateNavigationButtons() { // 必须在UI线程上更新 Dispatcher.Invoke(() => { BackButton.IsEnabled = webView.CoreWebView2?.CanGoBack == true; ForwardButton.IsEnabled = webView.CoreWebView2?.CanGoForward == true; }); } // 处理新窗口请求:在本窗口内打开,而不是弹出新窗口 private void CoreWebView2_NewWindowRequested(object sender, CoreWebView2NewWindowRequestedEventArgs e) { e.Handled = true; // 告诉WebView2我们已经处理了这个请求 webView.CoreWebView2?.Navigate(e.Uri); // 在当前WebView2中导航到新URL }4.3 实现自定义标题栏按钮功能
// 最小化 private void MinimizeButton_Click(object sender, RoutedEventArgs e) { this.WindowState = WindowState.Minimized; } // 最大化/还原 private void MaximizeButton_Click(object sender, RoutedEventArgs e) { this.WindowState = this.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; } // 关闭 private void CloseButton_Click(object sender, RoutedEventArgs e) { this.Close(); }5. 扩展功能:收藏夹与简单设置
为了更贴近“浏览器”体验,我们实现一个简单的收藏夹管理和设置窗口。
5.1 实现收藏夹功能首先,创建一个简单的数据模型和存储逻辑。我们使用 JSON 文件来存储收藏夹。
// Models/Bookmark.cs using System; namespace EdgeLegacyWebView2.Models { public class Bookmark { public string Id { get; set; } = Guid.NewGuid().ToString(); public string Title { get; set; } public string Url { get; set; } public DateTime AddedDate { get; set; } = DateTime.Now; } }// Services/BookmarkService.cs using EdgeLegacyWebView2.Models; using Newtonsoft.Json; using System.Collections.Generic; using System.IO; using System.Linq; namespace EdgeLegacyWebView2.Services { public class BookmarkService { private readonly string _bookmarkFilePath; private List<Bookmark> _bookmarks; public BookmarkService() { string appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "EdgeLegacyWebView2"); Directory.CreateDirectory(appDataPath); // 确保目录存在 _bookmarkFilePath = Path.Combine(appDataPath, "bookmarks.json"); LoadBookmarks(); } public List<Bookmark> GetAllBookmarks() => _bookmarks; public void AddBookmark(string title, string url) { if (!_bookmarks.Any(b => b.Url.Equals(url, System.StringComparison.OrdinalIgnoreCase))) { _bookmarks.Add(new Bookmark { Title = title, Url = url }); SaveBookmarks(); } } public void RemoveBookmark(string id) { var bookmark = _bookmarks.FirstOrDefault(b => b.Id == id); if (bookmark != null) { _bookmarks.Remove(bookmark); SaveBookmarks(); } } private void LoadBookmarks() { if (File.Exists(_bookmarkFilePath)) { string json = File.ReadAllText(_bookmarkFilePath); _bookmarks = JsonConvert.DeserializeObject<List<Bookmark>>(json) ?? new List<Bookmark>(); } else { _bookmarks = new List<Bookmark>(); } } private void SaveBookmarks() { string json = JsonConvert.SerializeObject(_bookmarks, Formatting.Indented); File.WriteAllText(_bookmarkFilePath, json); } } }然后,在MainWindow.xaml.cs中集成收藏功能:
// 在MainWindow类中添加字段 private readonly BookmarkService _bookmarkService = new BookmarkService(); // 收藏当前页面 private void FavoritesButton_Click(object sender, RoutedEventArgs e) { if (webView.CoreWebView2 != null) { string currentUrl = webView.CoreWebView2.Source; string currentTitle = webView.CoreWebView2.DocumentTitle; // 简单弹窗获取用户确认或修改标题 var dialog = new Microsoft.Win32.InputDialog("添加收藏", "标题:", currentTitle); if (dialog.ShowDialog() == true) { string title = dialog.ResponseText; _bookmarkService.AddBookmark(title, currentUrl); StatusText.Text = $"已收藏: {title}"; } } } // 可以创建一个简单的 InputDialog 窗口 (Window)注:InputDialog是一个自定义的简单输入对话框,需要你创建一个新的 WPF Window。这里因篇幅省略,你可以使用MessageBox配合InputBox或第三方库实现。
5.2 实现简单设置窗口创建一个新的 WPF 窗口SettingsWindow.xaml,用于配置主页、搜索引擎等。
<!-- SettingsWindow.xaml --> <Window x:Class="EdgeLegacyWebView2.SettingsWindow" ...> <StackPanel Margin="10"> <TextBlock FontWeight="Bold" Margin="0,0,0,5">常规设置</TextBlock> <Label>主页:</Label> <TextBox x:Name="HomePageTextBox" Width="300"/> <Label>默认搜索引擎:</Label> <ComboBox x:Name="SearchEngineComboBox" Width="200"> <ComboBoxItem Content="Bing" IsSelected="True"/> <ComboBoxItem Content="Google"/> <ComboBoxItem Content="百度"/> </ComboBox> <CheckBox x:Name="EnableJavascriptCheckBox" Content="启用 JavaScript" IsChecked="True" Margin="0,10,0,0"/> <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,20,0,0"> <Button Content="保存" Width="80" Margin="0,0,10,0" Click="SaveButton_Click"/> <Button Content="取消" Width="80" Click="CancelButton_Click"/> </StackPanel> </StackPanel> </Window>在MainWindow.xaml.cs中打开设置窗口:
private void SettingsButton_Click(object sender, RoutedEventArgs e) { var settingsWindow = new SettingsWindow(); settingsWindow.Owner = this; settingsWindow.ShowDialog(); // 可以在这里应用设置,例如更改主页 }6. 常见问题与排查思路
在开发和使用基于 WebView2 的应用时,你可能会遇到以下典型问题。
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
运行时异常:Microsoft.Web.WebView2.Core.CoreWebView2未初始化 | 1. 未等待EnsureCoreWebView2Async完成就访问CoreWebView2属性。2. 初始化环境失败(如运行时未安装)。 | 1.确保异步初始化:所有对webView.CoreWebView2的访问必须在CoreWebView2InitializationCompleted事件触发之后,或使用await webView.EnsureCoreWebView2Async()确保完成。2.检查运行时:确认目标机器已安装 WebView2 运行时。可在应用启动时检测,并引导用户下载。 |
| 应用启动时卡住或白屏 | 1. 环境创建路径 (userDataFolder) 权限不足。2. 网络代理或企业防火墙阻止 WebView2 组件下载或连接。 | 1.检查路径权限:确保应用有权限在指定路径(如LocalApplicationData)下创建和写入文件。2.配置环境选项:创建 CoreWebView2Environment时,可通过options参数设置代理或禁用自动更新 (--disable-auto-reload)。 |
| JavaScript 与 C# 互操作失败 | 1. 未正确注入脚本或注册对象。 2. 通信时序问题(页面未加载完成)。 | 1.使用ExecuteScriptAsync:确保在NavigationCompleted事件后执行脚本。2.使用 AddHostObjectToScript:将 C# 对象暴露给 JS 前,需在初始化完成后调用,并在 JS 端通过chrome.webview.hostObjects.sync访问。 |
| 内存占用过高 | 1. 页面本身资源消耗大。 2. 未及时清理不再使用的 WebView2 实例。 | 1.监控进程:使用任务管理器查看WebView2相关进程 (MicrosoftEdge*)。2.显式清理:在窗口关闭时,调用 webView.Dispose()或设置webView = null以触发垃圾回收。对于多窗口应用,更需注意实例管理。 |
| 无法加载本地文件 (file://) | 1. 默认出于安全考虑,WebView2 对本地文件访问有限制。 2. 路径格式不正确。 | 1.授予权限:在环境创建后,通过webView.CoreWebView2.SetVirtualHostNameToFolderMapping将虚拟主机名映射到本地文件夹,然后通过https://虚拟主机名/...访问。2.使用绝对路径:确保 file://协议后的路径是完整的绝对路径。 |
| 自定义标题栏与系统窗口行为冲突 | 1. 隐藏了默认标题栏 (WindowStyle=”None”) 但未实现拖动、双击最大化等行为。 | 1.实现窗口拖动:在自定义标题栏的MouseLeftButtonDown事件中调用this.DragMove()。2.实现双击最大化:监听标题栏的 MouseDoubleClick事件,切换WindowState。 |
7. 最佳实践与工程建议
将想法转化为稳定可交付的产品,还需要遵循以下工程实践。
7.1 部署与分发策略
- 固定版本运行时:对于企业环境或需要严格版本控制的场景,将特定版本的 WebView2 运行时(
MicrosoftEdgeWebView2RuntimeInstaller.exe)打包进安装程序,并静默安装。这能保证所有用户环境一致。 - 引导安装:如果选择依赖“常青版运行时”,必须在应用启动时检测运行时是否存在。如果不存在,应友好地引导用户到官方下载页面,或自动启动安装程序。
private async Task<bool> CheckWebView2RuntimeAsync() { try { var env = await CoreWebView2Environment.GetAvailableBrowserVersionStringAsync(); return !string.IsNullOrEmpty(env); } catch (WebView2RuntimeNotFoundException) { return false; } }
7.2 性能与资源管理
- 延迟加载:如果应用有多个标签页或视图,不要一次性初始化所有 WebView2 实例。采用懒加载策略,在需要显示时才创建和初始化。
- 进程模型理解:WebView2 采用多进程模型。一个浏览器进程 (
browser process) 管理多个渲染进程 (renderer process)。关闭主窗口后,这些进程可能不会立即退出。复杂的应用需要管理进程生命周期。 - 清理缓存:通过环境选项
CoreWebView2EnvironmentOptions可以设置缓存路径。定期清理或限制缓存大小,避免磁盘空间被无限占用。
7.3 安全增强
- 禁用不必要的功能:在
CoreWebView2Settings中,关闭生产环境不需要的功能。webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false; // 禁用默认右键菜单 webView.CoreWebView2.Settings.IsZoomControlEnabled = false; // 禁用缩放控制 webView.CoreWebView2.Settings.IsStatusBarEnabled = false; // 禁用状态栏 // 谨慎禁用:webView.CoreWebView2.Settings.IsScriptEnabled = false; // 禁用JS - 内容过滤与拦截:在
NavigationStarting事件中,检查e.Uri,可以阻止导航到黑名单中的网址。private void WebView_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e) { if (IsUrlBlocked(e.Uri)) { e.Cancel = true; StatusText.Text = "访问被阻止"; } } - 谨慎暴露 Host Object:通过
AddHostObjectToScript暴露给 JavaScript 的 C# 对象,相当于提供了 API 接口。务必确保该对象的方法和属性是安全的,避免暴露敏感信息或执行危险操作。
7.4 用户体验优化
- 自定义右键菜单:监听
CoreWebView2的ContextMenuRequested事件,可以完全替换浏览器默认的右键菜单,实现与宿主应用风格一致的自定义菜单。 - 下载管理:监听
DownloadStarting事件,可以接管文件下载,指定保存路径或取消下载。 - 快捷键支持:在窗口级别监听键盘事件,实现 F5 刷新、Ctrl+F 查找等浏览器常用快捷键。
- 多标签页支持:这是实现“浏览器感”的关键。你可以创建一个
TabControl,每个TabItem的内容都是一个独立的WebView2控件。需要仔细管理每个 WebView2 实例的生命周期和资源。
通过以上步骤,你已经成功构建了一个基于现代 Chromium 内核、拥有经典 Edge 风格界面的桌面浏览器外壳应用。这个项目清晰地演示了 WebView2 控件强大的集成能力,从环境初始化、基本导航、UI 交互到数据持久化和扩展功能。在实际产品化过程中,你可以在此基础上继续深化,例如集成密码管理、扩展插件系统、更完善的开发者工具、或者与后端服务进行深度通信。WebView2 打开的是一扇通往现代 Web 技术的大门,让你能够在桌面应用中无缝融合本地应用的性能与 Web 生态的丰富性。