[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

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

  1. Hola, muy buen artículo. He seguido todos los pasos pero me pasa una cosa y es que al ejecutar la aplicación en Windows phone me da un error en this.AppFrame.Navigate(typeof(Pagina2), «Esto es un parámetro»); porque this.AppFrame es null, sin embargo si la ejecuto para Windows 8.1 si me funciona bien y por mas vueltas que le doy no logro encontrar el error. Gracias.

Responder a avelasco85 Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *