Compartir a través de


Este artículo proviene de un motor de traducción automática.

Silverlight

Composición de aplicaciones con prism de Web

Shawn Wildermuth

Descarga de código disponible en la MSDN Code Gallery
Examinar el código en línea

En este artículo se describe:

  • Silverlight 2
  • Composición de la aplicación
  • Inserción de dependencia
En este artículo usa las tecnologías siguientes:
Silverlight 2, prism

Contenido

¿Por qué prism?
Introducción a la inserción de dependencia
Comportamiento de inicio
Modularidad
Composición de la interfaz de usuario
Agregación de eventos
Comandos de delegado
Conclusión

la primera experiencia con Silverlight es probablemente algo pequeño: un Reproductor de vídeo, una simple aplicación gráficos o incluso un menú. Estos tipos de aplicaciones son simples y sencillos de diseño y segmentación en capas rigurosas con responsabilidades independientes es excesiva.

Problemas de superficie, sin embargo, cuando intenta aplicar un estilo estrechamente acoplado a aplicaciones de gran tamaño. Medida que crece el número de piezas móviles, el estilo simple del desarrollo de aplicaciones se cae separar. Parte de la solución es disponer en capas (consulte mi artículo"Modelo-View-ViewModel en aplicaciones de Silverlight 2"), pero es una arquitectura de acoplamiento ajustado sólo uno de una serie de problemas que deben resolverse en grandes proyectos de Silverlight.

En este artículo, mostraré cómo crear una aplicación mediante las técnicas de composición de la biblioteca de aplicación compuesto del proyecto prism. El ejemplo que desarrollar es un simple editor de datos de la base de datos.

A medida que cambian los requisitos y crezca de un proyecto, es útil si puede cambiar partes de la aplicación sin necesidad de estos cambios en cascada en todo el sistema. Modularizing una aplicación le permite generar componentes de la aplicación por separado (y escasamente acoplada) y cambiar partes completas de la aplicación sin afectar al resto del código.

Además, quizás no desee cargar todas las piezas de la aplicación a la vez. Imagine una aplicación de administración de clientes en el que los usuarios iniciar sesión y pueden, a continuación, administrar su canalización de oportunidades, así como comprobar el correo electrónico desde cualquiera de sus oportunidades. Si un usuario comprueba el correo electrónico varias veces al día pero administra la canalización sólo cada día o dos, ¿por qué cargar el código para administrar la canalización hasta que sea necesario? Sería estupendo si una aplicación admite la carga de petición de partes de la aplicación, una situación que puede solucionarse mediante modularizing la aplicación.

El equipo de patterns & practices de Microsoft creó un proyecto denominado prism (o CompositeWPF) que pretende solucionar los problemas como éstos para las aplicaciones de Windows Presentation Foundation (WPF) y prism se ha actualizado para admitir Silverlight así. El paquete prism es una mezcla de marco y orientación para crear aplicaciones. El marco, llama a la biblioteca de aplicaciones de componentes (CAL), permite lo siguiente:

  • Aplicación modularidad: generar aplicaciones de componentes con particiones.
  • Composición de la interfaz de usuario: permite escasamente acoplados componentes a interfaces de usuario del formulario sin discreto conocimiento del resto de la aplicación.
  • Ubicación del servicio: distintos servicios horizontales (por ejemplo, registro y autenticación) desde servicios verticales (lógica empresarial) para promover limpiar capas de una aplicación.

La CAL se escribe con estos mismos principios de diseño en cuenta, y para los programadores de aplicaciones es un marco de estilo buffet, lo que necesita y dejar el resto. la figura 1 muestra el diseño básico de CAL en relación con su propia aplicación.

fig02.gif

Figura 1 biblioteca de la aplicación compuesta

La CAL admite estos servicios para ayudarle a redactar la aplicación de partes más pequeñas. Esto significa que la CAL de controladores se cargan las piezas (y cuándo), así como proporcionar funcionalidad de base. Puede decidir cuál de estas capacidades ayudarle a hacer su trabajo y que podría obtener de la forma.

Mi ejemplo en este artículo se utiliza como parte de la CAL como sea posible. Es una aplicación de shell que utiliza la CAL para cargar varios módulos en tiempo de ejecución, coloque las vistas en regiones (tal como se muestra en la figura 2 ) y servicios de soporte técnico. Pero antes de entrar a ese código, deberá comprender algunos conceptos básicos sobre la inserción de dependencia (también denominado inversión de control o IoC). Muchas de las características de CAL se basan en inserción de dependencia, para comprender los principios básicos le ayudará a desarrollar la arquitectura de su proyecto de Silverlight con prism.

fig03.gif

Figura 2 arquitectura de aplicaciones compuestas

Introducción a la inserción de dependencia

En el desarrollo típicas, un proyecto se inicia con un punto de entrada (un archivo ejecutable, una página default.aspx y así sucesivamente). Puede desarrollar la aplicación como un proyecto gigante, pero en la mayoría de los casos que existe cierto nivel de modularidad en que la aplicación cargue un número de ensamblados que forman parte del proyecto. El ensamblado principal sabe qué ensamblados que necesita y crea duros referencias a dichos componentes. En tiempo de compilación, el proyecto principal conozca todos los ensamblados a los que se hace referencia y la interfaz de usuario consta de los controles estáticos. La aplicación está en el control de qué código necesita y normalmente sabe todo el código podría utilizar. Esto será un problema, sin embargo, porque desarrollo tiene lugar dentro del proyecto de aplicación principal. A medida que crece una aplicación monolítica, tiempo de generación y los cambios conflictivos pueden ralentizar el desarrollo.

Inserción de dependencia pretende invertir esta situación proporcionando instrucciones que configurar las dependencias en tiempo de ejecución. En lugar del proyecto controlar estas dependencias, un fragmento de código llama a un contenedor es responsable de inyectar en ellos.

Pero ¿por qué es esto importante? En primer lugar, modularizing el código debe facilitar a probar. Para poder intercambiar dependencias del proyecto permite la limpieza de pruebas para poder sólo el código que se va a probar el origen de una prueba de errores, en lugar del código en algún lugar de la cadena anidada de dependencias. Ofrecemos un ejemplo concreto. Imagine que tiene un componente que otros programadores utilizan para buscar direcciones de empresas determinadas. El componente depende de un componente de acceso de datos que recupera los datos. Al probar el componente, inicie comprobando contra la base de datos y algunas de las pruebas producirá un error. Pero como las compilaciones de la base de datos y esquema están cambiando constantemente, no sabe si las pruebas están fallando debido de su propio código o código de acceso a datos. Con dependencia de disco duro del componente en los datos de componente de acceso, probar la aplicación se convierte en no confiable y causas renovación mientras localizar errores en el código o en código del otro.

El componente podría este aspecto:

public class AddressComponent
{
  DataAccessComponent data = new DataAccessComponent();

  public AddressComponent()
  {
  }

  ...
}

En lugar de un componente conectado, no se pudo aceptar una interfaz que representa el acceso a datos, tal como se muestra aquí:

public interface IDataAccess
{
  ...
}

public class AddressComponent
{
  IDataAccess data;

  public AddressComponent(IDataAccess da)
  {
    data = da;
  }

  ...
}

Normalmente, se utiliza una interfaz para poder crear una versión que le permite ajustar el código. Este enfoque se suele denomina "simulacro". Simulacro significa crear una implementación de la dependencia que realmente no representa la versión real. Literalmente, está creando una implementación de simulacro.

Este enfoque es mejor porque la dependencia (IDataAccess) puede insertarse en el proyecto durante la construcción del objeto. La implementación del componente IDataAccess dependerá de los requisitos (prueba o real).

¿Eso es básicamente cómo funciona la inserción de dependencia, pero no cómo se controla la inserción? El trabajo del contenedor es controlar la creación de tipos, que lo permitirle registrar tipos y, a continuación, resolverlos. Por ejemplo, suponga que tiene una clase concreta que implementa la interfaz IDataAccess. Durante inicio de la aplicación, puede indicar el contenedor para registrar el tipo. En ninguna otra parte de la aplicación donde se necesita el tipo, puede pedir el contenedor para resolver el tipo como se muestra aquí:

public void App_Startup()
{
  container.RegisterType<IDataAccess, DbDataAccess>();
}

...

public void GetData()
{
  IDataAccess acc = container.Resolve<IDataAccess>();
}

Según la situación (pruebas o producción), puede intercambiar la implementación de IDataAccess simplemente cambiando el registro. Además, el contenedor puede controlar la inserción de construcción de dependencias. Si un objeto que debe crearse constructor del contenedor toma una interfaz que puede resolver el contenedor, resuelve el tipo y lo pasa al constructor, como en la figura 3 .

Figura 3 resolución de tipos por el contenedor

public class AddressComponent : IAddressComponent
{
  IDataAccess data;

  public AddressComponent(IDataAccess da)
  {
    data = da;
  }
}

...

public void App_Startup()
{
  container.RegisterType<IAddressComponent, AddressComponent>();
  container.RegisterType<IDataAccess, DbDataAccess>();
}

public void GetAddresses()
{
  // When we ask the container to create the AddressComponent,
  // it sees that a constructor takes a IDataAccess object
  // so it automatically resolves that dependency
  IAddressComponent addr = container.Resolve<IAddressComponent>();
}

Observe que constructor del AddressComponent toma una implementación de IDataAccess. Cuando el constructor, crea la clase AddressComponent durante la resolución, automáticamente crea la instancia de IDataAccess y lo pasa a la AddressComponent.

Cuando registre tipos con el contenedor, también para indicar el contenedor para tratar con la duración del tipo de modos especiales. Por ejemplo, si está trabajando con un componente de registro, podría desea tratarlo como un singleton para cada parte de la aplicación que necesita el registro no obtiene su propia copia (que es el comportamiento predeterminado). Para ello, puede proporcionar una implementación de la clase LifetimeManager abstracta. Se admiten varios directores de duración. ContainerControlledLifetimeManager es un singleton por proceso y PerThreadLifetimeManager es un singleton por subproceso. Para ExternallyControlledLifetimeManager, el contenedor conserva una referencia débil para el objeto singleton. Si el objeto es liberado externamente, el contenedor crea una nueva instancia; en caso contrario, devuelve el objeto activo dentro de la referencia débil.

Utilice la clase de LifetimeManager especificando al registrar un tipo. A continuación puede ver un ejemplo:

container.RegisterType<IAddressComponent, AddressComponent>(  new ContainerControlledLifetimeManager());

En la CAL, el contenedor de IoC se basa en el marco Unity desde el grupo de patterns & practices. Usaré el contenedor Unity en los ejemplos siguientes, pero también hay una serie de alternativas de código fuente abierto para el contenedor de IoC Unity, como Ninject, sesión, Castle y código. Si está familiarizado con y ya está utilizando un contenedor de IoC distinto Unity, puede suministrar su propio contenedor (aunque tarda un poco más esfuerzo).

Comportamiento de inicio

Normalmente en una aplicación de Silverlight, el comportamiento de inicio es simplemente crear clase de la página XAML principal y asignarlo a la propiedad de la aplicación RootVisual. En una aplicación compuesta, sigue siendo necesario este trabajo, pero en lugar de crear la clase de página XAML, una aplicación compuesta utiliza normalmente una clase de programa previo para controlar el comportamiento de inicio.

Para empezar, necesita una nueva clase que deriva de la clase UnityBootstrapper. Esta clase es en el ensamblado Microsoft.Practices.Composite.UnityExtensions. El programa previo contiene métodos reemplazables que controlan las distintas partes de comportamiento de inicio. A menudo, no reemplazará cada método de inicio, sólo el los necesarios. Los dos métodos que debe reemplazar son CreateShell y GetModuleCatalog.

El método de CreateShell es donde se crea la clase XAML principal. Normalmente esto se denomina el shell porque es el contenedor visual de componentes de la aplicación. Mi ejemplo incluye a un programa previo que crea una nueva instancia de la clase de shell y asigna a RootVisual de antes de devolver esta nueva clase de shell, tal como se muestra aquí:

public class Bootstrapper : UnityBootstrapper
{
  protected override DependencyObject CreateShell()
  {
    Shell theShell = new Shell();
    App.Current.RootVisual = theShell;
    return theShell;
  }

  protected override IModuleCatalog GetModuleCatalog()
  {
    ...
  }
}

El método GetModuleCatalog, que explicaré en la sección siguiente, devuelve la lista de módulos para cargar.

Ahora que tiene una clase de programa previo, puede utilizar en método de inicio de la aplicación de Silverlight. Normalmente, crear una nueva instancia de la clase de programa previo y llame a su método de ejecución, como se muestra en la figura 4 .

Figura 4 crear una instancia del programa previo

public partial class App : Application
{

  public App()
  {
    this.Startup += this.Application_Startup;
    this.Exit += this.Application_Exit;
    this.UnhandledException += this.Application_UnhandledException;

    InitializeComponent();
  }

  private void Application_Startup(object sender, StartupEventArgs e)
  {
    Bootstrapper boot = new Bootstrapper();
    boot.Run();
  }

  ...
}

El programa previo también está implicado en registrar tipos con el contenedor que distintas partes de la aplicación necesita. Para ello, reemplazar el método ConfigureContainer del programa previo. Esto proporciona una oportunidad para registrar los tipos que se van a utilizarse en el resto de la aplicación. figura 5 muestra el código.

Tipos de registro de la figura 5

public class Bootstrapper : UnityBootstrapper
{
  protected override void ConfigureContainer()
  {
    Container.RegisterType<IShellProvider, Shell>();
    base.ConfigureContainer();
  }

  protected override DependencyObject CreateShell()
  {
    // Get the provider for the shell
    IShellProvider shellProvider = Container.Resolve<IShellProvider>();

    // Tell the provider to create the shell
    UIElement theShell = shellProvider.CreateShell();

    // Assign the shell to the root visual of our App
    App.Current.RootVisual = theShell;

    // Return the Shell
    return theShell;
  }

  protected override IModuleCatalog GetModuleCatalog()
  {
    ...
  }
}

Aquí, el código registra una interfaz para una clase que implementa la interfaz IShellProvider, que se crea en nuestro ejemplo y no es parte del marco de trabajo CAL. De esta forma se puede utilizar en la implementación del método CreateShell. Se puede resolver la interfaz y utilícela para crear una instancia del shell se puede asignarlo a RootVisual y devolverlo. Esta metodología puede parecer de trabajo adicional, pero como profundizar en cómo la CAL le ayuda a generar la aplicación, queda claro cómo está ayudando este programa previo.

Modularidad

En un entorno típico. NET, el ensamblado es la unidad principal del trabajo. Esta designación permite a los desarrolladores trabajar en su código de forma independiente entre sí. En la CAL, cada una de estas unidades de trabajo es un módulo y de CAL utilizar un módulo, necesita una clase que puede comunicarse el comportamiento de inicio del módulo. Esta clase también necesita que admite la interfaz IModule. La interfaz IModule requiere un método denominado Initialize que permite al módulo propio configurado para ser utilizado en el resto de la aplicación. El ejemplo incluye un módulo de ServerLogger que contiene las capacidades de registro para nuestra aplicación. La clase de ServerLoggingModule admite la interfaz IModule tal como se muestra aquí:

public class ServerLoggerModule : IModule
{
  public void Initialize()
  {
    ...
  }
}

El problema es que no sabemos lo que queremos inicializar en nuestro módulo. Puesto que es un módulo ServerLogging, parece lógico que desea registrar un tipo que realiza el registro para nosotros. Queremos utilizar el contenedor para registrar el tipo para que quien necesita la utilidad de registro puede utilizar simplemente nuestra implementación sin conocer el tipo exacto de registro que realiza.

Obtenemos el contenedor creando un constructor que toma la interfaz IUnityContainer. Si recuerda la discusión de inserción de dependencia, el contenedor utiliza la inserción de constructor para agregar tipos que conocen. IUnityContainer representa el contenedor en nuestra aplicación, para si podemos agregar ese constructor, a continuación, se pueda guardarlo y utilizar en nuestra inicialización como modo:

public class ServerLoggerModule : IModule
{
  IUnityContainer theContainer;

  public ServerLoggerModule(IUnityContainer container)
  {
    theContainer = container;
  }

  public void Initialize()
  {
    theContainer.RegisterType<ILoggerFacade, ServerBasedLogger>(
      new ContainerControlledLifetimeManager());
  }
}

Una vez inicializado, este módulo es responsable de la implementación de registro para la aplicación. Pero, ¿cómo este módulo se cargan?

Cuando utiliza la CAL para redactar una aplicación, deberá crear un ModuleCatalog que contiene todos los módulos de la aplicación. Para crear este catálogo, reemplazar GetModuleCatalog llamada del programa previo. En Silverlight, puede rellenar este catálogo con código o XAML.

Con código, crear una nueva instancia de la clase ModuleCatalog y llénelo con los módulos. Por ejemplo, mirar esto:

protected override IModuleCatalog GetModuleCatalog()
{
  var logModule = new ModuleInfo()
  {
    ModuleName = "ServerLogger",
    ModuleType =       "ServerLogger.ServerLoggerModule, ServerLogger, Version = 1.0.0.0"
  };

  var catalog = new ModuleCatalog();
  catalog.AddModule(logModule);

  return catalog;
}

Aquí, simplemente se agregar un solo módulo denominado ServerLogger, el tipo definido en ModuleType propiedad del ModuleInfo. Además, puede especificar las dependencias entre los módulos. Porque algunos módulos que dependen de otros, el uso de dependencias ayuda al catálogo saber el orden en que conectar las dependencias. Mediante la propiedad ModuleInfo.DependsOn, puede especificar qué módulos con nombre son necesarios para cargar otro módulo.

Puede cargar el catálogo directamente desde un archivo XAML, tal como se muestra aquí:

protected override IModuleCatalog GetModuleCatalog()
{
  var catalog = ModuleCatalog.CreateFromXaml(new Uri("catalog.xaml", 
                                                     UriKind.Relative));
  return catalog;
}

El archivo XAML contiene la misma información de tipo que puede crear con código. La ventaja de mediante XAML es que puede cambiar sobre la marcha. (Imagine recuperar el archivo XAML desde un servidor o desde otra ubicación basada en sesión el usuario.) En la figura 6 se muestra un ejemplo de un archivo catalog.xaml.

Figura 6 A ejemplo Catalog.xaml archivo

<m:ModuleCatalog 
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:sys="clr-namespace:System;assembly=mscorlib"
  xmlns:m="clr-namespace:Microsoft.Practices.Composite.Modularity; 
    assembly=Microsoft.Practices.Composite">
  <m:ModuleInfoGroup InitializationMode="WhenAvailable">
    <m:ModuleInfo ModuleName="GameEditor.Client.Data"
        ModuleType="GameEditor.Client.Data.GameEditorDataModule, 
          GameEditor.Client.Data, Version=1.0.0.0"/>
    <m:ModuleInfo ModuleName="GameEditor.GameList"
      ModuleType="GameEditor.GameList.GameListModule,
      GameEditor.GameList, Version=1.0.0.0"
      InitializationMode="WhenAvailable">
      <m:ModuleInfo.DependsOn>
        <sys:String>GameEditor.Client.Data</sys:String>
      </m:ModuleInfo.DependsOn>
    </m:ModuleInfo>
  </m:ModuleInfoGroup>
</m:ModuleCatalog>

En este catálogo XAML, el grupo incluye dos módulos y el segundo módulo depende de la primera. Puede utilizar un catálogo específico que XAML basado en funciones o permisos, como podría con código.

Una vez cargado el catálogo por el programa previo, intenta crear instancias de las clases de módulo y permitirle inicializar a sí mismos. En el código de ejemplos, los tipos tienen hacer referencia a la aplicación (por lo tanto, ya cargado en memoria) para este catálogo de trabajo.

Esto es que esta utilidad se convierte en indispensable de Silverlight. Aunque la unidad de trabajo es el ensamblado, puede especificar un archivo .xap que contiene el módulo o módulos. Para ello, especifique un valor de referencia en ModuleInfo. El valor de referencia es una ruta de acceso al archivo .xap que contiene el módulo:

protected override IModuleCatalog GetModuleCatalog()
{
  var logModule = new ModuleInfo()
  {
    ModuleName = "ServerLogger",
    ModuleType =
      "ServerLogger.ServerLoggerModule, ServerLogger, Version= 1.0.0.0",
    Ref = "ServerLogger.xap"
  };

  var catalog = new ModuleCatalog();
  catalog.AddModule(logModule);

  return catalog;
}

Cuando especifica un archivo .xap, el programa previo sabe que el ensamblado no está disponible y sale al servidor y recupera el archivo .xap de forma asincrónica. Una vez cargado el archivo .xap, prism carga el ensamblado y crea el tipo de módulo e inicializa el módulo.

Para los archivos .xap que contienen varios módulos, puede crear un ModuleGroup que contiene un conjunto de objetos ModuleInfo y establecer a la referencia de la ModuleGroup para cargar todos los módulos desde un archivo .xap único:

var modGroup = new ModuleInfoGroup();
modGroup.Ref = "MyMods.xap";
modGroup.Add(logModule);
modGroup.Add(dataModule);
modGroup.Add(viewModule);

var catalog = new ModuleCatalog();
catalog.AddGroup(modGroup);

Para las aplicaciones de Silverlight, esto es una forma para redactar sus aplicaciones desde varios archivos .xap, que permite a versión diferentes secciones de la aplicación compuesta por separado.

Al crear módulos de Silverlight para alojarse en un archivo .xap, crear una aplicación de Silverlight (no en una biblioteca de Silverlight). Después, hacer referencia a todos los proyectos de módulo que desea colocar en el archivo .xap. Tiene que quita los archivos app.xaml y page.xaml porque este archivo .xap no se carga y se ejecutarán como un archivo .xap típica. El archivo .xap es simplemente un contenedor (se puede convertir un archivo .zip, no importa). Además, si se hace referencia a proyectos que ya se hace referencia en el proyecto principal, también puede cambiar esas referencias a la copia local = false en las propiedades porque no necesita los ensamblados en el archivo .xap (la aplicación principal ya cargado, por lo que el catálogo no intentar cargarlos en una segunda vez.)

Pero cargar una gran aplicación con varias llamadas a través del cable no parece que ayudaría a rendimiento. Es donde entra en juego la InitializationMode propiedad del ModuleInfo. InitializationMode admite dos modos: WhenAvailable, en la que el archivo .xap es cargado de forma asincrónica y, a continuación, inicializa (es el comportamiento predeterminado) y OnDemand, en el que se carga el .xap cuando solicita explícitamente. Puesto que el catálogo de módulo no conoce los tipos en los módulos hasta la inicialización, la resolución de tipos que se inicializan con OnDemand fallará.

Compatibilidad de petición para módulos y grupos permite cargar algunas funciones en una aplicación grande según sea necesario. Acelerado tiempo de inicio y otro código necesario se puede cargar como los usuarios interactuar con una aplicación. Esto es una característica excelente para utilizar cuando tenga autorización para separar las partes de una aplicación. Los usuarios que sólo necesitan unas partes de la aplicación no tienen que descargar código que no utilizará nunca.

Para cargar un módulo a petición, necesita tener acceso a una interfaz IModuleManager. A menudo, solicita esto en el constructor de la clase que necesita para cargar un módulo a petición. A continuación, utilice IModuleManager para cargar el módulo por llamada de LoadModule, como se muestra en la figura 7 .

Figura 7 llamada LoadModule

public class GameListViewModel : IGameListViewModel
{
  IModuleManager theModuleManager = null;

  public GameListViewModel(IModuleManager modMgr)
  {
    theModuleManager = modMgr;
  }

  void theModel_LoadGamesComplete(object sender, 
                                  LoadEntityCompleteEventArgs<Game> e)
  {
    ...

    // Since we now have games, let's load the detail pane
    theModuleManager.LoadModule("GameEditor.GameDetails");
  }
}

Los módulos son simplemente la unidad de modularización en sus aplicaciones. En Silverlight, mucho tratar un módulo como lo haría con un proyecto de biblioteca, pero con el trabajo adicional de inicialización de módulo, puede desacoplar los módulos del proyecto principal.

Composición de la interfaz de usuario

En una aplicación de explorador típica, el panel izquierdo muestra una lista o árbol de información y a la derecha contiene detalles sobre el elemento seleccionado en el panel izquierdo. En la CAL estas áreas se denominan regiones.

La CAL admite regiones definir directamente en XAML mediante el uso una propiedad adjunta en la clase RegionManager. Esta propiedad permite especificar regiones en el shell y, a continuación, indicar qué vistas se deben alojado en la región. Por ejemplo, nuestro shell tiene dos regiones, LookupRegion y DetailRegion, tal como se muestra aquí:

<UserControl 
  ...
  xmlns:rg=
    "clr-namespace:Microsoft.Practices.Composite.Presentation.Regions;
    assembly=Microsoft.Practices.Composite.Presentation">
  ...
  <ScrollViewer rg:RegionManager.RegionName="LookupRegion" />
  <ScrollViewer rg:RegionManager.RegionName="DetailRegion" />
</UserControl>

Se puede aplicar un RegionName al ItemsControl y sus controles derivadas (por ejemplo, ListBox); Selector y sus controles derivadas (por ejemplo, TabControl) y ContentControl y sus controles derivadas (por ejemplo, ScrollViewer).

Una vez definir regiones, puede dirigir módulos para cargar sus vistas en las áreas utilizando la interfaz IRegionManager, tal como se muestra aquí:

public class GameListModule : IModule
{
  IRegionManager regionManager = null;

  public GameListModule(IRegionManager mgr)
  {
    regionManager = mgr;
  }

  public void Initialize()
  {
    // Build the View
    var view = new GameListView();

    // Show it in the region
    regionManager.AddToRegion("LookupRegion", view);
  }
}

Esta utilidad permite definir las regiones en la aplicación donde pueden aparecen y, a continuación, tiene los módulos definir cómo colocar las vistas en la región, lo que permite el shell de ignorantes completamente de las vistas vistas.

El comportamiento de las regiones pueden variar según el tipo de control alojado. El ejemplo utiliza un ScrollViewer para que una y sólo una vista puede agregarse a la región. En contraste, regiones ItemControl permiten varias vistas. Como cada vista se agrega, se muestra como un elemento nuevo en ItemsControl. Dicha utilidad facilita la generación funcionalidad como un escritorio.

Si utiliza un modelo MVVM para definir las vistas, puede mezclar las regiones y los aspectos de ubicación de servicio del contenedor para hacer que las vistas y ver modelos ignorantes uno del otro y después dejar que el módulo combinarlas en tiempo de ejecución. Por ejemplo, si se cambia el GameListModule, puede registrar las vistas y modelos de vistas con el contenedor y, después, combinarlas antes de aplicar la vista a la región, como se muestra en la figura 8 .

Combinar vistas en un área de la figura 8

public class GameListModule : IModule
{
  IRegionManager regionManager = null;
  IUnityContainer container = null;

  public GameListModule(IUnityContainer con, IRegionManager mgr)
  {
    regionManager = mgr;
    container = con;
  }

  public void Initialize()
  {
    RegisterServices();

    // Build the View
    var view = container.Resolve<IGameListView>();

    // Get an Implemenation of IViewModel
    var viewModel = container.Resolve<IGameListViewModel>();

    // Marry Them
    view.ApplyModel(viewModel);

    // Show it in the region
    regionManager.AddToRegion("LookupRegion", view);
  }

  void RegisterServices()
  {
    container.RegisterType<IGameListView, GameListView>();
    container.RegisterType<IGameListViewModel, GameListViewModel>();
  }
}

Este enfoque permite utilizar la composición de IU manteniendo la separación estricta de MVVM.

Agregación de eventos

Cuando haya varias vistas en sus aplicaciones a través de composición de la interfaz de usuario, enfrenta a un problema común. Aunque haya creado vistas independientes para admitir las pruebas y desarrollo mejor, a menudo hay puntos de contacto en las vistas no pueden ser completamente aisladas. Lógicamente, están acoplados porque necesitan comunicarse, pero desea conservar correspondencia imprecisa como como sea posible, independientemente del lógico de acoplamiento.

Para habilitar el acoplamiento flexible y comunicación entre vista, la CAL admite un servicio denominado agregación de eventos. Agregación de eventos permite el acceso a diferentes partes del código para editores y consumidores de eventos globales. Dicho acceso proporciona una manera sencilla de comunicación sin que se está estrechamente y se consigue mediante IEventAggregator interfaz de la CAL. IEventAggregator le permite publicar y suscribirse a eventos a través de los distintos módulos de la aplicación.

Antes de que puede comunicarse, necesita una clase que deriva de EventBase. Normalmente, se crea un evento simple que se deriva el CompositePresentationEvent <t> clase. Esta clase genérica le permite especificar la carga del evento que se va a publicar. En este caso, el GameListViewModel va a publicar un evento una vez seleccionado un juego para que otros controles que desea cambiar su contexto, como el usuario selecciona un juego pueden suscribirse a ese evento. Nuestra clase de evento tiene el siguiente aspecto:

public class GameSelectedEvent : CompositePresentationEvent<Game>
{
}

Una vez definido el evento, el agregador de eventos puede publicar el evento llamando a su método GetEvent. Esto recupera el evento de singleton que va a agregar. Lo primero que llama a este método crea el objeto singleton. Desde el evento, puede llamar a método Publish para crear el evento. Publicar el evento es similar a desencadenar un evento. No es necesario publicar el evento hasta que lo necesite enviar información. Por ejemplo, cuando se selecciona un juego en el GameList, nuestro ejemplo publica el juego seleccionado mediante el evento nuevo:

// Fire Selection Changed with Global Event
theEventAggregator.GetEvent<GameSelectedEvent>().Publish(o as Game);

En otras partes de la aplicación compuesta, puede suscribirse al evento para llamarse una vez publicado el evento. El método SUBSCRIBE del evento permite especificar el método al que llamará cuando se publica el evento, una opción que permite solicitar la semántica de subprocesamiento para llamar al evento (por ejemplo, el subproceso de interfaz de usuario es utilizado habitualmente), y si desea que el agregador mantenga una referencia a la información pasada en para que no esté sujeto a la recolección de elementos no utilizados:

// Register for the aggregated event
aggregator.GetEvent<GameSelectedEvent>().Subscribe(SetGame, 
                                                  ThreadOption.UIThread, 
                                                  false);

Como suscriptor, también puede especificar filtros que se llaman sólo en situaciones específicas. Imagine un evento que devuelve el estado de una aplicación y un filtro que se llama sólo durante ciertos estados de datos.

El agregador de eventos le permite tener comunicación entre los módulos sin causar estrecho acoplamiento. Si suscribirse a un evento que nunca se publica o publicar un evento que nunca está suscrito, el código nunca se producirá.

Comandos de delegado

En Silverlight (a diferencia de WPF), no existe una infraestructura de comandos es true. Esto obliga al uso de código subyacente en las vistas para realizar tareas que debería realizarse más fácilmente directamente en XAML mediante la infraestructura de comandos. Hasta que Silverlight admite esta función, la CAL admite una clase que ayuda a resolver este problema: el DelegateCommand.

Para empezar a trabajar con DelegateCommand, deberá definir el DelegateCommand en su ViewModel por lo que puede enlazar a él. En el ViewModel crearía un nuevo DelegateCommand. El DelegateCommand espera el tipo de datos enviarse a la (a menudo objetos si no se utiliza ningún dato) y uno o dos métodos de devolución de llamada (o las funciones lambda). El primero de estos métodos es la acción ejecutar cuando se desencadena el comando. Opcionalmente, puede especificar una segunda devolución de llamada llamarse para comprobar si el comando puede activarse. La idea es habilitar la deshabilitación de objetos de la IU (botones, por ejemplo) cuando no es válido para desencadenar el comando. Por ejemplo, nuestro GameDetailsViewModel contiene un comando de admite guardar datos:

// Create the DelegateCommand
SaveCommand = new DelegateCommand<object>(c => Save(), c => CanSave());

Cuando se ejecuta SaveCommand, llama al método Save en nuestro ViewModel. El método CanSave se llama a para asegurarse de que el comando es válido. Esto permite la DelegateCommand deshabilitar la interfaz de usuario si es necesario. Como el estado de los cambios de la vista, puede llamar al método DelegateCommand.RaiseCanExecuteChanged para forzar una inspección nueva del método CanSave para habilitar o deshabilitar la interfaz de usuario según sea necesario.

Para enlazar este XAML, utilice el Click.Command propiedad que está en el espacio de nombres Microsoft.Practices.Composite.Presentation.Commands adjunta. A continuación, enlazar el valor del comando que el comando tiene en su ViewModel así:

<Button Content="Save"
        cmd:Click.Command="{Binding SaveCommand}"
        Style="{StaticResource ourButton}"
        Grid.Column="1" />

Ahora cuando se desencadena el evento Click, nuestro comando se ejecuta. Si lo desea, puede especificar un parámetro de comando para enviarse al comando, por lo que puede volver a utilizarlo.

Como quizá se pregunte, el único comando que existe en la CAL es el evento Click para un botón (o cualquier otro selector). Pero las clases que se puede utilizar para escribir sus propios comandos son bastante sencillos. El código de ejemplo incluye un comando para SelectionChanged en un ComboBox de ListBox. Este comando se denomina el SelectorCommandBehavior y deriva el CommandBehaviorBase <t> clase. Examina la implementación de comportamiento de comando personalizado le proporcionará punto de partida para escribir sus propios comportamientos de comandos.

Conclusión

Hay puntos débiles definitiva desarrollar grandes aplicaciones de Silverlight. Creando sus aplicaciones con acoplamiento flexible y modularidad, obtienen ventajas y puede ser ágil en responder para cambiar. El proyecto prism de Microsoft proporciona las herramientas y orientación para permitir que agilidad a la superficie del proyecto. Aunque prism no es un enfoque única, la modularidad de la CAL significa que puede utilizar lo que cabe en sus escenarios específicos y dejar el resto.

Shawn Wildemuth es MVP de Microsoft (C#) y fundador de Wildermuth Consulting Services. Es autor de numerosos artículos y de varios libros. Además, Shawn actualmente ejecuta el paseo de Silverlight, impartir Silverlight 2 alrededor del país. Se puede contactar en Shawn@wildermuthconsulting.com.