本文將介紹如何在.NET Core3環境下使用MVVM框架Prism的使用事件聚合器實現模塊間的通信 一.事件聚合器 在上一篇 ".NET Core 3 WPF MVVM框架 Prism系列之模塊化" 我們留下了一些問題,就是如何處理同模塊不同窗體之間的通信和不同模塊之間不同窗體的通信,Prism提 ...
本文將介紹如何在.NET Core3環境下使用MVVM框架Prism的使用事件聚合器實現模塊間的通信
一.事件聚合器
在上一篇 .NET Core 3 WPF MVVM框架 Prism系列之模塊化 我們留下了一些問題,就是如何處理同模塊不同窗體之間的通信和不同模塊之間不同窗體的通信,Prism提供了一種事件機制,可以在應用程式中低耦合的模塊之間進行通信,該機制基於事件聚合器服務,允許發佈者和訂閱者之間通過事件進行通訊,且彼此之間沒有之間引用,這就實現了模塊之間低耦合的通信方式,下麵引用官方的一個事件聚合器模型圖:
二.創建和發佈事件
1.創建事件
首先我們來處理同模塊不同窗體之間的通訊,我們在PrismMetroSample.Infrastructure新建一個文件夾Events,然後新建一個類PatientSentEvent,代碼如下:
PatientSentEvent.cs:
public class PatientSentEvent: PubSubEvent<Patient>
{
}
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);
}
}
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);
}
}
效果如下:
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程式集來實現間接依賴關係,實現了不同模塊之間通訊且低耦合的情況
三.取消訂閱事件
Prism還提供了取消訂閱的功能,我們在病人詳細窗體提供該功能,PatientDetailViewModel加上這幾句:
PatientDetailViewModel.cs:
private DelegateCommand _cancleSubscribeCommand;
public DelegateCommand CancleSubscribeCommand =>
_cancleSubscribeCommand ?? (_cancleSubscribeCommand = new DelegateCommand(ExecuteCancleSubscribeCommand));
void ExecuteCancleSubscribeCommand()
{
_ea.GetEvent<MedicineSentEvent>().Unsubscribe(MedicineMessageReceived);
}
效果如下:
四.幾種訂閱方式設置
我們在Demo已經通過消息聚合器的事件機制,實現訂閱者和發佈者之間的通訊,我們再來看看,Prim都有哪些訂閱方式,我們可以通過PubSubEvent類上面的Subscribe函數的其中最多參數的重載方法來說明:
Subscribe.cs:
public virtual SubscriptionToken Subscribe(Action<TPayload> action, ThreadOption threadOption, bool keepSubscriberReferenceAlive, Predicate<TPayload> filter);
1.action參數
其中action參數則是我們接受消息的函數
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:可以線上程池在非同步接受事件
3.keepSubscriberReferenceAlive參數
預設keepSubscriberReferenceAlive為false,在Prism官方是這麼說的,該參數指示訂閱使用弱引用還是強引用,false為弱引用,true為強引用:
- 設置為true,能夠提升短時間發佈多個事件的性能,但是要手動取消訂閱事件,因為事件實例對保留對訂閱者實例的強引用,否則就算窗體關閉,也不會進行GC回收.
- 設置為false,事件維護對訂閱者實例的弱引用,當窗體關閉時,會自動取消訂閱事件,也就是不用手動取消訂閱事件
4.filter參數
filter是一個Predicate
PatientDetailViewModel.cs:
_ea.GetEvent<MedicineSentEvent>().Subscribe(MedicineMessageReceived,
ThreadOption.PublisherThread,false,medicine=>medicine.Name=="當歸"|| medicine.Name== "瓊漿玉露");
效果如下:
五.源碼
最後,附上整個demo的源代碼:PrismDemo源碼