EdgeベースのWebViewコントロールでLocalの静的html,js,cssで構成されたWebページを表示する


はじめに

Windows10(1803以降)限定ですが、以下の記事に詳しいようにEdgeのエンジンを使ったWebViewが公開されています。
@IT WPFやWindowsフォームでEdgeのWebViewを使うには?[Windows 10 1803以降]

改めて最新のWebViewで試したところ上の記事と比較してnugetパッケージ名が変わっていたのと、Localの静的htmlを表示しようとして結構苦労したので、動作するサンプルとともに記事としてまとめておきます。

サンプルの概要

mermaid.jsはフローチャートなどのダイアグラム表示のための優れたライブラリです。わりと単純な文字列からダイアグラムが作れるため、テキストエディタでササッと書くとシーケンス図などが作れて大変便利です。mermaid.jsについては以下の記事が詳しいです。

mermaid.jsが素晴らしいけどなかなか使ってる人見かけないので実例晒す(追記あり)

便利なので、アプリケーション内でもオブジェクトの関係をフローチャートで表示したいときにmermaid.jsを使いたくなることがありますが、WPFからはWebViewなどを経由して使う必要があります。今回のEdgeエンジン版WebView以前は(Microsoft公式のものでは)IEのエンジンのものしかなかったのでmermaid.jsは動かなかったのです。

サンプルが目指したものはmermaid.jsライブエディターのようなものです。

サンプルのポイント

簡単にサンプルのポイントを説明します。

nugetパッケージ

Microsoft.Toolkit.Wpf.UI.Controls.WebViewをインストールします。

Microsoft.Toolkit.Win32.UI.Controlsには今はDLLが含まれておらず、サブセットそれぞれがパッケージ化されているようです。
https://github.com/Microsoft/WindowsCommunityToolkit/releases/tag/v5.0.0

WinFormsでEdgeベースのWebViewコントロールを使用する場合はMicrosoft.Toolkit.Forms.UI.Controls.WebViewをインストールします。確認はしていませんが、こちらも同じようなものだと思います。

Localの静的htmlの表示

IUriToStreamResolverを実装するクラスを引数にとるNavigateToLocalStreamUriを使う必要があります。

WebView.NavigateToLocalStreamUri(new Uri(mermaidHtmlFileName, UriKind.Relative), new StreamUriResolver());

遷移先として指示するhtmlファイルや、html内部で相対的に指示しているjsやcssファイルが要求されるたびに、IUriToStreamResolverのUriToStreamが呼び出されます。UriToStreamでは、相対Uriをローカルファイルのパスに変換してあげます。

private class StreamUriResolver : IUriToStreamResolver
{
    public Stream UriToStream(Uri uri)
    {
        Uri baseDir = new Uri(AppDomain.CurrentDomain.BaseDirectory);
        Uri target = new Uri(baseDir, uri.LocalPath.TrimStart('/'));
        return new FileStream(target.AbsolutePath, FileMode.Open);
    }
}

new Uri(baseDir, uri.LocalPath.TrimStart('/'))のあたりはちょっとイマイチかもしれません。もうちょっとうまい方法があればよいのですが・・・。

ReactivePropertyでテキストの変更監視

本題からそれますが、テキストの変更を監視して、一秒間の間の変更イベントを最後のイベントだけにして更新をかけます。ReactiveProperty+Rxを使うとこのあたり簡単にできて良いですね。

MermaidText
    .Throttle(TimeSpan.FromMilliseconds(1000))
    .Subscribe(x =>
    {
        if (x == null) return;
        WriteToHtml(x);
        Dispatcher.Invoke(WebView.Refresh);
    });

WebView内の拡大率制御

WebViewそのものには拡大率を制御するメソッドやプロパティが見つからなかったのでJavascriptでやるしかないようです。こちらもThrottleを使い以下のようにしました。

            ZoomFactor
                .Throttle(TimeSpan.FromMilliseconds(100))
                .Subscribe(x =>
                {
                    Dispatcher.Invoke(() =>
                        WebView.InvokeScriptAsync("eval", $"document.body.style.zoom = {x};")
                    );
                });

もともとはdocument.getElementById("main").style.zoom = x;としていたのですが、document.bodyでbodyが取れるとコメント頂いたので修正しました