使用Prism Event Aggregator实现WPF代码后台到ViewModel通信失败如何解决
核心问题原因
你判断的完全正确,问题就出在EventAggregator的实例化逻辑上。Prism注入给ViewModel的IEventAggregator是DI容器管理的全局单例,你在代码后台直接new EventAggregator()生成的是全新的实例,两个实例的订阅池完全独立,所以你在新实例上发布事件,原来在单例上的订阅自然收不到通知。
修复EventAggregator调用的方案
你只需要确保代码后台获取的EventAggregator和ViewModel注入的是同一个实例即可,推荐两种获取方式:
- 方式1:直接通过Prism的容器定位器获取
不需要修改任何公共属性,在后台代码中直接调用即可:// 引用Prism.Ioc命名空间 private readonly IEventAggregator _eventAggregator = ContainerLocator.Container.Resolve<IEventAggregator>(); - 方式2:通过当前View的DataContext传递
如果当前View的DataContext就是目标ViewModel,可以直接复用ViewModel里的实例,不需要额外从容器取:private void YourDataGrid_EndEditing(object sender, EventArgs e) { if (DataContext is YourViewModel vm) { vm.EventAggregator.GetEvent<AfterLineAmountPaidEvent>().Publish(new AfterLineAmountPaidEventArgs { InvoiceLinesSelectedAmount = InvoiceLinesDataGrid.ItemsSource }); } }
更符合MVVM规范的替代通信方案
EventAggregator适合跨多个不相关ViewModel的通信,你这个场景是同页面的View和ViewModel通信,其实有更轻量、更易维护的方案:
方案1:封装附加行为(无后台代码)
你可以把自定义DataGrid的专属结束编辑事件封装成附加行为,直接绑定到ViewModel的ICommand,完全符合MVVM规范,不需要写任何业务相关的后台代码:
public static class DataGridAttach { public static readonly DependencyProperty EndCellEditCommandProperty = DependencyProperty.RegisterAttached( "EndCellEditCommand", typeof(ICommand), typeof(DataGridAttach), new PropertyMetadata(default(ICommand), OnEndCellEditCommandChanged)); private static void OnEndCellEditCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not CustomDataGrid dataGrid) return; if (e.OldValue != null) { // 卸载旧事件 dataGrid.AfterCellEdit -= OnDataGridAfterCellEdit; } if (e.NewValue != null) { // 挂载控件库的专属结束编辑事件 dataGrid.AfterCellEdit += OnDataGridAfterCellEdit; } } private static void OnDataGridAfterCellEdit(object sender, AfterCellEditEventArgs e) { var dataGrid = (CustomDataGrid)sender; var command = GetEndCellEditCommand(dataGrid); if (command.CanExecute(dataGrid.ItemsSource)) { command.Execute(dataGrid.ItemsSource); } } public static void SetEndCellEditCommand(DependencyObject element, ICommand value) { element.SetValue(EndCellEditCommandProperty, value); } public static ICommand GetEndCellEditCommand(DependencyObject element) { return (ICommand)element.GetValue(EndCellEditCommandProperty); } }
然后在XAML中直接绑定即可:
<CustomDataGrid ItemsSource="{Binding InvoiceLines}" local:DataGridAttach.EndCellEditCommand="{Binding LineAmountPaidChangedCommand}" />
方案2:直接后台转发调用(最简实现)
MVVM并不禁止后台代码,只要后台代码不包含业务逻辑,仅做View层的事件转发是完全合理的,代码更简单:
private void CustomDataGrid_AfterCellEdit(object sender, AfterCellEditEventArgs e) { if (DataContext is YourViewModel vm) { vm.OnLineAmountPaidChanged(new AfterLineAmountPaidEventArgs { InvoiceLinesSelectedAmount = InvoiceLinesDataGrid.ItemsSource }); } }
注意事项
- 使用EventAggregator订阅事件时,如果你的订阅方法需要操作UI,建议加上
ThreadOption.UIThread参数避免跨线程异常:Subscribe(OnLineAmountPaidChanged, ThreadOption.UIThread) - 如果ViewModel会被多次创建销毁,记得保存订阅返回的
SubscriptionToken,在ViewModel释放时调用Unsubscribe方法,避免内存泄漏
内容的提问来源于stack exchange,提问作者Clay Hess
相关产品推荐
相关产品推荐

