[WPF,Prism] RegionManagerによる画面遷移方法まとめ

[WPF,Prism] RegionManagerによる画面遷移方法まとめ

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 機能が自動的に名前で解決を行い表示してくれます。

ナビゲーションによる遷移時の処理(イベント)を定義したい

ナビゲーションによる画面遷移が実行されるタイミングで何かしらの処理を実行したい場合は、ViewModelINavigationAware というインターフェースを実装することで実現できます。

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 インターフェースを実装すれば実現可能です。

IConfirmNavigationRequestINavigationAware を実装する?インターフェースです。

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 に格納されるのでエラーを検知して対応したい場合は、コールバック処理の中でこのエラーを参照するのが良いでしょう。

以上。

参考URL

C#カテゴリの最新記事