Navegación de página usando MVVM en la aplicación de la tienda

Tengo un gran dolor de cabeza con este problema. Realmente no me gustan las aplicaciones de la tienda, pero estoy obligado a usarlo en este caso. Solo he trabajado con XAML durante algunas semanas.

Mi pregunta es: ¿cómo puedo llamar un RelayCommand en mi ViewModel (desde mi View of course) que cambiará la página en mi vista? Y aún mejor, cámbielo usando URI, para poder pasar un parámetro de comando al archivo.

Estoy totalmente perdido en esto. Actualmente estoy usando this.Frame.Navigate(type type) en el código de vista detrás para navegar por las páginas.

Realmente quisiera decir REALMENTE que aprecio una descripción de la A a la Z sobre qué hacer en este caso.

Supongo que podría hacer algo como construir un contenedor de cuadros en mi Vista y enviarlo a mi ViewModel y desde allí mover el cuadro actual a otro. Pero no estoy seguro de cómo funciona eso en las aplicaciones de la tienda.

Lamento mucho la falta de buenas preguntas, pero estoy a tiempo y necesito que mi View se conecte a mi ViewModel de una manera adecuada. No me gusta tener código de vista ni código de ViewModel.

Hay dos formas de hacerlo, una forma simple es pasar una acción de comando de retransmisión desde la vista al modelo de vista.

 public MainPage() { var vm = new MyViewModel(); vm.GotoPage2Command = new RelayCommand(()=>{ Frame.Navigate(typeof(Page2)) }); this.DataContext = vm; }  

Otra forma es usar un IocContainer y DependencyInjection. Este es un enfoque más estrechamente acoplado.

Necesitaremos una interfaz para la página de navegación para no tener que hacer referencia o saber nada acerca de PageX o cualquier elemento de la interfaz de usuario suponiendo que su modelo de vista está en un proyecto separado que no sabe nada sobre la interfaz de usuario.

Proyecto ViewModel:

  public interface INavigationPage { Type PageType { get; set; } } public interface INavigationService { void Navigate(INavigationPage page) { get; set; } } public class MyViewModel : ViewModelBase { public MyViewModel(INavigationService navigationService, INavigationPage page) { GotoPage2Command = new RelayCommand(() => { navigationService.Navigate(page.PageType); }) } private ICommand GotoPage2Command { get; private set; } } 

Proyecto de interfaz de usuario:

  public class NavigationService : INavigationService { //Assuming that you only navigate in the root frame Frame navigationFrame = Window.Current.Content as Frame; public void Navigate(INavigationPage page) { navigationFrame.Navigate(page.PageType); } } public abstract class NavigationPage : INavigationPage { public NavigationPage() { this.PageType = typeof(T); } } public class NavigationPage1 : NavigationPage { } public class MainPage : Page { public MainPage() { //I'll just place the container logic here, but you can place it in a bootstrapper or in app.xaml.cs if you want. var container = new UnityContainer(); container.RegisterType(); container.RegisterType(); container.RegisterType(); this.DataContext = container.Resolve(); } } 

Como dice Scott, podrías usar un NavigationService. Primero crearía una interfaz que no es necesaria en este ejemplo, pero será útil si utiliza Dependency Injection (buena solución con viewmodels y servicios) en el futuro 🙂

INavigationService:

 public interface INavigationService { void Navigate(Type sourcePage); void Navigate(Type sourcePage, object parameter); void GoBack(); } 

NavigationService.cs heredará INavigationService y necesitará los siguientes espacios de nombres

 using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; public sealed class NavigationService : INavigationService { public void Navigate(Type sourcePage) { var frame = (Frame)Window.Current.Content; frame.Navigate(sourcePage); } public void Navigate(Type sourcePage, object parameter) { var frame = (Frame)Window.Current.Content; frame.Navigate(sourcePage, parameter); } public void GoBack() { var frame = (Frame)Window.Current.Content; frame.GoBack(); } } 

Simple ViewModel para mostrar el ejemplo RelayCommand. NB I Navegar a otra página (Página2.xaml) usando DoSomething RelayCommand.

MyViewModel.cs

 public class MyViewModel : INotifyPropertyChanged { private INavigationService _navigationService; public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string propertyName) { var handler = PropertyChanged; if (handler != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } public MyViewModel(INavigationService navigationService) { _navigationService = navigationService; } private ICommand _doSomething; public ICommand DoSomething { get { return _doSomething ?? new RelayCommand(() => { _navigationService.Navigate(typeof(Page2)); }); } }} 

En un ejemplo simple, he creado el modelo de vista en MainPage.cs y he añadido NavigationService, pero puedes hacerlo en cualquier otro lugar, dependiendo de cómo sea tu configuración de MVVM.

MainPage.cs

 public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); var vm = new MyViewModel(new NavigationService()); this.DataContext = vm; } } 

MainPage.xaml (se une al comando DoSomething)

     

Espero que ayude.

Realmente no me gusta cuando ViewModel hace referencia a Views para navegar. Por lo tanto, prefiero un enfoque ViewModel primero. Utilizando ContentControls, DataTemplates para tipos de ViewModel y algún tipo de patrón de navegación en mis ViewModels.

Mi navegación se ve así:

 [ImplementPropertyChanged] public class MainNavigatableViewModel : NavigatableViewModel { public ICommand LoadProfileCommand { get; private set; } public ICommand OpenPostCommand { get; private set; } public MainNavigatableViewModel () { LoadProfileCommand = new RelayCommand(() => Navigator.Navigate(new ProfileNavigatableViewModel())); OpenPostCommand = new RelayCommand(() => Navigator.Navigate(new PostEditViewModel { Post = SelectedPost }), () => SelectedPost != null); } } 

Mi NavigatableViewModel se ve así:

 [ImplementPropertyChanged] public class NavigatableViewModel { public NavigatorViewModel Navigator { get; set; } public NavigatableViewModel PreviousViewModel { get; set; } public NavigatableViewModel NextViewModel { get; set; } } 

Y mi navegador:

 [ImplementPropertyChanged] public class NavigatorViewModel { public NavigatableViewModel CurrentViewModel { get; set; } public ICommand BackCommand { get; private set; } public ICommand ForwardCommand { get; private set; } public NavigatorViewModel() { BackCommand = new RelayCommand(() => { // Set current control to previous control CurrentViewModel = CurrentViewModel.PreviousViewModel; }, () => CurrentViewModel != null && CurrentViewModel.PreviousViewModel != null); ForwardCommand = new RelayCommand(() => { // Set current control to next control CurrentViewModel = CurrentViewModel.NextViewModel; }, () => CurrentViewModel != null && CurrentViewModel.NextViewModel != null); } public void Navigate(NavigatableViewModel newViewModel) { if (newViewModel.Navigator != null && newViewModel.Navigator != this) throw new Exception("Viewmodel can't be added to two different navigators"); newViewModel.Navigator = this; if (CurrentViewModel != null) { CurrentViewModel.NextViewModel = newViewModel; } newViewModel.PreviousViewModel = CurrentViewModel; CurrentViewModel = newViewModel; } } 

Mi MainWindows.xaml:

         

App.xaml.cs:

 public partial class App { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); new MainWindow {DataContext = new MyAppViewModel()}.Show(); } } 

MyAppViewModel:

 [ImplementPropertyChanged] public class MyAppViewModel { public NavigatorViewModel Navigator { get; set; } public MyAppViewModel() { Navigator = new NavigatorViewModel(); Navigator.Navigate(new MainNavigatableViewModel()); } } 

App.xaml:

        

La desventaja es que tiene más código de ViewModel que maneja el estado de lo que está mirando. Pero obviamente esa también es una gran ventaja en términos de Testabilidad. Y, por supuesto, sus ViewModels no necesitan depender de sus Vistas.

Además, utilizo Fody / PropertyChanged, de eso se trata [ImplementPropertyChanged]. Me impide escribir código OnPropertyChanged.

Aquí hay otra forma de implementar NavigationService, sin utilizar una clase abstracta y sin hacer referencia a los tipos de vista en su modelo de vista.

Suponiendo que el modelo de vista de la página de destino es algo como esto:

 public interface IDestinationViewModel { /* Interface of destination vm here */ } class MyDestinationViewModel : IDestinationViewModel { /* Implementation of vm here */ } 

Entonces su NavigationService puede simplemente implementar la siguiente interfaz:

 public interface IPageNavigationService { void NavigateToDestinationPage(IDestinationViewModel dataContext); } 

En su ventana principal ViewModel necesita inyectar el navegador y el modelo de vista de la página de destino:

 class MyViewModel1 : IMyViewModel { public MyViewModel1(IPageNavigationService navigator, IDestinationViewModel destination) { GoToPageCommand = new RelayCommand(() => navigator.NavigateToDestinationPage(destination)); } public ICommand GoToPageCommand { get; } } 

La implementación de NavigationService encapsula el tipo de vista (Página2) y la referencia al marco que se inyecta a través del constructor:

 class PageNavigationService : IPageNavigationService { private readonly Frame _navigationFrame; public PageNavigationService(Frame navigationFrame) { _navigationFrame = navigationFrame; } void Navigate(Type type, object dataContext) { _navigationFrame.Navigate(type); _navigationFrame.DataContext = dataContext; } public void NavigateToDestinationPage(IDestinationViewModel dataContext) { // Page2 is the corresponding view of the destination view model Navigate(typeof(Page2), dataContext); } } 

Para obtener el marco, simplemente llámelo en su página principal xaml:

  

En el código detrás de MainPage, inicializa tu bootstrapper pasando el frame raíz:

 public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); var bootstrapper = new Bootstrapper(RootFrame); DataContext = bootstrapper.GetMainScreenViewModel(); } } 

Finalmente, aquí está la implementación de bootstrapper para completar;

 class Bootstrapper { private Container _container = new Container(); public Bootstrapper(Frame frame) { _container.RegisterSingleton(frame); _container.RegisterSingleton(); _container.Register(); _container.Register(); #if DEBUG _container.Verify(); #endif } public IMyViewModel GetMainScreenViewModel() { return _container.GetInstance(); } }