WPF Prism episode: 6.5 ~ いつだって Prism の画面遷移は RequestNavigate だった。 ~
前回はコントロールのイベントを Command に変換して VM とバインドする方法を紹介したので、今回は Prism で View を動的に切り替える方法を紹介します。
尚、この記事は Visual Studio 2017 Community Edition で .NET Framework 4.7.2 以上 と C# + Prism 7.1 + ReactiveProperty を使用して 、WPF アプリケーションを MVVM パターンで作成するのが目的で、C# での基本的なコーディング知識を持っている人が対象です。
動的に表示する View
View を動的に切り替える方法を紹介する前に fig. 1 のように画面右側へ表示するための View が予め必要になります。
fig. 1 で動的に表示しているのは以下 4 つの View です。
- 『個人識別情報』編集用の PersonalEditor
- 『測定日別身体検査データ』編集用の PhysicalEditor
- 『試験日別得点データ』編集用の TestPointEditor
- 編集データを取りまとめたカテゴリを表示するための CategoryPanel
現時点の XAML はここで取り立てて紹介するような記述は無いので、必要があれば GitHub リポジトリ を直接参照してください。(VM はテンプレートから自動生成されたままで OK です)
fig. 1 へ動的に表示している XAML は編集画面っぽくコントロールを配置していますが、この時点では View の動的切り替えの確認に View を識別できれば良いので TextBlock 1 つ置くだけでも構いません。
又、今回必要な View は全て 1 つの Prism Module(プロジェクト)内へ作成していますが、全てを別々の Prism Module に分けて作成することもできます。
このサンプルアプリ程度の規模であればそこまでの必要はないので 1 つの Prism Module(プロジェクト名:EditorViews)にまとめた形で作成しています。
Prism での画面遷移
Windows Form での画面遷移はダイアログを切り替える手法を使うことも多かったと思いますが、WPF ではダイアログをあまり使わず、fig. 1 のようにシングルウィンドウ内で動的に View(UserControl)を切り替える方法を採用するアプリが多い印象を受けます。
Windows Form でも UserControl を動的に Load して切り替えることは可能なので、View の切り替えを管理する部分を独自で作り込んでいたと思いますが、Prism では View の動的切り替え機能を標準で備えています。
前回のエントリで TreeView の SelectedItemChanged イベントを VM へバインドする所までは紹介して、実際の Command 実行部は空のままエントリを終了したので、今回は Command 内に記述する内容を紹介します。
IRegionManager.RequestNavigate で View を動的に切り替える
Prism では Region を管理する IRegionManager インタフェースが View を動的に切り替える機能を持っていて、Region とは episode: 2 でも紹介しましたが、MainWindow に配置した 2 つの ContentControl のことで、episode: 3 では MainWindow 起動時に左側の ContentControl(Region 名は NaviTree)へ TreeView を表示するために IRegionManager.RegisterViewWithRegion を呼び出す 方法を紹介しました。
Prism では View の動的切り替えを【Region Navigation】と呼んでいて、DI コンテナ(ここでは Unity)に予め登録した View を指定した Region に表示するための仕組みです。
先ほど出てきた IRegionManager.RegisterViewWithRegion は DI コンテナへの登録と Region への表示を同時に行うことができるので、表示する View が予め決まっている場合に使用されることが多いですが、条件によって表示する View を変えたい場合は src. 1 のように IRegionManager.RequestNavigate を使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | using System.Windows; using Prism.Mvvm; using Reactive.Bindings; using Reactive.Bindings.Extensions; namespace WpfTestApp.ViewModels { public class NavigationTreeViewModel : BindableBase, IDisposable { private Prism.Regions.IRegionManager regionManager = null; ~ 略 ~ /// <summary>コンストラクタ。</summary> /// <param name="data">アプリのデータオブジェクト(Unity からインジェクション)</param> /// <param name="rm">IRegionManager(Unity からインジェクション)</param> public NavigationTreeViewModel(WpfTestAppData data, Prism.Regions.IRegionManager rm) { this.regionManager = rm; ~ 略 ~ } ~ 略 ~ /// <summary>SelectedItemChangedイベントハンドラ。</summary> /// <param name="e">イベントデータを格納しているRoutedPropertyChangedEventArgs<object>。</param> private void nodeChanged(System.Windows.RoutedPropertyChangedEventArgs<object> e) { var viewName = string.Empty; var current = e.NewValue as TreeViewItemViewModel; switch (current.SourceData) { case PersonalInformation p: viewName = "PersonalEditor"; break; case PhysicalInformation p: viewName = "PersonalEditor"; break; case TestPointInformation t: viewName = "TestPointEditor"; break; case string s: viewName = "CategoryPanel"; break; } this.regionManager.RequestNavigate("EditorArea", viewName); } } } |
View の切り替えには IRegionManager のインスタンスが必要になるため、src. 1 のようにDI コンテナからコンストラクタへ IRegionManager をインジェクションしてもらい、フィールドへ退避しておきます。
そして、episode: 6 で紹介した TreeView.SelectedItemChanged イベントの Delegate 先である nodeChanged で表示する View を指定しています。
episode: 6 で紹介した通り、イベントパラメータも併せて受け取っているので、RoutedPropertyChangedEventArgs.NewValue から選択した TreeViewItem(TreeViewItemViewModel)を取得することができます。
TreeViewItemViewModel に SourceData 読み取り専用プロパティを定義して、コンストラクタに指定したデータオブジェクトを取得できるようにして、SourceData プロパティの型から対応した View を決定しています。
表示する View が決定したら IRegionManager.RequestNavigate メソッドのパラメータに以下の値を指定すると View を切り替えることができるようになります。
regionName |
View の表示先 Region 名を指定します。(ここでは Region 名: EditorArea に表示する指定をしています) |
source |
表示する View 名を指定します。 View 名は XAML ファイル名から拡張子以降(.xaml)を除いた文字列。(つまり XAML のクラス名) |
この時点でコンパイルエラー等も出ていないので実行してみます。
TreeView の『新しい生徒』を選択しても作成した個人識別情報編集 View は表示されません… orz
Prism の Region Navigation に必要な設定
これは最初に紹介した通り、Prism の Region Navigation は DI コンテナへ登録した View を表示する仕組みなので、DI コンテナへ登録されていない View を表示しようとすると実行時エラーではなく fig. 2 のような表示になります。
前章で作成した編集 View を含む Prism Module(プロジェクト)はどこからも参照されていないので、この時点では Module さえ Load されていません。
まずは、編集 View を含むプロジェクト(EditorViews)への参照を Prism Shell(WpfTestApp プロジェクト)に追加して、episode: 2 で NavigationTree Module を追加した時と同じく src. 2 のように App.xaml のコードビハインドへ Module を Load する記述を追加します。
1 2 3 4 5 6 7 8 9 | public partial class App { ~ 略 ~ protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) { moduleCatalog.AddModule<NavigationTreeModule>(InitializationMode.WhenAvailable); moduleCatalog.AddModule<EditorViewsModule>(InitializationMode.WhenAvailable); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | using WpfTestApp.Views; using Prism.Ioc; using Prism.Modularity; using Prism.Regions; namespace WpfTestApp { public class EditorViewsModule : IModule { public void OnInitialized(IContainerProvider containerProvider) { } public void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterForNavigation<PersonalEditor>(); containerRegistry.RegisterForNavigation<PhysicalEditor>(); containerRegistry.RegisterForNavigation<TestPointEditor>(); containerRegistry.RegisterForNavigation<CategoryPanel>(); } } } |
DI コンテナへの View 登録は忘れやすいので注意してください。
表示する View へパラメータを渡す
IRegionManager.RequestNavigate メソッドには表示する View へ任意のパラメータを渡すためのオーバーロードが定義されていて、src. 4 のように第 3 パラメータへ Prism.Regions.NavigationParameters をセットするオーバーロードを呼び出すことで実現できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | using System.Windows; using Prism.Mvvm; using Reactive.Bindings; using Reactive.Bindings.Extensions; namespace WpfTestApp.ViewModels { public class NavigationTreeViewModel : BindableBase, IDisposable { ~ 略 ~ /// <summary>SelectedItemChangedイベントハンドラ。</summary> /// <param name="e">イベントデータを格納しているRoutedPropertyChangedEventArgs<object>。</param> private void nodeChanged(RoutedPropertyChangedEventArgs<object> e) { var viewName = string.Empty; var current = e.NewValue as TreeViewItemViewModel; switch (current.SourceData) { case PersonalInformation p: viewName = "PersonalEditor"; break; case PhysicalInformation p: viewName = "PhysicalEditor"; break; case TestPointInformation t: viewName = "TestPointEditor"; break; case string s: viewName = "CategoryPanel"; break; } var param = new Prism.Regions.NavigationParameters(); param.Add("TargetData", current.SourceData); this.regionManager.RequestNavigate("EditorArea", viewName, param); } ~ 略 ~ } } |
src. 4 のように Prism.Regions.NavigationParameters クラスへキーワードと View へ引き渡したい object を追加すると、表示先に指定した View でパラメータを受け取ることができます。
実際にパラメータを受け取るためには表示先の View(の VM)へ INavigationAware インタフェースを実装する必要がありますが、次回のエントリで紹介します。
後書きのような独り言
管理人は元々、Prism で View を動的切替(画面遷移)する手順を紹介しようと思ってこの連載記事を書き始めましたが、やっと辿り着きました!
episode: 2 を公開して 2 か月強くらいでしょうか… 結構長かったです。
管理人が Prism を使ったアプリを作ろうと思って情報を探した時は、Prism について体系的に書かれた情報をあまり見かけませんでした。
それがきっかけで、Prism 入門的な記事を書けば需要があるかと思い、この連載を書き始めましたが、Prism だけではなく WPF 自体もあまり知らず、その時調べた内容も Prism を説明する前提として記事内容に含めたのでかなり長い文章になってしまいました。
ここまで書いてきた印象ですが、Prism の紹介で作成するサンプルアプリは、シンプルな 1 画面だけのダイアログのようなアプリでは Prism で出来る事の 1/10 も伝えられないんじゃないかとは思いますが、ネットで見かけるのはシンプルなサンプルで紹介しているような記事が大半で、Prism の公式サンプル に出会うまでは結構苦労した覚えがあります。
この連載内で作成しているようなサンプルアプリがまさに最適だ!等とは思っていませんが、Prism の紹介にはちょうど良い程度の規模じゃないかと思っています。
Prism 自体は想像するよりも簡単に使えるライブラリであるにも拘らず、体系的に学習したり「Prism をプロジェクトで使用するために要件を満たすのか?」を調べるには、あまりに情報が足りない現状(日本語での情報は特に)だと思っていますが、この連載を読んだ後に「Prism って高機能だけど結構簡単に使えそう!」的なことを感じてもらえれば、記事を書いた甲斐があります。
Prism とは関係ない情報や、管理人の無駄口的な文章も多かったと思いますが、実質的に Prism を動かすためのコード量は決して多くない事は分かってもらえたのではないでしょうか?
実は管理人的に、2019年からは WPF が若干盛り上がるんではないかと予想しています。
2017 年末の Xamarin WPF サポートに始まり、Windows Community Toolkit version 5.0 で WPF で UWP コントロールを動作させるための XAML Islands API 上に構築された WindowsXamlHost の発表。加えて、.NET Framework 4.8 からは WPF も WinRT API にフルアクセスできるという情報も見かけるので、来年以降は WPF 界隈がかなり賑やかになるような(なってくれれば良い)予感がしています。
何だか、「この連載はこれで終わります。」的な文章になってしまいましたが、まだ何回かは書きたいと思っていますし、一応ネタも考えています。
今回はここまでとして、次回は編集画面に編集データを表示するために、TreeView から Module へデータを受け渡す辺りを紹介する予定です。
又、今回作成したソースも GitHub リポジトリにアップしておきます。
次回記事「TreeViewItem を MVVM パターンで選択する【extra: 2 WPF Prism】」