ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

WinForm集成WebView2:打造你的自用个性化浏览器

WinForm集成WebView2:打造你的自用个性化浏览器 简介基于WebView2内核的WinForm桌面浏览器程序源码面向C#/.NET桌面端开发者可帮助快速打造类似Edge、Chrome的个性化浏览器适用于需要自研内嵌浏览器、工具型客户端或定制化浏览器外壳的Windows项目。压缩包内共134个文件约15.33MB以dll动态库、png图标与界面资源、cs源码文件及xml配置为主另有.sln工程与.csproj项目文件可在Visual Studio 2019中直接打开编译。该源码完整集成WebView2内核借助系统Edge组件完成页面渲染同时展示WinForm窗体与浏览器控件的衔接方式涵盖初始化配置、导航加载、资源管理、窗体大小联动等主要开发环节便于理解桌面浏览器程序从启动到交互的完整流程。源码与配置分离目录组织清晰适合照着工程实践WebView2的常用API也方便在此基础上增加收藏、下载、主题定制等个性化功能作者还提供了配套指导文章链接遇到编译环境问题时可直接对照。已有1097人学习下载过这份源码。1. WinForm 加 WebView2为什么做个自用浏览器比想象中更实用市面上能找到的浏览器壳要么套个 CEF 大得吓人要么就是给浏览器加几个收藏夹。真正到了生产环境你会发现很多桌面程序不需要整个互联网只需要固定的几个内网系统还要启动快、能注入自己的脚本、能接管登录状态。WinForm 加 WebView2 做一个自用个性化浏览器就是奔着这三个需求去的。源码就在本地改起来比改 Chromium 源码快一个量级打包成安装程序也不复杂。这篇笔记从选型、最小实现到打包避坑按一个完整桌面程序开发的顺序讲适合想在自己的 Windows 机器或公司内网里快速落地一个浏览器外壳的开发者。2. WebView2 的运行时定位与 WinForm 集成原理选它而不是 Electron 的三个理由2.1 WebView2 与 WebBrowser 控件的本质差异老一批用 WinForm 做浏览器的人上来多半被 WebBrowser 控件坑过。它包的是系统 IE 内核默认文档模式走兼容性CSS Grid 不认识、ES6 不支持页面一复杂就乱版想拦截请求、注入脚本又要碰 COM 接口里的怪物设计。2019 年之后的 WebView2本质是把微软的 Chromium 内核通过 COM 暴露给 Win32 应用。它不是一个单纯控件的名字而是一套运行时加一个 SDK。控件只是个马甲真正的页面渲染发生在独立的 browser 进程里你的 WinForm 主进程只负责创建窗口、下发指令、接收事件。桌面程序开发新手容易把 WebView2 当成“高级版 WebBrowser”这是第一个理念误区。WebView2 有独立的 Runtime它依赖 Edge 的更新通道也可以固定版本打包。Runtime 的作用就是提供 Chromium 二进制SDK 里的控件只是调用它的入口。理解这一点之后你才知道部署阶段为什么要关心离线安装包、为什么要指定用户数据目录、为什么网页崩溃不会连累整个程序。WebView2 还支持多进程隔离一个标签页崩溃WinForm 窗体仍然能动这比 WebBrowser 控件“一个页面卡死全院瘫痪”的体验强太多了。2.2 为什么自用浏览器方案常常落在 WinForm WebView2Electron 胜在跨平台但如果你只在 Windows 上做公司内部小工具Electron 的个头和内存消耗常让人不爽。一个最简单的 Electron 壳装上 Chromium、Node、V8体积轻松超 150 MBWinForm WebView2 的发布产物只有几 MBRuntime 走系统 WebView2前提是目标机器上已经装了 Runtime。自用浏览器这类需求用户本来就一个人或一个部门WinForm 足够没必要引入一套构建链。第二个理由是好接 C# 生态。WinForm 里有现成的 NotifyIcon、TaskDialog、剪贴板、文件对话框登录、下载、导出 Excel 都能调用本地 API。网页负责内容桌面能力由 C# 兜底。C# WinForm MVVM 模式也能用但不要强迫它。WinForms 的天然模型是事件驱动你只要把 WebView2 的事件封装到自己的 ViewModel 或 Service 层就已经比大多数人干净了。硬要在控件上绑一堆依赖属性只会把项目写成一个四不像。第三个理由才是“自用”的核心源码可控。浏览器扩展对普通用户是好东西可对要维护自己桌面程序的人来说扩展的发布和签名流程是负担。WebView2 允许你在每次导航开始前注入脚本在请求发出时改写 Header在网页与 C# 之间传消息。你可以在源码里直接定义“这个站点只允许访问”而不是去配置一个外部的扩展。对于自用个性化浏览器这个自由度是最值钱的。对比一下常见的三种方案对比项WinForm WebView2WebBrowser 控件CefSharp内核Chromium随 Runtime 更新IE 内核系统自带内置 Chromium发布体积数 MB 加 Runtime极小但内核老旧数十到上百 MB进程隔离有独立浏览器进程无页面崩则控件崩有脚本注入官方 API操作 IHTMLDocument 黑匣子有但包重内置视频支持 HTML5 视频老网页多半不支持支持表格里能看出来WinForm WebView2 在体积、可维护性、功能完整度上找到了一个平衡点。如果你要做的真是一个供几十人使用的内部工具这个组合是最省心的。2.3 事件模型与配置项为个性化留的四个 Hook 点WebView2 的 API 表面看是一堆事件其实每一类都有对应的参数。做浏览器壳最常用的是四个 HookNavigationStarting、NavigationCompleted、NewWindowRequested、WebMessageReceived。NavigationStarting 的参数里带e.Uri和e.Cancel你可以在这里做 URL 拉黑如果还想改请求头用CoreWebView2.AddWebResourceRequestedFilter配合WebResourceRequested事件能精确到某个 URL 或某个资源类型。NavigationCompleted 可以拿到成功失败和 HTTP 状态码适合更新地址栏、加载动画、页面出错提示。NewWindowRequested 则是浏览器里target_blank的老大难默认情况下 WebView2 不会自动开新窗口你不处理链接点了没反应。必须在这个事件里自己创建 Tab 或重定向到当前 WebView。WebMessageReceived 是留给你自己的通信通道网页里的按钮可以通过window.chrome.webview.postMessage把数据发到 C# 端。还有一个容易被忽略的CoreWebView2.Settings对象。许多白屏、弹窗、右键菜单问题都出在这里。以下是常用初始化模板var options new CoreWebView2EnvironmentOptions { Language zh-CN, AdditionalBrowserArguments --disable-featuresmsWebOOUI, TargetCompatibleBrowserVersion 109.0.1518.46 }; var env await CoreWebView2Environment.CreateAsync( userDataFolder: C:\\browser-data, options: options); await webView.EnsureCoreWebView2Async(env); webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled false; webView.CoreWebView2.Settings.AreDevToolsEnabled true; webView.CoreWebView2.Settings.IsStatusBarEnabled false; webView.CoreWebView2.Settings.IsPasswordAutosaveEnabled false;这段代码在大多数 WinForm 项目里可以作为初始化模板。userDataFolder建议放在%LOCALAPPDATA%下你的应用名目录里不要往 C 盘根目录塞AreDevToolsEnabled在发布版里建议关掉IsPasswordAutosaveEnabled对自用浏览器看需求我一般关掉登录态用 Cookie 或配置文件保持避免浏览器把密码写进系统密码管理器后面换机器反而迁不走。部署阶段你要记住一个词Evergreen。这是 Runtime 的默认分发模式它跟着 Edge 更新好处是安全补丁不操心坏处是目标机器必须联网更新。如果你要给一台完全没有 WebView2 Runtime 的机器装程序就会遇到最出名的那句“could not find the webview2 runtime”。这个问题我放到第 5 章展开先把它归因到部署层而不是代码层。3. 从空目录到首屏加载WinForm WebView2 最小工程与导航工具条3.1 创建项目、引入 NuGet 包和初始化先用命令行快速建一个 WinForms 项目比手点向导更直接dotnet new winforms -n MyBrowser cd MyBrowser dotnet add package Microsoft.Web.WebView2Microsoft.Web.WebView2是微软官方维护的 SDKVS 里用 NuGet 管理器搜索同名包也行。它会在编译时把WebView2Loader.dll和运行所需的原生资源复制到输出目录。目标框架建议net8.0-windows或更高如果还在用 VS2015 配合 .NET Framework也可以选net462只是后面一些 C# 新语法要收敛一点。然后改主窗体代码做一个最基础的单页浏览器public partial class MainForm : Form { private WebView2? _webView; private TextBox _addressBox; private Button _backButton; private Button _forwardButton; private readonly string _homePage https://www.bing.com; public MainForm() { InitializeComponent(); Load MainForm_Load; BuildUI(); } private void BuildUI() { Text 自用浏览器; Size new Size(1280, 800); var panel new Panel { Dock DockStyle.Top, Height 42, Padding new Padding(4) }; _addressBox new TextBox { Dock DockStyle.Fill, Font new Font(Microsoft YaHei UI, 10) }; _addressBox.KeyDown AddressBox_KeyDown; panel.Controls.Add(_addressBox); Controls.Add(_webView new WebView2 { Dock DockStyle.Fill }); Controls.Add(panel); Controls.SetChildIndex(_webView, 0); } private async void MainForm_Load(object sender, EventArgs e) { var env await CoreWebView2Environment.CreateAsync( userDataFolder: Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), MyBrowser, UserData)); await _webView!.EnsureCoreWebView2Async(env); _webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled true; _webView.CoreWebView2.Settings.IsStatusBarEnabled false; _webView.CoreWebView2.Settings.AreDevToolsEnabled true; _webView.Source new Uri(_homePage); _addressBox.Text _homePage; } }这个初始化有几个关键点。userDataFolder一旦指定就是这个 WebView2 实例的“浏览器配置目录”登录 Cookie、LocalStorage、缓存都在这里。如果不传这个参数SDK 会自动在临时目录里建一个程序退出后不一定保留登录态。EnsureCoreWebView2Async是异步初始化必须 await否则后续事件和设置都会抛 InvalidOperationException。Controls.SetChildIndex(_webView, 0)保证 WebView2 先填充窗口再让 top panel 压在它上面。3.2 地址栏、后退、前进与刷新命令动作和事件参数地址栏回车导航是浏览器最简单的交互。常见的坑是用户只输入“baidu.com”而不是完整 URL所以要加协议补全。后退和前进按钮的状态要跟着页面历史走不要永远可用。处理代码如下private void AddressBox_KeyDown(object? sender, KeyEventArgs e) { if (e.KeyCode Keys.Enter) { Navigate(_addressBox.Text); e.SuppressKeyPress true; } } private void Navigate(string url) { url url.Trim(); if (string.IsNullOrEmpty(url)) return; if (!url.Contains(://)) url https:// url; try { _webView!.Source new Uri(url); } catch (UriFormatException) { _addressBox.Text 网址格式不对; } } private void UpdateNavButtons() { if (_webView null || _webView.CoreWebView2 null) return; _backButton.Enabled _webView.CanGoBack; _forwardButton.Enabled _webView.CanGoForward; }在MainForm_Load里挂上这些事件_webView.NavigationStarting (s, e) { _addressBox.Text e.Uri; UpdateNavButtons(); }; _webView.NavigationCompleted (s, e) { UpdateNavButtons(); if (!e.IsSuccess) { _addressBox.Text $加载失败{e.HttpStatusCode}; } }; _backButton.Click (s, e) _webView.GoBack(); _forwardButton.Click (s, e) _webView.GoForward();事件参数e.Uri是导航目标的完整地址注意它可能不是用户输入的那个字符串可能被服务器重定向过所以每次导航都同步地址栏。e.HttpStatusCode只有在完成时才有意义跳转类响应往往不是 200但页面能正常显示不要在完成事件里把 3xx 当错误弹窗。3.3 页面加载前注入脚本和样式让网页按你的方式显示注入脚本是“个性化”最直接的一环。比如把网页背景改成豆沙绿护眼、给所有图片加圆角或者在页面里挂一个全局消息桥。用AddScriptToExecuteOnDocumentCreatedAsync在每次文档创建前执行比页面加载后再用ExecuteScriptAsync更稳定因为前者能抢在页面自己的 JavaScript 之前跑。string script (function () { document.documentElement.style.backgroundColor #f2f4f7; const style document.createElement(style); style.textContent img { border-radius: 8px; }; document.head.appendChild(style); window.__desktopBridge { send: function (payload) { window.chrome.webview.postMessage(payload); } }; })(); ; string scriptKey await _webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync( customStyle, script);这个 API 的第一个参数是脚本唯一标识第二个是脚本内容。它会返回一个字符串键之后可以调用RemoveScriptToExecuteOnDocumentCreatedAsync(scriptKey)移除。脚本只在每个页面文档创建时执行一次如果页面内部用异步加载又改了 DOM你需要自己监听DOMContentLoaded或 MutationObserver。还有一个控制粒度更细的 HookAddWebResourceRequestedFilter。比如你想屏蔽某个域名下的图片请求可以这样写_webView.CoreWebView2.AddWebResourceRequestedFilter( https://*.example.com/*, CoreWebView2WebResourceContext.All); _webView.CoreWebView2.WebResourceRequested (s, e) { if (e.Request.Uri.Contains(ads-example)) { e.Response _webView.CoreWebView2.Environment.CreateWebResourceResponse( null, 204, No Content, ); } };这个过滤事件会在请求发出前触发你可以直接替换e.Response达到广告过滤、改接口返回值的效果。注意过滤器默认走异步如果事件处理里需要等待磁盘或网络必须调用e.GetDeferral()否则响应会被丢。4. 多个标签页与用户数据隔离把 WebView2 用成一个独立工作区4.1 用 TabControl 管理多个 WebView2动态创建和销毁自用浏览器要有标签页不然又回到一个窗口一个网页的老路。WinForm 里的 TabControl 可以直接装 WebView2每个 TabPage 里面放一个独立的 WebView2 控件。先在建好的工程里加一个TabControlDock 设为 Fill地址栏 Panel 放在它上面然后实现动态加页。private async void AddBrowserTab(string url) { var page new TabPage(新标签); var wv new WebView2 { Dock DockStyle.Fill }; page.Controls.Add(wv); tabControl.TabPages.Add(page); tabControl.SelectedTab page; await wv.EnsureCoreWebView2Async(_sharedEnv); wv.CoreWebView2.Settings.IsStatusBarEnabled false; wv.NavigationStarting (s, e) { string title e.Uri; if (title.Length 16) title title.Substring(0, 16); page.Text title; }; wv.Source new Uri(url); }这里要保证所有 Tab 共享同一个CoreWebView2Environment。如果每个 Tab 各自用EnsureCoreWebView2Async()无参重载SDK 在内部可能会为每个控件建独立环境Cookie 和登录态就串不起来了。正确做法是在主窗体加载时先建一个_sharedEnv之后每个 Tab 都传同一个实例。这样 Chromium 的磁盘缓存、Cookie 存储、Service Worker 注册表都指向同一个目录标签页之间像同一个浏览器窗口里的多个标签。关闭标签页时不要只把 TabPage 从集合里移除。WebView2 控件持有的浏览器内核进程和内存资源必须释放否则程序跑一天会看到内存只涨不降。private void CloseCurrentTab() { var page tabControl.SelectedTab; if (page null) return; var wv page.Controls.OfTypeWebView2().FirstOrDefault(); wv?.Dispose(); tabControl.TabPages.Remove(page); page.Dispose(); }Dispose会通知 WebView2 关掉它对应的浏览器进程。如果你在观察子进程会看到那个进程的退出不是立即的但至少托管对象不会变成内存黑洞。还有一个坑TabPage 里可能还有别的控件关闭前先DisposeWebView2再Remove否则页面上的脚本还在跑异步回调又访问已经释放的控件偶尔会抛 ObjectDisposedException。4.2 共享登录态与多账号隔离Environment 参数怎么传第 4.1 节说共享环境是为了让标签页像同一个浏览器但自用场景里总会有“我要同时登录两个后台账号”的需求。最简单的做法不是开两个浏览器进程而是建另一个CoreWebView2Environment只换userDataFolderCookie 和缓存立刻物理隔离。var mainEnv await CoreWebView2Environment.CreateAsync( Path.Combine(baseDir, profile-main), null); var workEnv await CoreWebView2Environment.CreateAsync( Path.Combine(baseDir, profile-work), null);把mainEnv给业务 TabworkEnv给另一个窗口模式。这样同一个程序里会出现两个互不干扰的登录态彼此 Session、Cookie、LocalStorage 完全独立。注意磁盘占用会翻倍因为每个 userDataFolder 都是一套完整的 Chromium 用户数据包含缓存和 GPU 缓存。自用机器无所谓如果是远程桌面共享给团队用要给每个 Profile 设配额。如果你需要在不同环境间传递登录比如把主环境的 Cookie 复制到工作环境可以用CoreWebView2.CookieManager。这块属于敏感操作我只说思路通过GetCookiesAsync把 cookie 列表读出来再调用AddOrUpdateCookie写入目标环境。但别把密码明文写在配置文件里自用浏览器也别偷懒。还有一个和 MVVM 相关的事多个 Tab 意味着多个 WebView2 实例WinForm 里处理这种动态子控件的惯用方法是把WebView2放进DictionaryTabPage, WebView2用 TabPage 做 key。这样切换 Tab 时你可以直接找到对应 WebView 来更新地址栏和按钮状态。没有键控表Index 一变就会串页。5. 打包部署避坑清单Runtime 找不到、安装失败与图标玄学5.1 “could not find the webview2 runtime”到底是谁的锅在开发机上这个错不常见因为 VS 或 Edge 已经装好了 Runtime。到了部署机最常见的一句报错就是Could not find the webview2 runtime.现象程序一启动就弹错误框或者窗体起来但 WebView2 区域一片空白日志里写着找不到 WebView2 Runtime。原因很简单目标机器没有安装 Microsoft Edge WebView2 Runtime。它可能装了 Edge 浏览器但 Edge 的 Runtime 组件没被单独安装。解决途径有三条一是程序安装前检测并调用 Runtime 安装器二是采用带 Runtime 的固定版本部署三是用命令行手动安装。对自用机器最靠谱的是第一条直接做进安装程序里。检测是否已安装可以用 registry 或者查看文件路径$paths ( C:\Program Files\WindowsApps\Microsoft.WebView2\, C:\Program Files (x86)\Microsoft\EdgeWebView\Application\ ) $paths | ForEach-Object { Test-Path $_ }这只是验证文件存在严谨的检测要读版本号。但作为自用程序能跑起来就行别为版本匹配消耗太多时间。如果在脚本里发现路径不存在就执行下面的离线安装包。5.2 离线安装包与固定版本离线机器上的兜底方案Runtime 安装器分 Evergreen 和 Fixed Version 两种。Evergreen 版默认走在线更新安装包小但它会拉取当前最新 Runtime。内网离线机器装 Evergreen如果安装包本身就是离线 installer那依然可用因为安装器会把所需组件全部释放到本机真正的差别在于后续更新需要联网。固定版本则是把指定版本的 Runtime 完整目录拷贝到应用目录程序用browserExecutableFolder指过去更新由你控制不会因为 Runtime 自动升级而改变行为。离线安装器的静默安装一般是这样MicrosoftEdgeWebView2RuntimeInstaller.exe /silent /install这里有个血泪教训静默安装返回 exit code 2几乎都是参数写错。有的脚本会在参数里加/s小写有的加/quiet但这些都不是官方参数。官方正规参数是/silent和/install大小写敏感。如果你遇到“error: installing webview2 failed with exit code 2”先把参数改成标准形式再试。固定版本方式在代码里是这样指定的var options new CoreWebView2EnvironmentOptions { TargetCompatibleBrowserVersion 109.0.1518.46 }; var env await CoreWebView2Environment.CreateAsync( browserExecutableFolder: D:\MyBrowser\runtime\109.0.1518.46, userDataFolder: D:\MyBrowser\data, options: options);当browserExecutableFolder指向一个包含msedgewebview2.exe和WebView2Loader.dll的目录时SDK 就不会再找系统全局 Runtime。这种部署方式适合已经因为某种原因锁死内核版本的场景。注意固定版本目录体积不小安装包会变大但换来的是“程序走到哪里都一样”。5.3 用 Inno Setup 做安装包文件和参数容易被忽略的地方WinForm 程序打包成安装程序常见习惯是直接写 Inno Setup 脚本轻量、可复现。最容易的翻车点是[Files]段只拷贝了发布目录没开recursesubdirs导致runtimes文件夹下的原生依赖没进来另一个是[Run]段没把 Runtime 安装器放到程序启动之前。一个可靠的最小 Inno 脚本大概长这样[Setup] AppNameMyBrowser AppVersion1.0 DefaultDirName{autopf}\MyBrowser OutputDir.\Output OutputBaseFilenameMyBrowserSetup Compressionlzma2 SolidCompressionyes [Files] Source: .\bin\Release\net8.0-windows\*; DestDir: {app}; Flags: recursesubdirs Source: .\tools\MicrosoftEdgeWebView2RuntimeInstaller.exe; DestDir: {tmp}; Flags: deleteafterinstall [Run] Filename: {tmp}\MicrosoftEdgeWebView2RuntimeInstaller.exe; Parameters: /silent /install; Flags: waituntilterminated Filename: {app}\MyBrowser.exe; Description: 启动 MyBrowser; Flags: nowait postinstall skipifsilent几个关键点第一个[Files]必须带recursesubdirs把runtimes目录下各种架构的.dll都复制进去。第二个源文件是离线安装包放到{tmp}并设置deleteafterinstall安装完自动清理不会污染安装目录。[Run]段的顺序很重要Runtime 安装器必须要在启动程序之前执行。如果你把两个条目写反了第一次启动大概率仍会闪退或白屏。安装器在静默模式下有没有成功Inno 脚本默认是不管返回值的。实际上也不能只看进程退出码很多失败发生在安装之后。更稳的做法是程序第一次启动时自己调用一个 Runtime 检测 API检测不到就给出友好提示而不是继续崩溃。5.4 图标、管理员权限与首次启动白屏的 3 个小题现象一安装完成后桌面快捷方式没有图标。原因通常是项目只设置了 WinForm 窗体的Icon没有设置项目装配体图标或者 Inno Setup 里的Icons段没写IconFilename。解决在项目属性里设置 Application Icon再在 Inno 脚本里用{app}\MyBrowser.exe作为图标源不要单独引一个绝对路径图标文件。现象二程序明明装了 Runtime但启动白屏过几秒又能加载。原因可能是首次创建WebView2Environment时Runtime 还要初始化 GPU 进程和网络服务耗时较长白屏期间没有任何 UI 反馈看起来像卡死。解决在EnsureCoreWebView2Async之前显示一个“初始化中...”的 Panel完成后再隐藏同时把AreDevToolsEnabled打开一次直接在空白区域右键检查有没有报错。现象三以管理员身份运行后WebView2 反而报找不到用户数据目录。原因是程序请求了管理员权限Environment.SpecialFolder.LocalApplicationData指向的仍是系统管理员账户的路径而如果安装包是用普通用户身份装的用户数据目录没权限写。解决不要轻易提权如果必须提权把userDataFolder明确设置到一个所有用户都可写的公共目录比如C:\ProgramData\MyBrowser\UserData的子目录并给该目录加写权限。这是我踩过一次的玄学坑列在这里当个记性。6. 把网页的按钮接进 C#WebMessage 双向通道与调试日志做自用浏览器到最后往往不是为了上网而是为了把网页变成桌面程序的前端。最好的办法是 WebMessageReceived 双向通道网页里点击“导出报表”消息送到 C#C# 调用 Excel COM 生成文件再通过PostWebMessageAsString把结果回传给页面。页面不用改接口桌面端也不用心跳轮询。一个最小 bridge 长这样_webView.CoreWebView2.WebMessageReceived (s, e) { string? message e.TryGetWebMessageAsString(); if (message null) return; if (message.StartsWith(save:)) { string fileName message[5..]; File.WriteAllText(Path.Combine(D:\exports, fileName), ok); } }; await _webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync( bridge, window.bridge { save: (name) window.chrome.webview.postMessage(save: name) };);TryGetWebMessageAsString只能解析字符串消息如果你在页面端发了对象要用e.WebMessageAsJson拿原始 JSON。接收完还可以调用CoreWebView2.PostWebMessageAsString(done)把结果返回给网页网页端监听window.chrome.webview.addEventListener(message, ...)即可。我自己每个 WebView2 项目都会保留一个调试开关。发布版默认调低日志级别但在开发时启动参数加--enable-logging和--v1就能看到 Chromium 的网络和渲染日志。这个开关在CoreWebView2EnvironmentOptions.AdditionalBrowserArguments里加别在窗体代码里硬写。你永远不知道下一次白屏是 Runtime 没装还是某个页面把 GPU 搞崩了。把这两样东西一起埋进初始化里是让我少走最多弯路的一段代码。希望帮到你。本文还有配套的精品资源点击获取
返回列表