0

I am working on a WPF app using the MVVM pattern. Additionally, I have been utilizing the Prism Event Aggregator functionality to communicate between view models.

We are using a library of controls and one of the controls we are using (an altered/customized datagrid) has events that the library author has created. For example, when a cell has ended editing...similar to a loss focus. The issue I am facing is that the library control utilizes the code behind instead of the view model for the event method.

I figured I would simply utilize the event aggregator to let the VM know about the event from the code behind. It is not working. My vm uses a simple subscribe in the constructor...

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

The OnLineAmountPaidChanged method never gets hit.

In the code-behind, I am publishing the event...

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

I am wondering if it has to do with the instantiation of the Prism library and the Event Aggregator. In the VM, I am creating it via the constructor...

IEventAggregator eventAggregator

I am extending the VM with a base VM...

: base(eventAggregator, messageDialogService)

I then assign the instantiation to a private that I use as shown in previous code...

private readonly IEventAggregator _eventAggregator;

In the code-behind, I instantiate the event aggregator as follows...

private readonly IEventAggregator _eventAggregator = new EventAggregator();

When I step through the code using breakpoints, I notice that the subscriptions change once the code hits the code-behind from 2 (two) to 0 (zero). This is why I think that it is getting reinstantiated for the app in the code behind with the way I am utilizing the library.

Is there a different/better way to accomplish this communication? Am I instantiating the event aggregator incorrectly?

Any advice is helpful.

Clay Hess
  • 228
  • 6
  • 24

2 Answers2

0

Your guess is correct. The problem is you have two EventAggregator objects. Your code is not supposed to instantiate the EventAggregator. It is supposed given to be given to you from Prism. Your code-behind needs to get the same instance of EventAggregator that your view model gets.

The nice thing is that you can inject that same EventAggregator to the view that goes with your viewmodel the same way that the viewmodel gets it. Via constructor injection. Then pass it along to any other code-bhiend from there.

Here's an example. I have a Prism module called my ExploreModule. Inside the module-derived class my RegisterTypes function looks like this:

public void RegisterTypes(IContainerRegistry reg)
{
    reg.RegisterForNavigation<ExploreView>(ModuleKey.Explore);
}

In my app, the view-model that goes with my ExploreView is called ExploreVm. You don't see it being listed here because I use Prism's "view-model-locator" approach. But basically whenever Prism creates my ExploreView, it creates an ExploreVm to go along with it.

This means that I can add any registered-service I want to the constructor of either ExploreVm or ExploreView. Including IEventAggregator

So I edit my ExploreVm to take IEventAggregator. Here's the one I use. It adds IEventAggregator as well another service that I personally created and registered. Since Prism creates this view model for me, it just handes me both services.

public ExploreVm(ICaptureService  capSvc, IEventAggregator agg)
{
    // ...
}

And I can also edit my ExploreView the same way if I want

public ExploreView(IEventAggregator aggregator)
{
    Aggregator = aggregator;
    InitializeComponent();
}

You should have a similar view/view-model pair where you can do the same.

Now if I had some child view/control (not created by Prism) that I needed to get access to IEventAggregator, then I would expose the IEventAggregator in a property or use some other way to pass it down. But this Prism-created view/view-model is the entry point.

Regardless, the key point is you do not create the EventAggregator. Prism does.

Joe
  • 5,394
  • 3
  • 23
  • 54
  • Do you have an example please? I am fairly new to WPF and began with MVVM, thus code-behind is somewhat unfamiliar to me. Do I simply create a constructor in the code-behind like I do in the VM? – Clay Hess Nov 05 '21 at 15:15
  • 1
    Look at the view-model that gets the `EventAggregator` in its constructor. I assume that somewhere, you registered that view model. Probably in a Prism Module, right? You must have, in order for Prism to create it for you. Well when you registered it you also registered a view to go with it, right? So go to that view class. Find its constructor and simply add an `EventAggregator` parameter to it. Since Prism creates both the view model and the view for you, Prism will see that your view needs that `EventAggregator` and supply it for you. The good news is that it will be the right one. – Joe Nov 05 '21 at 15:19
  • Basically any view and view model that Prism creates for you can simply add any registered services to its constructor and they will be handed it to it. – Joe Nov 05 '21 at 15:20
  • For the sake of others, I want to post what will not work to save others some time. Simply adding to the code-behind (IEventAggregator eventAggregator) and assigning it like is done in a VM did not work. – Clay Hess Nov 05 '21 at 15:21
  • Then whatever view you added that constructor parameter to is not a registered view. Merely adding the parameter to the constructor only works for registered views and view models. Somewhere, up the "food chain" there is a view that Prism *does* create. That's where you need to add the parameter – Joe Nov 05 '21 at 15:23
  • Tell you what I'll edit my reply with an example. – Joe Nov 05 '21 at 15:25
  • Thanks for the code. It is enlightening, but, unfortunately, we are not utilizing the full Prism library/functionality as noted in my other response. We are picking and choosing. I am thinking I need to utilize DI to get the event aggregator that my services creates in App.xaml.cs. Correct or no? – Clay Hess Nov 05 '21 at 15:51
  • You can make minimal use of Prism but if you want to get at the one-and-only `IEventAggregator` you need to get it injected by Prism's IOC container. As long as you derive your app from Prism's Application class that alone ought to allow you to get access to it. Thereafter you can pass that one -and-only `IEventAggregator `around to your own views/view-models as needed. You can even expose it as a property on your ViewModel so that your view can get access to it – Joe Nov 05 '21 at 15:58
0

@Joe...

I wanted to post a comment so I can show some more code where perhaps you might point out where I went wrong.

My VMs are created with a VM factory class. Here is a snippet...

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;
        }

The VM factory code gets referenced from my App.xaml.cs using the IServiceCollection intyerface passed to my ConfigureServices method.

services.AddTransient<IViewModelFactory, ViewModelFactory>();

My App constructor is as follows...

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

I also add a singleton of the event aggregator in there...

services.AddSingleton<IEventAggregator, EventAggregator>();

So am I wiring things sup incorrectly? Should I do it differently?

Clay Hess
  • 228
  • 6
  • 24
  • OK so you are trying to use Prism with Microsoft's DependencyInjection? I'm not sure how well that works. The accepted answer to this question contains good info https://stackoverflow.com/questions/43664455/which-ioc-container-is-better-with-prism-forms I don't know how flexible things are for you. I'm guessing you can make this work but I've never personally done this. I use Prism with the Unity Container. So my app registers a bunch of services as do my modules with that container that Prism supplies to me in the form of `IContainerRegistry` – Joe Nov 05 '21 at 15:46
  • The idea with Prism is that you pick an IOC Container (a supported one) and in your app (which should derive from `Prism.Unity.PrismApplication`), you override the `RegisterTypes` function and register your own services/views. Generally you will register at least one view type along with a view-model. The IOC container already comes pre-registered with services (like `IEventAggregator`. If you have modules you register their module-specific types there. You could even register your VM factory as a service if you wanted. So that Prism-created views/view-models would get *it* injected – Joe Nov 05 '21 at 15:52
  • I found the best way for me to "get" Prism was from the PluralSight course. I'm not advertising for them, I'm sure there are other sources, even free YouTube videos. I just got the most from that. I think they let you watch the first one free anyway. That sort of thing will save you a lot of time in the learning curve. – Joe Nov 05 '21 at 15:54