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.
CodePudding user response:
@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?
CodePudding user response:
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.