galasoft - ¿Cómo navegar por las ventanas con MVVM Light para WPF?
nuget mvvmlight (3)
Acabo de comenzar un nuevo proyecto en el que la capa de presentación será realizada por WPF y MVVM Light por GalaSoft.
Necesito muchas vistas y no me queda claro cómo administrar la navegación a través de las ventanas.
En primer lugar, las plantillas que se ofrecen en MVVM Light para crear una nueva "Vista de MVVM de WPF" crean una nueva Window
que no es posible utilizar para la navegación por cuadro (es decir, al colocar un cuadro en mainView
y cambiar la ruta de origen para navegar ).
¿Simplemente tengo que cambiar de Window
a Page
para todas las vistas que creo usando plantillas?
¿O hay una forma diferente de realizar la navegación en WPF con el kit de herramientas MVVM Light?
Eventualmente lo hice de esta manera.
Siguiendo la idea de o_q, creé NavigationWindow como MainWindow y cambié todas las vistas a la página.
Luego, creé una interfaz y una clase que utilizaba la navegación:
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;
}
}
Luego, en viewModelLocator, creé todas las cadenas constantes nedded para almacenar las rutas a mis vistas:
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
En App.cs, en el controlador de eventos Application_Startup, con la ayuda de Unity IoC registré un singleton de 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);
Ahora, en mi ViewModelLocator, puedo registrar un mensaje "Galasoft" para detectar todos los eventos y navegar a una página; en el constructor tengo:
public ViewModelLocator()
{
CreateMain();
CreateFrontend();
CreateBackend();
CreateStartUp();
CreateOperative();
CreateLogin();
CreateConfiguration();
CreateOutOfOrder();
// Set Sturtup 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;
}
});
}
De esta manera mantengo a todos los ViewModels "ignorantes" ... ellos no saben nada acerca de la navegación, además, no tengo ningún código detrás.
Si necesito navegar usando un botón desde una vista, puedo resolver NavigationService desde el viewModel conectado y navegar a la página que necesito.
Y, lo más importante, ¡funciona!
Normalmente utilizo un ContentControl
para mostrar contenido dinámico. Su propiedad Content
está generalmente vinculada a una propiedad CurrentViewModel
en el ViewModel
principal, y los DataTemplates
se usan para decirle a WPF cómo dibujar los ViewModels
secundarios.
Para cambiar las vistas, simplemente cambie la propiedad CurrentViewModel
en el ViewModel
principal
Puedes encontrar un ejemplo en este artículo mío.
Para una aplicación navegable, querrás que tu vista de inicio sea un NavigationWindow
lugar de una 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" />
Código detrás:
using System.Windows.Navigation;
public partial class MainWindow : NavigationWindow
{
public MainWindow()
{
InitializeComponent();
}
}
Las plantillas de vista de luz de MVVM usan Window
, pero como ha adivinado, simplemente puede cambiarla. Si desea poder navegar hacia y desde esta vista, conviértalo en una Page
. Esta es la forma de navegar:
<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>
Código detrás:
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());
}
}