之前写过一篇文章来介绍WebView2:.Net桌面端开发使用WebView2,可以放弃CefSharp?
当时WebView2还是预览版,截止到今天,官方已经更新到1.0.705.50正式版本,对应的chrome 内核版本为:88.0.705.74,可以说更新的还挺频繁。刚好公司有个项目使用了CefSharp,就深入研究了一下WebView2的使用,这篇文章就来详细记录一下我的使用体验吧。
最新实战记录了WebView2的注意事项:WebView2的注意事项
老规矩,写技术类文章不注明开发环境还不如不写。
运行环境:.Net Framework 4.5.2 (由于我的项目是基于452,所以这里没有用.Net 5)
开发环境:VS2019 16.8.2
框架语言:WPF
这个安装过程在上篇文章已经有了,就不再详述。
由于WebView2是基于Chrome内核的,所以必须有Chrome内核环境,有如下三种方式可以获取:
有两种方法使用WebView2,上篇文章没有详细写第二种,再把官方文档的说明贴一下:
必须对CoreWebView2进行初始化,看一下微软官方的解释是因为创建 CoreWebView2 是一个昂贵的操作,它涉及启动 Edge 浏览器进程之类的操作。
有两种方法可导致创建 CoreWebView2:
1)设置 Source 属性(例如,可以通过标记执行此操作)。 这称为隐式初始化。
2)调用 EnsureCoreWebView2Async 方法。 这称为显式初始化。
<Border BorderThickness="2" BorderBrush="#2497F0">
<webView:WebView2 x:Name="webView" Source="http://www.baidu.com"/>
</Border>
在使用第二种方法的时候需要特别注意:
if (this.webView != null)
{
var path = AppDomain.CurrentDomain.BaseDirectory + "WebViewCache";
var env = await CoreWebView2Environment.CreateAsync(userDataFolder: path);
await webView.EnsureCoreWebView2Async();
webView.CoreWebView2.Navigate("www.bing.com");
}
如果只是在初始化的时候这么写,会发现最后一句话会报错,提示webView.CoreWebView2为空异常,这是因为创建 CoreWebView2 是通过调用底层的Chrome内核来初始化的,这里的await并不是等待CoreWebView2创建成功返回,而是等待去创建这个操作成功了返回。而真正的CoreWebView2的初始化才刚开始,所以下面直接执行Navigate报空异常。这里先需要注册一个CoreWebView2InitializationCompleted回调来等待CoreWebView2的初始化完成,
private void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
{
if (webView.CoreWebView2 != null)
{
webView.CoreWebView2.Navigate("www.bing.com");
}
}
这样就可以了。
另外,在初始化的时候可以进行初始化环境配置
userDataFolder:设置用户目录
browserExecutableFolder:设置Chrome内核路径
options:环境相关参数
if (this.webView != null)
{
var path = AppDomain.CurrentDomain.BaseDirectory + "WebViewCache";
var env = await CoreWebView2Environment.CreateAsync(userDataFolder: path,browserExecutableFolder:path,options:new CoreWebView2EnvironmentOptions());
await webView.EnsureCoreWebView2Async(env);
在我使用的当前环境下测试发现browserExecutableFolder这个内嵌Chrome内核的方式直接报错,可能是bug,或者是我的使用方式问题(虽然我自认就是按官方教程正确设置的),如果有高手欢迎指正
同时在初始化完成后可以对WebView2进行一些基本的功能设置
if (webView.CoreWebView2 != null)
{
webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
webView.CoreWebView2.Settings.AreDevToolsEnabled = false;
webView.CoreWebView2.Settings.IsZoomControlEnabled = false;
webView.CoreWebView2.Settings.IsStatusBarEnabled = false;
}
AreDefaultContextMenusEnabled :禁用网页右键功能
AreDevToolsEnabled:禁用开发工具,设为true,可通过F12打开开发者调试功能
IsZoomControlEnabled:禁用网页的放大缩小功能
IsStatusBarEnabled:禁用网页上加载页面的进度条功能
webView.CoreWebView2.NavigationStarting += WebView_NavigationStarting;
webView.CoreWebView2.NavigationCompleted += WebView_NavigationCompleted;
webView.CoreWebView2.WebMessageReceived += CoreWebView2_WebMessageReceived;
webView.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested;
...
WebView_NavigationStarting:跳转开始回调
WebView_NavigationCompleted:跳转结束回调
CoreWebView2_WebMessageReceived:收到页面消息回调
CoreWebView2_NewWindowRequested:打开新窗口时回调
下面演示在跳转开始判断当前地址是否是https,如果不是则取消跳转。
private void WebView_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e)
{
String uri = e.Uri;
this.txtUrl.Text = uri;
if (!uri.StartsWith("https://"))
{
//Uri
webView.CoreWebView2.ExecuteScriptAsync($"alert('{uri} is not safe, try an https link')");
e.Cancel = true;
}
}
以上只列出了几个常用的事件,详细可以查看官方文档:CoreWebView
客户端调用网页JS方法
if (webView.CoreWebView2 != null)
{
webView.CoreWebView2.ExecuteScriptAsync($"test('{this.txtUrl.Text}')");
}
JS调用客户端方法
这里略复杂一点,需要先注册一下AddHostObjectToScript:
webView.CoreWebView2.AddHostObjectToScript("bridge", new JavaScriptBridge());
在写一个类,专门用来提供给JS调用:JavaScriptBridge
这里需要注意必须得加上[ComVisible(true)]属性
[ComVisible(true)]
public class JavaScriptBridge
{
public string Func(string param)
{
string tmp = param;
return tmp + "测试一下呀";
}
}
在网页端:
const bridge = chrome.webview.hostObjects.bridge;
console.log(await bridge.Func("testing..."));
根据开头初始化提到的三种依赖方式,在实际使用中,可能第三种方式比较适合,即通过安装独立的WebView2 Runtime,也就是官方说的常青版程序,这个是将Chrome内核打包成一个程序,安装好就能受到官方的升级更新服务。
在运行程序前先检测一下是否安装了正确的内核程序:
由于安装程序过程并不可控,我这里用了比较笨的方法,就是在程序执行前每隔三秒去检测环境是否安装好。
[STAThread]
public static void Main()
{
if (!CheckWebView())
{
MessageBox.Show("检测到当前未安装运行环境,正在启动安装程序,请稍后...");
InstallWebView();
Stopwatch.StartNew();
while (!CheckWebView())
{
Thread.Sleep(3000);
}
}
var application = new App();
application.InitializeComponent();
application.Run();
}
private static bool CheckWebView()
{
try
{
var str = CoreWebView2Environment.GetAvailableBrowserVersionString();
if (!string.IsNullOrWhiteSpace(str))
{
return true;
}
}
catch (Exception)
{
return false;
}
return false;
}
private static void InstallWebView()
{
var path = AppDomain.CurrentDomain.BaseDirectory + "check";
using (Process process = new Process())
{
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = path + "\\MicrosoftEdgeWebview2Setup.exe";
try
{
process.Start();
}
catch (Exception ex)
{
//MessageBox.Show(ex.Message);
}
}
}
通过这个方法,就可以把Cefsharp那一堆内核程序从安装包里删除,大大缩小了软件包
一切都看起来很美好,然而…
我在实际使用过程中,发现WebView2目前对WPF并不友好,直接上图:
发现了吗?所有写在WebView2后面的代码全被覆盖了,我一开始以为是我的使用问题,经过在github上官方反馈,才得知这是一个已知的bug,而且提了有一年多了,并没有解决这个问题,Issue,甚至有外国程序员发贴diss
这个问题目前官方并没有明确的修复日期,希望官方能早日解决吧。
更新于2022年1月25日,这个问题官方在最新预览版本已解决,详细请查看WebView2 终于修复了页面 UI 可见性没有随 WebView2 可见性而更改的 Bug