Windows Phone Tips & Tricks. Servicio de navegación en MVVM

[Nota: Este artículo pertenece a la serie de Windows Phone Tips & Tricks de MSDN España]

Hola,

Cuando usamos MVVM en nuestras apps de Windows Phone, podemos utilizar una clase especial para realizar la navegación entre las diferentes vistas de la app, todo ello controlado por los vista-modelo. Con esta clase, a la que llamaremos NavigationService, un vista-modelo puede decir a qué vista hay que navegar en un momento dado. Y para ello le indicará al NavigationService cuál es el vista-modelo de la vista a la que hay que navegar, y qué parámetros quiere pasarle a dicho vista-modelo.

Hay varias formas de implementar un NavigationService. A continuación puedes ver la implementación que utilizo yo en mis proyectos, que es muy fácilmente reutilizable y me permite pasar parámetros complejos de un vista-modelo a otro.

Este es el interfaz del servicio:

namespace MyMVVMApp.Services.Interfaces
{
    public interface INavigationService
    {
        void NavigateTo<TDestinationViewModel>();
        void NavigateTo<TDestinationViewModel>(object navigationContext);
        void NavigateBack();
        void NavigateBack(object navigationContext);
        void ClearNavigationHistory();
    }
}

Y esta sería la implementación:

using Microsoft.Phone.Controls;
using MyMVVMApp.Services.Interfaces;
using MyMVVMApp.ViewModels.Interfaces;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Navigation;

namespace MyMVVMApp.Services
{
    // Navegación entre páginas
    public class NavigationService : INavigationService
    {
        // Correspondencia entre los vista-modelo y sus vistas
        private static IDictionary<Type, string> _viewModelRouting = new Dictionary<Type, string>()
        {
            { typeof(IMainViewModel), "/Views/MainPage.xaml" },
            { typeof(IItemViewModel), "/Views/ItemPage.xaml" },
            { typeof(IAboutInfoViewModel), "/Views/AboutInfoPage.xaml" }
        };

        // Parámetros complejos que se pasan a los vista-modelo asociados a las vistas
        private object _navigationContext;

        // Navega a la vista de un vista-modelo sin paso de parámetros
        public void NavigateTo<TDestinationViewModel>()
        {
            PhoneApplicationFrame rootFrame = Application.Current.RootVisual as PhoneApplicationFrame;
            rootFrame.Navigate(new Uri(_viewModelRouting[typeof(TDestinationViewModel)], UriKind.Relative));
        }

        // Navega a una vista pasándole parámetros complejos a su vista-modelo
        public void NavigateTo<TDestinationViewModel>(object navigationContext)
        {
            // Parámetros para el vista-modelo
            this._navigationContext = navigationContext;

            // Navega a la página y entérate de cuándo hemos llegado
            PhoneApplicationFrame rootFrame = Application.Current.RootVisual as PhoneApplicationFrame;
            rootFrame.Navigated += new NavigatedEventHandler(Page_Navigated);
            rootFrame.Navigate(new Uri(_viewModelRouting[typeof(TDestinationViewModel)], UriKind.Relative));
        }

        // Navega de vuelta a la vista anterior, sin pasarle parámetros a su vista-modelo
        public void NavigateBack()
        {
            PhoneApplicationFrame rootFrame = Application.Current.RootVisual as PhoneApplicationFrame;
            if (rootFrame.CanGoBack)
            {
                rootFrame.GoBack();
            }
        }

        // Navega de vuelta a la vista anterior, pasándole parámetros complejos a su vista-modelo
        public void NavigateBack(object navigationContext)
        {
            // Parámetros para el vista-modelo
            this._navigationContext = navigationContext;

            // Navega a la página anterior y entérate de cuándo hemos llegado
            PhoneApplicationFrame rootFrame = Application.Current.RootVisual as PhoneApplicationFrame;
            if (rootFrame.CanGoBack)
            {
                rootFrame.Navigated += new NavigatedEventHandler(Page_Navigated);
                rootFrame.GoBack();
            }
        }

        // Ya hemos llegado a la página a cuyo vista-modelo queremos pasarle los parámetros
        private void Page_Navigated(object sender, NavigationEventArgs e)
        {
            PhoneApplicationFrame rootFrame = Application.Current.RootVisual as PhoneApplicationFrame;
            rootFrame.Navigated -= Page_Navigated;

            // Pásale los parámetros a su vista-modelo
            ((e.Content as PhoneApplicationPage).DataContext as INavigable).NavigationContext = this._navigationContext;
        }

        // Limpia el historial de navegación entre páginas
        public void ClearNavigationHistory()
        {
            PhoneApplicationFrame rootFrame = Application.Current.RootVisual as PhoneApplicationFrame;
            while (rootFrame.RemoveBackEntry() != null) ;
        }
    }
}

Esta clase es muy fácil de adaptar a cualquier proyecto, ya que en principio solo habría que cambiar la tabla de correspondencias entre vista-modelos y sus vistas (propiedad _viewModelRouting).

Para que podamos pasar parámetros complejos de un vista-modelo a otro, los vista-modelo que puedan recibir parámetros implementarán este interfaz:

namespace MyMVVMApp.ViewModels.Interfaces
{
    public interface INavigable
    {
        object NavigationContext { set; }
    }
}

Usar este servicio en los vista-modelo es muy sencillo. Por ejemplo, imagina que estamos en un vista-modelo llamado MainViewModel cuya vista es MainPage.xaml y al que le hemos inyectado el NavigationService como dependencia. El usuario va a realizar una acción en MainPage que terminará provocando una navegación a otra página llamada ItemPage.xaml cuyo vista-modelo es ItemViewModel. Cuando el usuario realiza la acción, MainPage llama a un comando de MainViewModel que llegado el momento indicará que queremos hacer esa navegación. Para ello dirá que quiere ir a la página cuyo vista-modelo es ItemViewModel, y que queremos pasarle un parámetro que le hace falta:

// El usuario quiere editar un elemento
private void EditItem(Item item)
{
    this.NavigationService.NavigateTo<IItemViewModel>(item);
}

Con esa llamada NavigationService hará que de MainPage se navegue a ItemPage, y le pasará el parámetro a ItemViewModel en su propiedad NavigationContext:

public class ItemViewModel: BindableBase, IItemViewModel, INavigable
{
...
    // Parámetros de navegación recibidos de otro vista-modelo
    public object NavigationContext
    {
        set
        {
            // No hagas nada si los parámetros no son válidos
            if (value != null && !(value is Item)) { return; }

            // Info a mostrar en la vista
            Item currentItem = value as Item;
            if (currentItem != null)
            {
                // Estamos editando un elemento existente
                this.ItemName = currentItem.Name;
                this.ItemDescription = currentItem.Description;
                this.CurrentItem = currentItem;
            }
        }
    }

Espero que te sea de utilidad.

Un saludo,

Alejandro Campos Magencio (@alejacma)

PD: Mantente informado de todas las novedades de Microsoft para los desarrolladores españoles a través del Twitter de MSDN, el Facebook de MSDN, el Blog de MSDN y la Newsletter MSDN Flash.

About these ads

2 pensamientos en “Windows Phone Tips & Tricks. Servicio de navegación en MVVM

  1. Pingback: Windows Phone Tips & Tricks. Ejemplo de implementación del patrón MVVM | ¡Desarrolladores, desarrolladores, desarrolladores!

  2. Pingback: Tips & Tricks de desarrollo para Windows Phone - MSDN España - Site Home - MSDN Blogs

Deja un comentario

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s