はじめに
前回に引き続き Prism の使い方について解説していきます。
今回はポップアップ画面を閉じたときのデータの受け渡しや、EventAggregator
を使用した ViewModel 間のデータの受け渡しを見ていきます。
ポップアップ画面の作成方法などは前回の記事を参照して下さい。
EventAggregator とは?
EventAggregator
は異なる ViewModel 間でのデータのやり取りを可能にする機能で、Pub/Sub パターンで実装されています。
この機能を使用することで異なる ViewModel が依存すること無く、イベントを介して間接的にデータの受け渡しができるようになります。
また、再利用しやすくテストも容易になるメリットがあります。
基本的な使い方
前回、ポップアップ画面に画像のパスのList<Uri>
を渡すところまで実装したので、ShowImageViewModel
内の List を呼び出し元のMainWindowViewModel
から操作できるように実装します。
イベントの定義
まずはイベントを定義します。新しくIndexChangeEvent
クラスを作成します。
using Prism.Events;
namespace Sample_Prism.UI.Event
{
// PubSubEvent<T> を継承する
// T にはイベントが運ぶデータの型を指定する
internal class IndexChangeEvent : PubSubEvent<int>
{ }
}
PubSubEvent<T>
クラスを継承させます。今回、画像のインデックスを操作するのでint
を指定します。
もっと複雑なデータを渡したい場合は、クラスを定義して指定することも出来ます。
UI 層以外からのプロジェクトからアクセスさせる予定はないのでinternal
のままにしておきます。
イベントの発行(Publish)
イベントを発行するための処理を実装していきます。
この処理はメッセージを送信する側で実装します。今回はMainWindowViewModel
が対象となります。
// 省略
+ using Sample_Prism.UI.Event;
namespace Sample_Prism.UI.ViewModels
{
public class MainWindowViewModel : BindableBase
{
private readonly IEventAggregator _eventAggregator;
// 省略
+ public DelegateCommand ExecutePrevImageCommand
+ => _executePrevImageCommand ?? (_executePrevImageCommand = new DelegateCommand(OnPrevImageCommand));
+ private DelegateCommand _executePrevImageCommand;
+ public DelegateCommand ExecuteNextImageCommand
+ => _executeNextImageCommand ?? (_executeNextImageCommand = new DelegateCommand(OnNextImageCommand));
+ private DelegateCommand _executeNextImageCommand;
public MainWindowViewModel(IRegionManager regionManager,
IDialogService dialogService,
+ IEventAggregator eventAggregator)
{
_regionManager = regionManager;
_dialogService = dialogService;
+ _eventAggregator = eventAggregator;
}
// 省略
private void OnNextImageCommand()
{
// インデックスを加算する処理
+ _eventAggregator.GetEvent<IndexChangeEvent>().Publish(1);
}
private void OnPrevImageCommand()
{
// インデックスを減算する処理
+ _eventAggregator.GetEvent<IndexChangeEvent>().Publish(-1);
}
}
}
イベントを購読する(Subscribe)
画像を表示している側のShowImageViewModel
で発行されたイベントを購読する処理を記述します。
MainWindowViewModel
側のボタンでイベントが発火すると、指定しているメソッドが呼び出されてインデックスが更新されます。
+ using Prism.Events;
// 省略
namespace Sample_Prism.UI.ViewModels
{
public class ShowImageViewModel : BindableBase, IDialogAware
{
// 省略
+ private int _currentIndex = 0;
+ private int _listSize = 0;
// 省略
+ public ShowImageViewModel(IEventAggregator eventAggregator)
{
// イベ ントを購読する処理を一回実行しておけば良いので、メンバ変数で保持しない
+ eventAggregator.GetEvent<IndexChangeEvent>().Subscribe(UpdateIndex);
}
// イベントを受け取った時に実行するメソッドを追加
+ private void UpdateIndex(int index)
{
int newIndex = _currentIndex + index;
// インデックスがリストの範囲内にあるかチェック
if (newIndex >= 0 && newIndex < _listSize)
{
_currentIndex = newIndex;
}
ImageUri = _imageUriListParam[_currentIndex];
}
// 省略
/// <summary>
/// 呼び出し元からパラメータが渡されたときの処理を書く
/// </summary>
/// <param name="parameters"></param>
public void OnDialogOpened(IDialogParameters parameters) // IDialogAware追加時に実装される
{
_imageUriListParam = parameters.GetValue<List<Uri>>(nameof(MainWindowViewModel.ImageUriList));
// 受け取ったリストの数を保持する
+ _listSize = _imageUriListParam.Count;
ImageUri = _imageUriListParam[0];
}
}
}
Subscribe
メソッドには実行されるActionデリゲート
を指定します。Action
は引数を受け取って戻り値を返さない定義済みデリゲートです。
定義済みデリゲートについては以下の記事で まとめています。
以下のようにラムダ式で指定することも出来ます。
public ShowImageViewModel(IEventAggregator eventAggregator)
{
eventAggregator.GetEvent<IndexChangeEvent>().Subscribe((int index) =>
{
int newIndex = _currentIndex + index;
// インデックスがリストの範囲内にあるかチェック
if (newIndex >= 0 && newIndex < _listSize)
{
_currentIndex = newIndex;
}
ImageUri = _imageUriListParam[_currentIndex];
});
}
ここまでの実装でビルドして動作を確認します。
異なる ViewModel からイベントを発行しデータを渡すことが出来ました。
最後に
MVVM で実装していると、ポップアップ画面にどのように値を渡すか悩むことが多かったですが、Prism の機能を使用することで簡単に解決することが出来ます。
ただ、多用すると逆にどこになんのデータが渡されているのか見通しが悪くなることもあるので注意が必要だと感じました。