Jounce, Parte 3: Navegación básica

Traducción aproximada del artículo Jounce Part 3: Basic Navigation publicado en inglés por Jeremy Likness el 23 de octubre del 2010 en su blog C#er:Image.

 

La navegación es siempre un tema interesante y algo en lo que noto que muchos experimentan dificultades al desarrollar aplicaciones en Silverlight. Yo no soy amigo de los métodos de navegación que lo obligan a uno a cambiar el comportamiento de las vistas, es por eso que no me gusta mucho la infraestructura de navegación incluida en Silverlight (donde hay que usar páginas en vez de el control de usuario normal y hay que entender sobre “frames‘” e interactuar con casos especiales). Jounce usa una forma menos intrusiva para separar las solicitudes de navegación y su mecanismo interno.

En su núcleo se encuentran dos clases, ViewNavigationArgs y ViewNavigatedArgs, que deriva de la primera:

 

public class ViewNavigationArgs : EventArgs
{
  public ViewNavigationArgs(Type viewType)
  {
    ViewType = viewType.FullName;
  }

  public ViewNavigationArgs(string viewType)
  {
    ViewType = viewType;
  }

  public bool Deactivate { get; set; }

  public string ViewType { get; private set; }

  public override string ToString()
  {
    return string.Format(
        Resources.ViewNavigationArgs_ToString_ViewNavigation,
        Deactivate ?
            Resources.ViewNavigationArgs_ToString_Deactivate :
            Resources.ViewNavigationArgs_ToString_Activate,
        ViewType);
  }
}

 

Como pueden notar, la clase no es gran cosa fuera del ViewType que es la etiqueta usada para la vista (discutida en el artículo anterior) y el indicador de si está siendo desactivada. Con publicar un mensaje de tipo ViewNavigationArgs se inicia el ciclo de navegación de Jounce. Simple ¿verdad?

Por supuesto, es posible que no quieran usar mi mecanismo de navegación, por lo que Jounce no asume nada sobre su funcionamiento y solamente provee un sistema para notificarnos sobre los eventos. Cuando el evento es disparado, Jounce se encarga de crear la vista (recuerden, sin embargo, que no la inserta en la jerarquía visual) y conectarle el modelo de vista (esto ocurre en el evento Loaded de manera que la conexión no ocurre sino hasta que la vista es insertada en la jerarquía visual). La tarea de colocar la vista en la jerarquía visual es responsabilidad del cliente, ya sea manualmente o mediante un administrador de regiones (trataré sobre esto más adelante).

Hay varias razones por las que elegí incluir la etiqueta de la vista en el evento de navegación. La primera es que es consistente con el sistema de etiquetas que hemos venido usando para enlazar vistas con modelos de vistas, dándole así un nombre común a la vista que uno desear ver sin tener que enterarse de cómo ha sido implementada. Un modelo de vista simplemente necesita publicar un evento con la etiqueta para “navegar” a una vista sin tener que hacer referencia a controles de usuario o cualquier otro artefacto de navegación. De hecho, es posible crea una vista nueva con un estilo completamente diferente y, si la etiqueta es la misma, la navegación seguirá funcionando.

Otra razón por la que la etiqueta es efectiva es que muchas aplicaciones cargan módulos de manera dinámica. Cuando se usan estos archivos XAP, no se tiene acceso al tipo del ensamblaje hasta después de que han sido cargados. Nunca me ha parecido correcto que el mecanismo de navegación sepa si es necesario cargar un módulo a no, por lo que he creado una clase auxiliar, ViewXapRoute.

La clase simplemente le informa a Jounce que una vista con cierta etiqueta en particular se encuentra en un XAP diferente. La clase tiene este aspecto:

 

public class ViewXapRoute
{
  private ViewXapRoute()
  {
  }

  public static ViewXapRoute Create(string viewName,
                                    string viewXap)
  {
    return new ViewXapRoute
            {
              ViewName = viewName,
              ViewXap = viewXap
            };
  }

  public string ViewName { get; private set; }

  public string ViewXap { get; private set; }

  public override string ToString()
  {
    return string.Format(
            Resources.ViewXapRoute_ToString_View_Route_View,
            ViewName, ViewXap);
  }
}

 

 

La solución RegionManagement contiene un ejemplo mostrando cómo funciona esta navegación dinámica. En el archivo RequestSquare.xaml.cs pueden ver expuesta una ruta para la vista con la etiqueta “Dynamic” e indicando el archivo RegionManagementDynamic.xap como el lugar donde puede ser encontrada. El código es:

 

[Export]
public ViewXapRoute DynamicRoute
{
  get
  {
    return ViewXapRoute.Create("Dynamic",
                               "RegionManagementDynamic.xap");
  }
}

 

Con esta indicación Jounce puede fácilmente llevar a cabo la navegación, incluso si ello implica cargar el archivo XAP. Examinemos ahora un par de eventos, ambos cargan una vista, pero uno de ellos carga una vista residente en otro archivo XAP. ¿Pueden adivinar cuál es cual?

 

private void Button_Click(object sender,
                      System.Windows.RoutedEventArgs e)
{
    EventAggregator.Publish(
            new ViewNavigationArgs(Square.SQUARE));
}

private void ButtonDynamic_Click(object sender,
                             System.Windows.RoutedEventArgs e)
{
    EventAggregator.Publish(new ViewNavigationArgs("Dynamic"));
}

 

La única diferencia es que uno usa una cadena mágica, aunque se puede igualmente usar una constante (mi idea es demostrar diferentes formas). Noten que ambas vistas son solicitadas de la misma manera, cumpliendo nuestro objetivo: el módulo que solicita la vista no necesita saber si es o no dinámica. La indicación de que la vista reside en un XAP separado se dan en otro lugar y Jounce sabe dónde encontrarla y enterarse de si debe cargar el archivo.

En el artículo anterior cubrimos un poco sobre lo que ocurre cuando se da un evento de navegación (la vista y el modelo de vista son asociados y hay ciertos métodos en el modelo de vista que son invocados). Pasemos ahora a estudiar de lleno el controlador del evento VewNavigationArgs en la clase ViewRouter:

 

public void HandleEvent(ViewNavigationArgs e)
{
  if (e.Deactivate)
  {
    ViewModelRouter.DeactivateView(e.ViewType);
    EventAggregator.Publish(
        new ViewNavigatedArgs(e.ViewType){Deactivate = true});
    return;
  }

  var viewLocation =
      (from location in ViewLocations
       where location.ViewName.Equals(e.ViewType,
                     StringComparison.InvariantCultureIgnoreCase)
       select location).FirstOrDefault();

  if (viewLocation != null)
  {
    DeploymentService.RequestXap(viewLocation.ViewXap,
        exception =>
        {
          if (exception != null)
          {
            throw exception;
          }
          _ActivateView(e.ViewType);
        });
    }
    else
    {
        // Activar la vista de forma directa
        _ActivateView(e.ViewType);
    }
}

 

Noten que si el evento es una desactivación, el modelo de vista es informado mediante llamar el método Deactivate. Esto permite al modelo de vista guardar su estado o liberar referencias, o hacer lo que necesite para terminar cuando la vista está siendo sustituida por otra.

Cuando el evento es una activación, Jounce busca una indicación sobre la ubicación de la vista. Si la encuentra y el archivo XAP no ha sido cargado entonces detiene la activación hasta que el archivo sea cargado. De otro modo, la activación es inmediata.

Noten también que al final del ciclo en que reacciona al evento, Jounce dispara ViewNavigatedArgs señalando que ha terminado con la configuración preliminar de modelos de vista y con el cargado de XAPs. Esto es algo importante de entender sobre la navegación con Jounce: el evento navigation es disparado antes que que Jounce haga nada, y el evento navigated se dispara después de hacer sus labores. Por eso, si uno desea ser notificado cuando los modelos de vista ya han sido activados, hay que subscribirse al segundo evento.

De nuevo, la idea de Jounce es ser liviana y servir como guía en vez de imponer un sistema específico. Ya que no todo el mundo usa regiones, el núcleo de la infraestructura de Jounce no incluye regiones. La administración de regiones está contenida en un ensamblaje separado opcional. Hablaré más sobre la administración de regiones en el siguiente artículo, pero es importante aclarar que no trata de  interferir. En el caso en que el ensamblaje es incluido, el administrador de regiones se engancha al evento navigated y lo usa para mover vistas a las regiones de destino.

Bueno, ya hemos disparado eventos, configurado modelos de vista, y hasta informado a los mismos modelos de vista que algo ha ocurrido, pero puede que se pregunten, “¿Dónde ocurre la navegación en sí? Hasta el momento no he vista pasar nada con las vistas.”

Esta es otra área donde Jounce presenta gran flexibilidad, pero he incluido un ejemplo en las guías rápidas para ayudarles. El proyecto SimpleNavigation muestra una forma en la que se puede manejar la navegación.

Este proyecto de muestra contiene varias vistas etiquetadas con información adicional. Jounce provee otros atributos que pueden ser usados para categorizarlas o analizarlas. El ejemplo supone que una vista con la categoría “Navigation” está disponible desde el menú principal de navegación. También deseamos proveer un nombre simple para representar la vista en el menú, junto con una descripción. Una de estas vistas, que se compone de un cuadrado rojo, tiene las siguientes etiquetas:

 

[ExportAsView("RedSquare",Category="Navigation",MenuName = "Square",ToolTip = "Click to view a red square.")]
public partial class RedSquare
{
  public RedSquare()
  {
    InitializeComponent();
  }
}

 

Hay que notar que estos atributos son opcionales. La vista principal de navegación, que no es opcional, es etiquetada solamente con el nombre:

 

[ExportAsView("Navigation")]
public partial class Navigation
{
  public Navigation()
  {
    InitializeComponent();
  }
}

 

El modelo de vista de navegación obtiene la metainformación de las vistas:

 

[ImportMany(AllowRecomposition = true)]
public Lazy<UserControl, IExportAsViewMetadata>[] Views
        { get; set; }

 

Y luego procesa las vistas para crear una colección observable con la etiqueta de la vista, el nombre a desplegar en el botón de navegación y el texto de ayuda para el botón. Noten que sólo se procesan las vistas en la categoría de navegación:

 

foreach(
    var v in from viewInfo in Views
        where viewInfo.Metadata.Category.Equals("Navigation")
        select Tuple.Create((ICommand)NavigateCommand,
                    viewInfo.Metadata.ExportedViewType,
                    viewInfo.Metadata.MenuName,
                    viewInfo.Metadata.ToolTip))
{
    _buttonInfo.Add(v);
}

 

El comando de navegación sólo se ejecuta si es para una vista diferente a la actual (llevamos cuenta de la vista actual en la propiedad CurrentView). También se limita a disparar el evento de navegación con la etiqueta apropiada. Observen además el método de extensión que simplifica el convertir una cadena en una clase de tipo ViewNavigation. Este es el comando:

 

NavigateCommand = new ActionCommand<string>(
view =>
    {
        CurrentView = view;
        EventAggregator.Publish(view.AsViewNavigationArgs());
    },
view => !string.IsNullOrEmpty(view) &&
        !view.Equals(CurrentView));

 

Y el XAML en donde el comando es enlazado:

 

<Button Margin="5"
        Command="{Binding Item1}"
        CommandParameter="{Binding Item2}"
        Content="{Binding Item3}"
        ToolTipService.ToolTip="{Binding Item4}"/>

 

Básicamente, la metainformación de las vistas es usada para generar una colección con información y los botones son atados a las vistas mostrando un nombre más claro al usuario, proveyendo texto de ayuda y, al ser presionados, disparan el evento de navegación para la vista.

El caparazón del proyecto es un ContentControl atado a una propiedad llamada CurrentView:

 

<ContentControl Grid.Row="1" Content="{Binding CurrentView}"/>

 

El modelo de vista pone atención a estos eventos de navegación. Cuando ocurren, usa el enrutador de modelos de vista para obtener la vista correspondiente y enlazarla al control. El enrutador provee un indexador que facilita encontrar una vista en particular (sin necesariamente entender los detalles internos o la implementación de la vista) ya que necesita inspeccionar todas las vistas para poder atarlas a modelos de vista. Tan sólo hay que pasar la etiqueta y, si ya ha sido hallada, la vista será devuelta. Presten atención al código siguiente, pues es probablemente lo que han estado esperando. Es un controlador que escucha los eventos de navegación, y cuando ocurre uno, obtiene la vista para que aparezca en el presentador de contenido:

 

public void HandleEvent(ViewNavigationArgs publishedEvent)
{
    if (!publishedEvent.ViewType.Equals("Navigation"))
    {
        CurrentView = Router[publishedEvent.ViewType];
    }
}

 

Y ahí está. El botón dispara el evento, el modelo de vista lo escucha, busca la vista correspondiente y la inserta en el presentador de contenido.

Si lo anterior parece mucho trabajo, estoy de acuerdo. Pero es a propósito: Jounce no desea imponer una infraestructura de navegación, por lo que tiene que encargarse de publicar y subscribirse a eventos de navegación y hacer algo con ellos cuando ocurren.

La ventaja de este modelo es que la navegación no requiere páginas. Puede ser un ítem, un submódulo, o hasta un diminuto control escondido al pie de una página. Es completamente agnóstico a la jerarquía visual, haciendo fácil crear aplicaciones compuestas ya que dondequiera que necesitemos que aparezca algo, sólo necesitamos disparar un evento con la etiqueta apropiada.

Si quisiéramos simplificarlo aún más, podríamos ignorar los eventos y el manejo de las vistas. La idea sería no tener que lidiar del todo con las vistas. Siendo desarrolladores, queremos enfocarnos en el código. Así que, ¿cómo podemos hacer que las vistas se cuiden solas dejando que nos concentremos en el código? Vamos a necesitar un patrón introducido en Prism llamado administración de regiones.

Al examinar la administración de regiones en mi próximo artículo, empezarán a notar un patrón recurrente en Jounce: etiquetas y enlaces. Para asociar un modelo de vista a una vista, etiquetamos ambos y le indicamos a Jounce que los enlace. Si tenemos la vista en un XAP diferente, etiquetamos la vista y exportamos un enlace que lo asocia al archivo XAP. La administración de regiones funciona de forma similar. En vez de tener que estar a la expectativa de eventos de navegación y obteniendo vistas manualmente, podemos etiquetar un área de la página nombrándola como una región y luego etiquetar la vista dejándole sabe a Jounce sobre la relación entre ambas.

Aprenderemos más sobre este tema en la próxima. Mientras tanto, pueden descargar el ejemplo de navegación simple para explorar más a fondo estos conceptos.

 

Jeremy Likness

 

Etiquetas asignadas:
 

Responder



Licencia de uso

El contenido de las traducciones está sujeto a los términos de protección de derechos de uso de los autores originales quienes han autorizado su publicación en este blog. Asegúrese de entender los terminos de la licencia de cada autor antes de usar tal contenido.

Mis propios artículos son publicados bajo los términos de la Licencia Reconocimiento-Compartir bajo la misma licencia 3.0 Estados Unidos de Creative Commons:

Creative Commons License
Blog de Maromas Digitales by Maromas Digitales, LLC is licensed under a Creative Commons Reconocimiento-Compartir bajo la misma licencia 3.0 Estados Unidos License.

License

The contents of all translated articles is subject to the copyright and licensing terms of the original authors and has been published here with their express permission. Verify the original author's licensing terms before using the contents of these articles.

My own articles are licensed under the Creative Commons Attribution-Share Alike 3.0 United States License:

Creative Commons License
Blog de Maromas Digitales by Maromas Digitales, LLC is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.