WPF中使用WebView2控制元件

2023-01-31 06:01:50

WebView2簡介

概述

WebView2 全稱 Microsoft Edge WebView2 控制元件,此控制元件的作用是在本機桌面應用中嵌入web技術(html,css,javascript),從名字就可以看出來WebView2使用了Edge核心渲染web內容。

通俗來說,WebView2控制元件是一個UI元件,允許在桌面應用中提供web能力的整合,即俗稱的混合開發。

優勢

  • 助力程式開發和維護:相比桌面應用開發,一般來說web技術更加的靈活
  • 無需升級:以往增加了新功能都需要升級客戶現場的桌面應用程式,引入web技術之後,省去了升級的煩惱
  • 擴充套件web應用:補足了web技術的短板,不能或者很難和宿主機互動,存取作業系統api。

支援的執行時平臺

  • Win32 C/C++
  • .NET Framework 4.5 或更高版本
  • .NET Core 3.1 或更高版本
  • .NET 5
  • .NET 6
  • WinUI 2.0
  • WinUI 3.0

程序模型

當在WPF程式中引入 WebView2 控制元件後,WPF程式和WebView2控制元件的程序模型如下:

  • WPF程式程序和WebView2控制元件是程序隔離的
  • 維護WebView2執行的實際是一組程序

基本使用

本文程式碼基於 .NetFramework 4.8

安裝WebView2執行時

開發之前需要先安裝WebView2的執行時。

同理,當程式開發完畢,在客戶機器上面部署WPF應用程式的時候也應該先安裝WebView2執行時,此部分將放在部署環節詳細討論。

有三種方式安裝WebView2執行時:

  • 常青版載入程式:就是一個小的載入程式,方便傳輸,但是下載的時候需要公網環境。WebView2執行時實際通過此載入程式完成安裝。
  • 常青版獨立安裝程式:完整安裝包,可離線安裝
  • 已修復版本:特定版本安裝

WebView2執行時下載:https://developer.microsoft.com/zh-cn/microsoft-edge/webview2/

安裝WebView2Sdk

通過 Nuget 安裝

  • 名字:Microsoft.Web.WebView2
  • 安裝命令:NuGet\Install-Package Microsoft.Web.WebView2 -Version 1.0.1518.46
  • 作者:Microsoft

開啟一個網頁

新建WPF應用程式,並通過如下程式碼在Window中新增WebView2 XAML 的名稱空間。


xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"

引入WebView2控制元件,並設定 Source 屬性為 https://www.microsoft.com


  <Grid>
        <wv2:WebView2 Name="wv2"     Source="https://www.microsoft.com"   />
    </Grid>

執行程式,將可以看到WPF程式中開啟了巨硬官網

通過程式碼控制開啟的網頁


private void Window_Loaded(object sender, RoutedEventArgs e)
 {
    this.wv2.Source = new Uri("http://baidu.com");
}

導航事件

導航事件是巨硬官方的一個叫法,通俗來說就是在WebView2中開啟一個網址的步驟。

開啟一個網頁的過程

  • NavigationStarting:開始導航,導航生成網路請求。 主機可能會在事件期間禁止請求
  • SourceChanged:開始導航,導航生成網路請求。 主機可能會在事件期間禁止請求
  • ContentLoading:開始載入新頁面的內容。
  • HistoryChanged:歷史記錄更新
  • BasicAuthenticationRequested
  • DOMContentLoaded:完成對 DOM 內容的分析,但尚未完成載入頁面上的所有影象、指令碼和其他內容。
  • NavigationCompleted:完成在新頁面上載入內容。

可通過委託的方式攔截各個事件:


private void Window_Loaded(object sender, RoutedEventArgs e)
{
    this.wv2.Source = new Uri("http://baidu.com");
    //this.wv2.Source = new Uri("about:blank");
    //導航開始
    this.wv2.NavigationStarting += wv2_NavigationStarting;
    //源已經更改
    this.wv2.SourceChanged += Wv2_SourceChanged;
    //內容載入中
    this.wv2.ContentLoading += Wv2_ContentLoading;
    //導航結束
    this.wv2.NavigationCompleted += Wv2_NavigationCompleted;
}
private void Wv2_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
{
    throw new NotImplementedException();
}
private void Wv2_ContentLoading(object sender, CoreWebView2ContentLoadingEventArgs e)
{
    throw new NotImplementedException();
}
private void Wv2_SourceChanged(object sender, CoreWebView2SourceChangedEventArgs e)
{
    throw new NotImplementedException();
}
private void wv2_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e)
{
    throw new NotImplementedException();
}


更改url的過程

更改url的過程依然遵循上述步驟,只是稍微複雜了一些.

空url

某些情況下,可能需要一個預設的頁面,那麼空url是一個很好的選擇。


this.wv2.Source = new Uri("about:blank");

進階使用

WPF和Web通訊

概述

當web頁面中點選一個按鈕需要通知WPF宿主程式,或者向WPF傳遞一些指令和資料的時候,需要用到 postMessage 和 WebMessageReceived 。

  • postMessage 是 js 方法,位於 window.chrome.webview.postMessage ,當需要向WPF程式傳送資料的時候,只需要呼叫此方法,並傳遞引數就可以。此方法僅在WebView2控制元件內部有用,在Edge中呼叫將報異常(Cannot read properties of undefined 'postMessage')

  • WebMessageReceived 是 c# 事件,位於 Microsoft.Web.WebView2.Wpf.WebView2。可通過委託此事件來接收web網頁中傳送過來的訊息。

Html程式碼範例



<!DOCTYPE html>
<html>
<head>
    <meta charset=utf-8 />
    <title>TestWebView2</title>
</head>
<body>
    </br></br></br></br></br>
    <button type="button" onclick="postMsg()">給WPF宿主程式傳送msg</button>
    <script>
        window.onload = function () {
            //alert("onload-success");
        }
        function postMsg() {
            var args = "msg ,from webView2";
            window.chrome.webview.postMessage(args);
            alert("傳送成功,內容:" + args);
        }
    </script>
</body>
</html>



C#程式碼範例



private void Window_Loaded(object sender, RoutedEventArgs e)
{
    //this.wv2.Source = new Uri("http://baidu.com");
    //this.wv2.Source = new Uri("about:blank");
    this.wv2.Source = new Uri("file:///E:/code/WPF/ramble-wpf/RambleWPF/html/PostMessage.html");
    this.wv2.WebMessageReceived += Wv2_WebMessageReceived;
}
private void Wv2_WebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e)
{
    //接收到的字串
    string msg = e.TryGetWebMessageAsString();
    //接收到的json
    string msgJson = e.WebMessageAsJson;
}



常見問題

解決程式安裝到C槽導致Webview2無法開啟網頁問題

現象

如果將原始碼放到C槽並在VS中偵錯或者將打包好的程式放到C槽啟動,都會發生無法開啟網頁的問題。

原因

此問題的原因是檔案許可權問題,WebView2在工作的時候需要指定一個資料夾來存放臨時檔案和資料,即UserDataFloder,需要此資料夾的讀寫許可權。若不顯示指定udf,將會在程式啟動檔案同級目錄新建一個 RambleWPF.exe.WebView2 的資料夾存放臨時檔案,RambleWPF為WPF應用程式的名字。

解決辦法

解決辦法有兩個:

  • 顯示指定非系統磁碟下資料夾作為UDF。比如D:\wvUDF ,需要確保此碟符一定存在,還有特殊的情況,有些作業系統的系統磁碟可能不是C槽,而是D槽。
  • 程式啟動的時候修改預設UDF的資料夾許可權,這個方法看起來有趣,但是可以做到一勞永逸,因為方法1總有破綻。

顯示指定UDF

通過設定 WebView2控制元件的CreationProperties屬性可以實現自定義UDF。

需要注意的是,必須在WebView2.CoreWebView2物件初始化之前設定UDF,那麼CoreWebView2物件什麼時候初始化呢?有以下方式:

  • 在xaml中設定 WebView2 的Source 屬性
  • 在程式碼中手動設定 Source,如 this.wv2.Source= new Uri("http://baidu.com")
  • 呼叫 WebView2.EnsureCoreWebView2Async 方法

範例程式碼如下:



public partial class WebView2Demo : Window
{
    public WebView2Demo()
    {
        InitializeComponent();
        InitializeAsync();
    }
    async void InitializeAsync()
    {
        wv2.CreationProperties = new Microsoft.Web.WebView2.Wpf.CoreWebView2CreationProperties
        {
            UserDataFolder = "D:\\A\\wvUDF"
        };
        await wv2.EnsureCoreWebView2Async();
    }
    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        this.wv2.Source = new Uri("http://baidu.com");
    }
}



提升預設UDF資料夾許可權

在程式啟動的時候提升預設UDF的讀寫許可權

範例程式碼如下:



/// <summary>
/// 給 WebView2Bug.exe.WebView2 資料夾賦予寫入許可權
/// </summary>
private void InitWebView2DirAccess()
{
    try
    {
        string path = AppDomain.CurrentDomain.BaseDirectory;
        string webview2DataDir = path + "WebView2Bug.exe.WebView2";
        DirectoryInfo dir = new DirectoryInfo(webview2DataDir);
        System.Security.AccessControl.DirectorySecurity security = dir.GetAccessControl();
        //給資料夾追加 Everyone 的寫入許可權
        security.AddAccessRule(new System.Security.AccessControl.FileSystemAccessRule("Everyone", System.Security.AccessControl.FileSystemRights.Write, AccessControlType.Allow));
        dir.SetAccessControl(security);
    }
    catch (Exception ex)
    {
        string  msg = ex.Message;
    }
}



上述程式碼有點偏激,僅作為參考,實際開發中,不應該為EveryOne 使用者提升如此大的許可權。

參照