Prism の Region に部分 View がいます。【step: 5 .NET Core WPF Prism MVVM 入門 2020】

前回記事「Prism の DI コンテナらは Ioc 上に歌う【step: 4 .NET Core WPF Prism MVVM 入門 2020】」

 

前回は Prism の DI コンテナの使い方と DI コンテナを活用して Ioc(Inversion of Control:制御の反転)を実現するための方法を紹介しました。今回は Prism の Region を操作する IRegionManager と、部分 View の表示と破棄について紹介します。

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

Region とは

これまでにも何度か書きましたが、Prism は部分 View と呼ばれる UserControl を Window 上へ動的に表示することができ、部分 View を表示する領域の事を Region(リージョン)と呼びますRegion は Window 上へ 1 つ又は複数を同時に配置することができます

Prism Template Pack の【Prism Blank App (.NET Core)】等から作成したプロジェクトに含まれる Window にはデフォルトで src. 1 のように Region が 1 つ配置されます。

src. 1 の通り Region の実体は ContentControl 等のコントロールですが全てのコントロールが Region に設定できる訳ではありません。Region として使用できるコントロールについては後述しますが、以降では Region として使用できるコントロールの中でも使用頻度が高いと思われる ContentControl を例に Prism の Region について紹介します。

RegionManager

コントロールを Region として使用する場合、src. 1 の通り RegionManager 添付プロパティの RegionName へ一意の Region 名を指定します。複数の Region に同一名を設定すると『RegionCreationException』が Throw されます。

src. 1 で Region 名を指定している RegionManager 添付プロパティには RegionName 以外のプロパティも定義されていますが使用することはありません。

Shell で部分 View を表示する

Region は Window 上へ複数配置できますが、1 つの Region へ同時に表示できる部分 View は 1 つです。部分 View はスタートアッププロジェクトに含むこともできますが、前回紹介した通りスタートアッププロジェクトはアプリ基盤と言う位置付けのため、この連載ではスタートアッププロジェクトに部分 View を追加しません。

そのため fig. 1 のように部分 View を含む Module を新規追加しました。Module は複数の部分 View を含むこともできます

fig. 1 部分 View を複数含んだ Module

step: 3 で紹介した通りこのサンプルアプリは ViewModelLocator の命名規則を変更しているため、View と VM を同一の名前空間に配置しています。

又、追加した Module にも MahApps.Metro と Material Design In XAML Toolkit をインストールしています。Module へのインストールについては WPF UI Gallery exhibition #1 で紹介しています。

尚、Module の部分 View は全て Border コントロールを 1 つ配置しただけの単純な UserControl で、View と同名の色を Border の Background へ 設定しています。
以降では fig. 1 の ColorViews Module に置いた部分 View を Shell の Region に表示する方法を紹介します。



Shell に単一の部分 View を表示する

まずは src. 1 のように Shell 一杯に Region を 1 つ配置すると fig. 2 のような画面になります。

fig.2 単一 View

黄枠で囲んだ範囲が部分 View で ColorViews Module の Tomato.xaml を src. 2 のように表示しています。
※ スタートアッププロジェクトに ColorViews Module の参照を追加する必要があります。

部分 View の表示には Prism が提供する IRegionManager を利用するため、前回紹介した通り Shell のコンストラクタへパラメータを追加して Prism(DI コンテナ)から IRegionManager をインジェクションしてもらいます。

前回も紹介しましたが、Shell は部分 View を配置する土台なので、所謂ビジネスロジックのようなデータ操作は行いませんが、src. 2 のような UI 操作は行います。src. 2 では IRegionManager の RegisterViewWithRegion を呼び出して View の登録と Region への表示を一文で実行しています。

Region への部分 View 表示は IRegionManager の RequestNavigate 等でも可能ですが、アプリ起動時の部分 View 表示には RegisterViewWithRegion が便利です。RegisterViewWithRegion で表示した部分 View と VM は DI コンテナへの登録もされるため、部分 View(の VM)のコンストラクタへパラメータを追加すれば前回紹介したインジェクションも行われます。

Region に表示する部分 View の動的切り替えについては次回以降のエントリで紹介する予定なので、IRegionManager のプロパティやメソッド等はその都度紹介します。

Shell に複数の部分 View を表示する

Shell の MainWindow.xaml を src. 3 のように変更すると部分 View を複数表示することができます。

src. 3 のように ContentControl を 3 つ配置してそれぞれの RegionManager.RegionName へ違う名前を設定すると Shell へ 3 つの Region が配置されたことになります。

部分 View の表示は src. 2 と同じく src. 4 のように RegisterViewWithRegion で表示します。

3 つの Region に対して RegisterViewWithRegion を呼び出すと fig. 3 のような Shell になります。

fig.3 複数の部分 View

このように Shell のコンストラクタ等で IRegionManager.RegisterViewWithRegion を呼び出せば画面起動時に表示する View を指定することができます。

Module で部分 View を表示する

前章までは Shell 側に部分 View の表示を実装していましたが、Module 側に実装することもできます。Module 側に実装する場合は src. 5 のように IModule に処理を追加します。

前章と同じく IRegionManager.RegisterViewWithRegion を使用するのは変わりませんが、IModule.OnInitialized のパラメータに渡される IContainerProvider から IRegionManager を取得するのが違う所です。

但し、前章と違ってスタートアッププロジェクトに Module の参照を追加するだけでなく src. 6 のように Module の登録処理も追加する必要があります。

src. 6 の 25 行目にベースメソッドの呼び出しを残していますが、ベースメソッドは何も実装されていないので削除しても構いません。

src. 5 の IModule.OnInitialized は登録された Module しか呼び出されないので、src. 6 の IModuleCatalog.AddModule を忘れると OnInitialized は実行されません。複数の Region へ部分 View を表示する場合も src. 4 と同じく IRegionManager.RegisterViewWithRegion を複数回呼び出します。

部分 View 表示方法のまとめ

部分 View の表示を Shell 側に実装する場合と、Module 側に実装する方法を紹介しましたが、どちらが優れている等はありませんし、どちらが正しいと言う事もありません。管理人的に表示制御は Shell に集約されている方が見通し易いと思うので Shell 側に実装する方を選びます。

src. 6 の Module 登録を実装した場合は別の方法もあって、src. 7 のように Module 側では DI コンテナへの部分 View 登録のみ行います。

そして表示処理は src. 8 のように Shell 側に実装すると言う方法です。

この方式だと Module 側は部分 View の登録のみ、Shell 側は表示指示のみと責務が分割されますが、実際は src. 7 の登録処理が無くても fig. 2 と同じ画面が表示されるので微妙な気はします。
IRegionManager.RegisterViewWithRegion も IContainerRegistry.RegisterForNavigation も DI コンテナへの登録を行いますが、登録重複エラー等にはならないようです

管理人好みなのはアプリ起動時から終了まで表示し続ける部分 View は IRegionManager.RegisterViewWithRegion で登録、動的に切り替える部分 View は src. 7 のように IContainerRegistry.RegisterForNavigation で登録する方法です。

このように部分 View は色々な方法で表示できるので、前回紹介した DI コンテナからのインジェクションも含めて扱い易い方法を選択すれば良いと思います。

部分 View に Region を追加

ここまで Region は Shell だけに配置していましたが、部分 View にも Region を追加できます。fig. 2 で表示した部分 View の Tomato.xaml を src. 9 のように変更します。

そして src. 10 のように Tomato 部分 View の VM へ DarkTurquoise の表示処理を追加します。

Shell の場合と同くインジェクションされる IRegionManager の RegisterViewWithRegion を呼び出すと fig. 4 のような Shell が表示されます。

fig.4 部分 View 上の Region

黄枠がそれぞれ部分 View なので、想定通りの画面になっていると思います。又、src. 10 の 13 行目は Shell 側に実装しても fig. 4 と全く同じ画面が表示されるので、どちらで呼び出しても構いません。

このように Region を部分 View に配置しても Shell と同じように表示できます。但し、今回は同一 Module 内の部分 View を表示しているので、例えば別 Module の部分 View を指定した場合はどうか?とか、部分 View 上の Region で View 切り替えを実行した場合はどうか?等は試していないので、又、Region の View 動的切り替えを紹介する辺りで試してみます。



ContentControl を継承したコントロール

これまでの Region は全て ContentControl を使用してきましたが、実は ContentControl を継承したコントロールであれば Region として使用できます

.NET API ブラウザーで ContentControl を確認 すると Window や HeaderedContentControl が派生コントロールなので、Window や GroupBox(HeaderedContentControl から派生)でも『prism:RegionManager.RegionName=”Region 名”』を追加すれば Region として使用できます

又、WPF UI Gallery case: 1-1 で紹介している MahApps.Metro の HamburgerMenu や TransitionContentControl も ContentControl を継承したコントロールなので、Region として使用可能です

ContentControl を Region として使用する場合の注意点が 1 つあります。ContentControl は Focusable のデフォルト値が true なので Tab キー等でフォーカスを移動すると ContentControl 自体がフォーカスを受け取ってしまうため画面上からフォーカスが消えたように見えてしまいます。

これを避けるために ContentControl.Focusable に false を設定するのを忘れないようにしてください。
(2020/8/30 追記)

Prism が Region として認識するコントロール

Prism で Region として認識するコントロールは ContentControl 以外も用意されていて、前回も紹介した PrismInitializationExtensions クラスRegisterDefaultRegionAdapterMappings で登録されているコントロールが Region として使用できます

RegisterDefaultRegionAdapterMappings で登録しているコントロールは以下の 3 つです。

  • Selector
  • ItemsControl
  • ContentControl

上記 3 コントロールは Prism 内部で登録済みなので Region として使用できます。又、上記 3 コントロールから派生したコントロールも同じく Region として使用できますが、使用方法は ContentControl とほとんど変わらないはずなのでここでは紹介しません。興味がある人は試してみてください。

Region として使用するコントロールを追加する

コントロールを Region として使用するには IRegionAdapter を継承したクラスを作成する必要があります。管理人はあまり必要性を感じた事が無いので試した事がありませんが、Prism 公式サンプル の【03-CustomRegions】で StackPanel を Region として使用する例が紹介されているので、そちらを見てください。

情報はあまり多くないと思いますが、もし必要であれば【IRegionAdapter】等のキーワードで検索すればいくつか見つかると思います。

部分 View の Dispose

ここまでは部分 View の表示について紹介して来ましたが、本章では部分 View の破棄について紹介します。Prism の部分 View として使用する UserControl は Windows Form の UserControl とは違って IDisposable を継承していないため UserControl 自体の破棄は不要ですが、VM を破棄したいと思う事は多いと思います。

Prism Ver. 7.1.0 以前は部分 View の破棄はサポートされていなかったため『[Prism]UserControlの削除時にView/ViewModelをDisposeする – Qiita』のような Behavior を作成していたと思います。

ですが、Ver. 7.2.0.1367 から Xamarin 用の Prism に組み込まれていた IDestructible が WPF 版にも組み込まれたため部分 View の破棄が容易になりました。

IDestructible インタフェース

部分 View の VM を破棄するには src. 11 のように  IDestructible から継承するよう変更します。

IDestructible インタフェースは Destroy メソッドが 1 つ定義されているだけのシンプルなインタフェースなので、src. 11 のように Destroy メソッドへ破棄が必要な処理を追加します。src. 11 では破棄するオブジェクト等が存在しないため何も実装していません。

Prism が IDestructible.Destroy を呼び出すのは Region から部分 View を Remove する場合だけなので、どこかで Remove を呼び出す必要があります。部分 View の VM を破棄するタイミングはそれぞれ作成するアプリ毎に違うと思いますが、アプリ終了時に部分 View を全て破棄したいと考える事は多いと思います。

Shell の VM を Dispose する

アプリ終了時に全部分 View の VM を破棄するついでに Shell(MainWindow)の VM も破棄できた方が良いと思うので、src. 12 のように Shell の VM を IDisposable から継承するように変更して Dispose メソッドで Region の Remove 処理を呼び出します。

IRegionManager.Regions にはアプリ内に配置した全ての Region が格納されているので、33 ~ 36 行目のように Region 毎に RemoveAll を呼び出せば表示済み部分 View(VM)の IDestructible.Destroy が Prism から呼び出されます

ここでは全ての部分 View を破棄したいので RemoveAll を呼び出していますが、IRegionManager.Regions の IRegionCollection は IEnumerable を継承しているので src. 13 のように指定した部分 View のみ削除できます。

そして、Shell の VM を破棄するために、どこかで Dispose メソッドを呼び出す必要があるので、src. 14 のように Shell(MainWindow)のコードビハインドで Closed イベントをハンドルします。

src. 14 のように Window の DataContext を Dispose すれば、部分 View の VM も破棄されます。src. 14 を見て『コードビハインド?』と思う人も居るかもしれませんが、単なる終了処理のような実装はコードビハインドに書いても MVVM パターンが崩れる訳ではありません

ですが、src. 14 のような VM の Dispose 処理は他のアプリでも使いたい場合も多いと思います。Windows Form では Form を継承して処理を共通化する事も多かったと思いますが、WPF では Window を継承しなくても処理の共通化ができる仕組みが提供されています。

DataContext を Dispose するアクション

継承を利用した処理の共通化は手軽ですが、共通処理の追加・削除等には不向きなので WPF では共通処理を部品として作成可能なアクションや Behavior が導入されました。部品として再利用し易くなったので、DataContext の Dispose に WPF Prism episode: 18 で紹介した Livet のパッケージを利用する手もありますが、ソースファイル 1 つだけならそのままパクってプロジェクトに組み込んでも構わないと思います。

src. 14 の DataContext.Dispose は Livet の DataContextDisposeAction そのままなので、Livet をインストールせずソースコードを流用させてもらっています。

src. 15 のクラスを作成してスタートアッププロジェクトに追加します。

src. 15 は XAML から呼び出せるアクションとして利用できます。ここではスタートアッププロジェクトに追加していますが、他のプロジェクトなどに使い回すなら別プロジェクトに分ける方が良いと思います。src. 15 のアクション呼び出しには【System.Windows.Interactivity.dll から Xaml.Behaviors.Wpf へ】で紹介した Xaml.Behaviors.Wpf を利用します。

System.Windows.Interactivity.dll から Xaml.Behaviors.Wpf へ』でも紹介した通り、スタートアッププロジェクトには MahApps.Metro がインストール済のはずなので Xaml.Behaviors.Wpf をわざわざインストールする必要は無く、src. 16 のように Shell の XAML へ DataContextDisposeAction を呼び出しを追加します。

Xaml.Behaviors.Wpf の【Interaction.Triggers】へトリガーとして【EventTrigger】を指定すると【EventName】に設定したイベントが発火したタイミングでアクションが呼び出されます。【EventName】には対象コントロール(ここでは Window)から発火するイベントはどれでも指定できる(はずな)ので .NET API ブラウザー 等で探してください。

src. 16 のアクション呼び出しを追加すると src. 14 で紹介した MetroWindow_Closed イベントをコメントアウトしても部分 View や Shell の VM を破棄する処理が実行されることが確認できます。
※ Dispose や Destroy 等にブレークポイントを置いて確認してください。

アクションや Behavior について

DataContext の Dispose 呼び出しはかなり遠回しな紹介でしたが、アクションや Behavior は再利用可能な部品を作成する手段として用意されていますが、コードビハインドへの記述を避けるために用意されている訳ではない事を分かって欲しかったからです。

本章で紹介したように、他プロジェクト等で再利用するためであればアクションや Behavior を作成すれば良いと思いますが、そのプロジェクトでしか使用しないような処理ならコードビハインドで充分なので、アクションや Behavior を作成する手間をかけるかの検討は必要だと思います。



まとめ的な

Region だけで 1 エントリ埋まるか心配でしたが予想以上に書くことがありました。Prism の Region について全網羅できたとは思いませんが、現時点で管理人が知っていることはほとんど書けたと思います。隠しネタ的な情報もあるにはありますが、イマイチ使いどころが無いので又機会があれば紹介するつもりです。

一応、この連載は WPF Prism episode シリーズの焼き直しとして書き始めましたが、1 番の理由は何か新しい方法を見つけても WPF Prism episode シリーズだと情報がバラけ過ぎていて追記するエントリが決まらないからです。

そんな理由から新しい連載を開始したので、このエントリのように何か新しい方法を見つけた時に追記できるエントリが書けて良かったと思って今回のエントリはここまでとします。

今回のサンプルコードもいつものように GitHub リポジトリ へ上げています。

 

次回記事「Prism に Model ⇔ VM の双方向バインドは難しい【step: 6 .NET Core WPF Prism MVVM 入門 2020】」

 

 

おすすめ

コメントを残す

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

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