Jounce, Parte 2: Comenzado (etiquetas y enlaces)

Traducción aproximada del artículo Jounce Part 2: Getting Started (Tagging and Binding) publicado en inglés por Jeremy Likness el 19 de octubre del 2010 en su blog C#er:Image.

 

En esta segunda parte sobre la biblioteca Jounce voy a explicar cómo comenzar a usarla para conectar una vista a un modelo de vista.

El proyecto Jounce se encuentra disponible en línea en http://Jounce.Codeplex.com.

Jounce se auto-inyecta en la aplicación como un servicio. A mi parecer, IApplicationService es la manera correcta de controlar el inicio y término de una aplicación en Silverlight. También ofrece un punto idóneo para insertar valores de configuración ya que se obtiene acá un contexto que contiene los parámetros de inicialización y enganches a eventos como excepciones no atendidas.

Al crear una aplicación, podemos eliminar toda la maraña de código subyacente en App.xaml.cs. En vez de eso, insertamos el servicio de aplicación de Jounce en el XAML en una sección especial para este tipo de servicios:

 

<Application
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:Services="clr-namespace:Jounce.Framework.Services;
                  assembly=Jounce.Framework"
  x:Class="Jounce.App"
  >
  <Application.ApplicationLifetimeObjects>
    <Services:ApplicationService/>
  </Application.ApplicationLifetimeObjects>
</Application>

 

Eso es todo lo que Jounce necesita para registrarse y tomar parte en el ciclo de vida de la aplicación de Silverlight. Al comenzar, la aplicación invoca este método:

 

public void StartService(ApplicationServiceContext context)
{
  var logLevel = LogSeverity.Warning;

  if (context.ApplicationInitParams
              .ContainsKey(Constants.INIT_PARAM_LOGLEVEL))
  {
    logLevel = (LogSeverity) Enum.Parse(
     typeof (LogSeverity),
     context.ApplicationInitParams[Constants.INIT_PARAM_LOGLEVEL],
     true);
  }

  // empty one adds current deployment (xap)
  _mainCatalog = new AggregateCatalog(new DeploymentCatalog()); 

  var container = new CompositionContainer(_mainCatalog);

  CompositionHost.Initialize(container);
  CompositionInitializer.SatisfyImports(this);

  if (Logger == null)
  {
    ILogger defaultLogger = new DefaultLogger(logLevel);
    container.ComposeExportedValue(defaultLogger);
  }

  DeploymentService.Catalog = _mainCatalog;
  _mefDebugger = new MefDebugger(container, Logger);
}

 

Jounce lleva a cabo aquí varias tareas: revisa si en los parámetros de inicialización se indica un nivel de severidad para el registro de historial, si no lo encuentra entonces un valor predeterminado. Esto permite que podamos controlar el nivel de severidad del historial mediante añadir un parámetro al objeto de Silverlight en la página de web que lo contiene. Por ejemplo en este caso hacemos que el historial sea detallado y descriptivo:

 

<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
 <param name="source" value="ClientBin/Jounce.xap"/>
 <param name="onError" value="onSilverlightError" />
 <param name="initParams" value="Jounce.LogLevel=Verbose" />
 <param name="background" value="white" />
 <param name="minRuntimeVersion" value="4.0.50826.0" />
 <param name="autoUpgrade" value="true" />
 <a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=4.0.50826.0" style="text-decoration:none">
   <img src="http://go.microsoft.com/fwlink/?LinkId=161376" alt="Get Microsoft Silverlight" style="border-style:none"/>
 </a>
</object><iframe id="_sl_historyFrame" style="visibility:hidden;height:0px;width:0px;border:0px"></iframe>

 

Jounce crea un conglomerado general de todos los catálogos de creación y distribución (esto es lo que permite el cargado diferido de archivos XAP). Además, ocurren otros dos eventos claves. Primero, Jounce declara una importación del contrato de historial de esta manera:

 

[Import(AllowDefault = true, AllowRecomposition = true)]
public ILogger Logger { get; set; }

 

La parte AllowDefault le dice a MEF que permita valores nulos sin quejarse cuando no se encuentran implementaciones exportadas para este contrato. Si uno decide crear y exportar un registro de historial propio, Jounce lo encontrará y lo importará en este lugar. Si no, entonces usa el registro incluido que simplemente escribe la ventana del depurador.

El MefDebugger también es importante ya que envuelve el contenedor de MEF con código para depuración que emite gran cantidad de información de diagnóstico para ayudarnos a ver lo que ocurre dentro de la infraestructura de extensibilidad administrada. Si ejecutan la aplicación principal incluida con Jounce, verán un gran número de mensajes en la ventana correspondiente:

 

0/19/2010 6:44:29 AM Verbose Jounce.Core.MefDebugger :: MEF: Found part: Jounce.ViewModels.WelcomeViewModel
10/19/2010 6:44:29 AM Verbose Jounce.Core.MefDebugger ::    With import: Jounce.Core.ViewModel.BaseViewModel.EventAggregator (ContractName="Jounce.Core.Event.IEventAggregator")
10/19/2010 6:44:29 AM Verbose Jounce.Core.MefDebugger ::    With import: Jounce.Core.ViewModel.BaseViewModel.Router (ContractName="Jounce.Core.ViewModel.IViewModelRouter")
10/19/2010 6:44:29 AM Verbose Jounce.Core.MefDebugger ::    With import: Jounce.Core.ViewModel.BaseViewModel.Logger (ContractName="Jounce.Core.Application.ILogger")
10/19/2010 6:44:29 AM Verbose Jounce.Core.MefDebugger ::    With export: Jounce.ViewModels.WelcomeViewModel (ContractName="Jounce.Core.ViewModel.IViewModel")
10/19/2010 6:44:29 AM Verbose Jounce.Core.MefDebugger ::       With key: ViewModelType = Welcome
10/19/2010 6:44:29 AM Verbose Jounce.Core.MefDebugger ::       With key: ExportTypeIdentity = Jounce.Core.ViewModel.IViewModel
10/19/2010 6:44:29 AM Verbose Jounce.Core.MefDebugger ::    With export: Jounce.ViewModels.WelcomeViewModel (ContractName="Jounce.Core.ViewModel.IViewModel")
10/19/2010 6:44:29 AM Verbose Jounce.Core.MefDebugger ::       With key: ViewModelType = Welcome
10/19/2010 6:44:29 AM Verbose Jounce.Core.MefDebugger ::       With key: ExportTypeIdentity = Jounce.Core.ViewModel.IViewModel

 

Este es el resultado de tan sólo un componente encontrado y las importaciones y exportaciones envueltas. La información es de gran valor para averiguar las razones por las que una importación o exportación no funciona o actúa de forma inesperada. Les sugiero examinar el código fuente de MefDebugger y cómo pasa por todas las partes del contendor y luego se engancha a un evento de manera que si las conexiones del contenedor cambian (por ejemplo al cargar un archivo XAP) también puede informar sobre esos cambios.

El segundo evento importante es el método Starting que es llamado luego de la inicialización, pero antes de completar la configuración de la jerarquía visual:

 

public void Starting()
{
  Application.Current.UnhandledException +=
                        Current_UnhandledException;

  var viewInfo = (from v in Views
                  where v.Metadata.IsShell
                  select v).FirstOrDefault();

  if (viewInfo == null)
  {
    var grid = new Grid();
    var tb = new TextBlock
      {
        Text =
          Resources.ApplicationService_Starting_Jounce_Error_No_view
      };
    grid.Children.Add(tb);
    Application.Current.RootVisual = grid;

    Logger.Log(
        LogSeverity.Critical,
        GetType().FullName,
        Resources.ApplicationService_Starting_Jounce_Error_No_view);

     return;
  }

  Application.Current.RootVisual = viewInfo.Value;

  Logger.LogFormat(
      LogSeverity.Information, GetType().FullName,
      Resources.ApplicationService_Starting_ShellResolved,
      MethodBase.GetCurrentMethod().Name,
      viewInfo.Value.GetType().FullName);

  Logger.Log(LogSeverity.Information,
             GetType().FullName,
             MethodBase.GetCurrentMethod().Name);            

  EventAggregator.Publish(
       viewInfo.Metadata.ExportedViewType.AsViewNavigationArgs());
}

 

Jounce se registra con el controlador de excepciones no atendidas y provee un método que produce un mensaje personalizado. Para eso usa el agrupador de eventos (Event Aggregator, que será descrito más adelante) pero provee facilidades para subscribirse a excepciones específicas y hasta responder a ellas desde nuestro código.

A la hora de etiquetar vistas en Jounce, una (y sólo una) de ellas puede ser marcada como la vista principal o caparazón. La biblioteca busca esta vista especial y al asigna como raíz de la jerarquía visual (root visual). En caso de no encontrarla entonces crea y despliega un mensaje de error. La metainformación de la vista es importada en el servicio de la aplicación de esta manera:

 

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

 

Entonces Jounce registra algunos mensajes en el historial y dispara un evento de navegación. En un futuro voy a cubrir la navegación de Jounce, pero el punto por el momento es que este evento le informa a la biblioteca que debe ir a buscar y luego atar a la vista los modelos de vista asociados.

¿Cómo designamos una vista como la principal?

 

[ExportAsView("Welcome",IsShell = true)]
public partial class Welcome
{
    public Welcome()
    {
        InitializeComponent();
    }
}

 

Jounce nos da gran libertad en cómo etiquetar las vistas y modelos de vista. La única regla es que cada etiqueta deber ser única. En el caso anterior usé una cadena (mágica) de caracteres, pero también se pueden usar constantes (como en los ejemplos incluidos en el paquete). La vista arriba usa la etiqueta “Welcome” y es designada como la vista principal o caparazón. Eso hace que sea la primera vista en ser desplegada.

Por supuesto, el tema principal de la biblioteca es MVVM. EN el ejemplo inicial de Jounce he creado un simple modelo de vista que al ser cargado produce un mensaje de bienvenida e comienza una animación. Este es código:

 

[ExportAsViewModel("Welcome")]
public class WelcomeViewModel : BaseViewModel
{
  public WelcomeViewModel()
  {
    Title = "Welcome to Jounce!";
  }

  private string _title;

  public string Title
  {
    get { return _title; }
    set
    {
      _title = value;
      RaisePropertyChanged(()=>Title);
    }
  }

  public override void ActivateView(string viewName)
  {
    GoToVisualState("WelcomeState",true);
  }
}

 

El modelo de vista usa la misma etiqueta que la vista, aunque podría ser diferente, ya que Jounce permite que un modelo de vista sea asociado a varias vistas. Lo que importa es que tenga una etiqueta.

La clase base del modelo de vista ofrece varios servicios, incluyendo métodos que pueden ser redefinidos y que son invocados cuando el modelo de vista es creado (InitializeVm), cada vez que es activado (ActivateView), y cada vez que es desactivado (DeactivateView).

Cuando se dispara un evento de navegación de una vista, Jounce lo intercepta,y asocia el modelo de vista correspondiente y luego llama los métodos apropiados en este último.

Échenle un vistazo al direccionador de vistas. Es un recolector de eventos de navegación, lo que le permite recibir todos los mensajes de navegación globales:

 

public class ViewRouter : IPartImportsSatisfiedNotification,
                          IEventSink<ViewNavigationArgs>

 

También invoca el método ActivateView en ViewModelRouter:

 

public bool ActivateView(string viewName)
{
  Logger.LogFormat(LogSeverity.Verbose,
                   GetType().FullName,
                   Resources.ViewModelRouter_ActivateView,
                   MethodBase.GetCurrentMethod().Name,
                   viewName);

  if (HasView(viewName))
  {
    var view = ViewQuery(viewName);

    var viewModelInfo = GetViewModelInfoForView(viewName);

    if (viewModelInfo != null)
    {
      var firstTime = !viewModelInfo.IsValueCreated;

      var viewModel = viewModelInfo.Value;

      if (firstTime)
      {
        viewModel.GoToVisualState = (state, transitions) =>
          JounceHelper.ExecuteOnUI(() =>
            VisualStateManager.
                 GoToState(view, state, transitions));

        _BindViewModel(view, viewModel);
        viewModel.Initialize();
      }
      viewModel.Activate(viewName);
    }

    return true;
  }
  return false;
}

 

Como pueden ver, se localiza y ata un modelo de vista que corresponda a la vista. (El direccionador de modelos de vista crea la asociación en el LayoutRoot. Si no lo encuentra entonces lo ata al DataContext del control inmediato superior.) También se puede notar que Jounce asocia una acción llamada GoToVisualState que es ejecutada en el hilo de la interfase gráfica. Esto permite que el modelo de vista inicie una transición de estado visual sin tener que hacer referencia a la vista. En nuestro ejemplo, el método ActivateView es redefinido de esta manera:

 

public override void ActivateView(string viewName,
                     IDictionary<string, object> viewParameters)
{
    base.ActivateView(viewName, viewParameters);
    GoToVisualState("WelcomeState",true);
}

 

Iniciando la animación y el despliegue del mensaje “Welcome to Jounce.”

Queda una pieza más. Nunca indicamos cómo determinar que el modelo de vista “Welcome” va junto con la vista “Welcome”. Con el fin hacer Jounce más flexible decidí no usar el patrón de localización tradicional. En vez de eso, lo que hago es exportar asociaciones que enlazan modelos de vista con vistas. La ventaja de este sistema es que las asociaciones pueden ser consolidadas en una clase que sirva de repositorio para la aplicación entera o pueden ser insertadas en las vistas de manera que es obvio cuál es su modelo de vista respectivo. Es posible incluso crear una interfase diferente, por ejemplo basada en convenciones o de tipo fluido, y exportar las asociaciones al contenedor obteniendo el mismo resultado.

En la vista Welcome la asociación con el modelo de vista es creada usando las etiquetas de ambos componentes y es marcada para exportación:

 

[Export]
public ViewModelRoute Binding
{
  get
  {
    return ViewModelRoute.Create("Welcome", "Welcome");
  }
}

 

Y eso es todo. Desde nuestra la perspectiva como usuarios, esto es lo que hay que hacer:

  1. Agregar el servicio de aplicación de Jounce a App.xaml
  2. Crear un modelo de vista y etiquetarlo
  3. Crear una vista, etiquetarla, y designarla como caparazón
  4. Exportar una asociación entre la vista y el modelo de vista

Jounce se encarga del resto.

El proyecto Jounce se encuentra disponible en línea en http://Jounce.Codeplex.com.

 

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.