使用 Prism 事件聚合器从代码隐藏到视图模型进行通信

Posted

技术标签:

【中文标题】使用 Prism 事件聚合器从代码隐藏到视图模型进行通信【英文标题】:Use Prism Event Aggregator to Communicate from Code Behind to View Model 【发布时间】:2021-12-19 15:16:03 【问题描述】:

我正在使用 MVVM 模式开发 WPF 应用程序。此外,我一直在利用 Prism 事件聚合器功能在视图模型之间进行通信。

我们正在使用一个控件库,并且我们正在使用的其中一个控件(更改/自定义的数据网格)具有库作者创建的事件。例如,当一个单元格结束编辑时......类似于丢失焦点。我面临的问题是库控件利用背后的代码而不是事件方法的视图模型。

我想我会简单地利用事件聚合器让 VM 从背后的代码中了解事件。它不工作。我的 vm 在构造函数中使用了一个简单的订阅...

_eventAggregator.GetEvent<AfterLineAmountPaidEvent>().Subscribe(OnLineAmountPaidChanged);

OnLineAmountPaidChanged 方法永远不会被命中。

在代码隐藏中,我正在发布事件...

_eventAggregator.GetEvent<AfterLineAmountPaidEvent>().Publish(
                    new AfterLineAmountPaidEventArgs
                    
                        InvoiceLinesSelectedAmount = InvoiceLinesDataGrid.ItemsSource
                    );

我想知道它是否与 Prism 库和事件聚合器的实例化有关。在 VM 中,我通过构造函数创建它...

IEventAggregator eventAggregator

我正在使用基本 VM 扩展 VM...

: base(eventAggregator, messageDialogService)

然后我将实例化分配给我使用的私有,如前面的代码所示...

private readonly IEventAggregator _eventAggregator;

在代码隐藏中,我按如下方式实例化事件聚合器...

private readonly IEventAggregator _eventAggregator = new EventAggregator();

当我使用断点单步执行代码时,我注意到一旦代码从 2(二)到 0(零)命中代码隐藏,订阅就会发生变化。这就是为什么我认为它正在以我使用库的方式在后面的代码中为应用程序重新实例化。

是否有不同/更好的方式来完成这种沟通?我是否错误地实例化了事件聚合器?

任何建议都是有帮助的。

【问题讨论】:

【参考方案1】:

你的猜测是正确的。问题是您有两个 EventAggregator 对象。您的代码不应该实例化 EventAggregator。它应该是从 Prism 给你的。您的代码隐藏需要获得与您的视图模型相同的 EventAggregator 实例。

好消息是您可以将相同的EventAggregator 注入到与您的视图模型一起使用的视图中,就像视图模型获取它一样。通过构造函数注入。然后从那里将它传递给任何其他代码隐藏者。

这是一个例子。我有一个名为 ExploreModule 的 Prism 模块。在模块派生类中,我的 RegisterTypes 函数如下所示:

public void RegisterTypes(IContainerRegistry reg)

    reg.RegisterForNavigation<ExploreView>(ModuleKey.Explore);

在我的应用程序中,与我的ExploreView 一起使用的视图模型称为ExploreVm。您看不到它在此处列出,因为我使用 Prism 的“视图模型定位器”方法。但基本上每当 Prism 创建我的 ExploreView 时,它都会创建一个 ExploreVm 来配合它。

这意味着我可以将任何我想要的注册服务添加到ExploreVmExploreView 的构造函数中。包括IEventAggregator

所以我编辑我的ExploreVm 以获取IEventAggregator。这是我使用的那个。它添加了IEventAggregator 以及我个人创建和注册的另一项服务。由于 Prism 为我创建了这个视图模型,所以它只为我提供了两种服务。

public ExploreVm(ICaptureService  capSvc, IEventAggregator agg)

    // ...

如果我愿意,我也可以用同样的方式编辑我的ExploreView

public ExploreView(IEventAggregator aggregator)

    Aggregator = aggregator;
    InitializeComponent();

你应该有一个相似的视图/视图模型对,你可以在其中做同样的事情。

现在,如果我有一些需要访问IEventAggregator 的子视图/控件(不是由 Prism 创建的),那么我将在属性中公开IEventAggregator 或使用一些其他方式来传递它。但是这个 Prism 创建的视图/视图模型是入口点。

无论如何,关键是您不要创建 EventAggregator。棱镜可以。

【讨论】:

你有例子吗?我对 WPF 相当陌生,并从 MVVM 开始,因此代码隐藏对我来说有些陌生。我是否像在 VM 中一样在代码隐藏中创建一个构造函数? 查看在其构造函数中获取EventAggregator 的视图模型。我假设您在某个地方注册了该视图模型。可能在棱镜模块中,对吧?您必须拥有,才能让 Prism 为您创建它。好吧,当您注册它时,您还注册了一个与之配套的视图,对吧?所以去那个视图类。找到它的构造函数并简单地添加一个EventAggregator 参数给它。由于 Prism 为您创建视图模型和视图,因此 Prism 将看到您的视图需要 EventAggregator 并为您提供它。好消息是它会是正确的。 基本上,Prism 为您创建的任何视图和视图模型都可以简单地将任何已注册的服务添加到其构造函数中,然后将其交给它。 为了别人,我想发布一些不起作用的东西来节省别人的时间。简单地添加到代码隐藏 (IEventAggregator eventAggregator) 并像在 VM 中那样分配它是行不通的。 那么,无论您添加构造函数参数的哪个视图都不是注册视图。仅将参数添加到构造函数仅适用于已注册的视图和视图模型。在“食物链”的某处,Prism确实创造了一种观点。这就是你需要添加参数的地方【参考方案2】:

@乔...

我想发表评论,以便我可以显示更多代码,也许您可​​能会指出我哪里出错了。

我的 VM 是使用 VM 工厂类创建的。这是一个sn-p...

public IReceiptDetailViewModel CreateReceiptDetailViewModel(
            int? customerId,
            Action<Guid> onTabClosed,
            Action onReloadCustomerInvoicesRequested)
        
            IReceiptDetailViewModel viewModel = new ReceiptDetailViewModel(
                customerId,
                _mapper,
                _customerDataProvider,
                _invoiceDataProvider,
                _receiptDataProvider,
                this,
                _eventAggregator,
                _messageDialogService,
                onTabClosed,
                onReloadCustomerInvoicesRequested,
                _windowService);

            return viewModel;
        

使用传递给我的 ConfigureServices 方法的 IServiceCollection intyerface 从我的 App.xaml.cs 引用 VM 工厂代码。

services.AddTransient<IViewModelFactory, ViewModelFactory>();

我的App构造函数如下...

   ConfigureServices(serviceCollection);
   _serviceProvider = serviceCollection.BuildServiceProvider();

我还在其中添加了一个事件聚合器的单例...

services.AddSingleton<IEventAggregator, EventAggregator>();

那么我的接线是否不正确?我应该采取不同的做法吗?

【讨论】:

好的,所以您正在尝试将 Prism 与 Microsoft 的 DependencyInjection 一起使用?我不确定它的效果如何。这个问题的公认答案包含很好的信息***.com/questions/43664455/…我不知道事情对你来说有多灵活。我猜你可以完成这项工作,但我从未亲自做过。我将 Prism 与 Unity Container 一起使用。因此,我的应用程序使用 Prism 以 IContainerRegistry 形式提供给我的容器注册了一堆服务,就像我的模块一样 Prism 的想法是您选择一个 IOC 容器(受支持的容器)并在您的应用程序(应从 Prism.Unity.PrismApplication 派生)中覆盖 RegisterTypes 函数并注册您自己的服务/意见。通常,您将注册至少一种视图类型以及视图模型。 IOC 容器已经预先注册了服务(例如IEventAggregator。如果您有模块,您可以在那里注册它们的模块特定类型。如果您愿意,您甚至可以将您的 VM 工厂注册为服务。这样 Prism 创建的视图/视图模型会被 it 注入 我发现“获得”Prism 的最佳方式是 PluralSight 课程。我不是为他们做广告,我相信还有其他来源,甚至是免费的 YouTube 视频。我只是从中得到了最多。我想他们还是让你免费看第一个。这样的事情会在学习曲线上为您节省大量时间。

以上是关于使用 Prism 事件聚合器从代码隐藏到视图模型进行通信的主要内容,如果未能解决你的问题,请参考以下文章

从PRISM开始学WPFMVVM事件聚合器EventAggregator?

从PRISM开始学WPFMVVM事件聚合器EventAggregator?

Prism:如何在区域中注入视图模型实例?

导航后未放置ViewModel实例

从资源加载任务栏图标并使用 Prism 注入视图模型

如何在Prism框架中的模块之间正确发送事件消息?