Check out Rachel's good answer in this post. I also like to separate my Views and ViewModels, because then I know when I screwed up MVVM ground rules.
Your ViewModel should not have any references to the View, but the View must have a reference to the ViewModel. Consider, for example, my custom SplashScreen factory (the two important lines are "var viewModel..." and "var splashScreen..."):
namespace MyCompany.Factories
{
using System.Threading;
using MyCompany.Views;
using MyCompany.ViewModels;
public static class SplashScreenFactory
{
public static SplashScreenViewModel CreateSplashScreen(
string header, string title, string initialLoadingMessage, int minimumMessageDuration)
{
var viewModel = new SplashScreenViewModel(initialLoadingMessage, minimumMessageDuration)
{
Header = header,
Title = title
};
Thread thread = new Thread(() =>
{
var splashScreen = new SplashScreenView(viewModel);
splashScreen.Topmost = true;
splashScreen.Show();
splashScreen.Closed += (x, y) => splashScreen.Dispatcher.InvokeShutdown();
System.Windows.Threading.Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
return viewModel;
}
}
}
The Factories project has references to both MyCompany.ViewModels and MyCompany.Views. The Views project only has a reference to MyCompany.ViewModels.
We first initiate the ViewModel from our caller (in this case, from SplashScreenFactory; or maybe from App.xaml.cs if you want; I prefer using my own Bootstrapper class for reasons beyond this discussion).
Then we initiate the View by passing the ViewModel reference into the View's constructor. This is called Dependency Injection. You may also need to write a Behaviour for the Window class in order to Close the window from your ViewModel. So now I can do the following from my Bootstrapper class:
/// <summary>
/// Called from this project's App.xaml.cs file, or from the Deals Main Menu
/// </summary>
public class Bootstrapper
{
private SplashScreenViewModel _splashScreenVM;
public Bootstrapper()
{
// Display SplashScreen
_splashScreenVM = SplashScreenFactory.CreateSplashScreen(
"MyCompany Deals", "Planning Grid", "Creating Repositories...", 200);
// Overwrite GlobalInfo parameters and prepare an AuditLog to catch and log errors
ApplicationFactory.AuditedDisplay(
Assembly.GetExecutingAssembly().GetName(),
() =>
{
// Show overwritten version numbers from GlobalInfo on SplashScreen
_splashScreenVM.VersionString = string.Format("v{0}.{1}.{2}",
GlobalInfo.Version_Major, GlobalInfo.Version_Minor, GlobalInfo.Version_Build);
// Initiate ViewModel with new repositories
var viewModel = new PlanningGridViewModel(new MyCompany.Repositories.PlanningGridHeadersRepository(),
new MyCompany.Repositories.PlanningGridLinesRepository(),
_splashScreenVM);
// Initiate View with ViewModel as the DataContext
var view = new PlanningGridView(viewModel);
// Subscribe to View's Activated event
view.Activated += new EventHandler(Window_Activated);
// Display View
view.ShowDialog();
});
}
/// <summary>
/// Closes SplashScreen when the Window's Activated event is raised.
/// </summary>
/// <param name="sender">The Window that has activated.</param>
/// <param name="e">Arguments for the Activated event.</param>
private void Window_Activated(object sender, EventArgs e)
{
_splashScreenVM.ClosingView = true;
}
Note that I have control over the SplashScreen's View by subscribing to the View's Activated event, and then closing the view with the "ClosingView" boolean INotifyProperty that sets the View's "Close" DependencyProperty in turn (sounds complicated, but once you get to know DependencyProperties, it becomes simple).
The point is, don't try to drive your navigation from RootFrame. Just view.Show() the RootFrame, and control the rest from RootFrameViewModel.
Hope this helps :-)