WPF Prism episode: 11 ~ Prism が画面遷移キャンセルするのは IConfirmNavigationRequest だけど INavigationAware じゃない ~

前回記事「ErrorTemplate は Resources タグ、時々、ResourceDictionary ファイルのなか。【episode: 10 WPF Prism】」

 

前回は ReactiveProperty で定義したプロパティへ Validation を設定するシリーズの最終章として、Validation の ErrorTemplate を設定する方法を紹介しました。
今回は久々 Prism に戻り、VM の継承元を INavigationAware インタフェースから IConfirmNavigationRequest インタフェースに変更することで Prism の Navigation(画面遷移)をキャンセルする方法を紹介します。

尚、この記事は Visual Studio 2017 Community Edition で .NET Framework 4.7.2 以上 と C# + Prism 7.1 + ReactiveProperty + Extended WPF Toolkit™ を使用して 、WPF アプリケーションを MVVM パターンで作成するのが目的で、C# での基本的なコーディング知識を持っている人が対象です。

Prism で画面遷移をキャンセル

このサンプルアプリのように、ツリーノード等をクリックしたタイミングで View が切り替わるような UI を作成する場合、他 View への切り替えをキャンセルしたい場合もあると思います。

そんな場合は、以前から何度も紹介すると予告していた Prism の IConfirmNavigationRequest インタフェースを使用すると、画面遷移をキャンセルできるようになります
このインタフェースは Prism の実装 を見て分かる通り、INavigationAware インタフェースを継承しているため、epsode: 7episode: 8 で紹介したメソッドに加えて、ConfirmNavigationRequest メソッドが定義されています。

episode: 9 でも書きましたが、このサンプルアプリでは身体測定データの測定日は表示上のキーとして扱いたいので、「未入力」や「既にリスト内に同一日付のデータが存在する」等の場合(要するに Validation の結果がエラーになっている場合)は他の View へ遷移させたくありません。
そのような場面で使用するのが ConfirmNavigationRequest メソッドです。

画面遷移をキャンセルしたい View の VM を以下のように変更します。

まず、VM の継承元を INavigationAware インタフェースから IConfirmNavigationRequest インタフェースに変更しています。(10 行目)

そして View の遷移判定に使用する測定日プロパティは、前回紹介した通り初回表示時の Validation を無視する設定にしているので、測定日からフォーカスが移動していない状態でエラーの有無を取得すると当然『エラー無し』が返されます。
View が初回表示直後であってもエラーの有無が取得出来るように、ConfirmNavigationRequest メソッドの先頭で ReactiveProperty.ForceValidate() メソッドを呼び出して Validation を強制的に実行しています。
(全プロパティの Validation を強制実行しています)

エラーが 1 つでも存在する場合は他 View への遷移を中止したいので、Validation の強制実行後に各プロパティの HasErrors プロパティ からエラーの有無を取得して遷移判定に利用しています。
実際に他 View への遷移をコントロールするには、ConfirmNavigationRequest メソッドの 第 2 パラメータ:continuationCallback へ false をセットすると中止され、true をセットすると正常に遷移します

サンプルアプリを実行すると、下 fig.2 のように『エラー無し』の場合は他の View へ遷移できますが、エラーが存在する場合は他 View への Navigation(遷移)が中止されるようになります。

fig.2 ナビゲーションの中止

このように VM の継承元を INavigationAware インタフェースから IConfirmNavigationRequest インタフェースに変更すると、ConfirmNavigationRequest メソッド内で遷移する・しないをコントロールできるようになります

但し、上の fig.2 を見れば分かると思いますが、View の切り替え自体はキャンセルされていますが、左側の TreeView は選択ノードがクリックしたノードに変更されてしまっています。
これでは見た目的にイマイチなので、TreeView の SelectedItemChanged イベント内でノードの選択がキャンセル出来るか試してみます。

Prism で画面遷移の結果を受け取って処理を実行する

前章で紹介した ConfirmNavigationRequest メソッドで画面遷移をキャンセルすると Prism 自体には通知されますが、TreeView には何も通知されないため選択ノードが変更されてしまいます。
では、TreeView を配置した NavigationTree Module へ画面遷移のキャンセルが通知できると TreeView の ノード選択もキャンセルできそうですが、NavigationTree Module と EditorViews Module は独立した別々のプロジェクトなので、互いに参照関係もありません。

このような場合でも、episode: 6 で紹介した IRegionManager.RequestNavigate メソッドは Prism Navigation(画面遷移)の結果通知を受け取る事が出来ます
RequestNavigate メソッドには Navigation の結果をコールバックで受け取る overload が定義されているので、そのコールバックで TreeView のノード選択がキャンセルできるか試してみます。

IRegionManager.RequestNavigate メソッドの呼び出し部を以下のように書き換えると、Navigation の結果が受け取れるようになります。

IRegionManager.RequestNavigate メソッドの第 3 パラメータに指定するコールバックの定義は以下のようになっています。

private void【Method Name】(NavigationResult result)

前章で Navigation(画面の遷移)をキャンセルする際のコールバックにセットした bool 値は上記コールバックのパラメータ:NavigationResult.Result プロパティに返されます

そして、TreeView.SelectedItemChanged イベントのパラメータ:RoutedPropertyChangedEventArgs.OldValue には直前に選択されていたツリーノード(ここでは TreeViewItemViewModel)が格納されている為、その IsSelected = true にセットして再選択することでノード選択のキャンセルをエミュレートしてみます。

TreeView ノードの IsSelected プロパティを変更すると TreeView.SelectedItemChanged イベントも当然再実行されるため、不恰好ですがフラグを用意してイベントの再実行を抜けています。
イマイチイケてないのでググってみると、以下の 2 つの方法でイベントの再実行が防げそうなので試してみましたが結局ダメでした…。結果はダメでしたが一応紹介します。

まず 1つ目は ReactiveProperty Ver.2.7.0 から追加された BusyNotifier
管理人の書き方が悪いのかもしれませんが、上記リンク先の通りに書いて実行しても無限ループに突入してしまいました。

そして 2 つ目は ReactiveProperty Ver.2.9.0 から追加された AsyncReactiveCommand
このクラスはボタンのダブルクリック防止には便利そうです。

この【AsyncReactiveCommand】ほとんど情報がヒットしませんが、Qiita の『AsyncReactiveCommandでWPFのお手軽ダブルクリック抑制』で紹介されていたので試してみましたが、今回のようにイベントハンドラ内部からイベントが発火するトリガーを変更するようなパターンでは無理のようで、BusyNotifier のときと同じく無限ループに突入してしまいました。
今回はダメでしたが、ボタンのダブルクリックを防止したいような場合に又、試してみます。

イベント再発火の防止は上記コード通りフラグで制御するとして、実際に実行してみると、以下のように選択ノードも変更されなくなります。

fig.3 SelectedItemChange のキャンセル

と、イケるやん!と喜んだのも束の間… マウスの右ボタンでノードを選択すると、コンテキストメニューが表示されて、クリックしたノードへ選択が変更されてしまいます… orz
episode:8 で右クリックでもノードを選択できるようにした影響かもしれません…

これ以上、アプリ側で対応するのは面倒 難しそうなので、SelectedItemChanged イベントをキャンセルするのはここまでにします。
実際に対応する場合はカスタムコントロールとして TreeView を継承して作成する方が逆に楽そうなので、対応方法が分かればエントリとして公開するかもしれません。

上の理由から今回 GitHub リポジトリ へ公開している実装は、Validation のエラーが存在する場合、マウスの左クリックではノードの選択がキャンセルされるが、マウスの右クリックでは選択ノードが変わってしまう状態のままでコミットしています。

TreeView ノードの選択キャンセルは完全動作とは言えない状態ですが、Prism の IConfirmNavigationRequest インタフェースと IRegionManager.RequestNavigate メソッドが連動していて、Navigation(画面遷移)をキャンセルした結果は Navigation を要求した側に通知されることは分かってもらえたと思います。

 

今回はここまでで、次回は Prism の Navigation(画面遷移)をキャンセルしたタイミングで Prism 組み込みのメッセージボックスを表する方法を紹介する予定です… が、今回までは週一で更新していましたが episode:12 を来週(2019/3/17辺り)公開するのは難しいかもしれません。
いつもの通り、ここで紹介したソースコードは GitHub リポジトリ へアップしています。

 

次回記事「Prism メッセージボックスの Service な日常【episode: 12 WPF Prism】」

 

 

おすすめ

コメントを残す

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

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