.NET Core 3 WPF MVVM框架 Prism系列之事件聚合器
本文將介紹如何在.NET Core3環境下使用MVVM框架Prism的使用事件聚合器實現模組間的通訊
一.事件聚合器
在上一篇 .NET Core 3 WPF MVVM框架 Prism系列之模組化 我們留下了一些問題,就是如何處理同模組不同窗體之間的通訊和不同模組之間不同窗體的通訊,Prism提供了一種事件機制,可以在應用程式中低耦合的模組之間進行通訊,該機制基於事件聚合器服務,允許釋出者和訂閱者之間通過事件進行通訊,且彼此之間沒有之間引用,這就實現了模組之間低耦合的通訊方式,下面引用官方的一個事件聚合器模型圖:
二.建立和釋出事件
1.建立事件
首先我們來處理同模組不同窗體之間的通訊,我們在PrismMetroSample.Infrastructure新建一個資料夾Events,然後新建一個類PatientSentEvent,程式碼如下:
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原始碼