[Universal Apps] Navegación entre páginas, paso de parámetros y gestión de la tecla volver (2/2)

Introducción

Anteriormente vimos como como se realiza la navegación entre páginas en Windows Phone 8.1 además de aprender como gestionar la tecla física atrás. En el artículo actual pasaremos a aplicar y utilizar los conceptos básicos previos tal y como lo utilizaríamos en una aplicación real. Vamos a aprender a navegar utilizando un servicio de navegación e incluso

MVVM en escena!

Realmente podemos mantener una implementación MVVM muy similar a como hacíamos hasta ahora en Windows Phone 8. Comenzamos por la base. Contamos con dos ficheros clave en nuestro proyecto Universal para implementar el patrón MVVM:

  • PageBase
  • ViewModelBase

PageBase

Como revelamos con su nombre, será una clase de la que heredaran todas las páginas de nuestra aplicación. Y si, comentamos que serán todas las páginas de la aplicación (ya sean del proyecto Windows o del proyecto Windows Phone). Las páginas en ambas plataformas es exactamente igual, un objeto de tipo Page. Esta clase cuenta con varios objetivos:

  • Establecer el Frame activo en todo momento para que el acceso al mismo sea sencillo.
  • Permitir el acceso de los eventos de navegación desde nuestras viewmodels.

Además podría interesarnos además:

  • Gestionar las transiciones entre páginas.
  • Gestionar estados (Loading, etc.)
  • Gestionar el estado segun la conexión de red.

Con la funcionalidad básica (facilitarnos el acceso a Frame y eventos de navegación) quedaría:

public class PageBase : Page
{
     private ViewModelBase _vm;
 
     protected override void OnNavigatedTo(NavigationEventArgs e)
     {
         base.OnNavigatedTo(e);
 
         _vm = (ViewModelBase)this.DataContext;
         _vm.SetAppFrame(this.Frame);
         _vm.OnNavigatedTo(e);
     }
 
     protected override void OnNavigatedFrom(NavigationEventArgs e)
     {
         base.OnNavigatedFrom(e);
         _vm.OnNavigatedFrom(e);
     }
}

Gestionamos los eventos básicos de navegación (al entrar y al salir de la página) de modo que, en el método OnNavigateTo obtenemos la viewmodel de la página y asignamos el Frame.

ViewModelBase

Continuamos con la segunda de nuestras clases base. En esta ocasión trataremos la clase base de la que heredarán todos los viewmodels. Los objetivos básicos de la clase son:

  • Notificar cambios (implementar INotifyPropertyChanged).
  • Acceso al objeto Frame que nos permitirá realizar la navegación.
  • Permitir el acceso a los eventos de navegación.
public abstract class ViewModelBase : INotifyPropertyChanged
{
     private Frame appFrame;
     private bool isBusy;
 
     public Frame AppFrame
     {
         get { return appFrame; }
     }
 
     public bool IsBusy
     {
         get { return isBusy; }
         set
         {
             isBusy = value;
             RaisePropertyChanged();
         }
     }
 
     public event PropertyChangedEventHandler PropertyChanged;
 
     public abstract Task OnNavigatedFrom(NavigationEventArgs args);
 
     public abstract Task OnNavigatedTo(NavigationEventArgs args);
 
     public void RaisePropertyChanged([CallerMemberName]string propertyName = "")
     {
         var Handler = PropertyChanged;
         if (Handler != null)
             Handler(this, new PropertyChangedEventArgs(propertyName));
     }
 
     internal void SetAppFrame(Frame viewFrame)
     {
         appFrame = viewFrame;
     }
}

El acceso a los eventos de navegación lo lograremos implementando en la viewmodel los métodos abstractos OnNavigatedFrom y OnNavigatedTo. Esto no permite guardar y recuperar parámetros o estados.

Utilizando PageBase

En la carpeta Views añadiremos nuestras páginas. En nuestro ejemplo, tendremos dos páginas:

  • Pagina1
  • Pagina2

Cada página será un objeto de tipo PageBase, tanto en XAML:

<base:PageBase
    x:Class="Ejemplo_NavegacionMVVM02.Views.Pagina1"
    xmlns:base="using:Ejemplo_NavegacionMVVM02.Views.Base"
    mc:Ignorable="d"
    Background="LightCoral">
    <Grid>
 
    </Grid>
</base:PageBase>

Como en el code-behind:

/// <summary>
/// Pagina1.
/// </summary>
public sealed partial class Pagina1 : PageBase
{
     public Pagina1()
     {
         this.InitializeComponent();
     }
}

Añadimos en la primera página (Pagina1) un botón que permita navegar a la segunda página:

<Button Content="Navegar a página 2"
        HorizontalAlignment="Center"
        />

Para permitir navegar a la segunda página, necesitamos definir un comando en la viewmodel. Creamos una clase derivada de ViewModelBase:

public class Pagina1ViewModel : ViewModelBase
{
 
}

Implementamos los métodos de navegación definidos en ViewModelBase:

public class Pagina1ViewModel : ViewModelBase
{
     public override System.Threading.Tasks.Task OnNavigatedFrom(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
     {
         return null;
     }
 
     public override System.Threading.Tasks.Task OnNavigatedTo(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
     {
         return null;
     }
}

Y añadimos un comando que permita navegar de la página principal a la segunda página:

public class Pagina1ViewModel : ViewModelBase
{
     //Commands
     private ICommand _navigateCommand;
 
     public override System.Threading.Tasks.Task OnNavigatedFrom(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
     {
         return null;
     }
 
     public override System.Threading.Tasks.Task OnNavigatedTo(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
     {
         return null;
     }
 
     public ICommand NavigateCommand
     {
         get { return _navigateCommand = _navigateCommand ?? new DelegateCommand(NavigateCommandDelegate); }
     }
 
     public void NavigateCommandDelegate()
     {
         this.AppFrame.Navigate(typeof(Pagina2), "Esto es un parámetro");
     }
}

Recordamos que el control Frame hospeda controles Page y tiene un historial de navegación que se puede utilizar para ir hacia atrás y hacia adelante por las páginas que ya visitaste. Tras obtener el Frame correspondiente, utilizamos el método Navigate para realizar la navegación a otra página. Tenemos dos sobrescrituras del método Navigate:

  • Navigate(TypeName). Provoca que el Frame cargue el contenido especificado por el tipo pasado como parámetro.
  • Navigate(TypeName, Object). En este caso, además de indicar el tipo del contenido a cargar (primer parámetro), podemos pasar un parámetro a la página que se navega(segundo parámetro).

En nuestro ejemplo hemos utilizado la segunda sobrescritura del método pasando un parámetro.

Ahora ,necesitamos conectar nuestra vista con nuestro viewmodel. Podemos hacerlo de múltiples formas, desde el constructor de la vista, instanciandola en App, usando Ioc. En nuestro caso, utilizaremos Ioc. Usaremos Unity.

Una vez añadida la referencia correspondiente en cada proyecto (Windows y Windows Phone) creamos una nueva clase en la carpeta Base dentro de la carpeta ViewModels llamada ViewModelLocator:

public class ViewModelLocator
{
     readonly IUnityContainer _container;
 
     public ViewModelLocator()
     {
         _container = new UnityContainer();
 
         _container.RegisterType<Pagina1ViewModel>();
         _container.RegisterType<Pagina2ViewModel>();
     }
 
     public Pagina1ViewModel Pagina1ViewModel
     {
         get { return _container.Resolve<Pagina1ViewModel>(); }
     }
 
     public Pagina2ViewModel Pagina2ViewModel
     {
         get { return _container.Resolve<Pagina2ViewModel>(); }
     }
}

Sencillamente registramos nuestras viewmodels y creamos un par de propiedades públicas por cada viewmodel para poder resolverlas y acceder a ellas desde las vistas. A continuación, registramos nuestro locator en App.xaml que también tenemos en el proyecto Shared:

<locator:ViewModelLocator x:Key="Locator"/>

Asignamos la viewmodel como DataContext de nuestra viewmodel:

DataContext="{Binding Pagina1ViewModel, Source={StaticResource Locator}}"

Y todo preparado!. Nuestra segunda página sera similar a la primera (tipo PageBase):

<base:PageBase
    x:Class="Ejemplo_NavegacionMVVM02.Views.Pagina2"
    xmlns:base="using:Ejemplo_NavegacionMVVM02.Views.Base"
    mc:Ignorable="d"
    Background="LightSeaGreen"
    DataContext="{Binding Pagina2ViewModel, Source={StaticResource Locator}}">
    <Grid>
        <Button Content="Volver"
                HorizontalAlignment="Center"
                Command="{Binding NavigateBackCommand}"/>
    </Grid>
</base:PageBase>

Contará con su propia viewmodel:

public class Pagina2ViewModel : ViewModelBase
{
     //Commands
     private ICommand _navigateBackCommand;
 
     public override System.Threading.Tasks.Task OnNavigatedFrom(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
     {
         return null;
     }
 
     public override System.Threading.Tasks.Task OnNavigatedTo(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
     {
         if (args.Parameter != null)
             Debug.WriteLine(args.Parameter);
 
         return null;
     }
 
     public ICommand NavigateBackCommand
     {
         get { return _navigateBackCommand = _navigateBackCommand ?? new DelegateCommand(NavigateBackCommandDelegate); }
     }
 
     public void NavigateBackCommandDelegate()
     {
            this.AppFrame.GoBack();
     }
}

Utilizamos el método GoBack del Frame que navega al elemento más inmediato del historial de navegación, la página anterior.

Podéis descargar el ejemplo realizado a continuación:

Recordar qur cualquier duda o comentario lo podéis dejar en los comentarios.

Más información

Published 8/6/2014 21:28 por jsuarezruiz
Comparte este post: