How to navigate through windows with MVVM Light for WPF?
I usually use a ContentControl
to display dynamic content. It's Content
property is usually bound to a CurrentViewModel
property in the parent ViewModel
, and DataTemplates
are used to tell WPF how to draw the child ViewModels
.
To change views, simply change the CurrentViewModel
property in the parent ViewModel
You can find an example at this article of mine
Eventually I did it this way.
Following the idea of o_q, I created NavigationWindow as MainWindow and changed all the the views to page.
Then, I created an inteface and a class which using Navigation:
public interface INavigationService { event NavigatingCancelEventHandler Navigating; void NavigateTo(Uri pageUri); void GoBack(); } public class NavigationService : INavigationService { private NavigationWindow _mainFrame; #region Implementation of INavigationService public event NavigatingCancelEventHandler Navigating; public void NavigateTo(Uri pageUri) { if (EnsureMainFrame()) { _mainFrame.Navigate(pageUri); } } public void GoBack() { if (EnsureMainFrame() && _mainFrame.CanGoBack) { _mainFrame.GoBack(); } } #endregion private bool EnsureMainFrame() { if (_mainFrame != null) { return true; } _mainFrame = System.Windows.Application.Current.MainWindow as NavigationWindow; if (_mainFrame != null) { // Could be null if the app runs inside a design tool _mainFrame.Navigating += (s, e) => { if (Navigating != null) { Navigating(s, e); } }; return true; } return false; } }
Then, in viewModelLocator I created all the const string nedded to store the paths to my views:
public class ViewModelLocator { #region Views Paths public const string FrontendViewPath = "../Views/FrontendView.xaml"; public const string BackendViewPath = "../Views/BackendView.xaml"; public const string StartUpViewPath = "../Views/StartUpView.xaml"; public const string LoginViewPath = "../Views/LoginView.xaml"; public const string OutOfOrderViewPath = "../Views/OutOfOrderView.xaml"; public const string OperativeViewPath = "../Views/SubViews/OperativeView.xaml"; public const string ConfigurationViewPath = "../Views/SubViews/ConfigurationView.xaml"; #endregion }
In App.cs, in the Application_Startup event handler, with the help of Unity IoC I registered a singleton of NavigationService:
public partial class App : System.Windows.Application { private static IUnityContainer _ambientContainer; public static IServiceLocator AmbientLocator { get; private set; } ... private void Application_Startup(object sender, System.Windows.StartupEventArgs e) { _ambientContainer = new UnityContainer(); _ambientContainer.RegisterType<INavigationService, NavigationService>(new ContainerControlledLifetimeManager()); AmbientLocator = new UnityServiceLocator(_ambientContainer); ServiceLocator.SetLocatorProvider(() => AmbientLocator);
Now, in my ViewModelLocator, I can register a "Galasoft" message to catch all the events and navigate to a page; in the constructor I have:
public ViewModelLocator() { CreateMain(); CreateFrontend(); CreateBackend(); CreateStartUp(); CreateOperative(); CreateLogin(); CreateConfiguration(); CreateOutOfOrder(); // Set Startup Page... ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(StartUpViewPath, UriKind.Relative)); Messenger.Default.Register<MoveToViewMessage>(this, message => { switch (message.StateInfo.StateType) { case StateType.StartUpState: ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(StartUpViewPath,UriKind.Relative)); break; case StateType.LoginState: ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(LoginViewPath, UriKind.Relative)); break; case StateType.OperativeState: ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(OperativeViewPath, UriKind.Relative)); break; case StateType.ConfigurationState: ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(ConfigurationViewPath, UriKind.Relative)); break; case StateType.ClosedState: case StateType.OutOfOrderState: ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(OutOfOrderViewPath, UriKind.Relative)); break; default: ServiceLocator.Current.GetInstance<INavigationService>().NavigateTo(new Uri(StartUpViewPath, UriKind.Relative)); break; } }); }
In this way I keep all the viewModels "ignorant"... they don't know anything about navigation, plus I don't have code behind.
If I need to navigate by using a button from a view I can resolve NavigationService from the connected viewModel and navigate to the Page I need.
And, most important thing, it works!
For a navigable application, you'll want your start up view to be a NavigationWindow
instead of a Window
<NavigationWindow xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="MainWindow" Title="My Application Title" Height="300" Width="400" />
Code behind:
using System.Windows.Navigation;public partial class MainWindow : NavigationWindow{ public MainWindow() { InitializeComponent(); }}
The MVVM Light view templates use Window
, but as you have guessed, you can just change it. If you want to be able to navigate to and from this view, make it a Page
.This is how you navigate:
<Page x:Class="Page1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Page1"> <Grid> <!-- this button will navigate to another page --> <Button Content="Go to Page 2" Click="Button_Click" /> </Grid></Page>
Code Behind:
using System.Windows;using System.Windows.Controls;public partial class Page1 : Page{ public Page1() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { // the Page class has a property "NavigationService" which allows you to navigate. // you can supply the "Navigate" method with a Uri or an object instance of the page base.NavigationService.Navigate(new Page2()); }}