本文は転載です
原文著者:RyzenAdorer
原文タイトル:.NET Core 3 WPF MVVM フレームワーク Prism シリーズのイベントアグリゲーター
原文リンク:https://www.cnblogs.com/ryzen/p/12196619.html
本記事では、.NET Core3 環境下で MVVM フレームワーク Prism のイベントアグリゲーターを使用してモジュール間の通信を実現する方法を紹介します。
1. イベントアグリゲーター
前回の記事「.NET Core 3 WPF MVVM フレームワーク Prism シリーズのモジュール化」では、同じモジュール内の異なるフォーム間や異なるモジュール間のフォーム間での通信をどのように処理するかという問題を残しました。Prism は、アプリケーション内の疎結合なモジュール間で通信するためのイベントメカニズムを提供します。このメカニズムはイベントアグリゲーターサービスに基づいており、パブリッシャーとサブスクライバーがイベントを介して通信できるようにしますが、相互に直接参照することはありません。これにより、モジュール間の疎結合な通信方式が実現されます。以下に、公式のイベントアグリゲーターモデル図を示します。

2. イベントの作成と発行
2.1. イベントの作成
まず、同じモジュール内の異なるフォーム間の通信を処理します。PrismMetroSample.Infrastructure に Events フォルダーを作成し、新しいクラス PatientSentEvent を作成します。コードは次のとおりです。
PatientSentEvent.cs:
public class PatientSentEvent: PubSubEvent<Patient>
{
}
2.2. イベントの購読
次に、患者詳細フォームの PatientDetailViewModel クラスでこのイベントを購読します。コードは次のとおりです。
PatientDetailViewModel.cs:
public class PatientDetailViewModel : BindableBase
{
IEventAggregator _ea;
IMedicineSerivce _medicineSerivce;
private Patient _currentPatient;
// 現在の患者
public Patient CurrentPatient
{
get { return _currentPatient; }
set { SetProperty(ref _currentPatient, value); }
}
private ObservableCollection<Medicine> _lstMedicines;
// 現在の患者の薬剤リスト
public ObservableCollection<Medicine> lstMedicines
{
get { return _lstMedicines; }
set { SetProperty(ref _lstMedicines, value); }
}
// コンストラクター
public PatientDetailViewModel(IEventAggregator ea, IMedicineSerivce medicineSerivce)
{
_medicineSerivce = medicineSerivce;
_ea = ea;
_ea.GetEvent<PatientSentEvent>().Subscribe(PatientMessageReceived);// イベントの購読
}
// メッセージ受信処理関数
private void PatientMessageReceived(Patient patient)
{
this.CurrentPatient = patient;
this.lstMedicines = new ObservableCollection<Medicine>(_medicineSerivce.GetRecipesByPatientId(this.CurrentPatient.Id).FirstOrDefault().LstMedicines);
}
}
2.3. メッセージの発行
次に、患者リストフォームの PatientListViewModel でメッセージを発行します。コードは次のとおりです。
PatientListViewModel.cs:
public class PatientListViewModel : BindableBase
{
private IApplicationCommands _applicationCommands;
public IApplicationCommands ApplicationCommands
{
get { return _applicationCommands; }
set { SetProperty(ref _applicationCommands, value); }
}
private List<Patient> _allPatients;
public List<Patient> AllPatients
{
get { return _allPatients; }
set { SetProperty(ref _allPatients, value); }
}
private DelegateCommand<Patient> _mouseDoubleClickCommand;
public DelegateCommand<Patient> MouseDoubleClickCommand =>
_mouseDoubleClickCommand ?? (_mouseDoubleClickCommand = new DelegateCommand<Patient>(ExecuteMouseDoubleClickCommand));
IEventAggregator _ea;
IPatientService _patientService;
/// <summary>
/// コンストラクター
/// </summary>
public PatientListViewModel(IPatientService patientService, IEventAggregator ea, IApplicationCommands applicationCommands)
{
_ea = ea;
this.ApplicationCommands = applicationCommands;
_patientService = patientService;
this.AllPatients = _patientService.GetAllPatients();
}
/// <summary>
/// DataGrid ダブルクリックボタンコマンドメソッド
/// </summary>
void ExecuteMouseDoubleClickCommand(Patient patient)
{
// フォームを開く
this.ApplicationCommands.ShowCommand.Execute(FlyoutNames.PatientDetailFlyout);
// メッセージを発行
_ea.GetEvent<PatientSentEvent>().Publish(patient);
}
}
効果は以下のとおりです。

2.4. 複数購読・複数発行の実現
同様に、検索後の Medicine を現在の患者リストに追加する処理も上記と同様の手順で行います。Events フォルダーにイベントクラス MedicineSentEvent を作成します。
MedicineSentEvent.cs:
public class MedicineSentEvent: PubSubEvent<Medicine>
{
}
患者詳細フォームの PatientDetailViewModel クラスでこのイベントを購読します。
PatientDetailViewModel.cs:
public PatientDetailViewModel(IEventAggregator ea, IMedicineSerivce medicineSerivce)
{
_medicineSerivce = medicineSerivce;
_ea = ea;
_ea.GetEvent<PatientSentEvent>().Subscribe(PatientMessageReceived);
_ea.GetEvent<MedicineSentEvent>().Subscribe(MedicineMessageReceived);
}
/// <summary>
// イベントメッセージ受信関数
/// </summary>
private void MedicineMessageReceived(Medicine medicine)
{
this.lstMedicines?.Add(medicine);
}
薬剤リストフォームの MedicineMainContentViewModel でもこのイベントを購読します。
MedicineMainContentViewModel.cs:
public class MedicineMainContentViewModel : BindableBase
{
IMedicineSerivce _medicineSerivce;
IEventAggregator _ea;
private ObservableCollection<Medicine> _allMedicines;
public ObservableCollection<Medicine> AllMedicines
{
get { return _allMedicines; }
set { SetProperty(ref _allMedicines, value); }
}
public MedicineMainContentViewModel(IMedicineSerivce medicineSerivce,IEventAggregator ea)
{
_medicineSerivce = medicineSerivce;
_ea = ea;
this.AllMedicines = new ObservableCollection<Medicine>(_medicineSerivce.GetAllMedicines());
_ea.GetEvent<MedicineSentEvent>().Subscribe(MedicineMessageReceived);// イベントの購読
}
/// <summary>
/// イベントメッセージ受信関数
/// </summary>
private void MedicineMessageReceived(Medicine medicine)
{
this.AllMedicines?.Add(medicine);
}
}
検索 Medicine フォームの SearchMedicineViewModel クラスでイベントメッセージを発行します。
SearchMedicineViewModel.cs:
IEventAggregator _ea;
private DelegateCommand<Medicine> _addMedicineCommand;
public DelegateCommand<Medicine> AddMedicineCommand =>
_addMedicineCommand ?? (_addMedicineCommand = new DelegateCommand<Medicine>(ExecuteAddMedicineCommand));
public SearchMedicineViewModel(IMedicineSerivce medicineSerivce, IEventAggregator ea)
{
_ea = ea;
_medicineSerivce = medicineSerivce;
this.CurrentMedicines = this.AllMedicines = _medicineSerivce.GetAllMedicines();
}
void ExecuteAddMedicineCommand(Medicine currentMedicine)
{
_ea.GetEvent<MedicineSentEvent>().Publish(currentMedicine);// メッセージを発行
}
効果は以下のとおりです。

次に、現在の Demo プロジェクトのイベントモデルとアセンブリ参照状況を確認します。以下の図のとおりです。

PatientModule と MedicineModule の 2 つのモジュール間で通信が実現できていることがわかりますが、相互に参照することはなく、PrismMetroSample.Infrastructure アセンブリを参照することで間接的な依存関係を実現し、異なるモジュール間の通信を疎結合で実現しています。
3. イベント購読の解除
Prism には購読解除機能も用意されています。患者詳細フォームにこの機能を追加します。PatientDetailViewModel に次のコードを追加します。
PatientDetailViewModel.cs:
private DelegateCommand _cancleSubscribeCommand;
public DelegateCommand CancleSubscribeCommand =>
_cancleSubscribeCommand ?? (_cancleSubscribeCommand = new DelegateCommand(ExecuteCancleSubscribeCommand));
void ExecuteCancleSubscribeCommand()
{
_ea.GetEvent<MedicineSentEvent>().Unsubscribe(MedicineMessageReceived);
}
効果は以下のとおりです。

4. 購読方法の設定
Demo ではメッセージアグリゲーターのイベントメカニズムにより、サブスクライバーとパブリッシャー間の通信を実現しました。次に、Prism が提供する購読方法を確認します。PubSubEvent クラスの Subscribe 関数のうち、最も多くのパラメーターを持つオーバーロードメソッドを使用して説明します。
Subscribe.cs:
public virtual SubscriptionToken Subscribe(Action<TPayload> action, ThreadOption threadOption, bool keepSubscriberReferenceAlive, Predicate<TPayload> filter);
4.1. action パラメーター
action パラメーターは、メッセージを受信する関数です。
4.2. threadOption パラメーター
ThreadOption 型のパラメーター threadOption は列挙型です。コードは次のとおりです。
ThreadOption.cs:
public enum ThreadOption
{
/// <summary>
/// 呼び出しは、PubSubEvent{TPayload} が発行されたスレッドと同じスレッドで実行されます。
/// </summary>
PublisherThread,
/// <summary>
/// 呼び出しは UI スレッドで実行されます。
/// </summary>
UIThread,
/// <summary>
/// 呼び出しはバックグラウンドスレッドで非同期に実行されます。
/// </summary>
BackgroundThread
}
3 つの列挙値の役割:
PublisherThread: デフォルト設定。この設定では、パブリッシャーから渡されたメッセージを受け取ることができます。UIThread: UI スレッド上でイベントを受け取ることができます。BackgroundThread: スレッドプール上で非同期にイベントを受け取ることができます。
4.3. keepSubscriberReferenceAlive パラメーター
デフォルトでは keepSubscriberReferenceAlive は false です。Prism の公式ドキュメントでは、このパラメーターは購読に弱参照を使用するか強参照を使用するかを示しています。false は弱参照、true は強参照です。
trueに設定すると、短時間に複数のイベントを発行する場合のパフォーマンスが向上しますが、手動で購読を解除する必要があります。イベントインスタンスはサブスクライバーインスタンスへの強参照を保持するため、フォームを閉じても GC 回収が行われないためです。falseに設定すると、イベントはサブスクライバーインスタンスへの弱参照を保持します。フォームを閉じると自動的に購読が解除されるため、手動で購読を解除する必要はありません。
4.4. filter パラメーター
filter は Predicate のジェネリックデリゲートパラメーターで、戻り値はブール値です。購読のフィルタリングに使用できます。Demo を例に、PatientDetailViewModel の購読を変更します。コードは次のとおりです。
PatientDetailViewModel.cs:
_ea.GetEvent<MedicineSentEvent>().Subscribe(MedicineMessageReceived,
ThreadOption.PublisherThread,false,medicine=>medicine.Name=="当归"|| medicine.Name== "琼浆玉露");
効果は以下のとおりです。

五. ソースコード
最後に、Demo 全体のソースコードを添付します。PrismDemo ソースコード