Partager via


Cet article a fait l'objet d'une traduction automatique.

Silverlight

Applications avec prism composite Web

Shawn Wildermuth

Téléchargement de code disponible à partir du MSDN Code Gallery
Parcourir le code en ligne

Cet article explique :
  • Silverlight 2
  • Composition de l'application
  • Injection de dépendance
Cet article utilise les technologies suivantes :
Silverlight 2, prism

Contenu

Pourquoi prism ?
Présentation d'injection de dépendance
Comportement de démarrage
Modularité
Composition de l'interface utilisateur
Agrégation des événements
Commandes de délégué
Conclusion

votre première expérience avec Silverlight a été probablement quelque chose petit : un lecteur vidéo, une application graphique simple ou même un menu. Ces types d'applications sont simples et simple de conception et les segmentation en couches rigoureuses avec responsabilités distinctes est excessif.

Problèmes de surface, cependant, lorsque vous essayez d'appliquer un style étroitement couplé à des applications volumineuses. Comme le nombre d'éléments augmente, le style simple du développement d'application se situe séparer. Partie de la solution est la superposition (voir mon article» Modèle-vue-ViewModel dans les applications Silverlight 2"), mais une architecture étroitement couplée est simplement une d'un nombre de problèmes qui devront être résolus dans les grands projets Silverlight.

Dans cet article, je vous montrerai comment créer une application en utilisant les techniques composition de la bibliothèque application composite à partir du projet prism. L'exemple que je développe est un simple éditeur de base de données.

Besoins changent, et un projet arrivera à échéance, il est utile si vous pouvez modifier des parties de l'application sans avoir ces modifications en cascade dans tout le système. Modularizing une application permet de générer séparément les composants d'application (et faiblement couplés) et modifier des parties entières de votre application sans affecter le reste du code.

En outre, ne peut-être pas à charger tous les éléments de votre application à la fois. Imaginez une application de gestion de client dans lequel les utilisateurs session et peuvent puis gérer leur pipeline de prospect ainsi que vérifier messagerie de n'importe quel les prospects. Si un utilisateur vérifie le courrier électronique plusieurs fois par jour mais gère le pipeline uniquement chaque jour ou deux, pourquoi charger le code pour gérer le pipeline jusqu'à ce qu'elle est nécessaire ? Il serait pratique si une application prise en charge du chargement à la demande de parties de l'application, une situation qui peut être résolue en modularizing l'application.

L'équipe patterns & practices chez Microsoft a créé un projet appelé prism (ou CompositeWPF) qui est destiné à résoudre des problèmes similaires à celles-ci pour les applications Windows Presentation Foundation (WPF) et prism a été mise à jour pour prendre en charge Silverlight ainsi. Le package prism est un mélange d'infrastructure et de conseils pour la création d'applications. L'infrastructure, appelé composants application bibliothèque (CAL), permet les opérations suivantes :

  • Application modularité : générer des applications de composants partitionnées.
  • Composition de l'interface utilisateur : permet faiblement couplés composants formulaire des interfaces utilisateur insu discrète du reste de l'application.
  • Emplacement du service : distinctes horizontales services (par exemple, l'enregistrement et l'authentification) services verticales (logique métier) pour promouvoir nettoyer superposition d'une application.

La CAL est écrit avec ces mêmes principes de conception n'oubliez pas, et pour les développeurs d'applications, il est une structure de style de ballottage — prendre ce que vous avez besoin et laissez le reste. figure 1 illustre la disposition de la licence client par rapport à votre propre application de base.

fig02.gif

Figure 1 bibliothèque application composite

La licence d'accès client prend en charge ces services pour vous aider à composer votre application de parties plus petites. Cela signifie que la CAL gère les éléments sont chargés (et quand) ainsi que les fonctionnalités de base. Vous pouvez choisir parmi ces fonctionnalités vous aider à faire votre travail et qui peut s'afficher dans la façon dont.

Mon exemple de cet article utilise autant de CAL que possible. Il est une application shell qui utilise la CAL pour charger plusieurs modules au moment de l'exécution, placer des vues dans zones (comme illustré à la figure 2 ) et les services de support. Mais avant de à ce code, vous devez comprendre certains concepts de base sur l'injection de dépendance (également appelé inversion de contrôle ou IoC). Nombre de fonctionnalités de la CAL s'appuient sur injection de dépendance, afin que comprendre les notions de base va vous aider développer l'architecture de votre projet Silverlight avec prism à.

fig03.gif

La figure 2 architecture des applications composites

Présentation d'injection de dépendance

Dans le développement classique, un projet commence par un point d'entrée (un fichier exécutable, une page default.aspx et ainsi de suite). Vous pouvez développer votre application comme un seul projet géant, mais dans la plupart des cas qu'existe, un niveau de modularité dans votre application charge un nombre d'assemblys font partie du projet. L'assembly principal connaît les assemblys qu'il a besoin et crée durs les références à ces éléments. Au moment de la compilation, le projet principal est informé de tous les assemblys référencés et l'interface utilisateur se compose de contrôles statiques. L'application est en contrôle de code qu'il a besoin et généralement sait tout le code qu'il peut utiliser. Cela devient un problème, cependant, parce que développement a lieu dans le projet application principale. Heure de création comme une application monolithique augmente, et les modifications conflictuelles peuvent ralentir de développement.

Injection de dépendance vise à inverser cette situation en fournissant des instructions que définir des dépendances au moment de l'exécution. Au lieu du projet contrôle de ces dépendances, un morceau de code appelée un conteneur est responsable de leur injectant.

Mais pourquoi est-ce important ? Tout d'abord, modularizing votre code doit faciliter le travail à tester. La possibilité de permuter les dépendances du projet permet de tester nettoyage afin que seul le code doit être testée puisse être la source d'un test échoue, au lieu du code quelque part dans la chaîne imbriquée de dépendances. Voici un exemple concret. Imaginez que vous possédez un composant qui autres développeurs permet de rechercher des adresses de sociétés particuliers. Votre composant dépend d'un composant d'accès aux données qui extrait les données pour vous. Lorsque vous testez votre composant, vous commencez par tester par rapport à la base de données et certains des tests échouent. Mais parce que le schéma et versions de la base de données sont constamment, vous ne savez pas si vos tests échouent en raison de votre propre code ou le code d'accès aux données. Avec la dépendance matérielle de votre composant sur les données composant d'accès, test de l'application devient instable et causes évolution du lorsque suivi défaillances dans votre code ou dans l'autre code.

Votre composant peut ressembler à ceci :

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

  public AddressComponent()
  {
  }

  ...
}

Au lieu d'un composant câblé, vous a pu accepter une interface qui représente votre accès aux données, comme illustré ici :

public interface IDataAccess
{
  ...
}

public class AddressComponent
{
  IDataAccess data;

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

  ...
}

En règle générale, une interface est utilisée afin de pouvoir créer une version qui vous permet d'ajuster votre code. Cette approche est souvent appelée «factices». Simulacres signifie création d'une implémentation de la dépendance ne représente pas réellement la version réelle. Littéralement, vous créez une implémentation factice.

Cette approche est meilleure parce que la dépendance (IDataAccess) peut être injectée dans le projet pendant la construction de l'objet. L'implémentation du composant IDataAccess varient selon la configuration requise (test ou réel).

C'est essentiellement le fonctionne d'injection de dépendance, mais comment l'injection est gérée ? Le travail du conteneur consiste à gérer la création de types, qui elle effectue en vous permettant d'inscrire les types et les résoudre. Par exemple, vous possédez une classe concrète qui implémente l'interface IDataAccess. Au démarrage haut de l'application, vous pouvez indiquer le conteneur pour inscrire le type. Nulle part ailleurs dans votre application où vous devez le type, vous pouvez demander le conteneur de résoudre le type, comme illustré ici :

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

...

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

Selon la situation (test ou production), vous pouvez changer l'implémentation de IDataAccess simplement en modifiant l'enregistrement. En outre, le conteneur peut gérer construction injection de dépendances. Si un objet qui doit être créé par le constructeur du conteneur est une interface que le conteneur peut résoudre, il résout le type et le transmet au constructeur, comme illustré figure 3 .

La figure 3 type de résolution par le conteneur

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>();
}

Notez que constructeur de AddressComponent prend une implémentation de IDataAccess. Lorsque le constructeur crée la classe AddressComponent lors de la résolution, il crée automatiquement l'instance de IDataAccess et passe à la AddressComponent.

Lorsque vous enregistrez des types avec le conteneur, vous indiquez également le conteneur pour traiter la durée de vie du type de manière spéciale. Par exemple, si vous travaillez avec un composant de journalisation, vous souhaitez peut-être traiter comme un singleton afin que chaque partie de l'application qui a besoin de journalisation ne reçoit pas sa propre copie (qui est le comportement par défaut). Pour ce faire, vous pouvez fournir une implémentation de la classe abstraite LifetimeManager. Plusieurs responsables de durée de vie sont pris en charge. ContainerControlledLifetimeManager est un singleton par processus et PerThreadLifetimeManager est un singleton par thread. Pour ExternallyControlledLifetimeManager, le conteneur contient une référence faible pour le singleton. Si l'objet est libéré en externe, le conteneur crée une nouvelle instance, sinon il retourne l'objet live contenue dans la référence faible.

Vous utilisez la classe LifetimeManager en le spécifiant lorsque vous enregistrez un type. Voici un exemple :

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

Dans la CAL, le conteneur IoC est basé sur l'infrastructure Unity du groupe de modèles et pratiques. Vous utiliserez le conteneur Unity dans les exemples suivants, mais il existe également un certain nombre de possibilités d'open source vers le conteneur IoC Unity, telles que Ninject Spring.NET, Castle et StructureMap. Si vous êtes familier avec et déjà l'utilisation d'un conteneur IoC différent Unity, vous pouvez fournir votre propre conteneur (bien qu'il prend un peu plus d'efforts).

Comportement de démarrage

Normalement dans une application Silverlight, le comportement de démarrage consiste simplement à créer classe la page XAML principal et assignez-le à RootVisual propriété l'application. Dans une application composite, ce travail est toujours nécessaire, mais au lieu de créer la classe de page XAML, une application composite utilise généralement une classe amorçage pour gérer le comportement de démarrage.

Pour commencer, vous devez une nouvelle classe qui dérive de la classe UnityBootstrapper. Cette classe est dans l'assembly Microsoft.Practices.Composite.UnityExtensions. Le programme d'amorçage contient des méthodes substituables qui gèrent les différentes parties du comportement de démarrage. Souvent, vous ne remplace pas chaque méthode de démarrage uniquement le ceux nécessaires. Les deux méthodes que vous devez substituer sont CreateShell et GetModuleCatalog.

La méthode CreateShell est où la classe XAML principale est créée. Cela est généralement appelée l'interpréteur de commandes, car il est le conteneur visuel de composants de l'application. Mon exemple comprend un programme d'amorçage qui crée une nouvelle instance de la classe de shell et assigne à RootVisual avant de retourner cette nouvelle classe de shell, comme illustré ici :

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

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

La méthode GetModuleCatalog, j'expliquerai dans la section suivante, renvoie la liste des modules à charger.

Maintenant que vous disposez d'une classe de programme d'amorçage, vous pouvez l'utiliser dans méthode de démarrage de votre application Silverlight. En règle générale, vous créez une nouvelle instance de la classe du programme d'amorçage et appelez sa méthode Run, comme illustré figure 4 .

La figure 4 Création d'une instance du programme d'amorçage

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();
  }

  ...
}

Le programme d'amorçage est également complexe d'inscription de types avec le conteneur que différentes parties de l'application requis. Pour ce faire, vous substituez la méthode ConfigureContainer du programme d'amorçage. Cela vous permet d'enregistrer tous les types qui vont être utilisées par le reste de l'application. la figure 5 illustre le code.

Types d'enregistrement figure 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()
  {
    ...
  }
}

Ici, le code enregistre une interface pour une classe qui implémente l'interface IShellProvider, qui est créé dans notre exemple et fait pas partie de l'infrastructure d'accès. Ainsi nous pouvez l'utiliser dans notre implémentation de la méthode CreateShell. Nous pouvez résoudre l'interface et l'utiliser pour créer une instance de l'interpréteur de commandes pour nous peut affecter à RootVisual et retourner. Cette méthodologie peut sembler un travail supplémentaire, mais vous abordez comment la CAL vous aide à créer votre application, il devient clair comment ce programme d'amorçage vous aide.

Modularité

Dans un environnement .NET par défaut, l'assembly est l'unité principale de travail. Cette désignation permet aux développeurs de travailler sur leur code séparément de l'autre. Dans la CAL, chacun de ces unités de travail est un module et de CAL pour utiliser un module, il nécessite une classe qui peut communiquer comportement de démarrage du module. Cette classe doit également être à prend en charge l'interface IModule. L'interface IModule exige une méthode unique appelée Initialize qui permet au module lui-même configuré pour être utilisé dans le reste de l'application. L'exemple inclut un module ServerLogger qui contient les fonctionnalités de journalisation pour notre application. La classe ServerLoggingModule prend en charge l'interface IModule comme illustré ici :

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

Le problème est que nous ne savons pas ce que nous voulons initialiser de notre module. Étant donné qu'il s'agit d'un module ServerLogging, il semble logique que vous souhaitez enregistrer un type de journalisation pour nous. Nous souhaitons utiliser le conteneur pour inscrire le type afin que quiconque a besoin de la fonctionnalité de journalisation peut utiliser simplement notre implémentation sans connaître le type exact d'enregistrement qu'il exécute.

Nous d'obtenir le conteneur en créant un constructeur qui accepte l'interface IUnityContainer. Si vous vous souvenez pas la discussion injection de dépendance, le conteneur utilise injection de constructeur pour ajouter des types qu'il connaît. IUnityContainer représente le conteneur dans notre application, si nous ajoutons ce constructeur, nous pouvez puis enregistrez-le et utilisez dans notre initialisation comme pour :

public class ServerLoggerModule : IModule
{
  IUnityContainer theContainer;

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

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

Une fois initialisé, ce module est responsable de l'implémentation de l'enregistrement de l'application. Mais comment does ce module chargé ?

Lorsque vous utilisez la CAL pour rédiger une application, vous devez créer un ModuleCatalog qui contient tous les modules de l'application. Vous pouvez créer ce catalogue en remplacement GetModuleCatalog appel du programme d'amorçage. Dans Silverlight, vous pouvez remplir ce catalogue avec code ou XAML.

Avec du code, vous créez une nouvelle instance de la classe ModuleCatalog et remplissez avec les modules. Par exemple, regardez ce :

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;
}

Ici, vous ajoutez simplement un module unique appelé ServerLogger, le type défini dans ModuleType propriété de ModuleInfo. En outre, vous pouvez spécifier dépendances entre modules. La mesure où certains modules peuvent dépendent d'autres personnes, dépendances aide le catalogue de connaître l'ordre dans lequel mettre dans les dépendances. À l'aide de la propriété ModuleInfo.DependsOn, vous pouvez spécifier les modules nommés qui sont requis pour charger un autre module.

Vous pouvez charger le catalogue directement à partir d'un fichier XAML, comme illustré ici :

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

Le fichier XAML contient les informations type même que vous pouvez créer avec le code. L'avantage de l'utilisation de XAML est que vous pouvez le modifier à la volée. (Imaginez extraire le fichier XAML à partir d'un serveur ou d'un autre emplacement en sur lequel utilisateur connecté.) figure 6 montre un fichier catalog.xaml.

Figure 6 exemple Catalog.xaml fichier

<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>

Dans ce catalogue XAML, le groupe comprend deux modules et le deuxième module varie selon que la première. Vous pouvez utiliser un catalogue XAML spécifique en fonction de rôles ou autorisations, comme vous pourriez avec le code.

Une fois le catalogue est chargé par le programme d'amorçage, il tente de créer des instances de classes du module et les autoriser à s'initialiser. Dans les exemples de code ici, les types doivent être référencées par l'application (par conséquent, déjà chargé en mémoire) pour ce catalogue fonctionner.

Il s'agit où cette fonctionnalité devient indispensables à Silverlight. Bien que l'unité de travail est l'assembly, vous pouvez spécifier un fichier .xap qui contient le module ou modules. Pour ce faire, vous spécifiez une valeur de référence dans ModuleInfo. La valeur de référence est un chemin d'accès du fichier .xap qui contient le module :

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;
}

Lorsque vous spécifiez un fichier .xap, le programme d'amorçage sait que l'assembly n'est pas disponible et sort sur le serveur et extrait le fichier .xap asynchrone. Une fois le fichier .xap est chargé, prism charge l'assembly et crée le type de module et initialise le module.

Pour les fichiers .xap qui contient plusieurs modules, vous pouvez créer un ModuleGroup contient un ensemble d'objets ModuleInfo et définir la référence de ModuleGroup pour charger les modules à partir d'un fichier .xap unique :

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

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

Pour les applications Silverlight, cela consiste à rédiger vos applications à partir de plusieurs fichiers .xap, qui vous permet version différentes sections de votre application composée séparément.

Lorsque vous créez modules Silverlight hébergé dans un fichier .xap, vous créez une Application Silverlight (pas une bibliothèque Silverlight). Alors vous faire référence à tous les projets de module que souhaitez placer dans le fichier .xap. Vous devez supprimer les fichiers app.xaml et page.xaml car ce fichier .xap ne pas être chargé et exécuté comme un fichier .xap classique. Le fichier .xap est simplement un conteneur (peut être un fichier .zip, n'a pas d'importance). En outre, si vous référencez les projets qui sont déjà référencées dans le projet principal, vous pouvez modifier ces références à la copie locale = false dans les propriétés, car vous ne devez les assemblys dans le fichier .xap (l'application principale a déjà chargé, donc le catalogue n'essaie pas les charger une deuxième fois.)

Mais du chargement d'une application importante avec plusieurs appels sur le réseau ne semble pas comme elle pourrait améliorer les performances. Qui est là que InitializationMode propriété de ModuleInfo entre en jeu. InitializationMode prend en charge deux modes : WhenAvailable, dans lequel le fichier .xap est chargé de façon asynchrone et ensuite initialisé (il s'agit du comportement par défaut) et OnDemand, dans laquelle le .xap est chargé lorsque explicitement demandée. Comme le catalogue de module ne connaît pas les types dans les modules jusqu'à ce que l'initialisation, résoudre des types qui sont initialisés avec OnDemand échouera.

Prise en charge à la demande des modules et des groupes permet de charger certaines fonctionnalités dans une grande application selon les besoins. Démarrage accéléré et autre code requis peut être chargé que les utilisateurs interagissent avec une application. Ceci est une fonctionnalité très à utiliser lorsque vous êtes autorisé à séparer les parties d'une application. Utilisateurs doivent seulement quelques parties de l'application ne doivent pas télécharger de code qu'ils vous n'utilisez jamais.

Pour charger un module à la demande, vous devez accéder à une interface IModuleManager. La plupart des cas, vous demander ce dans le constructeur de la classe doit charger un module à la demande. Puis vous utilisez IModuleManager pour charger le module en appelant LoadModule, comme illustré figure 7 .

La figure 7 appel 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");
  }
}

Les modules sont simplement l'unité de la modularité de vos applications. Dans Silverlight, considérez un module beaucoup comme vous le feriez pour un projet de bibliothèque, mais avec travail supplémentaire de l'initialisation de module, vous pouvez découpler votre modules du projet principal.

Composition de l'interface utilisateur

Dans une application Explorateur par défaut, le volet gauche affiche une liste ou arborescence d'informations et le côté droit contient des détails sur l'élément sélectionné dans le volet de gauche. Dans la CAL, ces zones sont appelées des régions.

La licence d'accès client prend en charge régions définition directement dans XAML en utilisant une propriété jointe sur la classe RegionManager. Cette propriété vous permet de spécifier les régions dans votre environnement et puis indiquer les vues doivent être hébergés dans la zone. Par exemple, notre environnement possède deux pays, LookupRegion et DetailRegion, comme illustré ici :

<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>

Un RegionName peut être appliqué à l'un ItemsControl et ses contrôles dérivées (par exemple, ListBox); sélecteur et ses contrôles dérivées (par exemple, TabControl); et ContentControl et ses contrôles dérivées (par exemple, ScrollViewer).

Une fois que vous définissez des régions, vous pouvez diriger modules pour charger leurs vues dans les zones en utilisant l'interface IRegionManager, comme illustré ici :

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);
  }
}

Cette fonctionnalité permet de définir des zones dans votre application où vues peuvent apparaît et puis les modules définissent comment placer des vues dans la zone, permettant le shell être complètement ignorance des vues.

Le comportement des régions peut être différent selon le type de contrôle hébergé. L'exemple utilise un élément ScrollViewer afin qu'un et qu'une seule peut être ajouté à la zone de. Régions ItemControl permettent par contre, pour plusieurs vues. À mesure que chaque vue est ajoutée, il apparaît comme un nouvel élément dans le contrôle ItemsControl. Cette fonctionnalité facilite la génération des fonctionnalités comme un tableau de bord.

Si vous utilisez un modèle MVVM pour définir les affichages, vous pouvez mélanger les zones et les aspects d'emplacement de service du conteneur pour rendre vos affichages et afficher les modèles ignorance de l'autre puis laissez le module de les joindre au moment de l'exécution. Par exemple, si vous changez le GameListModule, vous pouvez enregistrer des vues et modèles de vues avec le conteneur et les joindre avant d'appliquer l'affichage dans la zone, comme illustré figure 8 .

La figure 8 jointure des vues dans une zone

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>();
  }
}

Cette approche vous permet d'utiliser composition de l'interface utilisateur tout en préservant la séparation stricte des MVVM.

Agrégation des événements

Après avoir plusieurs vues dans vos applications à interface composition, vous rencontrez un problème courant. Même si vous avez créé indépendants vues pour prendre en charge une meilleure test et développement, il existe souvent les points de contact où les vues ne peut pas être complètement isolés. Ils sont couplés logiquement, car ils devront communiquer, mais vous souhaitez conserver les comme faiblement couplés que possible sans tenir compte du couplage logique.

Pour activer un couplage lâche et la communication inter-vue, la CAL prend en charge un service appelé événement d'agrégation. Événement agrégation permet d'accéder à différentes parties du code aux éditeurs et les consommateurs d'événements globales. Un tel accès offre un moyen simple de communiquer sans être étroitement et à l'aide IEventAggregator interface de la CAL. IEventAggregator vous permet de publier et s'abonner aux événements sur les différents modules de votre application.

Vous pouvez communiquer, vous devez une classe qui dérive de EventBase. En général, vous créez un événement simple qui dérive de CompositePresentationEvent <t> classe. Cette classe générique permet de spécifier la charge utile de l'événement que vous souhaitez publier. Dans ce cas, le GameListViewModel va publier un événement après avoir sélectionné un jeu de sorte que les autres contrôles pour modifier leur contexte que l'utilisateur sélectionne un jeu peuvent s'abonner à cet événement. Notre classe d'événements ressemble à ceci :

public class GameSelectedEvent : CompositePresentationEvent<Game>
{
}

Une fois que l'événement est défini, l'agrégation événement pouvez publier l'événement en appelant sa méthode GetEvent. Il récupère l'événement singleton qui va être agrégée. La première occurrence qui appelle cette méthode crée le singleton. De l'événement, vous pouvez appeler la méthode Publish pour créer l'événement. L'événement de publication est similaire à déclencher un événement. Vous ne devez pas publier l'événement jusqu'à ce qu'il doit envoyer des informations. Par exemple, lorsqu'un jeu est sélectionné dans la GameList, notre exemple publie le jeu sélectionné en utilisant le nouvel événement :

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

Dans d'autres parties de votre application composée, vous pouvez vous abonner à l'événement à appeler après que l'événement est publié. La méthode Subscribe de l'événement permet de spécifier la méthode à appeler lorsque l'événement est publié, une option qui permet de demander sémantique de thread pour appeler l'événement (par exemple, le thread d'interface utilisateur est généralement utilisé), et si l'agrégateur de maintenir une référence aux informations transmises dans afin qu'il ne soit pas soumis à garbage collection :

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

En tant qu'abonné, vous pouvez également spécifier des filtres qui sont appelées uniquement dans des situations spécifiques. Imaginez un événement qui renvoie l'état d'une application et un filtre qui est appelé uniquement pendant certains états de données.

L'agrégation d'événements permet d'avoir de communication entre vos modules sans provoquer de couplage. Si vous publiez un événement qui est abonné jamais ou s'abonner à un événement est jamais publié, votre code échoue jamais.

Commandes de délégué

Dans Silverlight (contrairement à WPF), une infrastructure principale true n'existe pas. Cela force l'utilisation de code-behind dans les affichages pour accomplir des tâches doit être effectuées plus facilement directement dans XAML en utilisant l'infrastructure principale. Jusqu'à ce que Silverlight prend en charge cette fonctionnalité, la licence d'accès client prend en charge une classe qui vous permet de résoudre ce problème : le DelegateCommand.

Mise en route de DelegateCommand, vous devez définir le DelegateCommand dans votre ViewModel afin que vous pouvez lier données lui. Dans ViewModel, vous devez créer un nouveau DelegateCommand. Le DelegateCommand attend le type de données à envoyer à celui-ci (objet souvent si aucune donnée n'est utilisée) et un ou deux méthodes de rappel (ou fonctions lambda). Le premier de ces méthodes est l'action à exécuter lorsque la commande est déclenchée. Si vous le souhaitez, vous pouvez spécifier un deuxième rappel à appeler pour tester si la commande peut être déclenchée. L'idée est de permettre la désactivation des objets dans l'interface utilisateur (boutons, par exemple) lorsque n'est pas valide pour déclencher la commande. Par exemple, notre GameDetailsViewModel contient une commande pour la prise en charge l'enregistrement des données :

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

Lorsque SaveCommand est exécutée, elle appelle la méthode Save sur notre ViewModel. La méthode CanSave est ensuite appelée pour vous assurer que la commande est valide. Cela permet la DelegateCommand désactiver l'interface utilisateur si nécessaire. Comme l'état des modifications d'affichage, vous pouvez appeler la méthode DelegateCommand.RaiseCanExecuteChanged pour forcer une nouvelle inspection de la méthode CanSave pour activer ou désactiver l'interface utilisateur si nécessaire.

Pour lier ce XAML, utilisez le Click.Command propriété figurant dans l'espace de noms Microsoft.Practices.Composite.Presentation.Commands jointe. Puis lier la valeur de la commande à la commande que vous avez dans votre ViewModel, comme suit :

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

Maintenant lorsque l'événement Click est déclenché, notre commande est exécutée. Si vous le souhaitez, vous pouvez spécifier un paramètre de commande à envoyer à la commande afin de pouvoir la réutiliser.

Comme vous vous demandez peut-être, la seule commande qui existe dans la CAL est l'événement Click pour un bouton (ou tout autre sélecteur). Mais les classes que vous pouvez utiliser pour écrire vos propres commandes sont relativement simples. L'exemple de code inclut une commande pour SelectionChanged sur une zone de liste/liste modifiable. Cette commande est appelée le SelectorCommandBehavior et dérive de CommandBehaviorBase <t> classe. En examinant l'implémentation de comportement de commande personnalisée vous fournira un point de départ pour écrire vos propres comportements de commande.

Conclusion

Il y a douleur parfaites points lors du développement de grandes applications Silverlight. En créant vos applications avec un couplage lâche et modularité, vous obtenir les avantages et pouvez être souple en réponse à modifier. Le projet prism à partir de Microsoft fournit les outils et conseils pour permettre cette souplesse à venir à la surface de votre projet. Alors que prism n'est pas une approche unique, la modularité de la licence client signifie que vous pouvez utiliser ce qui tient dans vos scénarios spécifiques et laisse le reste.

Shawn Wildemuth est un MVP Microsoft (c#) et fondateur de Wildermuth Consulting Services. Il est l'auteur de plusieurs livres et de nombreux articles. En outre, Shawn exécute actuellement la présentation de Silverlight, apprentissage Silverlight 2 dans le pays. Il peut être contacté à Shawn@wildermuthconsulting.com.