ARTICLE DETAIL

资讯详情

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

WinForm+WebView2自用浏览器开发:从套壳到个性化改造

WinForm+WebView2自用浏览器开发:从套壳到个性化改造 简介这是一份基于 WebView2 内核的个性化浏览器桌面程序源码面向具备一定 C# 与 WinForm 基础、希望快速搭建定制浏览器或内嵌网页能力的开发者。项目使用 Visual Studio 2019 编写编译即可运行可作为自用浏览器、桌面客户端外壳或 Web 混合应用的起步模板。压缩包共 134 个文件约 15.33MB包含 8 个 cs 源码文件、1 个 sln 解决方案与 csproj 工程文件、47 个 dll 依赖库、32 个 png 界面素材以及 xml、config、resx 等配置与资源文件另有 winmd、idl、tlb 等 WebView2 互操作相关组件结构完整。目前已有 1097 人学习下载。读者可从中获得可直接编译的工程骨架、窗体与资源组织方式、WebView2 集成与依赖引用配置以及界面素材与配置文件的配套示例便于在此基础上扩展标签页、导航栏等个性化功能。1. 从 WinForm 套壳到自用浏览器WebView2 到底能省掉多少造轮子的时间很多人第一次听到「WinFormWebView2 自用个性化浏览器」脑子里浮现的是 Chromium 那套几百兆的源码树和几个月起步的编译周期。实际情况恰恰相反你不需要碰 Chromium 一行代码也不需要自己实现渲染引擎、网络栈、JS 虚拟机。WinForm 负责窗口、菜单、快捷键、托盘、本地文件读写WebView2 负责把 Edge 的渲染内核嵌进你的窗体里两者通过一个WebView2控件和一套异步 API 对接。这就是「套壳」这个词在桌面程序开发里最务实的含义——壳是你的核是微软维护的。这个方案解决的核心问题是你想做一个只属于自己的浏览器能固定首页、能屏蔽某些请求、能注入自己的脚本、能改图标改标题改按钮文字但不想从零写一个浏览器。适合谁适合有 C# WinForm 基础、想拿一个可控的桌面容器去承载 Web 内容的人比如做内部工具面板、做数据看板、做需要嵌入网页又要有本地能力的桌面程序。热词里反复出现的「c# winform 嵌入html」「winform 实现打开历史记录」「webview2 runtime」这些诉求本质上都是同一件事的不同侧面。我先把结论放前面这套东西的难点不在写代码而在运行时依赖、版本匹配和权限边界。代码本身可能两百行就能跑起来但如果你不知道 WebView2 Runtime 是什么、不知道用户机器上可能报could not find the webview2 runtime那你交付出去的程序在别人电脑上就是白屏。下面按「先跑通、再个性化、再避坑」的顺序拆。2. 把 WebView2 塞进 WinForm最小可运行工程与三个必调参数2.1 环境准备与 NuGet 包选择先明确一件事WebView2 在 WinForm 里不是系统自带的控件它来自 NuGet 包Microsoft.Web.WebView2。你新建一个 .NET Framework 4.6.2 或 .NET 6/8 的 WinForm 项目后第一步是装包。用 Package Manager Console 或直接改 csproj 都行命令行方式如下# 在项目目录下执行注意版本号按当前稳定版填不要照抄旧版本 dotnet add package Microsoft.Web.WebView2装完之后工具箱里会出现WebView2控件。如果你用的是 .NET Framework 项目且工具箱没刷新直接手写代码创建实例也可以不必依赖设计器。这里有个血泪经验设计器拖控件在 .NET Framework 老项目里偶尔会触发could not find the webview2 runtime的设计时异常因为设计器本身也要加载运行时。遇到这种情况改成纯代码创建问题就消失了。关于运行时必须区分两个概念SDK 包Microsoft.Web.WebView2你项目引用的和RuntimeMicrosoft Edge WebView2 Runtime用户机器上必须安装的。SDK 包会把 Runtime 的引导逻辑带进来但不会替你安装 Runtime。Win10/11 通常已经预装Win7 和部分精简版 Win10 需要手动装。热词里「webview2 win7版本下载」「win7安装webview2」说的就是这件事——Win7 上要装的是支持 Win7 的最后一个 Runtime 版本且系统需要打齐补丁。2.2 最小可运行代码初始化、导航、事件绑定下面这段代码是一个能跑起来的最小窗体包含初始化、导航到指定地址、以及处理导航完成事件。我把它写成纯代码形式方便你直接贴进Form1.csusing Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.WinForms; using System; using System.Windows.Forms; public class BrowserForm : Form { private WebView2 webView; public BrowserForm() { this.Text 自用浏览器; this.Width 1200; this.Height 800; webView new WebView2(); webView.Dock DockStyle.Fill; this.Controls.Add(webView); // Load 事件里做异步初始化不要在构造函数里 await this.Load async (s, e) { // 指定用户数据目录避免默认目录权限问题 var env await CoreWebView2Environment.CreateAsync( userDataFolder: C:\MyBrowserData); await webView.EnsureCoreWebView2Async(env); // 导航完成后再做后续操作 webView.NavigationCompleted OnNavigationCompleted; webView.CoreWebView2.Navigate(https://www.bing.com); }; } private void OnNavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e) { if (!e.IsSuccess) { MessageBox.Show($导航失败错误码{e.WebErrorStatus}); } } }逻辑说明CoreWebView2Environment.CreateAsync负责创建运行环境userDataFolder参数决定缓存、Cookie、LocalStorage 存哪里。不指定的话默认在程序目录下如果程序装在Program Files里就会因为写权限失败。EnsureCoreWebView2Async是真正把内核拉起来的调用必须 await。NavigationCompleted事件里的IsSuccess和WebErrorStatus是排查白屏的第一手信息。参数说明userDataFolder建议放在%LocalAppData%下比如Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), MyBrowser)。这样多用户、多实例都不会打架。如果你要做「谷歌浏览器多开」那种多实例隔离就给每个实例分配不同的 userDataFolder这是最干净的隔离方式。2.3 三个必调参数用户数据目录、启动参数、权限开关第一个必调参数是userDataFolder上面已经说了。第二个是CoreWebView2EnvironmentOptions里的AdditionalBrowserArguments用来给内核传启动参数。比如你想禁用某些特性、调整渲染行为可以这样写var options new CoreWebView2EnvironmentOptions( additionalBrowserArguments: --disable-featuresTranslate --langzh-CN); var env await CoreWebView2Environment.CreateAsync(null, userDataFolder, options);第三个是权限相关的设置。WebView2 默认会弹权限请求摄像头、麦克风、地理位置如果你做的是自用工具通常希望静默处理。通过CoreWebView2.PermissionRequested事件可以拦截webView.CoreWebView2.PermissionRequested (s, e) { // 自用场景下直接放行生产环境要按 Kind 判断 e.State CoreWebView2PermissionState.Allow; };这三个参数调完你的浏览器壳子基本就立住了。接下来才是「个性化」的部分——改标题、改图标、注入脚本、拦截请求。3. 个性化改造改标题图标、注入脚本、拦截请求的落地写法3.1 改标题、图标、按钮文字不碰源码也能改的边界热词里有一条很扎眼「修改程序界面改图标改文字标题logo改按钮改信息无需源码」。这句话在 WinForm 语境下要拆开看。如果你有源码改标题就是this.Text ...改图标就是this.Icon new Icon(app.ico)改按钮文字就是button1.Text ...没有任何技术含量。如果你没有源码那能改的只有资源段里的图标和字符串表用 Resource Hacker 这类工具可以替换但一旦程序做了强签名或资源校验改了就会崩。所以「无需源码」是有边界的别把它当成万能。有源码的情况下我一般会把界面文案抽到一个静态类里方便统一改public static class UiText { public const string AppTitle 我的工作台; public const string HomeUrl https://www.bing.com; public const string BtnRefresh 刷新; }这样改文字只动一个文件不用满项目搜字符串。图标方面WinForm 窗体的Icon属性接受.ico文件建议准备 16x16、32x32、48x48 多尺寸的 ico否则任务栏和标题栏会糊。3.2 注入脚本与 CSS把网页改成你想要的样子WebView2 提供了AddScriptToExecuteOnDocumentCreatedAsync可以在每个页面加载前注入 JS。这是做个性化最实用的能力。比如你想去掉某个网站的侧边栏、改背景色、自动填表单都可以在这里做await webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync( // 页面 DOM 就绪后执行 document.addEventListener(DOMContentLoaded, function() { var sidebar document.querySelector(.sidebar); if (sidebar) sidebar.style.display none; document.body.style.backgroundColor #f5f5f5; }); );逻辑说明这个脚本会在每次导航创建新文档时执行相当于一个常驻的 user script。注意它执行时机早于页面自己的脚本所以如果你要操作页面脚本生成的元素得用DOMContentLoaded或MutationObserver等。参数方面这个方法是异步的要在EnsureCoreWebView2Async之后调用。CSS 注入同理用 JS 创建一个style标签塞进去就行。热词里「浏览器翻译插件」「cat-catch 浏览器扩展」这类需求本质上都可以用注入脚本 请求拦截来近似实现不一定非要装扩展。3.3 请求拦截用 WebResourceRequested 做轻量级过滤WebView2 的AddWebResourceRequestedFilter配合WebResourceRequested事件可以拦截指定 URL 模式的请求。这是做广告过滤、请求改写的基础// 拦截所有图片请求作为示例 webView.CoreWebView2.AddWebResourceRequestedFilter(*, CoreWebView2WebResourceContext.Image); webView.CoreWebView2.WebResourceRequested (s, e) { // 命中规则就返回空响应相当于屏蔽 if (e.Request.Uri.Contains(ad.example.com)) { e.Response webView.CoreWebView2.Environment.CreateWebResourceResponse( null, 403, Blocked, Content-Type: text/plain); } };逻辑说明AddWebResourceRequestedFilter的第一个参数是 URI 匹配模式第二个参数是资源类型。不调用这个 filter 方法事件不会触发。CreateWebResourceResponse可以构造自定义响应传null流就是空响应。参数方面CoreWebView2WebResourceContext有很多枚举值All会拦截所有请求性能开销大建议按需指定Image、Script、XmlHttpRequest等。这里有个坑拦截All之后如果忘了给某些请求放行页面会直接白屏。我一般只在明确知道要拦什么的时候才加 filter不做全局拦截。4. 避坑与排查WebView2 在真实机器上的五类翻车现场4.1 现象程序启动报 could not find the webview2 runtime原因用户机器上没有安装 WebView2 Runtime或者安装的版本低于 SDK 要求的最低版本。Win7 和 LTSC 版 Win10 是高发区。解决在程序启动时检测 Runtime 是否存在不存在就引导安装。可以用CoreWebView2Environment.GetAvailableBrowserVersionString()做检测抛异常就说明没装。引导安装有两种方式一是让用户手动下载 Evergreen Runtime 安装包二是用 Fixed Version 模式把 Runtime 打包进程序目录。Fixed Version 体积大但可控适合内网或离线场景。热词里「webview2离线安装包」说的就是这个。4.2 现象导航正常但页面白屏日志无报错原因多半是userDataFolder指向了无写权限的目录或者多个实例共用了同一个 userDataFolder 导致锁冲突。解决把 userDataFolder 改到%LocalAppData%下并确保每个实例用独立子目录。排查时可以在EnsureCoreWebView2Async外面包 try-catch把异常信息打到日志文件里不要只靠 MessageBox。4.3 现象注入的脚本不生效或者时灵时不灵原因AddScriptToExecuteOnDocumentCreatedAsync是异步的如果在导航之后才调用对当前页面无效只对后续导航生效。另外如果页面用了 CSP内容安全策略注入的脚本可能被拦。解决把注入调用放在EnsureCoreWebView2Async之后、第一次Navigate之前。CSP 问题可以通过WebResourceRequested改写响应头来绕过但这属于进阶操作自用场景下更简单的办法是换一个没有严格 CSP 的页面做测试。4.4 现象程序内存占用持续上涨长时间运行后卡顿原因WebView2 内核本身有缓存和进程管理如果频繁导航且不释放旧页面内存会累积。另外注入的脚本如果绑定了全局事件又没解绑也会泄漏。解决定期调用webView.CoreWebView2.TrySuspendAsync()让内核进入低功耗状态或者在不需要时Dispose掉 WebView2 实例重建。注入脚本里避免在window上挂常驻监听用局部作用域。4.5 现象Win7 上安装 Runtime 后仍报错原因Win7 需要先安装 SHA-2 补丁和 .NET 4.6.2 以上运行时否则 Runtime 装不上或装上跑不起来。解决确认系统补丁齐全Runtime 用支持 Win7 的最后一个版本。如果还是不行考虑 Fixed Version 模式把整个 Runtime 目录随程序分发避免依赖系统安装。5. 进阶技巧用 Fixed Version 模式做完全可控的分发Evergreen 模式默认依赖用户机器上的 Runtime好处是自动更新、体积小坏处是你无法控制版本用户环境千差万别。如果你做的是自用工具或者要分发给一批固定用户Fixed Version 模式更省心——Runtime 跟着程序走不依赖系统安装。做法是从 NuGet 下载Microsoft.Web.WebView2.FixedVersionRuntime包解压后得到一个包含完整 Runtime 的目录然后在创建环境时指定这个目录var env await CoreWebView2Environment.CreateAsync( browserExecutableFolder: C:\MyApp\WebView2Runtime, userDataFolder: C:\MyApp\UserData, options: null);browserExecutableFolder指向 Fixed Version Runtime 的根目录里面应该有msedgewebview2.exe。这样即使目标机器没装任何 Runtime程序也能跑起来。代价是分发体积增加一百多兆且需要你自己跟进 Runtime 的安全更新。验证方法很简单找一台干净的、没装过 WebView2 Runtime 的虚拟机把程序拷进去跑。如果能正常加载页面说明 Fixed Version 配置正确。如果报错检查browserExecutableFolder路径是否指向了包含 exe 的那一层而不是它的父目录——这是最常见的路径错误。我自己的习惯是内部工具用 Fixed Version省去每次部署时解释「为什么打不开」的口舌面向不确定用户群的程序用 Evergreen减小分发体积。两种模式可以在同一套代码里通过配置切换不用改业务逻辑。希望帮到你。本文还有配套的精品资源点击获取
返回列表