Reputation: 5302
Imagine I have a View that shows a generic list of customers. In this case I would implement a CustomersViewModel
with a RelayCommand
bound to a XAML Button, to download it and to populate an ObservableCollection
of Customer
, bound to a ListView
.
If I want to define a CustomerDetailView
, a view to show some other information about a Customer, I would create a CustomerDetailViewModel
, a CustomerView
, and repeat the same logic. But the difference is that the ViewModel has to take the selected Customer as a parameter, while the CustomersViewModel
could be shown every time without external parameters.
In a WinForm solution I would put a parameter to construct the form the object I need.
My question is: what's the correct way to implement such navigation in order to respect MVVM pattern?
My navigation logic:
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
var nav = new NavigationService();
nav.Configure("CustomersView", typeof(CustomersView));
nav.Configure("CustomerDetailView", typeof(CustomerDetailView));
SimpleIoc.Default.Register<INavigationService>(() => nav);
SimpleIoc.Default.Register<CustomersViewModel>();
SimpleIoc.Default.Register<CustomerDetailViewModel>();
}
private RelayCommand _navigateToCustomerDetailCommand;
public RelayCommand NavigateToCustomerDetailCommand
{
get
{
return _navigateToCustomerDetailCommand
?? (_navigateToCustomerDetailCommand = new RelayCommand(
() =>
{
_navigationService.NavigateTo("CustomerDetail");
}
{
}
The options I thought:
Pass a parameter in some way to "NavigateTo" function, and define the relative ViewModel with such parameter in the constructor. I didn't find a way to do this, although it seems reasonable to me.
Navigate as above, then send a Message to the ViewModel. This idea is working, but I'm not convinced about it. In my opinion it seems a way to over complicate a simple thing.
return _navigateToCustomerDetailCommand
?? (_navigateToCustomerDetailCommand = new RelayCommand(
() =>
{
_navigationService.NavigateTo("CustomerDetail");
Messenger.Default.Send(new CustomMessageCustomerSelected(SelectedCustomer));
}
And in the ViewModel listen for that message:
public CustomerDetailViewModel(NavigationService _navigationService)
{
// ...
Messenger.Default.Register<CustomMessageCustomerSelected>
(
this,
(action) =>
{
SelectedCustomer = action.Value;
}
);
// ...
}
Upvotes: 2
Views: 331
Reputation: 6112
Both options are appropriate ways to do it. Which one you should use depends on the UI and workflow of your application. Specifically, how do you access the details view? If you, say, click a button to open a new "screen" (launch an entirely new view that replaces whatever the user was looking at), then you should use option 1. If you have something like a list of customers side-by-side with the details view and the view should update when a new customer is clicked, then use option 2.
To implement the first option, you want to use the SimpleIoc inversion of control container and ViewModelLocator class. You register your ViewModels with the locator, which is then used to handle requests for new VMs and call their constructors. This means you can easily send a message containing the customer as a parameter, and then give it to your VM's constructor. Here is a decent introduction to ViewModelLocator, albeit a few years old, and here is a quick and dirty intro to SimpleIoc.
As an aside, if you are going to have multiple concurrent users (particularly with option 2), I suggest sending the customer's ID, rather than an object. This means you have to go fetch it from storage, which is a small performance hit, but also means your users always get the latest data when they open the view, which in turn reduces editing conflicts.
Upvotes: 1