開発備忘録

WPF(XAML+C#)の話題を中心に.NET/Android/CIに関するブログです

(WPF + MVVM)Material Design In XAML でモダンなダイアログを表示する

先日、業務でMaterial Design In XAML を使用したダイアログ表示機能を作ったのでメモ。

Material Design In XAML って?

GoogleAndroid OS に採用されているデザインシステムである"Materail Design" をMicrosoftXAML で実現しますよというOSS プロダクトです。

参考ページ:http://materialdesigninxaml.net/

使用可能なGUI アーキテクチャにはWPF, UWP があります。
ここまでであればよくあるXAML 向けテーマなのですが、Material Design In XAMLAndroid の外観とほとんど同じ見た目にするだけでなく、XAML 向けアーキテクチャ専用のエラー通知や外観をカスタマイズするためのDataTemplate ももちろん使用できます。
さらに、使用したいスタイルやコントロールはデモプロジェクトを実行すればいつでも閲覧してコピペが可能です。

デモプロジェクトを実行するにはGitHub のMainDemo.Wpf を実行すればOKです。 要ビルドなのでリポジトリをクローンするなり、ZIP でダウンロードするなりでやってみてください。


余談ですが、個人的には上記のMainDemo.Wpf を実行しながらAdobe XD のAndoid のUI キットを使って画面設計したりワイヤーフレームを作ると実際に製作段階に入ったときにシームレスに移行できるのでオススメです。
もちろんXAML の知識は必要ですし、XD で独自のUI を作る場合はその工数も必要ではあります。
XAML + Adobe XD が使えて画面設計書と実際の画面デザインの差を埋めることに困っているという方向けですね。

動作環境

ダイアログの作成

デスクトップアプリにおいて必須といっても過言ではないダイアログ表示ですが、エンタープライズ向けだとこんなことがままあります。

Windows 標準でないダイアログを表示したい」

標準ダイアログでは実現できない使用がある場合は仕方ないですね。
そこでMaterial Design In XAML の登場です。

Material Design In XAML のダイアログは DialogHost を使用します。 DialogHost はダイアログをホストする(表示する領域、パネル)もので、ダイアログを表示するための方法をいくつか提供しています。
ダイアログの表示方法はMainDemo.Wpf を見ればわかりますのでここでは割愛します。
開発時に作るべきはこのダイアログの外観とViewModel です。
外観はUserControl を使用し、ViewModel はバインド可能な情報をプロパティとして持っていればOK です。
以下はダイアログ(UserControl) とそのViewModel です。

gist793013159cfac93d76e930dcfa9525d1

gistb8083d846942569d7e7d6374edc2dd8c

これらのダイアログを表示するためにウィンドウにDialogHost を配置します。
このDialogHost の配置の仕方次第でダイアログの表示領域が決まるのですが、今回はサンプルなので全体を覆うように表示するものとします。

gist17d320f707bb8bcf5bd92f621716c62c

DialogHost のIdentifier プロパティは複数のDialogHost が存在する場合にどこにホストするかの識別子です。
一意な名前であれば何でもいいですが、後で使用するということは覚えておきましょう。
なお、Identifier を使用しない方法もありますが、今回は説明しません。

ViewModel からダイアログを表示する

ダイアログが作成できたのでViewModel から表示してみましょう。
そのまま表示するのではMainDemo.Wpf と同じなので、MVVM アーキテクチャを使用して表示するようにします。
MVVM を使用したダイアログ(メッセージ)表示といえばServices パターンですね。
ということでIDialogService インターフェースとその具象クラスを作っていきます。

gist4df100caa40a1cfd2cc40ae88aecc55d

gist8f129f635c3738c8b6e1c472db367fa4

とりあえずQuestion メソッドを一つ作って、質問メッセージを表示するようにします。
他種別のメッセージを表示したい場合は、新たにメソッドを追加していく形になります。
ここで重要なのは戻り値がTask<bool> であること。
Windows 標準のMessageBox とは異なり、Material Design In XAML のダイアログは非同期で表示されます。
この辺りはUWP とおなじですね。
また、戻り値は以下の方法で返しています。

object result = await DialogHost.Show(dialog, _identifier);
return (result is bool selectedResult) && selectedResult;

DialogHost.Show の戻り値はobject 型ですが、この値はどこから設定されるのか?
正解は先ほど作成したダイアログのXAML にある以下の行です。

<Button Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
        Content="はい"
        IsDefault="True"
        Style="{StaticResource MaterialDesignFlatButton}">
    <Button.CommandParameter>
        <system:Boolean>True</system:Boolean>
    </Button.CommandParameter>
</Button>

DialogHost.CloseDialogCommand というコマンドが用意されていて、そのCommandParameter に設定した値がDialogHost.Show の戻り値となります。
今回のケースではXAML に直接bool 型の値を設定し、"はい"ボタンと"いいえ"ボタンのどちらを押下したかを識別できるようにしています。

さて、DialogService が作れたので、これを画面のViewModel で使用できるようにしましょう。

gist793fbb7ca9006cca375a48e92ed4f495

ダイアログの表示はIDialogService.Question メソッドを非同期で呼び出せばOK です。
アプリの終了処理もMVVM で書けばよかったんですが、ちょっと手抜きです。(ゴメンナサイ


余談その2なんですが、WPF はView のDataContext にViewModel を指定する際、Visual Studio の[書式]メニューから一覧を表示してViewModel を選択すると思います。
しかし、このとき一覧に表示されるにはViewModel にデフォルトコンストラクタが実装されていなければなりません。
デフォルトコンストラクタを実装してXAML デザイナーからViewModel を指定したい、けど、必要なインターフェースはコンストラクタの引数から引き渡したいというときは私はいつもデフォルトコンストラクタにObsolete 属性を指定しています。
こうすることでコード上ではViewModel のデフォルトコンストラクタ呼び出しができなくなります。
ただ、なぜXAML デザイナー上ではエラーが発生しないのかはよくわかりませんが、WPF がこれ以上進化しないことを考えればこの回避方法はわりとありな気がしています。


最後にViewModel の構築部分です。
今回の例ではPrism を使用しているのでBootstrapper でViewModel をインスタンス化しています。

gist43bbb1b00cc5e305126b27f38a4f7a25

DialogService のコンストラクタで渡している"MessageDialogHost" はView で指定したDialogHost のIdentifier プロパティの識別子です。

これで実行して、右上の閉じるボタンをクリックするとこんなメッセージダイアログが表示されます。
f:id:iyemon018:20181007191308p:plain

このようにMaterial Design In XAML を使用すれば、独自ダイアログも割と簡単に表示することが可能です。
また、ダイアログとして表示するUserControl、ViewModel をカスタマイズすればいろいろと応用することもできます。
Material Design In XAML はほかにもモダンなUI を構築するための仕組みがそろっているので、WPF を使用するのであれば一度見てみることをお勧めします。

今回使用したプロジェクトはこちら↓↓↓↓↓ github.com