Prism で RegionManager を使った画面遷移方法
Navigation Using the Prism Library for WPF | Prism
WPF でよく使われるフレームワーク Prism
の提供する RegionManager
を使った画面遷移方法の使い方をまとめます。
今回は Prism のテンプレートを使用して作成したプロジェクトをサンプルとして用います。
Region
の基本
Prism
では Region
という表示領域を管理し、そこに表示するコンポーネント(UserControll)を RegionManager
という管理クラスで切り替えることができます。
例えば “ViewA” と “ViewB” という名前の UserControll を作成し、これを表示切替できるようなサンプルを作成してみます。
<UserControl x:Class="PrismApp.Views.ViewA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<TextBlock>View A</TextBlock>
</Grid>
</UserControl>
こんな感じです。ViewBも同様なので割愛します。
表示する UserControll についても ViewModel と同じように DI で管理できます。
まず、App.cs で containerRegistry
に表示させる View の型を登録します。
App.cs
public partial class App
{
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
+ containerRegistry.RegisterForNavigation<ViewA>();
+ containerRegistry.RegisterForNavigation<ViewB>();
}
}
RegisterForNavigation<T>()
でナビゲーションで表示したい型を登録します。ここに登録しておくことで、View のクラスを直接参照せずに DI でいい感じに管理できます。
これでナビゲーションの準備ができましたので、実際に動かせるように実装します。
MainPageView.xaml
は以下のようにします。
<Window x:Class="PrismApp.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Height="350" Width="525" >
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding ShowViewCommand}" CommandParameter="ViewA">Show ViewA</Button>
<Button Command="{Binding ShowViewCommand}" CommandParameter="ViewB">Show ViewB</Button>
</StackPanel>
<ContentControl prism:RegionManager.RegionName="ContentRegion" />
</StackPanel>
</Window>
表示を切り替えるボタンと、表示領域を管理する Region
を定義しています。
<ContentControl prism:RegionManager.RegionName="ContentRegion" />
というのが Region
で、Regionには名前を付ける必要があります。ここでは “ContentRegion” という名前で表示領域を確保しています。
ここに先ほど作成した UserControll を表示させます。
MainPageViewModel.cs
public class MainWindowViewModel : BindableBase
{
public IRegionManager RegionManager { get; }
public DelegateCommand<string> ShowViewCommand { get; }
public MainWindowViewModel(IRegionManager regionManager)
{
this.RegionManager = regionManager;
this.ShowViewCommand = new DelegateCommand<string>(this.ShowView);
}
public void ShowView(string viewName)
{
this.RegionManager.RequestNavigate("ContentRegion", viewName);
}
}
コンストラクタで IRegionManager
を受け取っていますが、これは Prism 側でDIしてくれるので特に意識する必要はありません。
RequestNavigate()
でナビゲーションを実行しています。引数で表示したい View を指定します。ここではボタンのコマンド引数で指定した文字列(“ViewA”, “ViewB”)が渡されます。これで画面の表示が切り替わります。
こんな風に画面の表示内容が更新されることが分かります。
RequestNavigate()
で指定するビューの名前は表示したいユーザーコントロールの型の名前になります。この名前を使って Prism が提供する DI 機能が自動的に名前で解決を行い表示してくれます。
ナビゲーションによる遷移時の処理(イベント)を定義したい
ナビゲーションによる画面遷移が実行されるタイミングで何かしらの処理を実行したい場合は、ViewModel
に INavigationAware
というインターフェースを実装することで実現できます。
INavigationAware
を実装すると以下のように、ナビゲーションによって表示されるとき、ナビゲーションによって別のビューに表示が切り替わるときに実行する処理が定義できます。
public class ViewAViewModel : BindableBase, INavigationAware
{
public bool IsNavigationTarget(NavigationContext navigationContext) => true;
public void OnNavigatedFrom(NavigationContext navigationContext)
{
// このViewが表示された状態から切り替わるときに実行される
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
// このViewが表示されるときに実行される
}
}
初期化処理や後始末の終了処理を実行するのに使えそうです。
一度生成したビューを再利用する
INavigationAware
で実装する IsNavigationTarget()
はナビゲーション対象にしていい状態かどうかを取得するためのメソッドです。これをうまく実装することで既存のビューを再利用するか、新しく生成するかを制御できます。
例えば、IsNavigationTarget()
が常に true
を返すビューと常に false
を返すビューで動作を確認します。
IsNavigationTarget()
が true
を返す場合、このインスタンスが再利用されてナビゲーション時表示されます。逆に false
を返す場合は再利用されず、再利用可能なインスタンスが見つからなければ新しくインスタンスを生成して表示します。以下コードサンプルと実行例です。
サンプルコードと実行例
ViewAViewModel.cs
public class ViewAViewModel : BindableBase, INavigationAware
{
private int _Count;
public int Count { get => this._Count; set => base.SetProperty(ref this._Count, value); }
// 常に再利用可能
public bool IsNavigationTarget(NavigationContext navigationContext) => true;
public void OnNavigatedFrom(NavigationContext navigationContext) {}
// ナビゲーションによる遷移時にカウントアップ
public void OnNavigatedTo(NavigationContext navigationContext) => this.Count++;
}
ViewBViewModel.cs
public class ViewBViewModel : BindableBase, INavigationAware
{
private int _Count;
public int Count { get => this._Count; set => base.SetProperty(ref this._Count, value); }
// 常に再利用しない
public bool IsNavigationTarget(NavigationContext navigationContext) => false;
public void OnNavigatedFrom(NavigationContext navigationContext){}
public void OnNavigatedTo(NavigationContext navigationContext) => this.Count++;
}
IsNavigationTarget()
だけ異なる実装になっています。あとはビューそれぞれにカウント結果を描画するようにして動作をさせてみると以下のようになります。
常にビューを再利用する ViewA のほうはナビゲーションされるたびに毎回カウントアップされることが確認できます。逆に再利用しない ViewB では常にカウンタが1になっていることが確認できます。
この機能を使用するシナリオは、例えば一意なキーを持つデータを表示し足り編集したりする画面で、ナビゲーションパラメータでキーを渡して、そのキーに対応するインスタンスが生成済であればそれを再利用する、などです。
Navigation Using the Prism Library for WPF | Prism
上のページにそうありました。うまく利用することでインスタンス生成の処理コストを削減できるということです。
ナビゲーション時に引数を受け渡しする方法
ナビゲーションによって画面遷移をするときに、遷移先のビューに何かしらのデータを渡したい場面はよくあります。そのような場合には NavigationParameters
を使用します。
ナビゲーション実行時に RequestNavigate()
の引数で NavigationParameters
クラスのデータを渡してやればよいです。
var parameters = new NavigationParameters();
parameters.Add("hoge", 1);
parameters.Add("foo", "hello world");
this.RegionManager.RequestNavigate("ContentRegion", viewName, parameters);
NavigationParameters
はキーバリューのペアのコレクション、要するに Dictionary
みたいな形式でデータを管理します。キーの型は string
で値の型は object
で固定です。必要なだけデータを詰め込んで渡すことが可能です。
データを遷移先のビューで受け取るには NavigationContext.Parameters
を参照します。遷移時に実行される処理 OnNavigatedTo
が引数で NavigationContext
が渡されるのでそこから取得できます。
public void OnNavigatedTo(NavigationContext navigationContext)
{
var parameters = navigationContext.Parameters;
var hoge = (int)parameters["hoge"];
var foo = (string)parameters["hoge"];
}
object
型なので型変換が必要になりますので適宜実行しましょう。
ナビゲーション実行前の確認とキャンセル
ナビゲーションによる遷移が実行される前に、何かしらの確認処理を行ったり、確認の結果を踏まえて遷移をキャンセルしたりしたい場合は、IConfirmNavigationRequest
インターフェースを実装すれば実現可能です。
IConfirmNavigationRequest
は INavigationAware
を実装する?インターフェースです。
public class ViewAViewModel : BindableBase, IConfirmNavigationRequest
{
// ナビゲーションの実行前の確認処理
public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
{
// 引数のコールバック関数に確認結果を渡す
var result = MessageBox.Show("遷移してもよいですか?", "確認", MessageBoxButton.YesNo);
continuationCallback(result == MessageBoxResult.Yes);
}
public bool IsNavigationTarget(NavigationContext navigationContext) => true;
public void OnNavigatedFrom(NavigationContext navigationContext) { }
public void OnNavigatedTo(NavigationContext navigationContext) { }
}
ConfirmNavigationRequest()
以外は前述した通りなので割愛します。
ナビゲーション前の遷移確認は ConfirmNavigationRequest()
に実装します。そして、確認結果によってナビゲーションのリクエストを実施するかどうかは continuationCallback
という引数のコールバック関数に確認結果を渡すことで可能となります。
コールバック関数に true
を渡すとナビゲーションが実施され、false
を渡すとナビゲーションがキャンセルされます。コールバックを実行しないと遷移は行われないようですが、必ず実行するようにしたほうが良いと思われます。
上の例ではメッセージボックスを表示し、その結果を用いてナビゲーションによって遷移するかどうかを分岐しています。
ナビゲーションに関する情報(引数や遷移先について)は NavigationContext
に格納されているので、これを適宜参照して確認処理を実装するのが良いでしょう。
ナビゲーション呼び出し元でキャンセルを検知する
ナビゲーション処理がキャンセルされた場合、それを呼び出し元で検知することもできます。IRegionManager.RequestNavigate()
で実行している場合、引数でナビゲーション処理のコールバック関数が登録できます。このコールバックで NavigationResult
型の値を引数で受け取れるので、この中から結果を取り出して処理することが可能です。
// ナビゲーションの引数
var parameters = new NavigationParameters();
parameters.Add("hoge", 1);
parameters.Add("foo", "hello world");
this.RegionManager.RequestNavigate("ContentRegion", viewName, navigationResult =>
{
// ナビゲーション完了時のコールバック関数
if (navigationResult.Result ?? false)
{
// ナビゲーション失敗(キャンセル時)
// ..
}
}, parameters);
ナビゲーションのエラー
ナビゲーション時のエラーは NavigationResult.Error
に格納されるのでエラーを検知して対応したい場合は、コールバック処理の中でこのエラーを参照するのが良いでしょう。
以上。
コメントを書く