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

# はじめに
Windows10(1803以降)限定ですが、以下の記事に詳しいようにEdgeのエンジンを使ったWebViewが公開されています。
[@IT WPFやWindowsフォームでEdgeのWebViewを使うには？［Windows 10 1803以降］](http://www.atmarkit.co.jp/ait/articles/1807/04/news017.html)

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


# サンプルの概要
https://github.com/youmts/WpfMermaidJs

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

[mermaid.jsが素晴らしいけどなかなか使ってる人見かけないので実例晒す(追記あり)](https://qiita.com/uzuki_aoba/items/a01f8b0b52ced69c8092)

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

サンプルが目指したものは[mermaid.jsライブエディター](https://mermaidjs.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZ3JhcGggVERcbkFbQ2hyaXN0bWFzXSAtLT58R2V0IG1vbmV5fCBCKEdvIHNob3BwaW5nKVxuQiAtLT4gQ3tMZXQgbWUgdGhpbmt9XG5DIC0tPnxPbmV8IERbTGFwdG9wXVxuQyAtLT58VHdvfCBFW2lQaG9uZV1cbkMgLS0-fFRocmVlfCBGW2ZhOmZhLWNhciBDYXJdXG4iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9fQ)のようなものです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/205260/f8c527cc-925b-58b9-0560-9ff983214ff4.png)

# サンプルのポイント

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

## nugetパッケージ
`Microsoft.Toolkit.Wpf.UI.Controls.WebView`をインストールします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/205260/8ab3b4c4-1d03-5f0e-44b9-f231471c2d8b.png)

`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`を使う必要があります。

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

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

```csharp
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](https://github.com/runceel/ReactiveProperty)でテキストの変更監視

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

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

## WebView内の拡大率制御

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

```csharp
            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が取れるとコメント頂いたので修正しました:grinning: