今年(2020年)の8月にMicrosoftは.Netに対応させた「WebView2」のプレリリースを発表しました。
今回はその「WebView2」を Windows Form で実装していき、実装時の詰まった点・困った点について話していきます。
##WebView2とは?
「WebView2」とは、Windowsフォームに埋め込むことができるブラウザーコントロールの一種です。
Windowsフォームにブラウザーを埋め込むにはデフォルトの場合、「WebBrowser」を使用するのですが、「WebBrowser」は IE ベースとなっており、IE を使用すると一部 webページでは処理できない事象が発生します。
(先日、Rustでwasmを作成する記事を書いたのですが、IE ではwasmを処理できません)
今回使用する「WebView2」はEdgeベースとなっており、Edgeは「Chromium」ベースで作られてるため、IEで処理できないページを処理することができます。
##WebView2 の実装
基本的には、下記のMicrosoft公式ページを参照して頂ければ実装できます。
https://docs.microsoft.com/ja-jp/microsoft-edge/webview2/gettingstarted/winforms
##詰まった点・困った点
###1.Canary チャネルをダウンロード
さあ、これから実装しようとなって、Nugetからプレリリース版の「Microsoft.Web.WebView2」をダウンロードし、WindowsフォームのデザイナーにWebView2を設置してブラウザーを表示しようとしたら、なぜかブラウザーがロードされない…
どういうことなのか?と思い迷うこと一時間。
公式リファレンスを読み直してみたところ、前提条件に、**「WebView2 Runtime または windows 10、windows 8.1、または windows 7 にインストールされている 非安定した Microsoft Edge (Chromium) カナリアチャネル 」**とあります…
つまり、WebView2 Runtimeか、Microsoft Edge (Chromium) カナリアチャネルをダウンロードする必要があるということみたいです。
てっきり私は、最新の Edgeがダウンロードされているならば、「WebView2 Runtime」が取り込み済みとなっているのかと勘違いしてしまっていたのですが、そういうことではないそうです。
ということで、「Edge Canaryチャネル」をダウンロードして再度挑戦したところ、無事にロードさせることができました。
ちなみに、「Canaryチャネル」は下記サイト内で、画像の赤枠で囲った部分のやつです。
https://www.microsoftedgeinsider.com/ja-jp/download
まあ、こんなミス、自分だけかもしれませんが(笑)
###2.CoreWebView2がnull
まず、下記のコードを見てください。
このコードは正常に処理できる様に書いたコードですので、コピペしてもらってデバッグしても、エラーにならずに処理することができます。
(フォームのデザイナーは空の状態で大丈夫です。)
以降の話は、下記のコードを見比べながら読んで頂くとわかりやすいと思います。
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.WinForms;
using System;
using System.Windows.Forms;
namespace SampleWebView2Form
{
public partial class Form1 : Form
{
/// <summary>webviewのコントロール(今回はわかりやすい様に、デザイナーを使わずにコード側で実装します。)</summary>
private WebView2 WebView = new WebView2
{
Source = new Uri("https://docs.microsoft.com/ja-jp/microsoft-edge/webview2/gettingstarted/winforms"),
};
public Form1()
{
this.Controls.Add(WebView);
InitializeComponent();
//WebView2のサイズをフォームのサイズに合わせる
WebView.Size = this.Size;
this.SizeChanged += Form1_SizeChanged;
//WebView2のロード完了時のイベント
WebView.NavigationCompleted += WebView_NavigationCompleted;
}
/// <summary>WebView2のロード完了時</summary>
private void WebView_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
{
//WebView2のコントロールから CoreWebView2を取り出す
//WebView2のロードが完了する前に CoreWebView2を取り出そうとすると nullになる。
if (WebView.CoreWebView2 != null)
{
//ブラウザーのポップアップ発生時のイベント
WebView.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested;
}
else MessageBox.Show("WebView.CoreWebView2 == null");
}
/// <summary>WebView2でのポップアップを抑止したい</summary>
private void CoreWebView2_NewWindowRequested(object sender, CoreWebView2NewWindowRequestedEventArgs e)
{
//本来なら、これでポップアップを抑止できるはず
//だが、ロードの速度の都合で e.NewWindow(CoreWebView2)が nullになる場合があり、エラーが発生する
if (e.NewWindow != null)
{
e.NewWindow.Stop();
MessageBox.Show("ポップアップを抑止しました");
}
else
{
//ダミーのCoreWebView2を読み込ませてポップアップを抑止する
//e.NewWindow = new Microsoft.Web.WebView2.Core.CoreWebView2();
//↑ちなみに、これはできない
e.NewWindow = DummyWebView.CoreWebView2;
e.NewWindow.Stop();
//これでJavaScriptが実行できる
WebView.ExecuteScriptAsync("alert(\"ポップアップを抑止しました\");");
}
}
/// <summary>ポップアップ抑止に使用するダミーのWebView2(の中のCoreWebView2)</summary>
private WebView2 DummyWebView = new WebView2
{
Source = new Uri("https://www.google.co.jp/"),
};
/// <summary>サイズ変更時のイベントでWebView2のサイズをフォームに合わせる</summary>
private void Form1_SizeChanged(object sender, EventArgs e)
{
WebView.Size = this.Size;
}
}
}
WebView2には「CoreWebView2」というオブジェクトがあるのですが、この「CoreWebView2」に対してイベントを設定することで、ブラウザー側の動きに合わせて、C#側の処理を行っていくことができます。
ということで、ロードのタイミングでポップアップ時のイベントを設定します。
…するとどうなるかというと、例外回避のために設定した「CoreWebView2」の null判定に引っ掛かり、
"WebView.CoreWebView2 == null")
のメッセージが発生して、ポップアップ検知のイベントが設定されません。
public Form1()
{
this.Controls.Add(WebView);
InitializeComponent();
if (WebView.CoreWebView2 != null)
{
//ブラウザーのポップアップ発生時のイベント
WebView.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested;
}
else MessageBox.Show("WebView.CoreWebView2 == null");
}
原因は何かというと、この「CoreWebView2」というのがブラウザーのエンジン的な役割をしているため、読み込ませたいwebページのロードが終了しない限り、「CoreWebView2」がnullのまま、ということになってしまうからです。
それを解決するためには、下記の様にコードを修正します。
public Form1()
{
this.Controls.Add(WebView);
InitializeComponent();
//WebView2のロード完了時のイベント
WebView.NavigationCompleted += WebView_NavigationCompleted;
}
/// <summary>WebView2のロード完了時</summary>
private void WebView_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
{
//WebView2のコントロールから CoreWebView2を取り出す
//WebView2のロードが完了する前に CoreWebView2を取り出そうとすると nullになる。
if (WebView.CoreWebView2 != null)
{
//ブラウザーのポップアップ発生時のイベント
WebView.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested;
}
else MessageBox.Show("WebView.CoreWebView2 == null");
}
「WebView2」のナビゲーションが完了時に「CoreWebView2」を取得することで nullを回避できるため、例外回避に引っ掛からず、正常にイベントを設定することができます。
##ポップアップが止まらない
イベントも無事設定できたので、今度はポップアップを抑止したいと思います。
ポップアップを抑止したらタブコントロールの別のタブページにポップアップのページを表示する、というのが実装するならば自然な処理となるでしょうが、わざわざそれを書くと長くなるので、タブコントロールに関しては、今回は省きます。
ちなみに、ポップアップが発生する場合というのは、WebView2内でリンクを右クリックし、「リンクを新しいウィンドウで開く」を押下すると、Windowsフォームの外にリンク先のページが表示されます。
(今回の記事の最初に書いたコードで実行すると、既にポップアップが抑止されるようになっています。)
ポップアップを抑止するため、下記のコードを追加しました。
「CoreWebView2NewWindowRequestedEventArgs e」の「.NewWindow」が「CoreWebView2」なので、それを「.Stop()」すればポップアップを抑止できるでしょう。本当なら、e.Cancel みたいのがあるべきかと思いますが…
(ちなみに、e.Uri でポップアップのURLが取得できるので、ポップアップをタブコントロールの別タブにしたい場合は、このURLを使用して新規にWebView2を作成し、タブページを追加するというのが無難な処理なるかと思います。)
/// <summary>WebView2のロード完了時</summary>
private void WebView_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
{
if (WebView.CoreWebView2 != null)
{
//ブラウザーのポップアップ発生時のイベント
WebView.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested;
}
else MessageBox.Show("WebView.CoreWebView2 == null");
}
/// <summary>WebView2でのポップアップを抑止したい</summary>
private void CoreWebView2_NewWindowRequested(object sender, CoreWebView2NewWindowRequestedEventArgs e)
{
//ポップアップを抑止
if (e.NewWindow != null)
{
e.NewWindow.Stop();
MessageBox.Show("ポップアップを抑止しました");
}
else MessageBox.Show("e.NewWindow == null");
}
ということで実行してみると、またもや「null」…
原因は、さっきと同じでWebページ側がロード未完了なことです。
ならば、さっきみたいにナビゲーション完了で拾って.Stop()をかける、というのがやりたいわけですが、そもそもナビゲーション完了イベントを設定するためのWebView2コントロールが存在しません。(ポップアップ元のWebView2とは別の物が必要になるわけです)
「e.Navigatin」が nullならば作ればいい、というわけで = new CoreWebView2();
としようとしたんですが、それはダメらしい…
苦肉の策のとして下記の様な実装となりました。
/// <summary>ポップアップ時のイベント_WebView2でのポップアップを抑止したい</summary>
private void CoreWebView2_NewWindowRequested(object sender, CoreWebView2NewWindowRequestedEventArgs e)
{
//本来なら、これでポップアップを抑止できるはず
//だが、ロードの速度の都合で e.NewWindow(CoreWebView2)が nullになる場合があり、エラーが発生する
if (e.NewWindow != null)
{
e.NewWindow.Stop();
MessageBox.Show("ポップアップを抑止しました");
}
else
{
//ダミーのCoreWebView2を読み込ませてポップアップを抑止する
//e.NewWindow = new Microsoft.Web.WebView2.Core.CoreWebView2();
//↑ちなみに、これはできない
e.NewWindow = DummyWebView.CoreWebView2;
e.NewWindow.Stop();
//これでJavaScriptが実行できる
WebView.ExecuteScriptAsync("alert(\"ポップアップを抑止しました\");");
}
}
/// <summary>ポップアップ抑止に使用するダミーのWebView2(の中のCoreWebView2)</summary>
private WebView2 DummyWebView = new WebView2
{
Source = new Uri("https://www.google.co.jp/"),
};
「CoreWebView2」を newで作成することができないため、ダミーの「WebView2」を用意して、そこから「CoreWebView2」を取得します。
「e.NewWindow」に当てはめることで nullじゃなくなるため、「.Stop()」が適用できます。
ついでに、この処理でポップアップを抑止した場合、「ExecuteScriptAsync()」で、JavaScriptのアラートが出力される様な処理にしてあります。
処理に納得がいかない…とは思いましたが、まあ、プレリリース版だとこんなもんですかね?
もっと良い方法があったら教えてください。 m(_ _)m
##まとめ
とりあえず今回はこんなところです。
プレリリース版ということもあるのか、処理が所々おかしいのが気になりますね。
贅沢言わなければ、ユーザー側に「IE」を使わせないという規制をかけることができるので、そこは良いところでしょうか?
本当なら、開発者ツールが表示されない様に、
WebView.CoreWebView2.Settings.AreDevToolsEnabled = false;
とできるらしいのですが、それが反応しない…。
まあ、今だと日本語リファレンスも少ないし、処理も所々おかしいので、今後に期待、というところですかね。
<2020.11.16 追記>
WebView2を使ったJavaScriptとC#の連携についての記事を書いたので、リンクを貼っておきます。
https://qiita.com/NagaJun/items/baf00494e0841a5e767e