なべひろBlog

プログラミングをメインに仕事に関するアレコレを発信しています。

Prismで複数のページを持ったアプリを作ってみる

はしめに

私が作るアプリケーションはメイン画面と設定画面など複数の画面で構成されるのがほとんどです。

以前記事にしたIDialogServiceを使って設定ダイアログを出してもいいですが、手段は複数持っておき状況に合わせて使い分けしたいと思い、WinFormsでやった事のあるメインとなる部分の表示を切り替える手法をやってみます。

ソースファイルはこれです。

新しいユーザーコントロールの追加

まずは新しいユーザーコントロールを2つ追加してみます。

理由は分かりませんが、Viewsフォルダで行わないとViewとViewModelのファイルがが正しく生成されませんので注意が必要です。

ソリューションエクスプローラーのViewsフォルダで右クリック-「追加」-「新しい項目」で表示されたダイアログより「Prism」-「WPF」のツリーを辿って行き、「Prism UserControl(WPF)」を選択し、分かりやすい名前でユーザーコントロールを作成します。

今回は「Page1」と「Page2」のユーザーコントロールを作成てみます。

DIコンテナの登録

App.xaml.csを開いてください。

そこにあるRegisterTypesメソッドに下記2行を追加しDIコンテナに表示するユーザーコントロールを登録します。

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    // ページを登録
    containerRegistry.RegisterForNavigation<Page1>();
    containerRegistry.RegisterForNavigation<Page2>();
}

「Page1」と「Page2」が作ったユーザーコントロールです。

特に面倒な記述はありません。

これで終了です。

表示させる

メインウィンドウにページを切り替えるボタン2つと表示するページの領域を確保します。

コメント「登録されたページを表示」の下にある1行がDIコンテナに登録されたページを表示する部分です。

<Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="*"/>
   </Grid.RowDefinitions>

   <StackPanel Grid.Row="0" Orientation="Horizontal">
      <Button Content="Page1" Command="{Binding Page1View}" Margin="5"/>
      <Button Content="Page2" Command="{Binding Page2View}" Margin="5"/>
   </StackPanel>

   <!-- 登録されたページを表示 -->
   <ContentControl Grid.Row="1" prism:RegionManager.RegionName="ContentRegion" />
</Grid>
制御する

上記xamlではボタンが2つ配置されてますが、これを使ってページを切り替えてみます。

まずはボタンのCommandにBindingするReactiveCommandです。

public ReactiveCommand Page1View { get; }
public ReactiveCommand Page2View { get; }

ついでにボタン表示を抑制するための情報を持たせておきます。

private ReactivePropertySlim<int> Page { get; } = new ReactivePropertySlim<int>(1);

もう1つ、MainWindowViewModel内で使用するIRegionManagerも準備しましょう。

private readonly IRegionManager RegionManager;

コンストラクタで引数IRegionManagerを取得し上記で作成したMainWindowViewModel内で使用するIRegionManagerに入れときます。

コンストラクタは下記のようになります。

public MainWindowViewModel(IRegionManager regionManager)
   RegionManager = regionManager;

引数で得たregionManagerをprivate変数RegionManagerに入れておきます。

そして以下はコンストラクタ内で初期表示を行ったりボタンが押された時の表示制御を記述します。

// これでPage1が起動時に表示される。
RegionManager.RegisterViewWithRegion("ContentRegion", typeof(Views.Page1));
// 現在表示されているページのボタンは押せないように制御する
Page1View = Page.Select(p => p != 1).ToReactiveCommand();
Page2View = Page.Select(p => p != 2).ToReactiveCommand();
// ボタンが押された時の動作
Page1View.Subscribe(_ =>
   RegionManager.RequestNavigate("ContentRegion", nameof(Views.Page1));
   Page.Value = 1;
}).AddTo(Disposable);
Page2View.Subscribe(_ =>
   RegionManager.RequestNavigate("ContentRegion", nameof(Views.Page2));
   Page.Value = 2;
}).AddTo(Disposable);

RequestNavigateの引数にxamlのContentControlにあるRegionNameで示した名称と表示するページ名を渡します。

これで無事表示の切り替えは成功します。

各ページにある破棄しなければいけない変数の処理

各ページのViewModelクラスはIDisposableを継承すればアプリを閉じる時Disposeメソッドが呼ばれるかと期待してたのですが甘かったです。

ViewModelに終了処理を記述したメソッドを置いてMainWindowViewModelが終了する時(Closedイベント等をキャッチ)にそのメソッドを呼べば解決しますが、イマイチスマートではありません。

そこで各ページのViewModelはIDestructibleを継承しDestroyメソッドを置いて対処します。

これで各ページが破棄される時Destroyメソッドが呼び出され自分で破棄する動作を別で作る必要がなくなります。

但し各ページが破棄されるタイミングは自分で処理する必要があります。

ReactivePropertyを使ってれば終了時の破棄処理は必須?だと思うので同じ所で良いです。

foreach (var region in RegionManager.Regions)
{
   region.RemoveAll();
}
R

emoveAllが実行されると各ページのViewModelにあるDestroyメソッドが実行されます。

RemoveAllするのも破棄するメソッドを呼ぶのも手間は同じじゃない?といったツッコミはご遠慮くださいw

滅多に表示しないページは閉じたら破棄したい

各ページは一度開くとメモリ上に存在し続けます。

しかし設定画面などがそうですが、一度設定が決まれば二度と開かないページもあります。

そんなページは閉じたら破棄する事で無駄なメモリ消費を抑制できます。

そんな場合ViewModelはIRegionMemberLifetimeを継承し、下記の1行を追加してください。

public bool KeepAlive => false;

これでページを閉じた(他のページが表示された)タイミングで破棄されます。

これで終わりですが,xamlに「ContentControl」を複数置いて必要なページだけを切り替えれば柔軟な表示ができるアプリが作れるかと思います。

Prism関連記事

MVVMなWPFアプリケーションでバーコードリーダーの情報をキャッチする。

PrismのIDialogServiceを試してみる

MVVMなWPFアプリケーションでドラッグ&ドロップをしてみる

Prismを使ったWPFアプリケーションで多重起動を抑制する

Prism使ったWPFでLivetCask.Messagingを使ってみる

PrismのUserControlを動的に配置する