WPF Prism episode: 20 ~ Prism ダイアログに MahApps.Metro が舞い降りた! ~

前回記事「MahApps.Metro と Material Design In XAML Toolkit たちは Prism でも余裕で生き抜くようです!【episode: 19 WPF Prism】」

 

前回は Prism のプロジェクトへ MahApps.Metro と Material Design In XAML Toolkit を導入する方法を紹介したので、今回は Prism から表示するメッセーボックスやダイアログを MahApps.Metro の MetroWindow で表示する方法を紹介します。

尚、この記事は Visual Studio 2019 Community Edition で .NET Core 3.0 以上 と C# + Prism 7.2 以降 + ReactiveProperty + Livet + MahApps.Metro + Material Design In XAML Toolkit を使用して 、WPF アプリケーションを MVVM パターンで作成するのが目的で、C# での基本的なコーディング知識を持っている人が対象です。

Material Design In XAML Toolkit の Style とコントロール

以前 episode: 6.5 で紹介したサンプルアプリは fig. 1 のような WPF 標準の良く見かける画面でした。

fig. 1 以前作成したサンプルアプリ

このサンプルアプリを元に Material Design In XAML Toolkit で装飾を軽く追加すると fig. 2 のような画面に変わります。

fig. 2 Material Design In XAML Toolkit のコントロール

この UI デザインを良いと思うかどうかは置いておいて、fig. 1 の画面と比べるとかなり雰囲気が変わったと思います。

TreeView のアイコンを画像ファイルから Material Design In XAML Toolkit の【PackIcon】に変更して、TextBox へ【MaterialDesignFloatingHintTextBox】を設定しただけで fig. 2 のような画面になるので色々試してみるだけでも楽しいと思います。



MahApps.Metro と Material Design In XAML Toolkit のアイコン

episode: 5 ではアセンブリリソースのアイコンを Image コントロールへ表示する方法を紹介しましたが、Material Design In XAML Toolkit をインストールすると src. 1 のように PackIcon コントロールが使用できるようになります。

Material Design In XAML Toolkit デモアプリの IconPack View から表示したいアイコンを選んで Kind プロパティに設定するだけで表示でき、src. 1 のようにバインドした VM から設定することもできます。尚、サイズ指定は任意です。

fig.3 Material Design In XAML Toolkit デモアプリの IconPack View

このような SVG フォーマットのアイコンは Material Design In XAML Toolkit だけでなく MahApps.Metro にも多数含まれていますし、MahApps.Metro には本体とは別に IconPack も Nuget から入手できますが、Material Design In XAML Toolkit の PackIcon 程お手軽には扱えないのでアイコンは Material Design In XAML Toolkit の方だけを使った方が良いと管理人的には思います。(いろんな種類のアイコンを使うと統一感もなくなると思いますし…)

又、PackIcon の Foreground を指定しないと黒で描画されますが、管理人的にちょっと味気なく感じたので Foreground にプライマリカラーを指定しています。
Material Design In XAML Toolkit では src. 1 の PackIcon.Foreground に指定しているように DynamicResource に定義された下表のような 8 種類の色を指定できます。

背景色前景色設定内容
PrimaryHueLightBrushPrimaryHueLightForegroundBrush明るいプライマリカラー
PrimaryHueMidBrushPrimaryHueMidForegroundBrush標準プライマリカラー
PrimaryHueDarkBrushPrimaryHueDarkForegroundBrush暗めのプライマリカラー
SecondaryAccentBrushSecondaryAccentForegroundBrushアクセントカラー

前景色は標準では白か黒なので、あまり指定することはないでしょうが、背景色を良い感じに使うと画面全体に統一感が出ると思います。

FloatingHint

又、src. 1 のように TextBox の Style へ【MaterialDesignFloatingHintTextBox】を設定すると fig. 2 のように項目ラベルを兼ねたウォーターマークが追加され軽い動きまで付加されるので管理人的には重宝しそうです。

【MaterialDesignFloatingHintTextBox】を設定すると以下のようなプロパティも有効になります。

プロパティ設定内容
HintAssist.Hint項目ラベル・ウォーターマークとして表示する文字列を指定します。
HintAssist.FloatingScaleHintAssist.Hint に設定した文字列がフローティング状態になった際のスケールを設定します。
0.8、0.9、1.2 のような指定が可能です。

尚、HintAssist には上表で紹介したプロパティ以外に HintAssist.FloatingOffse や HintAssist.HintOpacity 等のプロパティもありますが、読み取り専用のため設定はできません。

実際の XAML は以下の GitHub へのリンクから見てください。

Livet の WindowCloseCancelBehavior で Window Close 時にメッセージを表示する

本章では episode: 18 でインストールした LivetCask.Behaviors.WindowCloseCancelBehavior を利用して Window Close 時に問い合わせメッセージを表示する方法を紹介します。

と思って試してみた所、そのままでは使えませんでした…
WindowCloseCancelBehavior は Window の Close を Cancel するためのだけのビヘイビアのようで、Windows Form で良く見かける「アプリケーションを終了してよろしいですか?」的なメッセージボックスの戻り値によって Window の Close をコントロールできる訳ではないようです。

とは言っても、GitHub で WindowCloseCancelBehavior のソースコード を見ると 1 か所修正するだけで管理人の希望通りに動いてくれそうなのでパクってみました。

WindowCloseCancelBehavior を改造する

GitHub から WindowCloseCancelBehavior のソースコード を丸々コピーして新規に作成したビヘイビアが src. 2 の WindowClosingBehavior です。

実際に修正が必要だったのは src. 2 の 91 行目のみで他のハイライト行はビヘイビア名(クラス名)の変更に伴う修正です。この 91 行目は元々 true 固定で VM から CanClose プロパティを変更しても反映されなかったので CanClose の値を反映するよう修正しました。

WindowClosingBehavior をサンプルアプリに組み込む

WindowClosingBehavior は WindowCloseCancelBehavior を丸ごとコピーしただけなので XAML への組み込み方は WindowCloseCancelBehavior と全く同じで、src. 3 のハイライト部分を追加します。

WindowCloseCancelBehavior(WindowClosingBehavior)には ICommand とバインド可能な  CloseCanceledCallbackCommand も用意されていますが、src. 3 の 11 ~ 15 行目のように記述すると Livet の Core 機能を利用して DataContext から CloseCanceledCallbackMethodName に指定した名前と同じメソッドを自動的にバインドしてくれるようです。

そのため、VM へ ICommand を定義する必要もなく WPF 標準のコマンドバインディングより高速に動作するようなので、Livet のお作法に則った指定にしています。

但し、src. 3 のようにバインドすると通常のコマンドバインディングと Livet のメソッドバインディングが混在してしまうため、統一したい場合は CloseCanceledCallbackCommand を呼ぶ方が良いかもしれません。(管理人は動作未確認)

CloseCanceledCallbackMethodName と併せて ReactvieProperty で定義している CanClose プロパティもバインドしているので XAML 側の CanClose に【.Value】を付ける必要がありますが、今回も【.Value】をつけ忘れてメソッドが呼ばれない原因に気付くのにしばらくかかってしまったので皆さんは忘れないように気を付けてください(苦笑)

そして src. 3 の MainWindow.xaml とバインドする VM が src. 4 です。

ConfirmClose 内で呼び出しているメッセーボックスは episode: 16 で紹介したメッセーボックスの実装を丸ごとコピーしただけなのでここでは紹介しません。必要な場合は GitHubepisode: 16 を見てください。

実際の実装は src. 4 の通りですが、1 つだけ注意があって CanClose プロパティは Window.Close イベントが呼ばれる直前には必ず false に設定されている必要があることは覚えておいてください。Window.Close が呼ばれた時に true が設定されていると ConfirmClose コールバックは呼ばれません。

ここまでの状態で実行すると fig. 4 のように Window Close 時にメッセージが表示されるようになります。

fig. 4 ダイアログウィンドウとメッセーボックス

fig. 4 ではメッセーボックスだけでなくダイアログウィンドウも表示していますが、見て分かる通り両ウィンドウとも WPF の標準 Window で表示されています。

メッセージボックス、ダイアログウィンドウの両プロジェクトとも MahApps.Metro と Material Design In XAML Toolkit はインストール済みなのでボタン等は Material Design In XAML Toolkit のデザインテンプレートで表示されますが、継承元の変更が必要な MetroWindow には自動で変わりません。

Prism 7.1 までは『WPF PrismのPopupWindowActionで出すダイアログをMetroWindowにする – Qiita』に書かれている方法を使っていたようですが、Prism 7.2 からはかなり簡単になりました。

Prism から表示するダイアログを MetroWindow にする

Prism 7.2 で導入された IDialogService から表示するウィンドウを MetroWindow にするにはまず、スタートアッププロジェクトへ src. 5 のような partial クラスを追加します。(クラス名は自由に付けて構いません)

追加した partial クラスを継承したい Window(ここでは MetroWindow)と IDialogWindow から継承して、IDialogWindow のメンバを実装します。

スタートアッププロジェクトへ追加と書きましたが、Prism の Shell か Module プロジェクトであればどこへ追加しても構いません。(IContainerRegistry へ登録できるプロジェクトであればどのプロジェクトでも構いません
但し、この partial クラスは 全ダイアログウィンドウで共有されるので管理人的には Shell のプロジェクトへ置くのが 1 番自然だと考えています。

そして、src. 6 のように RegisterTypes で DialogWindow として登録します。

ここまで設定して実行すると fig. 5 のようにメッセージボックスもダイアログウィンドウも MetroWindow で表示されるようになります。

fig.5 Metroスタイルダイアログ

ダイアログを外からドラッグして来ているのは呼出元 Window から離れた位置に表示されるからです。
又、表示位置だけではなくタイトルも設定されていないのでちゃんと表示されるよう設定します。

Prism Dialog Window の設定

episode: 16 でも紹介しましたが、Prism 7.2 で導入された IDialogService から表示するダイアログのプロパティは src. 7 のように XAML から指定できます

但し、今回設定が必要な WindowStartupLocation は Dialog.WindowStyle から設定できません。
WindowStartupLocation は Window の Load 前に設定しないと反映されないため、Window Load 後に読み込まれる UserControl に書いても意味が無いからです。

では、WindowStartupLocation のように Dialog.WindowStyle から設定できないプロパティをどこで設定するかと言うと、追加した PrismNetCoreAppDialogWindow クラスは Window の単なるコードビハインドなので src. 8 のように記述するだけで前項の問題は解決できます。

コンストラクタで WindowStartupLocation を設定して、Load イベント内で DataContext から Title プロパティを取得して自分自身の Title へ設定するよう修正しています。

src. 8 のコードを追加して実行すると fig. 6 のように変わります。

fig.6 表示位置とタイトルを表示したダイアログウィンドウ

呼出元 Window の中央に表示されるようになり、タイトルも表示されるようになりました。
加えて Window 内に表示される View(UserControl)が Load される時に右から左へスライドする効果も付加されるようになるので、管理人的には結構満足しています。
ちなみに、Window 内部の View がスライドする効果は MetroWindow.WindowTransitionsEnabled で ON/OFF を切り替える事ができます



MetroWindow のプロパティを XAML から設定する

WindowStartupLocation はプロパティの性質上、Prism ダイアログの partial から設定しましたが、ウィンドウが Load された後からでも反映可能なプロパティであれば、MetroWindow 固有のプロパティを src. 9 のように XAML から設定することができます

src. 9 のように Style タグの TargetType に MetroWindow を設定すれば 13 行目のように MetroWindow 固有のプロパティを XAML から設定できます

メッセージボックスではあまりメリットは無いかもしれませんが、MetroWindow で表示されるダイアログでは最小化ボタンや最大化ボタン、閉じるボタン等を非表示にすることもできます。

但し、episode: 19 で紹介した Window の位置やサイズを保存・復元する SaveWindowPosition に true を指定するのは注意が必要です。

SaveWindowPosition は WindowStartupLocation 等と違い Setter Property の候補にも出て来ますし実際に設定も可能ですが、単一の Window を使い回しているため位置もサイズも全ダイアログで共有されてしまう問題があるので、使用しない方が良いと思います。

このように、追加した partial クラスに IDialogWindow の継承を追加すると MahApps.Metro の MetroWindow だけでなく Infragistics の XamRibbonWindowFluent.Ribbon の RibbonWindow でダイアログを表示できるようになります。

おわりに

さて、これで Prism 7.2 で追加された機能については全て紹介できたと思うので、WPF Prism episode シリーズとしてはしばらくお休みをして新シリーズを開始しようと思っています。

同じようなサンプルアプリばかりで少し飽きて来たので、別のアプリを作りながら WPF について紹介記事を書いていく予定なので、今までより更新間隔がさらに長くなりそうです。

そして今回紹介したソースコードもいつものように GitHub リポジトリ に上げています。

 

 

おすすめ

3件のフィードバック

  1. 匿名 より:

    以前、https://elf-mission.net/programming/wpf/episode12/ にて教えていただいた者です。
    その節は大変お世話になりました。

    episode16以降の記事を参考に、私が作成しているアプリケーションでPrismのバージョン7.2に対応させているところです。
    そこでまた壁にぶつかっているので、ご教示いただけますでしょうか。

    このアプリケーションにはメイン画面にメニューバーを持たせており、そこでアプリケーションを終了するという項目があります。
    構成は
    メイン画面(MainWindow、MainWindowViewModel)、
    メニュー(MainMenuCtrl、MainMenuCtrlViewModel)
    としています。

    MainMenuCtrlの「アプリケーション終了」メニューからMainMenuCtrlViewModelのExitCommandが実行
    →MainMenuCtrlViewModelからExitRequestを起こす
    →MainMenuCtrlに設定したトリガアクションでWindowをClose
    という流れにしているのですが、Prism7.2に準じてInteractionRequestを使わないようにしようと思えば
    どのような実装になるのでしょうか?

    読みづらくて申し訳ないのですが、下記が現在の実装です。
    (そもそも現在の実装そのものがマズいのかもしれませんが)

    MainMenuCtrl.xamlから抜粋


    MainMenuCtrlViewModel.csから抜粋

    public DelegateCommand ExitCommand { get; }

    public InteractionRequest ExitRequest { get; } = new InteractionRequest();

    public MainMenuCtrlViewModel()
    {
    ExitCommand = new DelegateCommand(() =>
    {
    // 終了処理を起こす
    ExitRequest.Raise(null);
    });
    }

    WindowCloseAction.csから抜粋

    protected override void Invoke(object parameter)
    {
    Window.GetWindow(AssociatedObject)?.Close();
    }

    よろしくお願いいたします。

    • 沖田玲朗 より:

      管理人です。
      episode: 12 でお役に立てたようなら良かったです。

      確かに Prism 7.2 からは InteractionRequest が Obsolute でマークされるので使わなくて済むなら使わなくしたいですよね…
      盲点でした。

      今まで VM 側から View を操作する例はあまり考えていなかったので悩む所ですが、調べてみて管理人なら以下の 2 つの内どちらかを使うと思います。

      ■ VMからウィンドウを閉じる添付ビヘイビア(http://sourcechord.hatenablog.com/entry/2014/04/05/225250)
      ■ ウィンドウを閉じるコマンドを ViewModel 側で実装する例(https://yoshiiz.blog.fc2.com/blog-entry-882.html)

      実装して試してみたいと思うのは『ウィンドウを閉じるコマンドを ViewModel 側で実装する例』の方ですが、Window を VM で操作するのは微妙な気がするのと、ビヘイビアを作っておけば他のプロジェクトへ使い回しもできるので最終的には『VMからウィンドウを閉じる添付ビヘイビア』側の方法を採用するような気がします。

      モヤっとするような回答しかできなくてすいません。

      • 匿名 より:

        回答ありがとうございます。

        両方の実装を試してみて、目的を達成できることを確認しました。
        ただ、私も添付ビヘイビアを使う方がしっくりきましたので、こちらを採用しようと思います。

        全然モヤっとするような内容ではありませんし、本当に助かりました。
        ありがとうございました。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください