【Prism系列】Prism事件聚合器
命令和事件
我们知道MVVM指的就是Model ViewModel 与 View,之前为了做到业务逻辑和前台彻底分离,我们使用了Command命令,将界面的的输入或者是某些控件的事件,转化为命令。这样业务逻辑就从View搬到了ViewModel。很明显这个传递方向是View -》ViewModel。
但是现在有个一个需求是需要ViewModel通知View某个事情,比如ViewModel告诉View你需要弹出一个窗口!那这个时候,我们通常在View中订阅一个事件,需要通知时在在ViewModel中发布一个事件,这样View就能收到ViewModel的消息了。此时我们的事件聚合器就登场了。
获取事件聚合器对象
我们先看一张图:

这些是Prism框架帮我们预先在容器里注册的内容,其中第11个就是我们今天的主角儿,事件聚合器,既然是注册好了,我们就可以通过构造函数注入,拿到这个对象。
//[Unity.Dependency]
IEventAggregator eventAggregator;
public PrismEventPageViewModel(IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
}
这里我试了一下,只能通过构造函数去拿这个对象,无法通过自动注入属性获取。
事件聚合器也是一个容器,拿到了事件事件聚合器的对象之后,就可以通过它获取事件对象了,
这个对象,需要我们先准备一个事件类:
internal class MyPrismEvent:PubSubEvent
{
}
internal class MyPrismEvent2 : PubSubEvent<object>
{
}
这里,我准备了两个类,都继承自PubSubEvent,一个表示不带参数,一个表示带一个参数,类型为object。
摘取,订阅,发布的关键代码:
//订阅部分
eventAggregator.GetEvent<MyPrismEvent>().Subscribe(doMyPrismEvent, ThreadOption.BackgroundThread);
eventAggregator.GetEvent<MyPrismEvent2>().Subscribe(doMyPrismEvent2);
//发布部分
eventAggregator.GetEvent<MyPrismEvent2>().Publish("123456789");
eventAggregator.GetEvent<MyPrismEvent>().Publish();
完整代码
我们在View类中进行订阅:
/// <summary>
/// Prism事件聚合器.xaml 的交互逻辑
/// </summary>
public partial class PrismEventPage : Page
{
//[Unity.Dependency]
IEventAggregator eventAggregator;
public PrismEventPage(IEventAggregator eventAggregator)
{
InitializeComponent();
this.eventAggregator = eventAggregator;
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
eventAggregator.GetEvent<MyPrismEvent>().Subscribe(doMyPrismEvent, ThreadOption.BackgroundThread);
eventAggregator.GetEvent<MyPrismEvent2>().Subscribe(doMyPrismEvent2);
}
private void Page_Unloaded(object sender, RoutedEventArgs e)
{
//这里需要卸载,如果Subscribe多次,回调也会执行多次
eventAggregator.GetEvent<MyPrismEvent>().Unsubscribe(doMyPrismEvent);
eventAggregator.GetEvent<MyPrismEvent2>().Unsubscribe(doMyPrismEvent2);
}
void doMyPrismEvent()
{
System.Diagnostics.Debug.WriteLine($"111111Event Action ID:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
MessageBox.Show("弹出窗口");
}
void doMyPrismEvent2(object obj)
{
System.Diagnostics.Debug.WriteLine($"Event Action ID:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
MessageBox.Show($"弹出窗口:{obj}");
}
}
这里注意几个点:
1 同样使用构造注入拿到事件聚合器的对象,这里的对象和在ViewMode中拿到的是相同的对象。
2 Page加载时订阅,Page卸载时取消订阅,不然Page多次加载会导致多次订阅,从而导致回调多次执行.
3 订阅事件时,可以通过ThreadOption这个枚举指定回调是执行在UI线程还是其他线程。

因为发布时的代码会在UI线程或其他线程。如果发布代码和回调都在UI,那他们之间会有一个先后关系,及发布代码在调用了Publish之后会等待回调完成,再执行Publish之后的代码。反之一个再UI线程一个再其他线程,就不存在这样的先后关系,具体可以更具我提供的代码进行测试。
我们在ViewModel类中进行发布:
internal class PrismEventPageViewModel
{
//[Unity.Dependency]
IEventAggregator eventAggregator;
public int MyProperty { get; set; } = 999;
public string Hello { get; set; } = "PrismEventViewModel";
public DelegateCommand BtnCommand { get; set; }
public DelegateCommand BtnCommand2 { get; set; }
public PrismEventPageViewModel(IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
BtnCommand = new DelegateCommand(Excute);
BtnCommand2 = new DelegateCommand(Excute2);
}
void Excute()
{
System.Diagnostics.Debug.WriteLine($"Excute!!!!! Action ID:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
eventAggregator.GetEvent<MyPrismEvent>().Publish();
Task.Delay(3000).Wait();
System.Diagnostics.Debug.WriteLine("Publish完成");
}
void Excute2()
{
eventAggregator.GetEvent<MyPrismEvent2>().Publish("123456789");
}
}
View和ViewModel的自动匹配
最后还有一个匹配的问题需要注意。在Page里必须加上这两句,之前测试Windows是不用的,但是Page这里必须使用(测试环境是vs2022 .net6.0)
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
2022年10月12日,最近测试,发现用用户控件作为子窗口的时候,也必须加上这两句,不然会导致bingding不上,所以这两句还是挺重要的,建议任何时候都加上!
我手动匹配都没起作用:
//不知道为啥对Page不起作用~~~
ViewModelLocationProvider.Register<PrismEventPage, PrismEventPageViewModel>();
但是加上上面两句就OK了。
聚合器的妙用(LoadingRow --> LoadedRow)
在实际的项目中,我遇到一个问题,就是DataGrid有一个事件叫做LoadingRow, 但是这个事件触发的时机是在添加一行之前,所以在这个事件里面无法获取刚刚添加的这一行的所有Cell。
但是DataGrid有没有提供LoadedRow这种事件。这里我们可以通过事件聚合器解决这个问题:
首先为添加DataGrid事件:
<DataGrid Grid.Row="2" x:Name="testDateGrid">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LoadingRow">
<prism:InvokeCommandAction Command="{Binding EventLoadingRow}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
然后这么写:

LoadingRow触发一个事件,这个事件订阅的时候,要使用UI线程,这样的话,就相当于构建了一个LoadedRow事件!
public class WaitEvent : PubSubEvent
{
}
//-----------------------------------------
//行增加事件
EventLoadingRow = new DelegateCommand<DataGridRowEventArgs>((e) => {
_eventAggregator.GetEvent<WaitEvent>().Publish();
});
_eventAggregator.GetEvent<WaitEvent>().Subscribe(() => {
var rowContainer = testDateGrid.GetRow(0);
//var cell = testDateGrid.GetCell(row, 0);
if (rowContainer != null)
{
DataGridCellsPresenter presenter = Tool.GetVisualChild<DataGridCellsPresenter>(rowContainer);
if (presenter == null) return;
DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(0);
if (cell == null)
{
testDateGrid.ScrollIntoView(rowContainer, testDateGrid.Columns[0]);
cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(0);
}
if (cell != null)
{
TextBlock tb = cell.Content as TextBlock;
Console.WriteLine(tb.Text);
// dgSourceData.ScrollIntoView(row, dgSourceData.Columns[colindex]);
//cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(colindex);
cell.Focus();
cell.Background = new SolidColorBrush(Colors.Red);//OK!问题解决,选中单元格变色
cell.Foreground = new SolidColorBrush(Colors.Yellow);
cell.FontSize = 20;
}
}
}, ThreadOption.UIThread);
这段代码的目的就是为了获取行中的单元格,并且修改单元格的颜色。
作者:宋桓公
出处:http://www.cnblogs.com/douzi2/
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。

浙公网安备 33010602011771号