本文來自轉載
原文作者: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 系列之模块化](https://dotnet9.com/2023/06/Modularization-of-the-Prism-family-of-the-dotNET-Core-3-WPF-MVVM-framework) 我们留下了一些问题,就是如何处理同模块不同窗体之间的通信和不同模块之间不同窗体的通信,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 兩個模塊之間做到了通訊,但卻不相互引用,依靠引用 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 已經通過消息聚合器的事件機制,實現訂閱者和發布者之間的通訊,我們再來看看,prim 都有哪些訂閱方式,我們可以通過 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>
/// The call is done on the same thread on which the <see cref="PubSubEvent{TPayload}"/> was published.
/// </summary>
PublisherThread,
/// <summary>
/// The call is done on the UI thread.
/// </summary>
UIThread,
/// <summary>
/// The call is done asynchronously on a background thread.
/// </summary>
BackgroundThread
}
三種枚舉值的作用:
- 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 源码