【WPF】ざっくりPrismの使い方②

【WPF】ざっくりPrismの使い方②

はじめに

前回に引き続き Prism の使い方について解説していきます。

今回はポップアップ画面を閉じたときのデータの受け渡しや、EventAggregatorを使用した ViewModel 間のデータの受け渡しを見ていきます。

ポップアップ画面の作成方法などは前回の記事を参照して下さい。

EventAggregator とは?

EventAggregatorは異なる ViewModel 間でのデータのやり取りを可能にする機能で、Pub/Sub パターンで実装されています。

この機能を使用することで異なる ViewModel が依存すること無く、イベントを介して間接的にデータの受け渡しができるようになります。

また、再利用しやすくテストも容易になるメリットがあります。

基本的な使い方

前回、ポップアップ画面に画像のパスのList<Uri>を渡すところまで実装したので、ShowImageViewModel内の List を呼び出し元のMainWindowViewModelから操作できるように実装します。

イベントの定義

まずはイベントを定義します。新しくIndexChangeEventクラスを作成します。

"IndexChangeEvent.cs"
using Prism.Events;

namespace Sample_Prism.UI.Event
{
    // PubSubEvent<T> を継承する
    // T にはイベントが運ぶデータの型を指定する
    internal class IndexChangeEvent  : PubSubEvent<int>
    { }
}

PubSubEvent<T>クラスを継承させます。今回、画像のインデックスを操作するのでintを指定します。

もっと複雑なデータを渡したい場合は、クラスを定義して指定することも出来ます。

UI 層以外からのプロジェクトからアクセスさせる予定はないのでinternalのままにしておきます。

イベントの発行(Publish)

イベントを発行するための処理を実装していきます。

この処理はメッセージを送信する側で実装します。今回はMainWindowViewModelが対象となります。

"MainWindowViewModel.cs"
// 省略
+ 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側のボタンでイベントが発火すると、指定しているメソッドが呼び出されてインデックスが更新されます。

"ShowImageViewModel.cs"
+ 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引数を受け取って戻り値を返さない定義済みデリゲートです。

subscribe.png

定義済みデリゲートについては以下の記事でまとめています。

以下のようにラムダ式で指定することも出来ます。

"ShowImageViewModel.cs"
public ShowImageViewModel(IEventAggregator eventAggregator)
{
    eventAggregator.GetEvent<IndexChangeEvent>().Subscribe((int index) =>
    {
        int newIndex = _currentIndex + index;

        // インデックスがリストの範囲内にあるかチェック
        if (newIndex >= 0 && newIndex < _listSize)
        {
                _currentIndex = newIndex;
        }

        ImageUri = _imageUriListParam[_currentIndex];
    });
}

ここまでの実装でビルドして動作を確認します。

slide-image.gif

異なる ViewModel からイベントを発行しデータを渡すことが出来ました。

最後に

MVVM で実装していると、ポップアップ画面にどのように値を渡すか悩むことが多かったですが、Prism の機能を使用することで簡単に解決することが出来ます。

ただ、多用すると逆にどこになんのデータが渡されているのか見通しが悪くなることもあるので注意が必要だと感じました。

参考