(4/7). NET Core 3 WPF MVVM Framework Prism Series Event Aggregator

(4/7). NET Core 3 WPF MVVM Framework Prism Series Event Aggregator

How to use the MVVM framework Prism using the event aggregator to achieve communication between modules in the. NET Core3 environment

最后更新 6/11/2023 12:09 AM
RyzenAdorer
预计阅读 8 分钟
分类
WPF
专题
WPF MVVM Framework Prism Series
标签
.NET C# WPF Prism MVVM

This article comes from reprint

Original author: RyzenAdorer

Original title: . NET Core 3 WPF MVVM Framework Prism Series Event Aggregator

Original link: www.cnblogs.com/ryzen/p/12196619.html

This article will introduce how to use the event aggregator of the MVVM framework Prism to achieve communication between modules in the. NET Core3 environment

1. event aggregator

在上一篇 [].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. Create and publish events

2.1. create an event

First, let's handle the communication between different forms in the same module. We create a new folder Events in PrismMetroSample.Infrastructure, and then create a new class PatientSentEvent, with the code as follows:

PatientSentEvent.cs:

public class PatientSentEvent: PubSubEvent<Patient>
{
}

2.2. subscription event

Then we subscribe to the event in the PatientDetailViewModel class of the patient detail form, with the following code:

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. released a message

Then we publish the message in the PatientListViewModel of the patient list form, with the following code:

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);
    }

}

The effects are as follows:

2.4. Achieve multiple subscriptions and multiple publications

Similarly, when we add searched Medicine to the current patient list, we create an event class MedicineSentEvent in the Events folder:

MedicineSentEvent.cs:

 public class MedicineSentEvent: PubSubEvent<Medicine>
 {

 }

Subscribe to this event in the PatientDetailViewModel class of the Patient Details form:

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);
 }

The MedicineMainContentViewModel in the Drug List form also subscribes to this event:

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);
   }
}

Post an event message in the SearchMedicineViewModel class of the Search Medicine form:

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);//发布消息
 }

The effects are as follows:

Then let's take a look at the current event model and assembly references of the Demo project, as shown below:

We found that PatientModule and MedicineModule communicate, but they do not reference each other. They rely on referencing the PrismMetroSample.Infrastructure assembly to realize indirect dependencies, achieving communication and low coupling between different modules

3. Unsubscribe from events

Prism also provides the ability to unsubscribe, which we provide in the patient detail form. The PatientDetailView Model adds these sentences:

PatientDetailViewModel.cs:

 private DelegateCommand _cancleSubscribeCommand;
 public DelegateCommand CancleSubscribeCommand =>
       _cancleSubscribeCommand ?? (_cancleSubscribeCommand = new DelegateCommand(ExecuteCancleSubscribeCommand));

  void ExecuteCancleSubscribeCommand()
  {
      _ea.GetEvent<MedicineSentEvent>().Unsubscribe(MedicineMessageReceived);
  }

The effects are as follows:

4. Several subscription methods settings

At Demo, we have used the event mechanism of the message aggregator to realize communication between subscribers and publishers. Let's take a look at what subscription methods Prim has. We can explain this by overloading the most parameters of the Subscribe function on the PubSubEvent class:

Subscribe.cs:

public virtual SubscriptionToken Subscribe(Action<TPayload> action, ThreadOption threadOption, bool keepSubscriberReferenceAlive, Predicate<TPayload> filter);

4.1. action parameter

The action parameter is the function that we accept the message

4.2. threadOption parameter

ThreadOption type parameter ThreadOption is an enumerated type parameter with the following code:

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
}

Three enumerated values are used:

  • PublisherThread: Default setting. Use this setting to accept messages passed by publishers
  • UIThread: You can accept events on UI threads
  • BackgroundThread: You can accept events asynchronously in the thread pool

4.3. keepSubscriberReferenceAlive parameter

The default keepSubscriberReferenceAlive is false, which is what the official Prism says. This parameter indicates whether the subscription uses a weak reference or a strong reference. false is a weak reference, and true is a strong reference:

  • Setting it to true can improve the performance of publishing multiple events in a short period of time, but you need to manually unsubscribe the event because the event instance pair retains strong references to the subscriber instance. Otherwise, even if the form is closed, GC recycling will not occur.
  • Set to false, the event maintains a weak reference to the subscriber instance. When the form is closed, the event will be automatically unsubscribed, that is, there is no need to manually unsubscribe the event

4.4. filter parameter

filter is a generic delegate parameter of Predicate. The return value is a Boolean value and can be used to subscribe to filtering. Take our demo as an example to change the PatientDetailViewModel subscription, the code is as follows:

PatientDetailViewModel.cs:

  _ea.GetEvent<MedicineSentEvent>().Subscribe(MedicineMessageReceived,
ThreadOption.PublisherThread,false,medicine=>medicine.Name=="当归"|| medicine.Name== "琼浆玉露");

The effects are as follows:

v. source code

最后,附上整个 demo 的源代码:PrismDemo 源码

Keep Exploring

延伸阅读

更多文章