Traducción aproximada del artículo MEF instead of PRISM for Silverlight 3 Part 2 of 2: Region Management publicado en inglés por Jeremy Likness el 1 de marzo del 2010 en su blog C#er:Image.


En mi artículo anterior demostré cómo cargar módulos de manera dinámica usando la más reciente versión de MEF para Silverlight 3. En este artículo voy a explicar la administración de regiones con MEF. Si nuestro propósito al usar PRISM es cargar módulos y administrar regiones, podemos crear entonces una solución equivalente basada 100% en MEF para Silverlight.

Descargar el código fuente para este artículo.

Nota breve: aunque es conocida por su biblioteca de clases y funciones, la Guía de Asistencia Para Crear Aplicaciones Compuestas (PRISM) es en realidad más que nada un conjunto de pautas. La biblioteca es provista como muestra para ayudar en la orientación del tema. Mi razón para mencionarlo es porque al usar el concepto de un administrador de regiones y vistas estamos, técnicamente, implementando el concepto expuesto en PRISM y usando la guía CAG, pero usando MEF en vez de la biblioteca de referencia como mecanismo ejecutor.

Bien, sigamos adelante…

Lo primero que hice esta vez fue eliminar toda las piezas de IModuleInitializer. No las ocupamos pues MEF hace las inicializaciones necesarias a medida que carga los módulos. Eliminé las clases correspondientes en los módulos (aunque fue buen ejercicio verlos cargar) y ajusté la clase de navegación de esta manera:


[Export(typeof(INavigation))]
public class ViewNavigator : INavigation
{
  [Import]
  public ICatalogService CatalogService { get; set; }

  private readonly Dictionary<ViewType, string> _viewMap =
      new Dictionary<ViewType, string>
        {
           { ViewType.MainView, "DynamicModule.xap" },
           { ViewType.SecondView, "SecondDynamicModule.cs.xap" }
        };   

    private readonly List<string>
                  _downloadedModules = new List<string>();

    public void NavigateToView(ViewType view)
    {
        if (!_downloadedModules.Contains(_viewMap[view]))
        {
            var catalog = new DeploymentCatalog(_viewMap[view]);
            CatalogService.Add(catalog);
            catalog.DownloadAsync();
            _downloadedModules.Add(_viewMap[view]);
        }
    }
}


Debo agradecer a Glenn Block por señalar el excelente ejemplo que viene en el paquete MEF (el ejemplo DeploymentCatalogSample)… y que demuestra una forma diferente de cargar el catálogo,  extrayendo incluso el archivo XAP. En este caso, estoy asumiendo que tengo conocimiento previo de los controles y quiero hacer mi aplicación más eficiente por medio de cargarlos a medida que el usuario los selecciona.

Bueno, ya estamos reorganizados. ¡Se ve mucho más ordenado! Seguidamente quiero regiones basadas en tipos inflexibles. Lo que hice fue crear un nuevo proyecto conteniendo solamente las regiones y añadí esta clase para definir la enumeración:


public enum ViewRegion
{
    MainRegion,
    SubRegion
}

public static class ViewRegionExtensions
{
  private static readonly List<ViewRegion> _regions =
                   new List<ViewRegion> {
                                 ViewRegion.MainRegion,
                                 ViewRegion.SubRegion };       

  public static ViewRegion AsViewRegion(this string regionName)
  {
      foreach(var region in _regions)
      {
          if (regionName.Equals(region.ToString(),
                   StringComparison.InvariantCultureIgnoreCase))
          {
              return region;
          }
      }
      return ViewRegion.MainRegion;
  }
}


Desafortunadamente, creo que la versión de Enum en Silverlight no permite listar los valores de una enumeración. Por lo tanto, voy a usar una clase estática que contenga los valores y la voy a colocar en seguida de la enumeración de manera que sea fácil de recordar en etapas posteriores de mantenimiento del código. También agregué un método de extensión que me permite tomar una cadena y  usar AsViewRegion() para convertirla a un valor de la enumeración.

Para que la enumeración sea visible en XAML, necesitamos ayudar a Silverlight para que sepa cómo convertir de la cadena que tecleamos en el XAML al valor correspondiente de la enumeración que usamos en el código. Abajo está el convertidor de tipo. Básicamente dice que sólo sabe trabajar con cadenas y usa el método de extensión para convertirla cuando es llamado:


public class ViewRegionConverter : TypeConverter
{
  public override bool CanConvertFrom(
                         ITypeDescriptorContext context,
  				         Type sourceType)
  {
      return sourceType.Equals(typeof (string));
  }

  public override object ConvertFrom(
                         ITypeDescriptorContext context,
                         System.Globalization.CultureInfo culture,
                         object value)
  {
      return ((string) value).AsViewRegion();
  }
}


Ahora ya podemos crear nuestro comportamiento. Se trata de una propiedad adjunta que vamos a usar para dar la etiqueta “región” a un contenedor. Eventualmente esto podría hacerse complejo a medida que agregamos manejo de listas y controles con contenido, pero para el propósito de esta serie de artículos, lo mantendré simple y limitaré los contenedores a paneles. Todos los paneles tienen elementos secundarios por lo que es fácil añadirles un artículo al contenido. El comportamiento simplemente mantiene un diccionario estático que crea una correspondencia entre la región de vista y el panel al que está asociada y expone un método que nos deja solicitar el panel para una región de vista. Es como sigue:


public static class RegionBehavior
{
  private static readonly Dictionary<ViewRegion, Panel>
              _regions = new Dictionary<ViewRegion, Panel>();

  public static readonly DependencyProperty
       RegionNameProperty = DependencyProperty.RegisterAttached(
            "RegionName",
            typeof (ViewRegion),
            typeof (RegionBehavior),
            new PropertyMetadata(ViewRegion.MainRegion, null));

  public static ViewRegion GetRegionName(DependencyObject obj)
  {
      return (ViewRegion) obj.GetValue(RegionNameProperty);
  }

  [TypeConverter(typeof(ViewRegionConverter))]
  public static void SetRegionName(DependencyObject obj,
                                   ViewRegion value)
  {
      obj.SetValue(RegionNameProperty, value);
      var panel = obj as Panel;
      if (panel != null)
      {
          _regions.Add(value, panel);
      }
  }

  public static Panel GetPanelForRegion(ViewRegion region)
  {
      return _regions[region];
  }

}


Noten la indicación que le doy a SetRegionName sobre el convertidor de tipos. Eso le permite a XAML saber cómo hacer la conversión. De hecho esto incluso nos da IntelliSense en el XAML, ¡de manera que sabremos cuáles son las enumeraciones válidas! Ahora puedo ir a la carcasa principal y crear mi primera región (IntelliSense rápidamente me dio una lista de la cual escoger… ¡por el momento sólo hay dos opciones!)


<StackPanel Orientation="Vertical" Grid.Row="2"
  Regions:RegionBehavior.RegionName="MainRegion"/>


Podemos ejecutar la aplicación ahora para verificar que el panel está registrado en el diccionario del comportamiento, de manera que por este lado ya estamos listos. Ahora tenemos que incorporar las vistas en la región. Todavía estamos en el proyecto y espacio de nombres especial de  “Regions”. Por ahora simplemente voy a exportar el tipo UserControl para mi vista y especificar la región con un atributo. En MEF podemos crear un atributo con un tipo específico para la metainformación:


[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class TargetRegionAttribute : ExportAttribute
{
    public TargetRegionAttribute() : base(typeof(UserControl))
    {

    }

    public ViewRegion Region { get; set; }
}


El tipo es UserControl pero podemos especificar una región en la metainformación. Ahora puedo añadir una vista a mi módulo dinámico. También especifico una región aquí para una vista anidada.


<UserControl x:Class="DynamicModule.View"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Regions="clr-namespace:RegionsWithMEF.Regions;
                   assembly=RegionsWithMEF.Regions"
    >
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="0"
                      Text="Left Column of Outer View"/>
        <StackPanel Grid.Column="1" Orientation="Vertical"
               Regions:RegionBehavior.RegionName="SubRegion">
            <TextBlock Text="Right Column of Outer View"/>
        </StackPanel>
    </Grid>
</UserControl>


Seguidamente etiquetamos la vista como exportada con nuestro atributo modificado, de esta manera:


[TargetRegion(Region=ViewRegion.MainRegion)]
public partial class View
{
    public View()
    {
        InitializeComponent();
    }
}


Sencillo y fácil de entender. De una vez añadí otro control para una segunda vista y lo asocié a la vista “SubRegion”.

Ya tenemos nuestros propios atributos y exportaciones, pero todavía hay que unirlos.Cuando el catálogo es cargado, las vistas son etiquetadas como exportaciones. Sólo necesitamos que alguien las importe para poder entonces examinar sus metadatos y colocarlas en los lugares apropiados. Para esto, he creado el RegionManager.

Pero antes, regresemos un poco. Queremos examinar los metadatos y para eso necesito una interfase con propiedades de sólo lectura para poder extraerlos:


public interface ITargetRegionCapabilities
{
    ViewRegion Region { get;  }
}


¡Perfecto! Ahora puedo definir el RegionManager. Vamos a usar el objeto Lazy para definir las habilidades de importación y e inspección de metadatos. Lo pongo en una colección observable a la que luego escucho. Cuando se hay cambios, inspecciono los datos buscando controles nuevos y los dirijo a su región basándome en la metainformación:


[Export]
public class RegionManager
{
  private readonly List<UserControl> _controls =
                                 new List<UserControl>();

  [ImportMany(AllowRecomposition = true)]
  public ObservableCollection<
             Lazy<UserControl, ITargetRegionCapabilities>>
             Controls { get; set; }

  public RegionManager()
  {
     Controls = new ObservableCollection<
          Lazy<UserControl, ITargetRegionCapabilities>>();
     Controls.CollectionChanged += Controls_CollectionChanged;
  }

  void Controls_CollectionChanged(object sender,
                             System.Collections.Specialized
                             .NotifyCollectionChangedEventArgs e)
  {
     if (e.NewItems != null)
     {
        foreach(var item in e.NewItems)
        {
            var controlInfo =
                      item as Lazy<UserControl,
                                   ITargetRegionCapabilities>;

             if (controlInfo != null)
             {
                if (!_controls.Contains(controlInfo.Value))
                {
                   ViewRegion region =
                       controlInfo.Metadata.Region;
                   Panel panel =
                       RegionBehavior.GetPanelForRegion(region);
                   panel.Children.Add(controlInfo.Value);
                   _controls.Add(controlInfo.Value);
                }
            }
        }
     }
  }
}


Nota: si se preguntan por qué usé una lista junto con una colección observable, es debido a que, con la reconstitución, MEF es libre de reconstruir la lista completa. Necesito saber específicamente qué ha cambiado y qué nuevos elementos incluyen otros más antiguos que ya han sido constituidos. La lista interna me permite llevar cuenta de lo que ya he procesado y extraer lo que es nuevo. Si hay elementos personalizados, necesitan asegurarse de que tengan una buena implementación de los métodos Equals y GetHashCode, y si tienen artículos que pierden el enfoque y desaparecen, van a necesitar cambiar la lista a una de tipo WeakReference.


¡Eso es todo! El último paso es asegurarse de que el administrador de regiones participe en la secuencia entera de importaciones y exportaciones. Podría hacer las conexiones necesarias en el contenedor, pero como sé que el modelo de vista de la carcasa está, por así decirlo, a la cabeza de la jerarquía, voy a importar el administrador directamente. No lo he interconectado ya que no expone ningún método o propiedad: todo el trabajo es interno e interactúa con el diccionario estático que pusimos en nuestro comportamiento.


[Import]
public RegionManager Manager { get; set; }


Bien, pongámoslo en funcionamiento. Hice un corto vídeo para enseñarles los resultados. (El plugin para Silvelight me está dando algunos líos, mientras encuentro la solución, el vídeo puede ser visto aquí.)


Install	Microsoft	Silverlight

Etiquetas asignadas:
 

3 Respuestas a “Usando MEF en vez de PRISM en Silverlight – Parte 2 de 2: Administración de Regiones”

  1. [...] una demostración pueden examinar el artículo Usando MEF en vez de PRISM en Silverlight – Parte 2 de 2: Administración de Regiones donde uso metadatos para especificar los tipos de vistas y regiones,  y luego las interconecto [...]

  2. [...] artículo Usando MEF en vez de Prism – Parte 2 da una explicación detallada sobre el manejo de regiones. También pueden ver el artículo en [...]

  3. [...] artículo Usando MEF en vez de Prism – Parte 2 da una explicación detallada sobre el manejo de regiones. También pueden ver el artículo en [...]

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.