Modelo de navegación con Prism – Play Silverlight

En esta entrada quiero desarrollar el tema de navegación de aplicaciones Silverlight viendo dos enfoques, primero utilizando el API de navegación propio de Silverlight y por otro lado el necesario para aplicaciones modulares en las que se utiliza Prism.

Framework de Navegación de Silverlight

El framework de navegación de Silverlight gira en torno a tres componentes. El componente principal es el control Frame, que no es más que un área dentro de la cual se mostrará cierto contenido que se recuperará en base a un uri.

El segundo elemento principal es el control Page, que será el contenido de nuestra aplicación. Este tipo de control es básicamente un UserControl, al que se le añaden conceptos de navegación como eventos adicionales, contexto o título.

Por último, el tercer componente dentro del modelo de navegación es el botón hipervínculo (control HyperlinkButton). Este control se diferencia del botón típico en que incluye lógica que buscará en el árbol de navegación un elemento que implemente la interfaz INavigate, que contiene un método Navigate, que se utilizará para cargar en el contenedor el contenido ubicado en el uri especificado. Las dos propiedades principales de este control son TargetName, donde deberemos especificar el nombre del contenedor, y NavigateUri, donde indicaremos la URI del elemento a mostrar.

Más información sobre la API de navegación en el blog de David Poll.

Navegación en aplicaciones modulares con Prism

Pese a que Silverlight proporciona una API de navegación, resulta complicado utilizarla en aplicaciones modulares e implementadas usando el patrón MVVM. En Prism se distinguen dos modelos de navegación, el basado en vistas, que consiste en añadir o eliminar controles, y el basado en estados, en el que los controles existentes cambian de estado.

Navegación basada en estados

Este modelo de navegación se ha de aplicar en situaciones en las que se necesite mostrar una misma información en diferentes formatos (por ejemplo, mostrar un conjunto de elementos en forma de lista o en mosaico), reflejar el estado de la aplicación (mostrando un panel modal indicando que se está realizando una tarea), o interactuar con el usuario dentro de la misma vista (mostrando una ventana modal desde la que solicitar al usuario cierta información).

Para las dos primeras situaciones indicadas, Blend nos proporciona un comportamiento que nos ayuda a implementar este estilo de navegación, se trata de DataStateBehavior. Con este comportamiento podemos cambiar entre dos estados basándonos en una condición en la que entre el valor de una propiedad booleana.

En el siguiente video, se muestra el resultado de utilizar DataStateBehavior para cambiar la forma en que los datos de una colección se muestran al usuario, en un control DataGrid o en un mosaico utilizando un ItemsControl.

En la siguiente imagen aparece la configuración del mencionado comportamiento. En la propiedad Binding, asociaremos un propiedad de tipo booleana, ya sea del modelo de la vista o de otro control (En el ejemplo del video está asociado a la propiedad IsChecked de un control RadioButton). En TrueState indicaremos el estado al que iremos en caso de que la propiedad asociada sea verdadera y, en FalseState, el estado al que se iría en caso contrario.

Data State Behavior Properties

Data State Behavior Properties

Respecto a la interacción con el usuario, lo dejaré para una próxima entrada ya que es un tema con suficiente entidad para ello.

El uso de este modelo debe restringirse a situaciones sencillas, que impliquen pocos estados, ya que podemos acabar con vistas demasiado complejas y difíciles de mantener.

Navegación basada en vistas

El modelo de navegación basado en vistas es el más habitual y consiste en que, a diferencia del modelo basado en estados en el que se cambiaba de estado a una única vista, se dispone de una serie de vistas entre las cuales nos movemos.

La navegación en Prism basa su funcionamiento en el uso de regiones, que nos permite definir zonas en la interfaz de usuario donde podremos cargas vistas. La clase encargada de gestionar las regiones, activándolas y situándolas en la IU, es RegionAdapter. Un tercer componente nos permite acceder a las regiones especificadas en nuestra aplicación, la clase RegionManager.

Se puede definir una región en varios tipos de controles según necesitemos mostrar una o más vistas en la misma. Así, podemos utilizar un control de tipo ContentControl cuando queramos mostrar una única vista en cada momento, un TabControl en el que cada pestaña mostrará una vista asociada asignada la región, o un ListBox en el que cada elemento corresponderá con una vista.

La navegación dentro de un región implicará mostrar una nueva vista en una región, vista que identificaremos a partir de su URI (que normalmente corresponderá con su nombre). Para llevar a cabo la navegación utilizaremos el método RequestNavigate, especificado en la interfaz INavigateAsync e implementado por la clase Region, de la siguiente manera:

IRegion mainContent = ...;
mainContent.RequestNavigate(new Uri ("ItemsView", UriKind.Relative));

También podemos llamar al método RequestNavigate a través de la clase RegionManager, con el que podemos especificar el nombre de la región donde se cargará la vista.

IRegionManager regionManager = ...;
regionManager.RequestNavigate("MainContent",
                               new Uri("ItemsView", UriKind.Relative));</pre>

Funcionalidades adicionales

La API de navegación que proporciona Prism no se limita a cargar vistas en regiones, sino que ofrece funcionalidades relacionadas realmente útiles que mencionaré a continuación:

Permite a vistas y modelo de vistas participar en el proceso de navegación

Prism incluye la interfaz INavigationAware, que puede ser implementada por una vista o un modelo de vista, con la que podemos controlar ciertos eventos que se producen durante la navegación. Esta interfaz se compone de los siguientes elementos

  • IsNavigationTarget, permite a una vista o modelo de vista al que ya se haya accedido indicar si es capaz de manejar peticiones de navegación. Esto es, que no se pueda acceder a él devolviendo false, o en caso contrario dando como resultado true.
  • OnNavigatedTo, se llama a este método cuando finaliza una operación de navegación, lo que nos facilitará la inicialización del módulo.
  • OnNavigatedFrom, este método se llamará antes de ejecutar una operación de navegación, lo que nos resultará muy útil en caso de que necesitamos guardar el estado de la aplicación antes de salir de cierta pantalla.
Paso de parámetros

A veces nos encontramos con la necesidad de especificar información adicional en el proceso de navegación. Para ello, Prism nos proporciona la clase NavigationContext, que recibiremos como parámetro en los tres métodos que he nombrado en la sección anterior donde podremos guardar la información que necesitemos. En concreto, NavigationContext incluye la propiedad Parameters (de tipo UriQuery) que funciona como un diccionario, donde podremos almacenar y recuperar información.

Por ejemplo, una vista reutilizable que, según desde dónde se llame, mostrará una información u otra, utilizaremos los parámetros para diferenciarlas.

Historial de navegación

Otra funcionalidad interesante es la posibilidad de gestionar el historial de navegación de una región. La clase NavigationContext proporciona acceso al servicio de navegación de Prism (NavigationService), encargado de llevar a cabo las acciones relacionadas con la navegación, y que nos proporciona acceso a la región que participa en el proceso de navegación y al historial de navegación asociada a ésta. El historial de navegación lo encontraremos en la propiedad Journal del servicio de navegación.

Se pueden encontrar más funcionalidades en la documentación oficial de Prism.

Un caso práctico

Ahora iré describiendo los pasos para montar la infrastructura necesaria para crear una sencilla aplicación modular en la que aplicar los conceptos que he comentado en esta entrada.

Comenzaremos definiendo las regiones de las que constará nuestra aplicación. Para este ejemplo sencillo definiremos solo dos: una región en el lateral en la que situaremos el menú de la aplicación, y una region que ocupará el resto de espacio donde irá el contenido de los módulos.


<ItemsControl prism:RegionManager.RegionName="Menu" ></ItemsControl>
<ContentControl prism:RegionManager.RegionName="MainContent"  />

La región que definimos como Menu es un control de tipo ItemsControl y cada vista que registre en esa región será un elemento de dicho control. Por su parte, la región MainContent, es de tipo ContentControl por lo que solo se mostrará una única vista a la vez.

El siguiente paso será crear los módulos que compondrán nuestra aplicación. Para ello, crearemos un proyecto de tipo Silverlight Application, dentro del cual crearemos 3 componentes principales:

  • Una clase con el nombre del módulo, que implementará la interfaz IModule.
  • Un conjunto de vistas que se mostrarán en el módulo. Para el caso básico serán solo dos, una que será lo que se mostrará en el menú lateral y otra el contenido del módulo en sí.
  • Un conjunto de modelos de vistas que que deberán ir asociados a las vistas antes mencionadas.

De estos tres componentes el que merece la pena comentar es el de la clase que implementa la interfaz IModule, ya que es ahí donde haremos la inicialización del módulo y donde registraremos nuestras vistas en las regiones correspondientes. En el siguiente fragmento de código se muestra cómo se incializa el módulo:

public void Initialize()
{
   // Con esta línea registramos la vista con un nombre concreto para hacer
   // referencia durante la navegación al módulo mediante éste
   _container.RegisterType("ItemsView");
   //Se registran las vistas en las regiones correpondientes
   _regionManager.RegisterViewWithRegion(Regions.MainContent.ToString(), () => _container.Resolve());
   _regionManager.RegisterViewWithRegion(Regions.Menu.ToString(), () => _container.Resolve());
}

Por último, queda determinar cómo se lanzará un proceso de navegación. En el anterior paso hemos registrado una vista de la clase ItemsNavigationView en la región Menu. Esa vista, o mejor dicho su modelo de la vista, incluirá  la lógica de navegación. La vista en sí sólo incluirá un botón, en cuya propiedad Command asociaremos una propiedad de tipo ICommand del modelo de la vista. En su método Execute insertaremos el siguiente código:

_regionManager.RequestNavigate(Regions.MainContent.ToString(), "ItemsView");

Como se ve utilizamos el método RequestNavigate del gestor de regiones, pasando como parámetros el nombre de la región y el nombe de la vista. El resultado sería el siguiente.

Dejo el código fuente de la aplicación que creo puede servir como punto de partida para iniciar un proyecto. PrismNavigation.rar.doc. (Eliminar extensión -doc y descomprimir).

Aquí termina este pequeño resumen de lo que Prism nos ofrece en lo que a navegación se refiere y que, como se puede ver, incluye soporte para todas las necesidades que podamos tener en relación con la navegación

 

Anuncios
Esta entrada fue publicada en Patrones, Prism, Silverlight y etiquetada , , , , , , . Guarda el enlace permanente.

Responder

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