WebView2
WebView2和CEF相比,在WPF中CEF相当于把渲染的界面生成图片再加载,而WebView2则没有这一步,性能有显著提升。
但是这种方式暂时没有找到支持Flash的方法。
这种方式可以支持Win7,XP尚未进行测试。
但是在安装的时候64位的Win7竟然无法安装32位的WebView2 运行时,所以建议64位的就安装64位的运行时。
官方教程 https://docs.microsoft.com/zh-cn/microsoft-edge/webview2/get-started/wpf
安装运行时
WebView2 实在诱人,最新的 Edge(Chromium) 性能强悍,而且所有使用 WebView2 的应用可以共用一个运行时(说人话就是一个安装了应用时,其他应用就不用装了)。
Windows 11 已经自带 WebView2 ,就连 Office 也会自动部署 WebView2 ,目前 WebView2 已经被部署到 2亿台电脑,并且还在继续增加 …… 未来是属于 WebView2 的。
重要的是 WebView2 仍然支持老旧的、即将被淘汰的 Windows 7 —— 拥有良好的兼容性。
WebView2是依赖于Edge chromium内核的,有如下三种方式可以获取:
- 安装开发版的Edge (Chromium),稳定版的Edge目前不支持WebView控件,不知道后续会不会开放。
- 安装独立的WebView2 Runtime,它可以独立下载和升级。
- 程序内嵌入Edge chromium内核
这三种方式运行效果基本一致,主要特点是:
前两种方式和以前使用IE的浏览器控件非常类似,浏览器内核和程序是分离的,程序可以保持非常小的体积,浏览器内核可以单独升级。
第一种方式目前还不支持Edge的稳定版,无法使用于生产环境
第三种方式和以前的CEF比较类似,将chromium嵌入了程序,可以控制chromium的版本,减少依赖性,同时可以控制浏览器的版本,避免升级导致的不稳定。
但是相应的程序包会特别大,配置也相对更麻烦。
所以这里我推荐第二种方式。
下载地址:
https://developer.microsoft.com/zh-cn/microsoft-edge/webview2/#download-section
项目使用
使用WebView2
安装Microsoft.Web.WebView2程序包
1
| Install-Package Microsoft.Web.WebView2
|
添加名字空间
1
| xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
|
添加控件
1
| <wv2:WebView2 Name="webView" Source="https://www.psvmc.cn"/>
|
代码设置URL
1
| webView.Source = new Uri("https://www.baidu.com");
|
窗口关闭时要关闭WebView2,否则会报错。
1 2 3 4
| private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { webView.Dispose(); }
|
判断运行时是否安装
注意
建议专门一个页面进行检测,检测成功后再跳转到展示页面。
判断是否安装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public static bool IsInstallWebview2() { string result = ""; try { result = CoreWebView2Environment.GetAvailableBrowserVersionString(); } catch (System.Exception) { } if (result == "" || result == null) { return false; } return true; }
|
安装并重启
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
|
public static async Task InstallRuntimeAsync() { ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072; var webClient = new WebClient(); bool isDownload = false; string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); string microsoftEdgeWebview2Setup = System.IO.Path.Combine( desktopPath, "MicrosoftEdgeWebview2Setup.exe" ); try { webClient.DownloadFileCompleted += ( s, e ) => { if (e.Error != null) { Console.WriteLine(@"下载失败:" + e.Error.Message); } else if (e.Cancelled) { Console.WriteLine(@"下载取消"); } else { isDownload = true; } }; await webClient.DownloadFileTaskAsync( "https://go.microsoft.com/fwlink/p/?LinkId=2124703", microsoftEdgeWebview2Setup ); } catch (Exception) { } if (isDownload) { await Task.Delay(300); await Task.Run( () => { Process.Start( microsoftEdgeWebview2Setup, " /install" )?.WaitForExit(); } ); if (IsInstallWebview2()) { Process.Start(Application.ResourceAssembly.Location); Application.Current.Shutdown(); } } }
|
调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| private async void Window_Loaded(object sender, RoutedEventArgs e) { webView.CoreWebView2InitializationCompleted += WebView_CoreWebView2InitializationCompleted; sendBtn.Click += SendBtn_ClickAsync; await webView.EnsureCoreWebView2Async(); if (!ZWebView2Utils.IsInstallWebview2()) { await ZWebView2Utils.InstallRuntimeAsync(); } else { Console.WriteLine(@"Webview2已安装"); } }
|
其中
1
| ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;
|
是为了解决在Win7环境下载文件报错:
请求被中止: 未能创建 SSL/TLS 安全通道
其中重启应用
1 2
| Process.Start(Application.ResourceAssembly.Location); Application.Current.Shutdown();
|
工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
| using System.Diagnostics; using Microsoft.Web.WebView2.Core;
namespace SchoolClient.Utils { using System; using System.Net; using System.Threading.Tasks; using System.Windows;
public class ZWebView2Utils { public static bool IsInstallWebview2() { string result = ""; try { result = CoreWebView2Environment.GetAvailableBrowserVersionString(); } catch (Exception) { } if (string.IsNullOrEmpty(result)) { return false; } return true; }
public static void OpenDownloadPage() { Process.Start("https://go.microsoft.com/fwlink/p/?LinkId=2124703"); }
public static async Task InstallRuntimeAsync() { ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072; var webClient = new WebClient(); bool isDownload = false; string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); string microsoftEdgeWebview2Setup = System.IO.Path.Combine( desktopPath, "MicrosoftEdgeWebview2Setup.exe" ); try { webClient.DownloadFileCompleted += ( s, e ) => { if (e.Error != null) { Console.WriteLine(@"下载失败:" + e.Error.Message); } else if (e.Cancelled) { Console.WriteLine(@"下载取消"); } else { isDownload = true; } }; await webClient.DownloadFileTaskAsync( "https://go.microsoft.com/fwlink/p/?LinkId=2124703", microsoftEdgeWebview2Setup ); } catch (Exception) { } if (isDownload) { await Task.Delay(300); await Task.Run( () => { Process.Start( microsoftEdgeWebview2Setup, " /install" )?.WaitForExit(); } ); if (IsInstallWebview2()) { Process.Start(Application.ResourceAssembly.Location); Application.Current.Shutdown(); } } } } }
|
加载本地文件
你可以读取HTML文件,然后读取NavigateToString
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| private void Window_Loaded(object sender, RoutedEventArgs e) { webView.CoreWebView2InitializationCompleted += WebView_CoreWebView2InitializationCompleted; webView.EnsureCoreWebView2Async(); }
private void WebView_CoreWebView2InitializationCompleted(object sender, Microsoft.Web.WebView2.Core.CoreWebView2InitializationCompletedEventArgs e) { string rootPath = Environment.CurrentDirectory; string filepath = System.IO.Path.Combine(rootPath, "html", "index.html"); Console.WriteLine(filepath); if (webView != null && webView.CoreWebView2 != null) { string text = System.IO.File.ReadAllText(filepath); webView.CoreWebView2.NavigateToString(text); } }
|
或者
你也可以通过Navigate连接到本地文件:
1 2 3
| string rootPath = Environment.CurrentDirectory; string filepath = System.IO.Path.Combine(rootPath, "html", "index.html"); webView.Source = new Uri("file:///" + filepath);
|
连接使用外部浏览器打开
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| WebView.NavigationStarting += WebView_NavigationStarting;
private void WebView_NavigationStarting (object? sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs e) { if (e.Uri.StartsWith("http://") || e.Uri.StartsWith("https://")) { e.Cancel = true; Process.Start(new ProcessStartInfo(e.Uri) { UseShellExecute = true }); } }
|
互操作
C#向JS发消息
发送纯文本
本机代码
1 2 3 4
| if (webView != null && webView.CoreWebView2 != null) { webView.CoreWebView2.PostWebMessageAsString(inputTB.Text); }
|
JS代码
1 2 3 4 5
| <script type="text/javascript"> window.chrome.webview.addEventListener('message', arg => { document.querySelector(".outer").innerHTML = arg.data; }); </script>
|
发送JSON
1 2 3 4
| if (webView != null && webView.CoreWebView2 != null) { webView.CoreWebView2.PostWebMessageAsJson("{\"color\":\"blue\"}"); }
|
JS接收
1 2 3 4 5
| <script type="text/javascript"> window.chrome.webview.addEventListener('message', arg => { document.querySelector(".outer").innerHTML = arg.data.color; }); </script>
|
唯一的差别在于
接收的时候会自动转换为JSON对象。不过我还是建议传递字符串,转换的操作放在JS中处理。
C#调用JS代码
执行代码
1 2 3 4 5 6 7
| private async void SendBtn_ClickAsync(object sender, RoutedEventArgs e) { if (webView != null && webView.CoreWebView2 != null) { await webView.CoreWebView2.ExecuteScriptAsync("alert('123')"); } }
|
调用JS方法
CSharp
1 2 3 4 5 6 7
| private async void SendBtn_ClickAsync(object sender, RoutedEventArgs e) { if (webView != null && webView.CoreWebView2 != null) { await webView.CoreWebView2.ExecuteScriptAsync("receiveMsg('你好')"); } }
|
JS中
1 2 3
| function receiveMsg (msg) { console.info(msg); }
|
JS如果要修改Vue的data的属性
1 2 3 4 5 6 7 8 9 10 11
| let vueObj = new Vue({ el: ".z_chat_outer", data: { msgList: [] } })
function receiveMsg (msg) { let msgList = vueObj.msgList; msgList[msgList.length - 1].msg += msg; }
|
JS调用C#代码
CSharp中定义
定义数据交互的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| [ClassInterface(ClassInterfaceType.AutoDual)] [ComVisible(true)] public class ScriptCallbackObject { public string GetMessageInfo() { return "C#返回的信息"; }
public void ShowMessage(string arg) { Console.WriteLine(arg); MessageBox.Show("【网页调用C#】:" + arg); } }
|
代码中注册事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| private void Window_Loaded(object sender, RoutedEventArgs e) { webView.CoreWebView2InitializationCompleted += WebView_CoreWebView2InitializationCompleted; webView.EnsureCoreWebView2Async(); }
private async void WebView_CoreWebView2InitializationCompleted( object? sender, Microsoft.Web.WebView2.Core.CoreWebView2InitializationCompletedEventArgs e) { string rootPath = Environment.CurrentDirectory; string filepath = System.IO.Path.Combine(rootPath, "html", "index.html"); await webView.EnsureCoreWebView2Async(null); if (webView == null || webView.CoreWebView2 == null) return; webView.CoreWebView2.AddHostObjectToScript("csobj", new ScriptCallbackObject()); await webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync( "var csobj = window.chrome.webview.hostObjects.csobj;"); await webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync( "var csobj_sync= window.chrome.webview.hostObjects.sync.csobj;");
webView.CoreWebView2.Navigate("file:///" + filepath); }
|
JS异步调用
异步调用取值
1 2 3 4
| async function myfunc() { let msg = await window.chrome.webview.hostObjects.csobj.GetMessageInfo(); document.querySelector(".mytext").innerText = msg; }
|
当然上面的代码也可以简写为
1 2 3 4
| async function myfunc() { let msg = await csobj.GetMessageInfo(); document.querySelector(".mytext").innerText = msg; }
|
这是因为我们已经在C#中创建了JS的对象
1
| webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync("var csobj = window.chrome.webview.hostObjects.csobj;");
|
JS同步调用
同步调用取值
1 2 3 4 5 6
| <script type="text/javascript"> function myfunc() { var msg = window.chrome.webview.hostObjects.sync.csobj.GetMessageInfo(); document.querySelector(".mytext").innerText = msg; } </script>
|
同步调用
1 2 3 4 5
| <script type="text/javascript"> function myfunc() { window.chrome.webview.hostObjects.sync.csobj.ShowMessage("你好吗"); } </script>
|
注意
window.chrome.webview.hostObjects.csobj是异步的,要想同步就要用window.chrome.webview.hostObjects.sync.csobj。
打包
程序运行的时候会在根目录生成一个文件夹,类似于xhschool.exe.WebView2,也就是可执行程序名.WebView2
这个是运行过程中生成的,打包并不需要打包到安装包中,可以设置忽略。
Flash支持
很遗憾,现在还没找到WebView2支持Flash的方式。
目前要想支持Flash只有两种选择:
重新渲染
在个别电脑上出现奇葩的问题,网页加载后,页面不渲染。
这里只能通过InvalidateVisual重新触发渲染。
InvalidateVisual、InvalidateArrange 和 InvalidateMeasure 是 WPF 中三个不同的方法,用于标记需要进行重新绘制、重新排列和重新测量的元素。
InvalidateVisual: 该方法用于标记元素和其子元素需要重新绘制视觉呈现。
当元素的外观或视觉效果发生改变时,通过调用该方法告诉 WPF 引擎需要更新元素的可视化。比如当一个元素的背景色、边框样式或其他可视化属性改变时,可以调用 InvalidateVisual 来触发重绘操作。
InvalidateArrange: 该方法用于标记元素及其子元素需要进行重新排列。
当元素的大小、位置或布局参数发生改变时,通过调用该方法告诉布局系统需要重新计算元素的布局。调用 InvalidateArrange 方法会触发元素的 ArrangeOverride 方法,从而实现重新排列。
InvalidateMeasure: 该方法用于标记元素及其子元素需要进行重新测量。
当元素的大小或布局参数发生改变时,通过调用该方法告诉布局系统需要重新计算元素的测量。调用 InvalidateMeasure 方法会触发元素的 MeasureOverride 方法,从而实现重新测量。
总结来说
InvalidateVisual 用于重新绘制元素的视觉呈现
InvalidateArrange 用于重新排列元素
InvalidateMeasure 用于重新测量元素
它们通常一起使用,以确保元素能够正确地进行绘制、排列和测量,以反映最新的属性或状态的变化。