I am building a WPF browser application with MVVM pattern.
I have a first page (ConsultInvoice) with a dataGrid. When I double click on one of the row I want to navigate
Basically you should avoid passing such parameters into the ViewModels constructor, as wiring it with Inversion of Control/Dependency Injection becomes a pain. While you can use Abstract Factory pattern to resolve objects with runtime parameters, it's imho not suitable for ViewModels.
Instead I always suggest using a form of navigation pattern, similar to how Microsoft's Patterns & Practices team has done with Prism. There you have an INavigationAware
interface which your ViewModels can implement. It has 2 methods, NavigateTo
and NavigateFrom
.
And there is a navigation service. The navigation service will switch the views and before switching calling NavigateFrom
in the current ViewModel (if it implements it. One can use it to check if data is saved and if necessary cancel the navigation. After the new View has been loaded and the ViewModel assigned to it, call NavigateTo
in the newly navigated ViewModel.
Here you'd pass the parameters required for the ViewModel, in your case invoiceId
. Try avoid passing whole models or complex objects. Use the invoiceid
to fetch the invoice data and to populate your editing ViewModel.
A basinc implementation from my former answer (can be found here):
public interface INavigationService
{
// T is whatever your base ViewModel class is called
void NavigateTo() where T ViewModel;
void NavigateToNewWindow();
void NavigateToNewWindow(object parameter);
void NavigateTo(object parameter);
}
public class NavigationService : INavigationService
{
private IUnityContainer container;
public NavigationService(IUnityContainer container)
{
this.container = container;
}
public void NavigateToWindow(object parameter) where T : IView
{
// configure your IoC container to resolve a View for a given ViewModel
// i.e. container.Register(); in your
// composition root
IView view = container.Resolve();
Window window = view as Window;
if(window!=null)
window.Show();
INavigationAware nav = view as INavigationAware;
if(nav!= null)
nav.NavigatedTo(parameter);
}
}
// IPlotView is an empty interface, only used to be able to resolve
// the PlotWindow w/o needing to reference to it's concrete implementation as
// calling navigationService.NavigateToWindow(userId); would violate
// MVVM pattern, where navigationService.NavigateToWindow(userId); doesn't. There are also other ways involving strings or naming
// convention, but this is out of scope for this answer. IView would
// just implement "object DataContext { get; set; }" property, which is already
// implemented Control objects
public class PlotWindow : Window, IView, IPlotView
{
}
public class PlotViewModel : ViewModel, INotifyPropertyChanged, INavigationAware
{
private int plotId;
public void NavigatedTo(object parameter) where T : IView
{
if(!parameter is int)
return; // Wrong parameter type passed
this.plotId = (int)parameter;
Task.Start( () => {
// load the data
PlotData = LoadPlot(plotId);
});
}
private Plot plotData;
public Plot PlotData {
get { return plotData; }
set
{
if(plotData != value)
{
plotData = value;
OnPropertyChanged("PlotData");
}
}
}
}
An example of the INavigationAware
interface used in Prism can be found on the projects github repository.
This makes it easy to pass parameter and async
load your data (where there isn't any clean way to do this via constructor, as you can't await
an async
operation inside the constructor without locking, and doing this kind of things in the constructor is very discouraged).