步骤:
- 继承
PubSubEvent<T>
创建自定义事件类型- 创建事件发送者
- 创建事件接收者,并订阅指定的事件
1. 事件聚合器
Prism
提供了一种机制,可以实现应用程序中松散耦合组件之间的通信
。这种机制基于事件聚合器服务
,允许发布者和订阅者通过事件进行通信,并且彼此之间仍然没有直接引用。
事件聚合器提供多播
发布/订阅功能。这意味着可以有多个发布者引发相同的事件,并且可以有多个订阅者监听相同的事件。
通过事件聚合器服务
可以使用IEventAggregator
接口获取到事件聚合器。事件聚合器负责定位或构建事件,并在系统中保存事件的集合。首次访问一个事件时,如果该事件尚未构建,则构建。
2. 事件类型
PubSubEvent
是Prism
中对积累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. 样例
创建一个名为EventAggregatorApp
的WPF应用程序
项目,并在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>();
}
}
}
- 创建
Views
与ViewModels
文件夹,并将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.xaml
与PublisherView.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.xaml
与SubscriberView.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
文件夹中,新建名称为MainWindowViewModel
、PublisherViewModel
和SubscriberViewModel
三个类,源码分别如下:
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;
}
}
}
- 在项目层级下,新建
MessageSentEvent
、PublisherModule
与SubscriberModule
三各类,源码分别如下:
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)
{
}
}
}
- 编译,运行程序,效果如下:
可以在
未订阅
的情况下设置订阅选项