【Prism 8】事件聚合器(Event Aggregator)

步骤:

  1. 继承PubSubEvent<T>创建自定义事件类型
  2. 创建事件发送者
  3. 创建事件接收者,并订阅指定的事件

1. 事件聚合器

Prism 提供了一种机制,可以实现应用程序中松散耦合组件之间的通信。这种机制基于事件聚合器服务,允许发布者和订阅者通过事件进行通信,并且彼此之间仍然没有直接引用。

事件聚合器提供多播发布/订阅功能。这意味着可以有多个发布者引发相同的事件,并且可以有多个订阅者监听相同的事件。

通过事件聚合器服务可以使用IEventAggregator接口获取到事件聚合器。事件聚合器负责定位或构建事件,并在系统中保存事件的集合。首次访问一个事件时,如果该事件尚未构建,则构建。

2. 事件类型

PubSubEventPrism中对积累EventBase的唯一实现。此类维护订阅者列表并向订阅者发送事件。PubSubEvent是一个泛型类,在使用时需要指定具体类型以进行特化。

一般是通过继承该类,特化一个自定义事件类型

3. 发布

发布者通过事件聚合器服务获取到EventAggregator,并调用Publish方法来触发事件。

4. 订阅

订阅者通过事件聚合器服务获取到EventAggregator,并调用Subscribe方法进行注册。之后,注册的事件被触发是,通过参数指定委托进行相应。

4.1 订阅类型

  • ThreadOption.PublisherThread 与发布者使用相同线程,默认方式
  • ThreadOption.BackgroundThread 使用线程池线程
  • ThreadOption.UIThread 使用UI线程

4.2 事件过滤

订阅者在注册事件订阅是可以通过参数指定过滤的事件条件,只有满足条件的事件才能被订阅者真正使用。过滤通过System.Predicate<T>委托进行。

4.3 强引用订阅

默认情况下使用弱引用方式。强引用调用能够加速事件的传递,但必须手动的取消订阅。

5. 样例

创建一个名为EventAggregatorAppWPF应用程序项目,并在Nuget中安装Prism.Unity,此处使用的版本为8.1.97

  • 打开App.xaml,修改为如下内容:
<unity:PrismApplication
    x:Class="EventAggregatorApp.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:unity="http://prismlibrary.com/">
    <Application.Resources />
</unity:PrismApplication>
  • 打开App.xaml.cs,修改为如下内容:
using System.Windows;
using EventAggregatorApp.Views;
using Prism.Ioc;
using Prism.Modularity;

namespace EventAggregatorApp
{
    public partial class App
    {
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
        }

        protected override Window CreateShell()
        {
            return this.Container.Resolve<MainWindowView>();
        }

        protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
        {
            moduleCatalog.AddModule<PublisherModule>();
            moduleCatalog.AddModule<SubscriberModule>();
        }
    }
}
  • 创建 ViewsViewModels文件夹,并将MainWindow.xaml重命名为MainWindowViewModel.xaml,最后将MainWindowViewModel.xaml移动到Views文件夹
  • 打开MainWindowViewModel.xaml,修改为如下内容:
<Window
    x:Class="EventAggregatorApp.Views.MainWindowView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:prism="http://prismlibrary.com/"
    xmlns:viewModels="clr-namespace:EventAggregatorApp.ViewModels"
    Title="{Binding Title}"
    Width="600"
    Height="480"
    d:DataContext="{d:DesignInstance viewModels:MainWindowViewModel}"
    prism:ViewModelLocator.AutoWireViewModel="True"
    mc:Ignorable="d">
    <DockPanel>
        <ContentControl prism:RegionManager.RegionName="LeftRegion" DockPanel.Dock="Top" />
        <Border
            Height="3"
            Background="LightGray" Margin="5"
            DockPanel.Dock="Top" />
        <ContentControl prism:RegionManager.RegionName="RightRegion" />
    </DockPanel>
</Window>
  • 打开MainWindowViewModel.xaml.cs,修改为如下内容:
namespace EventAggregatorApp.Views
{
    public partial class MainWindowView
    {
        public MainWindowView()
        {
            this.InitializeComponent();
        }
    }
}
  • Views文件夹下,新建名称为PublisherView的用户自定义控件,并修改PublisherView.xamlPublisherView.xaml.cs内容如下:
<UserControl
    x:Class="EventAggregatorApp.Views.PublisherView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:prism="http://prismlibrary.com/"
    xmlns:viewModels="clr-namespace:EventAggregatorApp.ViewModels"
    d:DataContext="{d:DesignInstance viewModels:PublisherViewModel}"
    prism:ViewModelLocator.AutoWireViewModel="True"
    mc:Ignorable="d">

    <StackPanel>
        <Border
            Margin="5"
            Background="LightGreen"
            CornerRadius="5">
            <TextBlock
                Margin="5"
                HorizontalAlignment="Center"
                FontSize="20"
                FontWeight="Bold">
                发送者
            </TextBlock>
        </Border>
        <TextBox Margin="5" Text="{Binding Message}" />
        <Button
            Margin="5"
            Command="{Binding SendMessageCommand}"
            Content="发送" />
    </StackPanel>
</UserControl>
namespace EventAggregatorApp.Views
{
    public partial class PublisherView
    {
        public PublisherView()
        {
            this.InitializeComponent();
        }
    }
}
  • Views文件夹下,新建名称为SubscriberView的用户自定义控件,并修改SubscriberView.xamlSubscriberView.xaml.cs内容如下:
<UserControl
    x:Class="EventAggregatorApp.Views.SubscriberView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:prism="http://prismlibrary.com/"
    xmlns:viewModels="clr-namespace:EventAggregatorApp.ViewModels"
    d:DataContext="{d:DesignInstance viewModels:SubscriberViewModel}"
    prism:ViewModelLocator.AutoWireViewModel="True"
    mc:Ignorable="d">
    <DockPanel>
        <Border
            Margin="5"
            Background="LightGreen"
            CornerRadius="5"
            DockPanel.Dock="Top">
            <TextBlock
                Margin="5"
                HorizontalAlignment="Center"
                FontSize="20"
                FontWeight="Bold">
                Subscriber
            </TextBlock>
        </Border>
        <UniformGrid
            Margin="5,2"
            Columns="4"
            DockPanel.Dock="Top">
            <TextBlock>订阅线程类型</TextBlock>
            <RadioButton Margin="3,0" IsChecked="{Binding IsPublisherThread}">Publisher</RadioButton>
            <RadioButton Margin="3,0" IsChecked="{Binding IsBackgroundThread}">Background</RadioButton>
            <RadioButton Margin="3,0" IsChecked="{Binding IsUiThread}">UI</RadioButton>
        </UniformGrid>

        <DockPanel Margin="5,2" DockPanel.Dock="Top">
            <TextBlock Margin="0,0,10,0">是否为强类型订阅</TextBlock>
            <CheckBox VerticalAlignment="Center" IsChecked="{Binding IsStrongReference}"/>
        </DockPanel>

        <UniformGrid Columns="2" DockPanel.Dock="Top">
            <Button Margin="5, 2" Command="{Binding SubscribeCommand}">注册订阅</Button>
            <Button Margin="5, 2" Command="{Binding UnsubscribeCommand}">取消订阅</Button>
        </UniformGrid>

        <ListBox Margin="5" ItemsSource="{Binding MsgCollection}" />
    </DockPanel>
</UserControl>
namespace EventAggregatorApp.Views
{
    public partial class SubscriberView
    {
        public SubscriberView()
        {
            this.InitializeComponent();
        }
    }
}
  • ViewModels文件夹中,新建名称为MainWindowViewModelPublisherViewModelSubscriberViewModel 三个类,源码分别如下:
using Prism.Mvvm;

namespace EventAggregatorApp.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        private string title = "EventAggregator";

        public string Title
        {
            get => this.title;
            set => this.SetProperty(ref this.title, value);
        }
    }
}
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;

namespace EventAggregatorApp.ViewModels
{
    public class PublisherViewModel : BindableBase
    {
        private readonly IEventAggregator eventAggregator;
        private string message = "Send a message form publisher to subscriber";

        private DelegateCommand sendMessageCommand;

        public PublisherViewModel(IEventAggregator eventAggregator)
        {
            this.eventAggregator = eventAggregator;
        }

        public string Message
        {
            get => this.message;
            set => this.SetProperty(ref this.message, value);
        }

        public DelegateCommand SendMessageCommand => this.sendMessageCommand ?? (this.sendMessageCommand = new DelegateCommand(() => this.eventAggregator.GetEvent<MessageSentEvent>().Publish(this.Message)));
    }
}
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using System.Collections.ObjectModel;
using System.Threading;

namespace EventAggregatorApp.ViewModels
{
    public class SubscriberViewModel : BindableBase
    {
        private ObservableCollection<string> msgCollection;
        public ObservableCollection<string> MsgCollection
        {
            get => this.msgCollection;
            set => this.SetProperty(ref this.msgCollection, value);
        }

        private bool isPublisherThread = true;
        public bool IsPublisherThread
        {
            get => this.isPublisherThread;
            set => this.SetProperty(ref this.isPublisherThread, value);
        }

        private bool isBackgroundThread;
        public bool IsBackgroundThread
        {
            get => this.isBackgroundThread;
            set => this.SetProperty(ref this.isBackgroundThread, value);
        }

        private bool isUiThread;
        public bool IsUiThread
        {
            get => this.isUiThread;
            set => this.SetProperty(ref this.isUiThread, value);
        }

        private bool isStrongReference;
        public bool IsStrongReference
        {
            get => this.isStrongReference;
            set => this.SetProperty(ref this.isStrongReference, value);
        }

        private DelegateCommand subscribeCommand;
        public DelegateCommand SubscribeCommand => this.subscribeCommand ?? (this.subscribeCommand = new DelegateCommand(this.SubscribeEvent));

        private void SubscribeEvent()
        {
            var threadOption = ThreadOption.PublisherThread;
            if (this.isBackgroundThread)
            {
                threadOption = ThreadOption.BackgroundThread;
            }

            if (this.isUiThread)
            {
                threadOption = ThreadOption.UIThread;
            }

            this.eventAggregator.GetEvent<MessageSentEvent>().Subscribe(this.ShowMessage,
                                                                        threadOption,
                                                                        this.isStrongReference,
                                                                        msg=>!msg.StartsWith("#"));
            this.msgCollection.Add($"ThreadOption: {threadOption}, StrongRef: {this.isStrongReference}");
        }

        private DelegateCommand unsubscribeCommand;
        public DelegateCommand UnsubscribeCommand => this.unsubscribeCommand ?? (this.unsubscribeCommand = new DelegateCommand(() =>
        {
            this.eventAggregator.GetEvent<MessageSentEvent>().Unsubscribe(this.ShowMessage);
            this.msgCollection.Add("订阅已取消");
        }));


        private void ShowMessage(string msg)
        {
            if (this.synchronizationContext != null && this.mainThreadId != Thread.CurrentThread.ManagedThreadId)
            {
                this.synchronizationContext.Post(o =>
                {
                    this.msgCollection.Add("已跨线程调用");
                    this.msgCollection.Add(msg);
                }, null);
            }
            else
            {
                this.msgCollection.Add(msg);
            }

            // 这种方式在后台线程时会报错,错误信息为:某个 ItemsControl 与它的项源不一致。
            // 原因是:在执行try语句块中的代码时,触发了一次的 CollectionChanged 事件,
            // 然后进入catch语句块,再次触发了两次 CollectionChanged 事件
            // 总共触发了三次 CollectionChanged 事件,但是集合中项增加的数量为2,造成项的数量与CollectionChanged事件发生的数量不匹配
            //try
            //{
            //    this.msgCollection.Add(msg);
            //}
            //catch
            //{
            //    if (this.synchronizationContext != null && this.mainThreadId != Thread.CurrentThread.ManagedThreadId)
            //    {
            //        this.synchronizationContext.Post(o =>
            //        {
            //            this.msgCollection.Add("已跨线程调用");
            //            this.msgCollection.Add(msg);
            //        }, null);
            //    }
            //}
        }

        private readonly int mainThreadId;
        private readonly SynchronizationContext synchronizationContext;
        private readonly IEventAggregator eventAggregator;
        public SubscriberViewModel(IEventAggregator eventAggregator)
        {
            this.MsgCollection = new ObservableCollection<string>();
            this.mainThreadId = Thread.CurrentThread.ManagedThreadId;
            this.eventAggregator = eventAggregator;
            this.synchronizationContext = SynchronizationContext.Current;
        }
    }
}
  • 在项目层级下,新建MessageSentEventPublisherModuleSubscriberModule三各类,源码分别如下:
using Prism.Events;

namespace EventAggregatorApp
{
    public class MessageSentEvent : PubSubEvent<string>
    {
    }
}
using EventAggregatorApp.Views;
using Prism.Ioc;
using Prism.Modularity;
using Prism.Regions;

namespace EventAggregatorApp
{
    public class PublisherModule : IModule
    {
        public void OnInitialized(IContainerProvider containerProvider)
        {
            // 获取区域管理器
            var regionManager = containerProvider.Resolve<IRegionManager>();

            // 将指定名称的区域与特定类型的视图相关联
            regionManager.RegisterViewWithRegion("LeftRegion", typeof(PublisherView));
        }

        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
        }
    }
}
using EventAggregatorApp.Views;
using Prism.Ioc;
using Prism.Modularity;
using Prism.Regions;

namespace EventAggregatorApp
{
    public class SubscriberModule : IModule
    {
        public void OnInitialized(IContainerProvider containerProvider)
        {
            var regionManager = containerProvider.Resolve<IRegionManager>();
            regionManager.RegisterViewWithRegion("RightRegion", typeof(SubscriberView));
        }

        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
        }
    }
}
  • 编译,运行程序,效果如下:

可以在未订阅的情况下设置订阅选项

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

源码下载

  • 6
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhy29563

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值