Foreword:
Usually you wouldn't want to have your PlotViewModel and pass it to a window, as it makes a few things more complicated.
There are to basic approaches View-First and ViewModel First. In View-First you create the View (Page, Window etc) and inject the ViewModel into it (usually via constructor). Though this makes it a bit difficult to and pass a parameter object to it.
Which is where the NavigationService comes. You resolve the View via IoC container, then pass a parameter to the ViewModel, i.e. if it's a UserViewModel
you'd pass the userId
to it and the ViewModel will load the user.
The solution: Navigation Service
You can either use an existing one (Prism, or other MVVM Frameworks which come with their own navigation services).
If you want a own simple one, you could create an INavigationService
interface and inject it into your ViewModels.
public interface INavigationService
{
// T is whatever your base ViewModel class is called
void NavigateTo<T>() where T ViewModel;
void NavigateToNewWindow<T>();
void NavigateToNewWindow<T>(object parameter);
void NavigateTo<T>(object parameter);
}
and implement it like (I am assuming you use a IoC container, since IoC is a key to MVVM to key the objects decoupled. Example with Unity IoC Container)
public class NavigationService : INavigationService
{
private IUnityContainer container;
public NavigationService(IUnityContainer container)
{
this.container = container;
}
public void NavigateToWindow<T>(object parameter) where T : IView
{
// configure your IoC container to resolve a View for a given ViewModel
// i.e. container.Register<IPlotView, PlotWindow>(); in your
// composition root
IView view = container.Resolve<T>();
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<PlotWindow>(userId); would violate
// MVVM pattern, where navigationService.NavigateToWindow<IPlotWindow>(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
{
}
and finally you implement your PlotViewModel
class and use the passed parameter to load the object
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");
}
}
}
}
Of course could modify the NavigationService
to also set the DataContext
inside it. Or use strings to resolve the View/Window (such as Prism for Windows Store Apps does).
And in the final code you open the window by calling navigationService.NavigateToWindow<IPlotView>(platId);
in your code (i.e. in an ICommand
which is bound to a buttons Command
Property in your XAML.