张春 发表于 2024-7-27 04:57:35

在WPF中使用WebView2详解

Microsoft Edge WebView2

Microsoft Edge WebView2 控件允许在本机应用中嵌入 web 技术(HTML、CSS 以及 JavaScript)。 WebView2 控件使用 Microsoft Edge 作为绘制引擎,以在本机应用中表现 web 内容。
使用 WebView2 可以在本机应用的不同部门嵌入 Web 代码,或在单个 WebView2 实例中天生所有本机应用。
https://i-blog.csdnimg.cn/direct/635690bbf9d0432eabd1010affca3049.png
现在使用混合技术开辟桌面客户端已经变得越来越常见了,如网易云音乐、QQ音乐等,因为可以嵌入现有的网页端页面,开辟成本是比较低的,而且还可以实现跨平台。
使用混合技术开辟的桌面客户端占用资源相对较多,反应速率也会慢点,如果需要对操作体系有较多的功能交互,保举使用原生API开辟。

WebView2已经出来好几年了,我一直没怎么使用过,前面一直使用的是CEFSharp,CEF是基于Google Chromium项目的开源Web browser控件,和CEFSharp一样,WebView2也支持winform和wpf。
简单来说,WebView2就是一个浏览器控件,类似WPF里自带的WebBrowser,只是WebView2的内核Chrome,WebBrowser的内核是IE,而且WebView2提供的可编程接口更多。

对于桌面开辟开说,我们可以使用WebView2来实现以下功能
1、爬虫
对于动态网页,可以嵌入WebView2来进行抓取。
2、自制浏览器
使用WebView2可以自己开辟简单的浏览器
3、嵌入本地网页
偶尔候要展示一些简单的网页内容,如报告、图表等,都可以使用WebView2进行加载表现 
4、开辟混合应用
借助WebView2的本机和Web互操作功能,开辟混合应用,例如:网易云音乐。

迩来我们有一个需求是需要绘制大量的图表,使用WPF的免费控件都不能很好的满足需求,背面就选择使用Echarts(Echarts是一款基于JavaScript的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表),然后嵌入网页到WPF中来实现。
原来是准备使用CEF的,同事在前期使用了WebView2,以是就继续使用WebView2。
这里我写篇文章做个记录,以便以后需要时查察。注意:本文仅介绍了WebView2中的常勤奋能,全部功能的使用和介绍可以访问WebView2的官方文档。

WebView2的优点



[*] 本机功能。 访问完整的本机 API 集。
[*] 代码共享。 向代码库添加 web 代码可以增加跨多个平台的重用。
[*] Microsoft 支持。 Microsoft 在受支持的平台上提供支持并添加新功能请求。
[*] 定期发布更新和安全修补的最新版 Chromium。
[*] 已修复版本分布。 也可以在应用中打包特定版本的 Chromium 位。
[*] 支持Windows10/11

WebView2支持的平台

编程语言


[*]Win32 C/C++
[*].NET Framework 4.6.2 +
[*].NET Core 3.1 +
[*]WinUI 2.0
[*]WinUI 3.0

操作体系


[*]Windows 11
[*]Windows 10
[*]Windows 10 IoT Enterprise LTSC x32 2019
[*]Windows 10 IoT Enterprise LTSC x64 2019
[*]Windows 10 IoT Enterprise 21h1 x64
[*]Windows Server 2022
[*]Windows Server 2019
[*]Windows Server 2016

安装WebView2运行时

如果电脑中没有安装新版本Microsoft Edge(基于Chrome内核),就需要安装WebView2运行时。
分发应用时,最好带上WebView2运行时的安装包,因为不确定用户电脑上是否会有WebView2运行时。
下载地点:
https://go.microsoft.com/fwlink/p/?LinkId=2124703

WebView2 运行时中的进程

WebView2 进程组是 WebView2 运行时进程的集合。 WebView2 进程组包括以下内容:


[*]单个浏览器进程。
[*]一个或多个呈现器进程。
[*]其他帮助步伐进程,例如 GPU 进程和音频服务进程。
WebView2 进程组中的进程数和状态大概会随着 WebView2 应用步伐使用 WebView2 功能而更改。 (但是,WebView2 进程组中只有一个特定的浏览器进程。) 例如,从同 CoreWebView2Environment一个 创建新的 WebView2 实例,但属性中 Source 具有不同的域,通常会启动新的呈现器进程。
呈现器进程的数目大概因以下条件而异:


[*] 在 WebView2 运行时中使用 站点隔离 功能。 
[*] 在使用雷同用户数据文件夹的 WebView2 实例中呈现的不同断开毗连源的数目。
控制何时创建这些额外进程的逻辑取决于Chromium体系结构,而且超出了 WebView2 运行时的范围。

WebView2 运行时进程和用户数据文件夹

WebView2 运行时进程集合中的所有进程都与浏览器进程相关联,浏览器进程又与单个用户数据文件夹相关联。 如果应用步伐使用多个用户数据文件夹,将为其中每个用户数据文件夹创建一组 WebView2 运行时进程。
用户数据文件夹可由多个应用步伐共享,但请务必考虑对性能和管理的影响,如 管理用户数据文件夹中所述。
https://i-blog.csdnimg.cn/direct/a1d76629556448838fd0649feb0d6190.png
若要使用多个用户数据文件夹,WebView2 应用步伐需要创建不同的 CoreWebView2Environment 对象。 WebView2通过配置的 CoreWebView2Environment 对象为给定的用户数据文件夹创建实例。 每个 CoreWebView2Environment 对象都需要配置不同的用户数据文件夹值。
为给定的用户数据文件夹创建第一 WebView2 个实例时,将启动与该用户数据文件夹关联的 WebView2 运行时进程集合的浏览器进程。 所有其他进程将由该浏览器进程的生存期管理。

多个情况对象

如果创建以雷同方式配置的多个 CoreWebView2Environment 对象, (包括共享同一用户数据文件夹) ,则它们将表示雷同的用户数据文件夹和雷同的关联进程集合。 使用这些对象中的任何 CoreWebView2Environment 一个创建具有一个 CoreWebView2 共享用户数据文件夹和关联的进程集合的 。
如果尝试使用另CoreWebView2Environment一个已使用的用户数据文件夹创建 CoreWebView2Environment ,而且未将两个CoreWebView2Environment对象配置为雷同(例如,如果它们使用不同的值创建CoreWebView2EnvironmentOptions.Language),则第二CoreWebView2Environment个对象将无法创建WebView2对象。 


在WPF中使用WebView2

这里以Visual Studio 2022和.NET6进行演示
1、使用Visual Studio创建一个WPF工程
2、使用nuget引入包Microsoft.Web.WebView2

https://img-blog.csdnimg.cn/img_convert/f51bb732a5fdc6e4f09a61434e1c8456.png
 3、在XAML中引入定名空间
xmlns:webview2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf" 4、放置一个WebView2控件
<Window x:Class="WpfWebView2Demo.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.openxmlformats.org/markup-compatibility/2006"
          xmlns:webview2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
          xmlns:local="clr-namespace:WpfWebView2Demo"
          mc:Ignorable="d"
          Title="MainWindow" Height="720" Width="1280">
      <Grid>
          <webview2:WebView2 x:Name="webview2"
          Source="https://myfreetime.cn"></webview2:WebView2>
      </Grid>
</Window>
5、运行效果

https://img-blog.csdnimg.cn/img_convert/6add074fb7c178888ce926364b07ef50.png

注意事项:
请将项目平台设置成x64而不是Any CPU。
以前在使用CEF时,如果使用Any CPU,在XAML计划器会无法及时预览CEF控件,WebView2固然没有这个题目,但是也会也现IntelliSense无法正常工作的题目。

基本导航功能

1、通过Source属性,可以设置初始URI(支持网址、本地文件等)
2、导航到指定内容
调用WebView2.CoreWebView2.Navigate函数即可执行导航,如
webView2.CoreWebView2.Navigate("https://www.bing.com"); webView2.CoreWebView2.Navigate("C:\Users\xxx\Documents\说明书.pdf");


webView2.CoreWebView2.Navigate("file:///C:/Users/username/Documents/GitHub/Demos/demo-to-do/index.html");

https://img-blog.csdnimg.cn/img_convert/6da05e1599852fe54ab15565abcee182.png
 3、退却
调用WebView2.CoreWebView2.GoBack函数即可退却
webview2.CoreWebView2.GoBack();
4、进步
调用WebView2.CoreWebView2.GoForward函数即可进步
webview2.CoreWebView2.GoForward();
导航到本地内容

WebView2提供了几种方式,可以导航到本地内容。
通过不同的方式加载本地内容时会有一些区别 ,如下所示
Scenario导航到文件路径导航到HTML字符串使用虚拟主机名称映射使用WebResourceRequestedOrigin-based DOM APIs✔️❌✔️✔️DOM APIs requiring secure context❌❌✔️✔️Dynamic content❌✔️❌✔️Additional web resources✔️❌✔️✔️Additional web resources resolved in WebView2 process✔️❌✔️❌
下面我们依次介绍各种方法
1、直接导航到本地文件

this.webview2.CoreWebView2.Navigate("D:\\demo.html");
通过文件路径方式加载的一些限定:


[*]指定文件URL时,应用步伐会导航到磁盘上的文件,而不是网络上的域。因此,无法在天生的文档中使用跨源资源。
[*]从不同文件URL加载的不同文档不被视为来自同一泉源,而且无法访问雷同的存储数据。
[*]某些web API仅限于安全的HTTPS URL,不实用于文件URL加载的文档。
[*]不能使用相对路径,必须使用绝对路径
[*]要允许从文件URI引用其他本地文件,或表现应用了XSL转换的XML文件,可以设置--allow file access from files浏览器参数。
方法如下:
1//允许从文件URI引用其他本地文件
2var options = new CoreWebView2EnvironmentOptions("--allow-file-access-from-files");
3var environment = await CoreWebView2Environment.CreateAsync(options: options);
4await webview2.EnsureCoreWebView2Async(environment);

[*]当通过文件URL加载文档时,文档的内容来自磁盘上的静态文件。这意味着无法动态修改此本地内容。这与从web服务器加载文档不同,在web服务器中,每个响应都可以动态天生。
[*]通过文件URL加载的文档,可以引用其他web资源,如CSS、脚本或图像文件,这些文件也通过文件URL提供。

在Windows平台下,可以指定文件的绝对路径进行访问,如
D:\demo.html
如果想独立于各个平台,应该使用标准的方式指定文件路径,如
file:///D:/demo.html file:///C:/Users/username/Documents/GitHub/Demos/demo-to-do/index.html
2、导航到HTML字符串


var html = System.IO.File.ReadAllText("D:\\demo.html");

this.webview2.CoreWebView2.NavigateToString(html);
通过HTML字符串加载时,会有一些限定:


[*]使用NavigateToString方法加载的文档的位置设置为about:black,原点设置为null。这意味着不能使用依赖于所界说的源的web API,如localStorage或indexedDB。
[*]某些web API仅限于安全的HTTPS URL,而且不可用于通过NavigateToString方法加载的文档,因为它们的位置设置为about:black。
[*]通过NavigateToString方法加载本地内容时,直接将内容作为参数提供给该方法。这也就意味着可以动态表现内容。
[*]使用NavigateToString方法加载本地内容不会使天生的文档引用其他web资源,如CSS、图像或脚本文件。该方法只允许您指定HTML文档的字符串内容,可以在HTML文档中内联表示这些(或者使用下面的两种方法)。

3、使用虚拟主机映射 

1 //将demo映射到D:\demohtml\preview文件夹
2 this.webview2.CoreWebView2.SetVirtualHostNameToFolderMapping("demo", @"D:\demohtml\preview", CoreWebView2HostResourceAccessKind.DenyCors);
4 //可以通过https的方式访问,实际映射到D:\demohtml\preview\index.html
5 this.webview2.CoreWebView2.Navigate("https://demo/index.html");
使用虚拟主机映射时,会有一些限定:


[*]通过虚拟主机名映射加载的本地内容会天生一个具有HTTP或HTTPS URL和相应泉源的文档。这意味着,需要localStorage或indexedDB等泉源的web API将起作用,而且属于同一泉源的其他文档将能够使用存储的数据。
[*]支持HTTPS的Web API
[*]通过虚拟主机名映射加载本地内容时,将虚拟主机名映像到磁盘上包含静态文件的本地文件夹。这意味着无法动态修改此本地内容。这与从web服务器加载文档不同,在web服务器中,每个响应都可以动态天生。
[*]通过虚拟主机名映射加载的本地内容具有支持相对URL分析的HTTP或HTTPS URL。这意味着加载的文档可以引用其他web资源,如CSS、脚本或图像文件,这些文件也通过虚拟主机名映射提供。

4、处理WebResourceRequested 事件

当WebView2尝试加载资源时,会触发WebResourceRequested 事件,我们可以使用此事件拦截请求并提供本地内容。在WebResourceRequested事件处理函数中,可以根据每个请求自界说本地内容的行为。
private void btn_handleWebResourceRequested_Click(object sender, RoutedEventArgs e)
   {
       //为WebResourceRequested事件添加Uri和资源上下文过滤器
       this.webview2.CoreWebView2.AddWebResourceRequestedFilter("https://demo/*", CoreWebView2WebResourceContext.All);
       this.webview2.CoreWebView2.WebResourceRequested += CoreWebView2_WebResourceRequested;      
   }

   private void CoreWebView2_WebResourceRequested(object? sender, CoreWebView2WebResourceRequestedEventArgs e)
   {
       //获取本地文件路径
       //注意:资源文件要放在一个路径下
       string assetsFilePath = @"D:\demohtml\preview" + e.Request.Uri.Substring("https://demo/*".Length - 1);
       try
       {
         FileStream fs = File.OpenRead(assetsFilePath);
         ManagedStream ms = new ManagedStream(fs);
         string headers = "";
         if (assetsFilePath.EndsWith(".html"))
         {
               headers = "Content-Type: text/html";
         }
         else if (assetsFilePath.EndsWith(".jpg"))
         {
               headers = "Content-Type: image/jpeg";
         }
         else if (assetsFilePath.EndsWith(".png"))
         {
               headers = "Content-Type: image/png";
         }
         else if (assetsFilePath.EndsWith(".css"))
         {
               headers = "Content-Type: text/css";
         }
         else if (assetsFilePath.EndsWith(".js"))
         {
               headers = "Content-Type: application/javascript";
         }

         e.Response = this.webview2.CoreWebView2.Environment.CreateWebResourceResponse(ms, 200, "OK", headers);
       }
       catch (Exception)
       {
         e.Response = this.webview2.CoreWebView2.Environment.CreateWebResourceResponse(null, 404, "Not found", "");
       }

   }
ManagedStream.cs
public class ManagedStream : Stream
{
      public ManagedStream(Stream s)
      {
          steam = s;
      }

      public override bool CanRead => steam.CanRead;

      public override bool CanSeek => steam.CanSeek;

      public override bool CanWrite => steam.CanWrite;

      public override long Length => steam.Length;

      public override long Position { get => steam.Position; set => steam.Position = value; }

      public override void Flush()
      {
         
      }

      public override long Seek(long offset, SeekOrigin origin)
      {
          return steam.Seek(offset, origin);
      }

      public override void SetLength(long value)
      {
         
      }

      public override int Read(byte[] buffer, int offset, int count)
      {
          int read = 0;
          try
          {
            read = steam.Read(buffer, offset, count);
            if (read == 0)
            {
                  steam.Dispose();
            }
          }
          catch
          {
            steam.Dispose();
            throw;
          }
          return read;
      }

      public override void Write(byte[] buffer, int offset, int count)
      {
      
      }

      private Stream steam;
}
处理WebResourceRequested事件来加载本地内容时,也会有一些限定:


[*]通过WebResourceRequested加载的本地内容会天生一个具有HTTP或HTTPS URL和相应泉源的文档。这意味着,需要localStorage或indexedDB等泉源的web API将起作用,而且属于同一泉源的其他文档将能够使用存储的数据。
[*]某些web API仅限于安全的HTTPS URL。使用WebResourceRequested可以将HTTPS URL web资源请求替换为我们自己的本地内容。
[*]通过WebResourceRequested加载本地内容时,我们在事件处理步伐中指定要加载的本地内容,也就是可以动态天生内容。
[*]WebResourceRequested修改通过支持相对URL分析的HTTP或HTTPS URL加载的内容。这意味着天生的文档可以引用其他web资源,如CSS、脚本或图像文件,这些文件也通过WebResourceRequested提供。
[*]通过文件URL或虚拟主机名映射加载内容时,分析发生在WebView2进程中。但是,WebResourceRequested事件是在宿主应用步伐进程的WebView2 UI线程上引发的,这大概会导致结果文档的加载速率减慢。(如果加载的文件过多,过大,会出现界面假死的情况)


导航事件

在WebView2导航到指定URI期间,会按顺序引发以下事件


[*]NavigationStarting
[*]SourceChanged
[*]ContentLoading
[*]HistoryChanged
[*]NavigationCompleted

https://img-blog.csdnimg.cn/img_convert/cad3ec7c085516461eb74b383806e155.png

如果导航失败,会按顺序引发以下事件


[*]SourceChanged
[*]ContentLoading
[*]HistoryChanged

阐明:
在NavigationStarting事件中,可以通过设置args.Cancel=true来取消导航 


本机端和 Web 端代码的互操作

Microsoft Edge WebView2 控件允许将 Web 内容嵌入本机应用步伐。 可以根据需要完成的任务,以不同的方式使用 WebView2。 
以是就需要本机与Web端进行互操作,例如:


[*]导航到其他网站后,更新本机主机窗口标题。
[*]从 Web 应用发送本机相机对象并使用其方法。
[*]在应用步伐的 Web 端运行专用 JavaScript 文件。

WebView2支持以下几种互操作方式
执行JS脚本

这个功能对于混合开辟很紧张。以前我在CEFSharp中嵌入Echarts时,就是调用类似的函数将数据传到页面上。
WebView2提供了两种方式执行JS脚本,这两种方式都可以执行JS脚本,只是执行时机不一样
API阐明ExecuteScriptAsync在 WebView2 控件中运行 JavaScript。 在页面 文档对象模型 (DOM) 加载内容 或 完成导航后调用此方法。AddScriptToExecuteOnDocumentCreatedAsync创建 DOM 时,在每个页面上运行。 在初始化 CoreWebView2 后调用此方法。
ExecuteScriptAsync

例如下面在浏览器弹框输出HelloWorld
1 await this.webview2.CoreWebView2.ExecuteScriptAsync("alert('HelloWorld')");
https://img-blog.csdnimg.cn/img_convert/a0e930ac9760b0f64bf04020fc031ef7.png

ExecuteScriptAsync会返回执行结果值的JSON串。
同时ExecuteScriptAsync也支持执行本地的js文件
例如我在运行路径下创建了一个changecolor.js
document.getElementsByTagName("body").style.backgroundColor = "green"; 然后加载执行:
var text = System.IO.File.ReadAllText(@"changecolor.js");
await webView2.CoreWebView2.ExecuteScriptAsync(text);  可以看到网页配景变成了绿色

https://img-blog.csdnimg.cn/img_convert/6119b504b0809ec87a754fd80572924a.png


AddScriptToExecuteOnDocumentCreatedAsync

这个会增加一个在DOM加载但是未创建之前调用的脚本,以是可以进行一些初始始工作。
例如,禁用右键菜单
1      private asyncvoid webview2_Loaded(object sender, RoutedEventArgs e)
2      {
3          await webview2.EnsureCoreWebView2Async();
4
5          if(this.webview2.IsLoaded)
6          {
7            //禁用右键菜单
8            await this.webview2.CoreWebView2.ExecuteScriptAsync("window.addEventListener('contextmenu', window => {window.preventDefault();});");
9          }
10      }
主机(应用步伐)和Web页面通信

WebView2提供了两种方式和Web页面通信


[*] 主机使用 CoreWebView2.PostWebMessageAsString 或 CoreWebView2.PostWebMessageAsJSON将消息发布到 WebView2 控件中的 Web 内容。 消息由添加到 window.chrome.webview.addEventListener的处理步伐捕获。


[*] WebView2 控件中的 Web 内容可以使用 将消息发布到主机 window.chrome.webview.postMessage。 主机使用主机上注册 WebMessageReceived 的任何内容处理消息。

使用PostWebMessageAsString

起首我们创建一个简单的网页(因为我不会前端 ,以是这真的是一个非常简单的页面)
这个页面会在收到字符串时进行打印
html
<!DOCTYPE html>
<html>
<head>
   <title>ScenarioWebMessage</title>
   <script>
         window.chrome.webview.addEventListener('message', arg => {
             document.writeln(arg.data)
         });
   </script>
</head>
<body>
   
</body>
</html>
 cs
this.webview2.CoreWebView2.PostWebMessageAsString(inputWindow.Input); 然后调用PostWebMessageAsString函数发送字符串到Web端,页面会进行打印

https://img-blog.csdnimg.cn/img_convert/aced93f6c7452a42fe2b878fb4e40d0b.png

PostWebMessageAsJson

这里发送一个简单的json串来指定页面的配景图片,依旧是一个非常简单的页面
html
<!DOCTYPE html>
<html>
<head>
      <title>ScenarioWebMessage</title>
      <script>
          window.chrome.webview.addEventListener('message', arg => {
               document.body.style.backgroundImage = "url('" + arg.data.background + "')";
          });
      </script>
</head>
<body>

</body>
</html>
 cs
var json = "{\"background\":\"https://myfreetime.cn/usr/uploads/2024/4/%E6%B8%85%E5%B9%B3%E8%B0%83%C2%B7%E5%90%8D%E8%8A%B1%E5%80%BE%E5%9B%BD%E4%B8%A4%E7%9B%B8%E6%AC%A2/jk3.jpg\"}";

this.webview2.CoreWebView2.PostWebMessageAsJson(json);
执行以后可以看到网页的配景换了

https://img-blog.csdnimg.cn/img_convert/0eb3cb6b7838a11d81da71d347ec0311.png

将消息发送到主机

在页面中,调用window.chrome.webview.postMessage可以将消息发送到主机
起首我们创建一个html页面,在这个页面中增加一个按钮和文本框,当点击按钮时,将消息发送到主机
html
<!DOCTYPE html>
<html>
<head>
   <title>ScenarioWebMessage</title>
   <script>
         function SetTitleText() {
             let titleText = document.getElementById("title-text");
             window.chrome.webview.postMessage(`${titleText.value}`);
         }
   </script>
</head>
<body>
   <div id="colorable">
         <h2>Receiving Messages</h2>
         <p>
             The host app can receive messages by registering an event handler
             with <code>ICoreWebView2::add_WebMessageReceived</code>. If you
             enter text and click "Send", this page will send a message to the
             host app which will change the text of the title bar.
         </p>
         <input type="text" id="title-text" />
         <button οnclick="SetTitleText()">Send</button>
   </div>
</body>
</html>
在WebView2.CoreWebView2.WebMessageReceived事件中处理吸收的消息
   private void CoreWebView2_WebMessageReceived(object? sender, CoreWebView2WebMessageReceivedEventArgs e)
   {
       this.lbl_status.Content = "从网页接收到:" + e.TryGetWebMessageAsString();
   }
 运行后在网页输入内容,在主机可以收到相应的内容

https://img-blog.csdnimg.cn/img_convert/47a8d62830dd23b1e1960c8a3651ad40.png


借助以上方法,可以做到消息的闭环,可以很好地将主机和Web交互起来。

处理与进程相关的事件

WebView2 使用多个进程来支持应用步伐中的 WebView2 控件。 由于这些进程可以在使用过程中退出,因此WebView2增加了一些事件通知。

 普通来说,当WebView2控件初始化完成后,WebView2控件将开始监视这些进程并报告以下事件:


[*] 任何进程失败。 当 WebView2 运行时中的任何 进程 失败时,CoreWebView2 将引发 事件 ProcessFailed 。
[*] 主浏览器进程退出。 如果main浏览器进程出于任何缘故原由退出,CoreWebView2Environment则会引发 事件BrowserProcessExited。 
[*] 主浏览器进程瓦解。 当main浏览器进程瓦解时,它将同时天生ProcessFailed事件和BrowserProcessExited事件,因为main浏览器进程因失败而退出。

这里我们做一个测试,
xaml
<webview2:WebView2 x:Name="webview2" Source="https://myfreetime.cn"
                     Loaded="webview2_Loaded"></webview2:WebView2> cs
   private asyncvoid webview2_Loaded(object sender, RoutedEventArgs e)
   {
       await webview2.EnsureCoreWebView2Async();

       if(this.webview2.IsLoaded)
       {
         this.webview2.CoreWebView2.ProcessFailed += CoreWebView2_ProcessFailed;
         this.webview2.CoreWebView2.Environment.BrowserProcessExited += Environment_BrowserProcessExited;
       }
   }

   private void Environment_BrowserProcessExited(object? sender, CoreWebView2BrowserProcessExitedEventArgs e)
   {
       MessageBox.Show("Environment_BrowserProcessExited");
   }

   private void CoreWebView2_ProcessFailed(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2ProcessFailedEventArgs e)
   {
       MessageBox.Show("CoreWebView2_ProcessFailed");   
   }
然后我们用任务管理器关闭WebView2的一个进程,可以看到会引发 ProcessFailed 事件

https://img-blog.csdnimg.cn/img_convert/357fd64ca89c6c34977507a8174d5941.png


打印

WebView2提供了以下打印函数
ShowPrintUI打开“WebView2 打印预览 ”对话框或操作体系的“ 打印 ”对话框。 易于实现,对自界说的支持最少。Print使用可选的以编程方式指定的打印设置将 WebView2 中的当前顶级文档打印到打印机。 可以使用此功能天生自己的“打印预览”对话框或打印体验。PrintToPdf以无提示方式将 WebView2 中的当前顶级文档打印为 PDF 文件。 可以使用它天生自己的代码来打印 PDF 文件。PrintToPdfStream以无提示方式将 WebView2 中的当前顶级文档打印到 PDF 流。 可以使用它来天生自己的代码来打印 PDF。 例如我们可以将当前网页打印为PDF
var filePath = Environment.CurrentDirectory + "\\output.pdf";
await this.webview2.CoreWebView2.PrintToPdfAsync(filePath);

自界说上下文菜单

WebView2提供了一个默认的上下文菜单,像下面这样

https://img-blog.csdnimg.cn/img_convert/ce803195808406240f9b7a951b4444f7.png

如果想自界说上下文菜单,WebView2提供了一个ContextMenuRequested 事件,在事件处理函数中可以自界说上下文菜单。

像下面这样
   private void btn_addcontextmenu_Click(object sender, RoutedEventArgs e)
   {
       this.webview2.CoreWebView2.ContextMenuRequested += CoreWebView2_ContextMenuRequested;
   }

   private void CoreWebView2_ContextMenuRequested(object? sender, CoreWebView2ContextMenuRequestedEventArgs e)
   {

       IList<CoreWebView2ContextMenuItem> menuList = e.MenuItems;
       CoreWebView2Deferral deferral = e.GetDeferral();
       e.Handled = true;
       ContextMenu cm = new ContextMenu();
       cm.Closed += (s, ex) => deferral.Complete();

       //添加上下文菜单
       AddContextMenu();

       cm.IsOpen = true;
   }
属性 AreDefaultContextMenusEnabled 控制是否可以打开任何上下文菜单。 如果 WebView2 AreDefaultContextMenusEnabled 设置设置为 False,则会禁用上下文菜单,而且 ContextMenuRequested 不会引发事件(例如用户右键单击时)。
当AreDefaultContextMenusEnabled 属性为True时且当前网页允许表现上下文菜单时,WebView2 控件才会引发 ContextMenuRequested 事件。
 AreDefaultContextMenusEnabled 默认值为True。

在添加自界说上下文菜单之前,我们还需要了解两个范例:
1、System.Windows.Controls.ContextMenu范例
自界说上下文菜单时,需要用到ContextMenu类做为上下文菜单容器。ContextMenu是WPF自带的上下文菜单类,如果你还没有打仗过,可以访问下面的链接
ContextMenu Class (System.Windows.Controls) | Microsoft Learn

2、Microsoft.Web.WebView2.Core.CoreWebView2ContextMenuItem范例
CoreWebView2ContextMenuItem是上下文菜单容器项,WebView2没有直接使用WPF的MenuItem范例,而是新增加了一个范例

 CoreWebView2ContextMenuItem界说的属性如下:
Children 当菜单项范例是子菜单时,获取菜单项的子菜单
CommandId 获取CoreWebView2ContextMenuItem的命令Id
Icon 获取图标,图标的泉源是PNG, Bitmap 或SVG 格式的数据流(IStream)
IsChecked 获取或设置菜单项是否被选中。
IsEnabled 获取或设置菜单项是否启用(仅实用于自界说菜单).
Kind 获取菜单项范例( CoreWebView2ContextMenuItemKind.)
Label 获取菜单项的文本,支持快捷键(使用&开头)
Name 获取菜单项未本地化的名字
ShortcutKeyDescription 获取菜单项本地化键盘快捷键

CoreWebView2ContextMenuItem界说的事件如下
CustomItemSelected 当用户选择了CoreWebView2ContextMenuItem,会引发CustomItemSelected事件

菜单项范例

对于不同的元素,上下文菜单项的表现会不一样,例如,对于图片,表现的是

https://img-blog.csdnimg.cn/img_convert/9c689da57cd1fe40fce4c4c4c0d3f98f.png
可以通过ContextMenuRequested 事件的参数CoreWebView2ContextMenuRequestedEventArgs.ContextMenuTarget.Kind来判断。
CoreWebView2ContextMenuRequestedEventArgs.ContextMenuTarget.Kind是一个CoreWebView2ContextMenuTargetKind枚举范例
取值如下:
Audio3 指示 这是为音频元素创建的上下文菜单
Image1 指示 这是为图像元素创建的上下文菜单
Page0 指示为页面创建的上下文菜单不包含任何其他内容。
SelectedText2 指示是为选择的文本创建的上下文菜单
Video4 指示是为视频 元素创建的上下文菜单

如何移除默认上下文菜单项

   private void btn_removeimagecontextmenu_Click(object sender, RoutedEventArgs e)
   {
       this.webview2.CoreWebView2.ContextMenuRequested += CoreWebView2_ContextMenuRequested1;
   }

   private void CoreWebView2_ContextMenuRequested1(object? sender, CoreWebView2ContextMenuRequestedEventArgs e)
   {
       CoreWebView2ContextMenuTargetKind context = e.ContextMenuTarget.Kind;
       if (context == CoreWebView2ContextMenuTargetKind.Image)
       {
         for (int index = 0; index < e.MenuItems.Count; index++)
         {
               if (e.MenuItems.Name == "saveImageAs")
               {
                   //移除另存为菜单项
                   e.MenuItems.RemoveAt(index);
                   break;
               }
         }
       }
   }
移除前

https://img-blog.csdnimg.cn/img_convert/bd09022626b4b6ec52c8fb968905ac82.png
 移除后

https://img-blog.csdnimg.cn/img_convert/3a21f38e663197defb9f5b83a56690b0.png


如何插入菜单项到默认菜单中

这里我们插入一个保存网页内容为PDF的菜单项
    private void CoreWebView2_ContextMenuRequested(object? sender, CoreWebView2ContextMenuRequestedEventArgs e)
    {
      iconStream = new MemoryStream(Properties.Resources.pdf);
      //创建新上下文菜单项
      CoreWebView2ContextMenuItem newItem = this.webview2.CoreWebView2.Environment.CreateContextMenuItem(
                  "保存网页为PDF", iconStream, CoreWebView2ContextMenuItemKind.Command);
      //菜单项选中事件
      newItem.CustomItemSelected += NewItem_CustomItemSelected;
      //插入到当前菜单项最后
      e.MenuItems.Insert(e.MenuItems.Count, newItem);
    }

    private void NewItem_CustomItemSelected(object? sender, object e)
    {
      var path = Environment.CurrentDirectory + "\\output.pdf";
      this.webview2.CoreWebView2.PrintToPdfAsync(path);
      System.Diagnostics.Process.Start("explorer.exe", $"/select, {path}");
    }
运行效果

https://img-blog.csdnimg.cn/img_convert/b4b12f56b36ee6b7be5d6cec5b662d03.png

添加自界说上下文菜单

通过下面的方式可以替换默认的上下文菜单,并创建自己的上下文菜单。
通过这种方式创建的上下文菜单没有WebView2默认上下文菜单的外观样式,需要自己创建MenuItem的样式。
private void CoreWebView2_ContextMenuRequested2(object? sender, CoreWebView2ContextMenuRequestedEventArgs e)
{
      IList<CoreWebView2ContextMenuItem> menuList = e.MenuItems;
      CoreWebView2Deferral deferral = e.GetDeferral();
      e.Handled = true;
      ContextMenu cm = new ContextMenu();
      cm.Closed += (s, ex) => deferral.Complete();
      ReplaceContextMenu(e, menuList, cm);
      cm.IsOpen = true;
}

void ReplaceContextMenu(CoreWebView2ContextMenuRequestedEventArgs args, IList<CoreWebView2ContextMenuItem> menuList, ItemsControl cm)
{
      MenuItem menuItem = new MenuItem();
      menuItem.Header = "HelloWorld";
      menuItem.IsEnabled = true;
      menuItem.Click += MenuItem_Click;
      //自定义样式
      menuItem.Style = this.FindResource("StyleMenuItem") as Style;
      cm.Items.Add(menuItem);
}

private void MenuItem_Click(object sender, RoutedEventArgs e)
{
      MessageBox.Show("HelloWorld");
}
表现效果如下:

https://img-blog.csdnimg.cn/img_convert/b504c961b1ddff25d37c807468394d9e.png


示例代码

点击下载

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 在WPF中使用WebView2详解