Groupement de contrôles pour créer un UserControl (Partie 2)

Réutiliser le User Control :
Dans le poste précedent, vous avez créé un nouveau UserControl (AddressControl) pour taper des informations d’adresse. En convertissant une partie de MainPage.xaml dans un UserControl, Expression Blend créé automatiquement les références et a ajouté l’élément à la page. Mais que faire si vous voulez ajouter le même UserControl à nouveau sur la même page? Ce poste vous montre comment procéder.

Pour ajouter un UserControl, dans ce cas, le AddressControl, à MainPage.xaml, il suffit de suivre les étapes suivante :

1. Continuer à partir de la section précédente et faire en sorte que MainPage.xaml soit ouvert sur ​​le plan de travail.

2. Ouvrez le panneau Actifs (Assets panel) dans Expression Blend et allez dans la catégorie de projet (Project category) en cliquant dessus.

Le AddressUserControl apparaît dans cette catégorie, comme le montre la Figure suivante :

assets panel

Avoir l’AddressUserControl ici peut ne pas être surprenant, mais ce que vous pouvez trouver surprenant, c’est que MainPage se trouve également ici. C’est dans cette catégorie parce que MainPage, comme AddressUserControl, est également un User control. Vous pouvez ouvrir la vue XAML pour AddressUserControl et MainPage et vérifier cette hypothèse. Comme vous l’avez vu, toutes les pages que vous avez créé en XAML servent de composants réutilisables et vous permettent de les imbriquer de User Control.

3. Faites glisser et déposez sur AddressUserControl MainPage.xaml sur le plan de travail. Un autre AddressUserControl est ajouté à MainPage.

4. Appuyez sur F5 pour exécuter l’application. L’application s’exécute dans la fenêtre du navigateur et affiche les deux AddressUserControls dans la même page. Ces deux contrôles se resemblent et fonctionnent de la même façon, mais vous permettent de stocker et d’afficher deux séries d’adresses.

Il ya plusieurs avantages à mettre quelque chose comme une adresse dans un UserControl, mais l’une de nos raisons préférés est que, après que cela devient un UserControl, il peut être géré de façon indépendante et entretenus. Par exemple, si vous changez le conteneur de disposition (layout container) du StackPanel à un GridPanel et réorganiser la façon dont les champs sont affichés, ils seront automatiquement mis à jour dans chaque page dans laquelle le AddressUserControl est utilisé.

Création de propriétés pour votre UserControl :

Après avoir créé un UserControl, il serait bon de l’initialiser (operation set) et de lire quelques valeurs que vous avez tapé dans chaque champ. Un moyen d’atteindre ce but c’est de créer des propriétés pour chaque champ. (L’autre façon d’y parvenir est de faire du Data binding, peut être que je le traiterais dans un autre poste).

Pour ajouter des propriétés à chacun des champs (à savoir StreetNumber, streetName, City, State, PostalCode, et Country) de la AddressUserControl créé dans poste précedent, ouvrez le fichier AddressUserControl.xaml.cs sur le plan de travail et ajouter le code suivant dans la classse AddressUserControl :

public string StreetNumber
{
  get
  {
    return txtStreetNumber.Text;
  }
  set
  {
   txtStreetNumber.Text = value;
  }
}
 
public string StreetName
{
  get
  {
   return txtStreetName.Text;
  }
  set
  {
   txtStreetName.Text = value;
  }
}
 
public string City
{
  get
  {
   return txtCity.Text;
  }
  set
  {
   txtCity.Text = value;
  }
}
 
public string State
{
  get
  {
   return txtState.Text;
  }
  set
  {
   txtState.Text = value;
  }
}
 
public string PostalCode
{
  get
  {
   return txtPostalCode.Text;
  }
  set
  {
   txtPostalCode.Text = value;
  }
}
 
public string Country
{
  get
  {
   return txtCountry.Text;
  }
  set
  {
   txtCountry.Text = value;
  }
}

Appuyez sur Ctrl+Maj+B pour compiler l’application, puis ouvrez le fichier MainControl.xaml en mode Création. Sélectionnez la première AddressUserControl et ouvrez le panneau de Propriétés. Dans la section Divers (Miscellaneous), vous trouverez désormais toutes les propriétés que vous venez d’ajouter. Modifiez les valeurs de ces propriétés et
Appuyez sur F5 pour exécuter l’application et vérifier si les valeurs apparaissent dans la
AddressUserControl.

Kamel DJELLAL
Chef de projet
EDIS CONSULTING – GROUPE UBISIDE

http://www.ubiside.fr/

Groupement de contrôles pour créer un UserControl (Partie 1)

Lorsque vous écrivez une application, l’application se compose de certains blocs de base que vous pouvez utiliser encore et encore. Ce bloc peut être une certaine logique qui existe comme un morceau de code (peut-être sous la forme d’une classe ou une méthode) ou sous la forme d’une interface utilisateur (qui est l’objet de la discussions ici).

Par exemple, votre application peut contenir une interface utilisateur simple qui permet aux
utilisateurs de votre site Web de taper des informations d’adresse. Vous utilisez peut-être ceci dans plusieurs pages; par exemple, vous pouvez l’utiliser pour recueillir l’adresse de l’utilisateur, l’adresse de travail, l’adresse d’expédition, et ainsi de suite. L’adresse peut se composé de plusieurs champs, tels que adresse, ville, et ainsi de suite. Silverlight vous permet de créer un élément d’interface utilisateur qui contient une autre interface utilisateur pour former un élément composite appelé un UserControl. En outre aux éléments d’interface utilisateur, UserControls vous permet d’ajouter un peu de logique telles que la validation dans le code.

Dans cette section, nous allons vous montrer comment créer un tel contrôle, ainsi que la façon de l’utiliser.

Exemple : créer un UserControl d’Adresse :

Dans cet exemple, nous allons vous montrer comment créer un type de contrôle que de nombreuses applications utilisent régulièrement – un contrôle pour recueillir des informations d’adresse.

Pour créer un contrôle d’adresses, vous devez avoir les champs suivants:

✓ Numero de la rue (Street Number)
✓ Nom de la rue (Street Name)
✓ Ville (City)
✓ État (State)
✓ ZIP ou code postal (Postal code)
✓ pays (Country)

Par souci de simplicité, vous pouvez définir tous ces champs comme des champs texte. Lire la suite pour voir comment.

1. Créer un nouveau projet Silverlight en choisissant Fichier ➪ Nouveau projet à partir du menu, et quand la boîte de dialogue Nouveau projet apparaît, sélectionnez Silverlight 4 Application + site Web et lui donner un nom approprié tel que AddressUserControlExample. Puis cliquez sur OK.
Un nouveau projet est créé et le fichier MainPage.xaml est représenté sur le plan de travail.

2. Double-cliquez sur le StackPanel dans le ToolsPanel pour l’ajouter à la MainPage. Faites glisser les poignées de redimensionnement pour définir la hauteur et la largeur du StackPanel à une dimension appropriée.

3. Ajouter six paires de TextBlocks et TextBoxes dans le plan de travail pour afficher les six champs d’adresse. Remplacer le texte par défaut dans le TextBlocks en double-cliquant dessus et en tapant sur ​​eux afin qu’ils lisent le numero de la rue, Nom de la rue, ville, État, code postal, et pays, comme le montre la figure suivante :

4. Ctrl+cliquez sur le TextBlocks un après l’autre jusqu’à ce que vous avez cliqué sur tous les TextBlocks sur le plan de travail. Ceci permet de sélectionner tous les TextBlocks et vous permet de modifier toutes les propriétés simultanément.

5. Allez dans le panneau de Propriétés, puis appuyez sur le bouton Gras dans le group text afin de rendre le texte dans tous les TextBlocks gras.

6. Ctrl+cliquez sur les contrôles TextBox un après l’autre jusqu’à ce que vous avez cliqué sur tous les TextBlocks sur le plan de travail.

7. Cliquez sur l’option Propriétés avancées à côté du champ de texte dans le Panneau Propriétés, et dans le menu, choisissez Réinitialiser (reset).

Le texte par défaut dans le contrôle TextBox est effacé et le code XAML pour les opérations que vous venez de réaliser devrait maintenant ressembler à ceci (certains balisage a été remplacé par ellipses […] Par souci de concision):

 <br />
<UserControl . . .> <br />
  <Grid x:Name=”LayoutRoot” Background=”White”> <br />
    <StackPanel HorizontalAlignment=”Left” <br />
    Margin=”8,22,0,167” Width=”291”> <br />
       <TextBlock Text=”Street Number” <br />
       TextWrapping=”Wrap” FontWeight=”Bold”/> <br />
       <TextBox TextWrapping=”Wrap”/> <br />
       <TextBlock Text=”Street Name” <br />
       TextWrapping=”Wrap” FontWeight=”Bold”/> <br />
       <TextBox TextWrapping=”Wrap”/> <br />
       <TextBlock Text=”City” TextWrapping=”Wrap” <br />
       FontWeight=”Bold”/> <br />
       <TextBox TextWrapping=”Wrap”/> <br />
       <TextBlock Text=”State” TextWrapping=”Wrap” <br />
       FontWeight=”Bold”/> <br />
       <TextBox TextWrapping=”Wrap”/> <br />
       <TextBlock Text=”Postal code” <br />
       TextWrapping=”Wrap” FontWeight=”Bold”/> <br />
       <TextBox TextWrapping=”Wrap”/> <br />
       <TextBlock Text=”Country” TextWrapping=”Wrap” <br />
       FontWeight=”Bold”/> <br />
       <TextBox TextWrapping=”Wrap”/> <br />
    </StackPanel> <br />
  </Grid> <br />
</UserControl> <br />

8. Utilisation de l’outil de sélection, sélectionnez le numéro de rue control TextBox du plan de travail et définir son nom à « txtStreetNumber » dans le panneau Propriétés. De même, définir le nom de rue, ville, État, Zones de texte Code postal, et pays à « txtStreetName », « txtCity », « txtState », « txtPostalCode », et « txtCountry », respectivement.
Donner un nom à ces champs sera utile lorsque vous définissez des valeurs pour plus tard.

9. Sélectionnez le StackPanel soit en cliquant dessus à partir des objets et Panneau Montage ou en utilisant l’outil de sélection et de sélection des StackPanel du plan de travail.

10. Faites un clic droit et choisissez en faire un contrôle utilisateur (Make Into User Control). Une UserControl boîte de dialogue apparaît, comme le montre la Figure suivante :

11. Remplacer UserControl1 dans le champ Nom en entrant AddressUserControl; puis cliquez sur OK. Un nouveau fichier nommé AddressUserControl.xaml est ajouté et l’ensemble des contenu du StackPanel être déplacée à ce nouveau contrôle.

12. Cliquez sur l’onglet MainPage.xaml sur le plan de travail. Le fichier MainPage.xaml s’ouvre et affiche un point d’exclamation en haut des champs d’adresse. Ceci apparait car le projet doit être compilé. Vous pouvez compiler le projet en appuyant sur Ctrl+Maj+B, ou vous pouvez appuyer sur F5 pour exécuter le projet.

13. Si vous avez compilé le projet, fermez le navigateur, et Expression Blend, cliquez sur le bouton sur le plan de travail XAML pour ouvrir la vue XAML de la MainPage.xaml.
Dans le fichier XAML, notez que tous les champs adresse que vous avez ajoutés sont
remplacée par une ligne unique qui contient un élément appelé
local: AddressUserControl:

 <br />
<UserControl ... <br />
xmlns:local=”clr-namespace:UserControls” <br />
x:Class=”AddressUserControlExample. <br />
MainPage” <br />
Width=”640” Height=”480”> <br />
  <Grid x:Name=”LayoutRoot” Background=”White”> <br />
    <local:AddressUserControl <br />
    HorizontalAlignment=”Left” Margin=”8,8,0,181” <br />
    Width=”291”/> <br />
  </Grid> <br />
</UserControl> <br />

Notez que dans l’élément UserControl, une ligne de départ avec le texte xmlns: local a été ajouté. Cet élément permet de savoir à Silverlight où le AddressUserControl vous venez d’ajouter est situé.

Prochain poste : on verra comment réutiliser ce UserControl et créer des propriétés pour ce dernier

Kamel DJELLAL
Chef de projet
EDIS CONSULTING – GROUPE UBISIDE

http://www.ubiside.fr/

Architecture Silverlight LOB – Concepts et technologies (Partie 7)

Dans ce poste, je vais parler de la construction d’un framework MVVM.

Comme je le disais dans le poste précédent, ce poste devrait être sur le OrderView. Ce view devrait permettre aux utilisateurs de choisir des éléments d’une liste de produits, les ajouter à l’ordre courant, choisir les quantités, voir le prix courant total et soumettre la commande. En outre, l’utilisateur doit être en mesure de filtrer, trier et avoir une pagination sur la liste des produits. Que devient le produit choisi, ajoutez-le à une autre collection ou de créer un détail de la commande fondé sur lui, puis mettre à jour certaines autres données sur l’UI et, enfin, soumettre les modifications sur le serveur. Le truc, c’est que tout le monde a sa propre façon de programmation et de la sorte quand vous vous retrouvez dans une équipe, vous pouvez constater que deux personnes code la même chose d’une manière différente, et on a un bug dans la situation A et l’autre a un bug dans la situation B. Ayant un bon cadre MVVM avec une méthodologie bien définie est indispensable pour prévenir ces situations. Dans ce poste, je veux parler des éléments essentiels que vous devez avoir dnas un bon cadre MVVM. Plus tard, je vais vous décrire un cadre MVVM sur lequel je travaille qui était basée sur WCF RIA Services, mais ne dépend pas vraiment de ??ça.

Depuis que nous suivons les meilleures pratiques, nous savons que l’utilisation d’une architecture MVVM bien que nous pouvons arriver à une solution dont l’extraction, le filtrage, le tri, la logique d’échange est entièrement séparé du view, ce qui nous permet d’avoir aussi des vues différentes pour le même view model. Par exemple, nous pouvons commencer avec l’aide d’un DataGrid et un DataPager par afficher nos articles, mais plus tard, fournir une vision nouvelle qui utilise les comboboxes pour sélectionner les options de tri, une zone de liste d’albums pour afficher les éléments et les boutons personnalisés pour la pagination. En outre, nous devrions être en mesure de séparer toute cette logique de sa logique d’accès aux données réelles pour pouvoir utiliser des objets fantaisie de notre modèle et de faire des tests unitaires pour nos ViewModels. Ce n’est pas une tâche facile mais c’est ce que je veux réaliser à partir de maintenant.

Eh bien, pour commencer,.NET/Silverlight nous offre déjà quelques classes et interfaces qui sont très pratiques pour les scénarios de MVVM.

¦ INotifyPropertyChanged – Utilisé pour déclencher un événement lors d’un changement de propriété. WPF/Silverlight binding framework : utilise cette interface pour mettre à jour la vue lors d’un changement de propriété.

¦ INotifyCollectionChanged – Utilisé pour déclencher un événement lorsque l’insérer, supprimer, effacer ou remplacer l’opération a été fait contre une collection. Les contrôles WPF/Silverlight qui ont une propriété ItemsSource utilisent généralement cette interface pour créer ou supprimer des éléments visuels dans un container. Par exemple, ListBoxes affiche de nouvelle ListBoxItems, DataGrids affiche de nouvelle DataGridRows.

¦ ICollectionView – Utilisé pour fournir des descriptions sorte de filtre, des descriptions de groupe, et la sélection de l’objet pour une collection IEnumerable et ont la vue d’afficher uniquement les éléments filtrés, triés selon les descriptions de tri et de mettre en évidence l’élément sélectionné. (a plus de fonctionnalités, mais ce sont les plus pertinentes pour le bien de ce poste).

¦ IPagedCollectionView – Utilisé pour fournir des options de pagination à une collection IEnumerable. Il est utilisé par la plupart DataPagers, qui font des appels à la MoveToPage (pageIndex int) la méthode et nous permet de s’inscrire à l’événement PageChanging chercher une nouvelle page d’entités à afficher.

¦ Il existe d’autres interfaces importantes comme IEditableObject, IEditableCollectionView mais je ne vais pas les couvrir dans ces postes. Ils sont utilisés pour mettre à jour les valeurs des propriétés d’un objet de façon atomique.

Malheureusement, WCF RIA Services collections n’appliquent pas ICollectionView ou IPagedCollectionView si nous devons fournir notre propre mécanisme de filtrage, de tri et de pagination des données. Toutefois, cela est assez facile à faire avec LINQ et des expressions et nous permet de composer des requêtes avec certains opérateurs de LINQ avec Silverlight. Silverlight a sa propre implémentation de IPagedCollectionView, appelé System.Windows.Data.PagedCollectionView mais il a été conçu pour fonctionner avec les collections en mémoire et non pas côté serveur « collections ». Dans cette série, je suis mon propre fournisseur IPagedCollectionView / mise en œuvre ICollectionView que j’ai conçu pour fonctionner avec tout type de sources de données. Quelle que soit l’option utilisée est entièrement au modèle de MVVM.

Donc, voyant voir comment mon framework MVVM fonctionne.

Tout d’abord, nous avons le concept de modèle:

public interface IDomainModel : IDisposable
{
    void RejectChanges();
    ISubmitOperation SaveChanges();
    ISubmitOperation SaveChanges(Action<ISubmitOperation> callback, object userState);
 
    IDomainQuery<T> GetQuery<T>();
 
    void Add<T>(T entity);
    void Remove<T>(T entity);
    void Attach<T>(T entity);
    void Detach<T>(T entity);
 
    bool HasChanges { get; }
    bool IsSubmitting { get; }
    bool IsLoading { get; }
}

Si vous êtes familier avec l’API DomainContext, vous trouverez cette interface très facile à comprendre. Cette interface modèle est censé vous fournir des fonctionnalités de base pour la récupération de données, de le (modifier et l’enregistrer. Pour des opérations plus spécifiques, vous pouvez étendre cette interface, ajouter des opérations spécifiques et vous avez votre ViewModel en dépendent.

L’interface IDomainQuery représente une requête sur un type d’entité certaines référentiel (quelle qu’elle soit). Comme une requête, il vous permet de filtrer, trier et la page de données.

public interface IDomainQuery : IDisposable
{
    void Cancel();
    void Load();
 
    event Action Loading;
    event Action<ILoadOperation> Loaded;
    event Action Canceled;
}
 
public interface IDomainQuery<T> : IDomainQuery
{
    /// <summary>
    /// Delegate that returns a lambda expression to apply in a Where operation
    /// </summary>
    Func<Expression<Func<T, bool>>> FilterExpression { get; set; }
 
    /// <summary>
    /// SortDescription values that are applied in a OrderBy/ThenBy operation
    /// </summary>
    Func<SortDescriptionCollection> SortDescriptions { get; set; }
 
    /// <summary>
    /// Contains the required data that are applied in a Skip and Take operation
    /// </summary>
    Func<PageDescription> PageDescription { get; set; }
 
    new event Action<ILoadOperation<T>> Loaded;
}
 
public sealed class PageDescription
{
    public int PageSize { get; set; }
    public int PageIndex { get; set; }
}

En outre, il a des événements pour vous permettre de savoir quand une operation a commencé et terminé. Il ya deux choses importantes à noter dans l’interface IDomainQuery. La premiere est le fait que la façon dont vous filtrer une IDomainQuery est de savoir comment vous filtrez une RIA Services EntityQuery et comment vous filtrez un objet IQueryable. Cela signifie qu’il est très facile à faire implémentations IDomainQuery que les dépôts de requête en mémoire ou des référentiels côté serveur (noter que EntityQuery ne pas mettre en œuvre IQueryable et ce n’est pas facile d’intégrer les deux concepts). L’autre chose importante est que le filtre, de tri, le mécanisme de la page est basée sur les délégués qui signifie que chaque fois que la requête est exécutée / réexécutée ces délégués sont toujours réévalués et seront toujours le reflet de votre état ​​actuel de ViewModel.

J’ai créé des interfaces pour des opérations de « load » et de « submit ». J’ai mis en œuvre toutes ces interfaces avec des classes qui comptent sur ​​Silverlight.

Si vous êtes familier avec DomainContext Vous êtes en train de vous demandez sans doute où sont les EntitySets et où avez-vous manipuler des collections. Eh bien, depuis ma mise en œuvre s’appuie sur WCF IDomainModel RIA Services, vous pouvez étendre cette classe et de manipuler votre DomainContext à mettre en œuvre toute opération que vous souhaitez mettre à disposition dans votre étendu IDomainModel. Mais ce cadre ne sont pas censés être consulté dans votre ViewModel. Au lieu de cela, j’utilise mon application ICollectionView, que j’ai appelé DomainCollectionView.

public interface IDomainCollectionView : ICollectionView, IPagedCollectionView, INotifyPropertyChanged
{
    void Add(object item);
    void Remove(object item);
}
 
public interface IDomainCollectionView<T> : IDomainCollectionView, IEnumerable<T>
{
    void Add(T item);
    void Remove(T item);
}

l’implementation actuelle repose sur une IDomainQuery et sera en vue pour les résultats qui sont récupérées dans chaque opération. Comme vous pouvez mettre en œuvre un IDomainQuery pour toute IEnumerable vous pouvez utiliser cette mise en œuvre de toute source de données.

public class DomainCollectionView<T> : ViewModelBase, IDomainCollectionView<T>
{
    private IDomainQuery<T> query;
    private ObservableCollection<T> sourceCollection;
 
    #region DomainCollectionView Members
 
    public DomainCollectionView(IDomainQuery<T> query, bool usePaging = true)
    {
        this.query = query;
        this.sourceCollection = this.CreateSourceCollection();
        this.CurrentItem = null;
        this.CurrentPosition = -1;
 
        //Apply sort and paging
        query.SortDescriptions = () => this.SortDescriptions;
        if (usePaging)
        {
            this.canChangePage = true;
            this.pageSize = 10;// default page size to 10
            query.PageDescription = () => new PageDescription { PageSize = this.PageSize, PageIndex = this.PageIndex };
        }
 
        query.Loaded += this.OnDataRefreshed;
    }
 
    private void OnDataRefreshed(ILoadOperation<T> loadOp)
    {
        using (this.DeferRefresh())
        {
            this.sourceCollection.Clear();
            foreach (var entity in loadOp.Entities)
                this.sourceCollection.Add(entity);
 
            this.ItemCount = loadOp.TotalEntityCount;
            this.TotalItemCount = loadOp.TotalEntityCount;
        }
    }
 
    //...
}

Maintenant que j’ai introduit le modèle et le IDomainCollectionView, il est temps que nous parlions un peu de la ViewModel.

Contrairement au modèle, ViewModels ne sont pas beaucoup réutilisables, car ils dépendent généralement sur les besoins de la vue. Cependant, beaucoup de view point dans les applications métier ont des besoins communs: la recherche/listing des entités, édition des entités, créer des associations entre entités. Nous pouvons créer des ViewModels à partir de ces concepts et de les étendre seulement et les implementer à la logique métier. Toutes les infrastructures et la plomberie peuvent être centralisés et réutilisés partout.

public class ListVM<T> : ViewModelBase, IListVM<T>
{
    private IDomainModel model;
 
    public ListVM() { }
 
    protected virtual IDomainModel CreateModel()
    {
        return ServiceLocator.Current.GetInstance<IDomainModel>();
    }
 
    public void Initialize(IListVMInitializeArgs args)
    {
        this.model = args.ExistingModel ?? this.CreateModel();
        var query = model.GetQuery<T>();
        this.Entities = new DomainCollectionView<T>(query, args.RequiresPaging);
        query.Load();
    }
 
    public IDomainCollectionView<T> Entities
    {
        get;
        private set;
    }
}

Avec juste quelques lignes de code, nous avons une base du ViewModel qui permet :

■ La visualisation des résultats d’une requête qui peuvent être triés et paginée sur le serveur. Juste lier un datagrid à une collection d’entités et jouer avec lui.
■ La réutilisation d’un modèle existant ou en créer un nouveau. L’implémentation actuelle peut utiliser un modèle commun de toute l’application qui est enregistré dans le conteneur Dependency Injection actuelle ou laisser un ViewModel dérivés décider quoi faire.

A bientôt,

Bonne programmation.

PS : cet article est une adaptation en français d’une suite d’articles par Manuel Felício

Kamel DJELLAL
Chef de projet
EDIS CONSULTING – GROUPE UBISIDE

http://www.ubiside.fr/

Architecture Silverlight LOB – Concepts et technologies (Partie 6)

Dans ce poste et le suivant, je vais métaler sur le développement client et comment créer une application Silverlight modulaire et composite. Dans ce post, je vais me concentrer sur la composition UI.

Lors de la construction composite Silverlight LOB applications, vous devez savoir que la modularité est un critère important. En tant que développeur, il devrait être plus facile pour vous de mantenir votre code et d’ajouter de nouvelles fonctionnalités à votre application sans avoir à changer le reste, plus le fichier de votre application XAP grossisse. Chaque module est un fichier séparé XAP, qui peuvent être téléchargés à la demande, selon l’intention de l’utilisateur à utiliser un certain type de fonctionnalité.

Ci-dessous est une architecture possible pour réaliser ce dont nous avons besoin :

Le dossier contient les main xap, que j’ai appelé MyApp. Ce xap est le seul responsable pour le téléchargement d’autres et fournir des moyens pour eux pour afficher son contenu dans l’application principale. Le dossier contient 4 modules essentiels pour le scénario que j’ai défini dans un précédent post. Le module de commandes permet aux utilisateurs non-salarié à remplir de nouvelles commandes. Le module de vente permet aux employés de consulter les commandes en cours et de les traiter. Le module de statistiques permet aux employés d’afficher des graphiques et des rapports de l’évolution des ventes. Caractéristiques pour le département des ressources humaines sont accomplies par le module d’administration, qui devrait également permettre aux utilisateurs de l’application à créer et / ou gérées. Les modules et les Shell sont complié dans le dossier ClientBin de « MyApp.Web ».

Le projet d’infrastructure est un élément clé. Il définit les moyens de communiquer entre les modules et entre l’application principale. En d’autres termes, il s’agit d’un contrat commun partagé par les modules. Quand elle est définie ou partiellement défini, le développement de module peut commencer. Notez que le développement du module de commandes ne dépend pas de la mise au point du module de vente ou le module de statistiques, ce qui signifie que des personnes différentes peuvent travailler sur chacun d’eux.

MyApp.Data.Silverlight est un projet qui possède du WCF RIA Services généré (le code), ainsi que les clients d’autres services qui sont utilisés par les modules.

La couche Serveur correspond à l’architecture que j’ai défini dans le premier post.

Alors, comment notre Shell télécharge les modules ? Nous avons juste besoin de spécifier un catalogue de module et le donner à l’amorçage PRISM, qui est responsable de l’initialisation cadre du PRISM. Ci-dessous le catalogue de module que j’ai défini pour ce scénario :

<prism:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    xmlns:prism="clr-namespace:Microsoft.Practices.Composite.Modularity;assembly=Microsoft.Practices.Composite">
 
    <prism:ModuleInfo Ref="MyApp.Modules.Administration.xap"
                      ModuleName="Administration"
                      ModuleType="MyApp.Modules.Administration.Module, MyApp.Modules.Administration, Version=1.0.0.0"
                      InitializationMode="WhenAvailable">
    </prism:ModuleInfo>
 
    <prism:ModuleInfo Ref="MyApp.Modules.Statistics.xap"
                      ModuleName="Statistics"
                      ModuleType="MyApp.Modules.Statistics.Module, MyApp.Modules.Statistics, Version=1.0.0.0"
                      InitializationMode="WhenAvailable">
    </prism:ModuleInfo>
 
    <prism:ModuleInfo Ref="MyApp.Modules.Sales.xap"
                      ModuleName="Sales"
                      ModuleType="MyApp.Modules.Sales.Module, MyApp.Modules.Sales, Version=1.0.0.0"
                      InitializationMode="WhenAvailable">
    </prism:ModuleInfo>
 
    <prism:ModuleInfo Ref="MyApp.Modules.Orders.xap"
                      ModuleName="Orders"
                      ModuleType="MyApp.Modules.Orders.Module, MyApp.Modules.Orders, Version=1.0.0.0"
                      InitializationMode="WhenAvailable">
    </prism:ModuleInfo>
</prism:ModuleCatalog>

Notez les attributs de InitializationMode. Ils nous permetent de spécifier les conditions dans lesquels les modules doivent être téléchargés sur demande. Pour l’instant, je veux que mes modules soit à télécharger dès que possible. plus tard, je vais vous montrer des techniques efficaces pour les télécharger sur demande. Toutefois, cela n’affecte pas la façon dont je vais les construire.

Pour commencer, notre Shell peut avoir une barre supérieure qui affiche un message « bonjour », un menu sur la gauche et une région où les modules peuvent afficher son contenu.

 <Border BorderThickness="5" CornerRadius="10">
        <tlk:DockPanel LastChildFill="True">
            <shellWelcome:WelcomeBar tlk:DockPanel.Dock="Top" Margin="5"/>
            <shellMenu:Menu tlk:DockPanel.Dock="Left" VerticalAlignment="Stretch" Margin="5,0,5,5"/>
            <shellContainer:MainContainer Margin="0,0,5,5"/>
        </tlk:DockPanel>
    </Border>

Le résultat visuel donne quelque chose comme ceci:

shell

Je voudrais que le menu et le conteneur principal soient dynamiques, en d’autres termes, avoir son contenu défini dynamiquement par des modules au lieu d’être défini dans le contrôle lui-même.

Nous pourrions créer une interface comme « imenu », mis en œuvre par notre ViewModel Menu et l’utiliser par des modules pour afficher le contenu. Bien que cela semble être une aproche découplée, car vos modules peut afficher son contenu sans en fonction de la commande de menu lui-même, il est effectivement associée à l’objet qui implémente l’interface « imenu ». Cela signifie que si vous souhaitez afficher vos options de menu dans d’autres endroits, comme une barre d’outils ou une barre de ruban, vous aurez besoin d’une autre interface pour interagir avec les contrôles. Une solution à ce problème consiste à utiliser une technique de publisher/subscriber en s’appuyant sur ​​un objet médiateur, qui est EventAggregator PRISM. Maintenant, ce que vous faites est la publication d’un message disant « Je veux afficher cette option de menu. Voyons comment nous pouvons faire ce qui suit:

public class MenuOptionMessage : CompositePresentationEvent<MenuOptionMessageArgs> { }
 
public class MenuOptionMessageArgs
{
    public ICommand Command { get; set; }
    public string Text { get; set; }
    public string ImageSourceUri { get; set; }
 
    public string Group { get; set; }
}

Le MenuOptionMessage PRISM est un événement dont la charge utile est un message MenuOptionMessageArgs. Donc, chaque fois que vous souhaitez publier un MenuOptionMessage vous créez un MenuOptionMessageArgs et dite au EventAggregator de le diffuser à tous les abonnés. Le EventAggregator peut être consulté par l’injection de dépendance ou le conteneur IoC. J’ai l’habitude de créer une classe utilitaire simple pour garder le code plus propre:

public class Messenger
{
    public static T Get<T>()
        where T : EventBase
    {
        var ev = ServiceLocator.Current.GetInstance<IEventAggregator>();
        return ev.GetEvent<T>();
    }
}

Maintenant, nous pouvons avoir nos modules publié MenuOptionMessages.

initialisation du module « Order » :

Messenger.Get<MenuOptionMessage>().Publish(new MenuOptionMessageArgs
{
    Command = Commands.NewOrderCommand,
    Text = "Create New Order",
    Group = "Orders"
});
Messenger.Get<MenuOptionMessage>().Publish(new MenuOptionMessageArgs
{
    Command = Commands.ViewOrdersCommand,
    Text = "View Orders",
    Group = "Orders"
});

Initialisation du module « Sales » :

Messenger.Get<MenuOptionMessage>().Publish(new MenuOptionMessageArgs
{
    Command = Commands.ViewPendingOrdersCommand,
    Text = "Pending Orders",
    Group = "Sales"
});
Messenger.Get<MenuOptionMessage>().Publish(new MenuOptionMessageArgs
{
    Command = Commands.ViewProcessedOrdersCommand,
    Text = "Processed Orders",
    Group = "Sales"
});

Le contrôle Menu affiche toutes les commandes groupées par son Groupe et crée une extension pour chaque groupe. Vous pouvez accéder au code source pour le confirmer.

Maintenant, ce que nous devons faire est d’avoir nos commandes d’affichage des vues.

PRISM a un concept de région, où vous de définir des régions au sein de votre Shell, leur donner un nom et ensuite vos modules injecter les vues dans ces régions en spécifiant le nom. Je ne suis pas fan de cette fonctionnalité, car elle crée un couplage étroit entre les vues et les régions. Outre cela signifie que votre logique écran fonctionne uniquement avec PRISM. J’utilise généralement une approche différente, qui nous permet d’utiliser également les régions PRISM, mais d’autres cadres de UI ainsi. En outre, il n’est pas difficile de construire votre propre cadre UI, si vous restez simple. Par exemple, au lieu de penser sur les régions, pensez à voir les view positions.

public enum ViewPosition
{
    Left,
    Right,
    Top,
    Bottom,
    Center,
    Floating
}

En utilisant la même technique que nous avons fait pour le menu, chaque fois que nous voulons afficher une vue, nous n’avons tout simplement de publier un message où nous spécifier la position et la vue et celui qui est responsable d’afficher cet vue dans la position spécifiée doit souscrire le message et agir en conséquence. Les Messages devraient être définis dans l’assembly Infastructure. Voyons comment le message est défini:

public class CreateViewMessage : CompositePresentationEvent<CreateViewMessageArgs> { }
 
public class CreateViewMessageArgs
{
    public Lazy<object> View { get; set; }
    public ViewPosition Position { get; set; }
    public string Title { get; set; } //optional
}

Notez le Lazy objcet. Cela vous permet d’avoir votre vue créés par l’abonné au lieu d’être créé par l’éditeur. Cela vous permet d’utiliser différents threads lors de la publication / abonnement, mais dans ce cas l’abonné doit utiliser le thread d’interface utilisateur.

Ci-dessous un exemple de la façon de publier un CreateViewMessage à partir du module de commande.

public class Commands
{
    public static ICommand NewOrderCommand = new ActionCommand(Commands.NewOrder);
    public static ICommand ViewOrdersCommand = new ActionCommand(Commands.ViewOrders);
 
    private static void NewOrder()
    {
        Messenger.Get<CreateViewMessage>().Publish(new CreateViewMessageArgs
        {
            View = new Lazy<object>(() => ServiceLocator.Current.GetInstance<OrderView>()),
            Position = ViewPosition.Center,
            Title = "New Order"
        });
    }
 
    private static void ViewOrders()
    {
        //to be implemented
    }
}

Notez le OrderView qui doit être créé par le ServiceLocator pour permettre l’injection de dépendance, si nécessaire.

Le contrôle MainContainer est responsable de ce message et de son inscription pour afficher la vue intérieur d’un contrôle onglet.

public partial class MainContainer : UserControl
{
    public MainContainer()
    {
        InitializeComponent();
        if (DesignerProperties.IsInDesignTool)
            return;
 
        Messenger.Get<CreateViewMessage>().Subscribe(this.HandleCreateView, ThreadOption.UIThread, true, this.CanHandleCreateView);
    }
 
    private bool CanHandleCreateView(CreateViewMessageArgs args)
    {
        return args.Position == ViewPosition.Center;
    }
 
    private void HandleCreateView(CreateViewMessageArgs args)
    {
        this.tabCtrl.Items.Add(new TabItem
        {
            Header = args.Title, Content = args.View.Value,
            VerticalContentAlignment = System.Windows.VerticalAlignment.Stretch,
            HorizontalContentAlignment = System.Windows.HorizontalAlignment.Stretch
        });
    }
}

Lorsque vous vous abonnez un message, vous pouvez spécifier un prédicat qui dit défini le mécanisme de publier que vous voulez vraiment pour traiter ce message. Dans ce cas, le conteneur principal vise seulement à des CreateViewMessages qui ont des vues à afficher dans la position centrale. J’aurais pu souscrire un autre composant du CreateViewMessage et afficher des vues à l’intérieur de fenêtres lorsque la situation est variable. Le paramètre ThreadOption est également important. Chaque message qui traite de l’UI doit être souscrit dans le thread d’interface utilisateur. Si vous voulez brancher un enregistreur de souscription/log messages vous n’avez pas besoin de vous inscrire sur le thread d’interface utilisateur. Dans ce cas, vous devez utiliser un thread d’arrière.

Il s’agit d’une aproche très simples sur la façon de faire la composition d’UI qui fonctionne et qui est très efficace.

Suite : Dans le prochain post je vais me concentrer sur ViewModels et comment nous pouvons construire le OrderView.

PS : cet article est une adaptation en français d’une suite d’articles par Manuel Felício

Kamel DJELLAL
Chef de projet
EDIS CONSULTING – GROUPE UBISIDE

http://www.ubiside.fr/

Architecture Silverlight LOB – Concepts et technologies (Partie 5)

La Spécification des métadonnées pour nos entités est la partie la plus facile et voici un bon endroit pour commencer la localisation de notre application.

La première des choses que nous avons à faire est de créer une classe buddy tel que c’est décrit dans l’exemple suivant:

[MetadataType(typeof(Customer.CustomerMetadata))]
public partial class Customer
{
    internal class CustomerMetadata
    {
        [Display(ResourceType = typeof(Metadata), Name = "MyApp_Customer_Number")]
        public int Number;
 
        [Display(ResourceType = typeof(Metadata), Name = "CommonProperties_Email")]
        public string Email;
 
        [Include]
        public EntityCollection<Order> Orders;
    }
}
[MetadataType(typeof(Employee.EmployeeMetadata))]
public partial class Employee
{
    internal class EmployeeMetadata
    {
        [Display(ResourceType = typeof(Metadata), Name = "CommonProperties_Name")]
        [StringLength(128, ErrorMessageResourceType = typeof(Metadata), ErrorMessageResourceName = "CommonErrors_StringLength")]
        public string Name;
 
        [Display(ResourceType = typeof(Metadata), Name = "CommonProperties_Email")]
        public string Email;
 
        [Include]
        public Department Department;
    }
}

L’attribut DisplayAttribute est très utile, car il annote les propriétés de votre information qui peut être consommé de différentes manières. Par exemple, lorsque vous utilisez votre entité dans un Datagrid, les en-têtes des cellules va automatiquement chercher pour DisplayAttributes et récupérer la chaîne localisée pour cette propriété. La même chose s’applique pour les labels dans un Dataform et si vous voulez créer votre propre UI framework, vous pouvez exploiter ce type d’annotation pour commencer la localisation de votre application. Remarquez comment j’ai réutilisé des noms de ressources pour les common properties comme l’email. La même chose aurait pu être fait pour le nom, mais cela dépend vraiment de chaque cas.

Vous pouvez placer les règles de validation sur vos propriétés et en utilisant des attributs comme StringLength, appliquer des expressions régulières ou même créer vos propres règles de validation personnalisées. La grande partie à ce sujet est que ces règles seront disponibles dans le client ainsi. Le DomainService fait en sorte que, avant l’exécution des opérations sur les entités de toutes les règles de validation sont appliquées. Notez que vous ne pouvez pas faire toutes sortes de validation sur vos entités en utilisant des métadonnées. Certaines validations peuvent nécessiter la connexion à une base de données, transactions, etc, et cette information n’est pas disponible dans ce type de contexte de validation. Je recommande que de telles validations soit effectuées après ExecuteChangeSet DomainService est invoquée et juste avant la méthode PersistChangeSet.

Plus de détails sur la spécification des métadonnées peuvent être trouvées dans la documentation ou autres ressources sur le web. Je veux simplement mettre l’accent sur ​​un aspect différent que peu de gens ont tendance à écrire. C’est le partage des ressources dans le client et le serveur. Le projet client ne compilera pas si les métadonnées ResourceManager n’est pas disponible. Comme il est défini sur le serveur, vous devez suivre ces étapes afin de l’activer sur le client:

■ Ajoutez les fichiers de ressources comme un lien dans le projet client
■ Renommez votre namespace par default dans votre projet client par le même nom que dans le namespace qui est défini dans le projet serveur qui contient les fichiers de ressources
■ Changer le .csproj du client où il fait référence au fichier lié Designer.cs et ajouter ces lignes de code.:

<Compile Include=”..\..\..\Server\Data\MyApp.Data\Resources\Metadata.Designer.cs”>
 
      <AutoGen>True</AutoGen>      <Link>Resources\Metadata.Designer.cs</Link>
 
      <DesignTime>True</DesignTime>
 
      <DependentUpon>Metadata.resx</DependentUpon>
 
</Compile>

■ Assurez-vous que les fichiers de ressources ont le même chemin relatif au projet client que dans le projet serveur. Dans ce cas, j’ai créé un dossier des ressources dans les deux projets.
■ Assurez-vous aussi que le fichier de ressources a marqué son modificateur d’accès comme public.

Ces étapes sont nécessaires car l’ajout de fichiers liés aux fichiers dépendants, comme Medatata.resx / Metadata.Designer.cs ne va pas les marquer comme dependant automatiquement, vous devez donc le faire manuellement.

Maintenant, vous devriez être en mesure de compiler votre application et commencer à tirer profit des métadonnées dans vos entités.

Suite : prochain poste

PS : cet article est une adaptation en français d’une suite d’articles par Manuel Felício

Kamel DJELLAL
Chef de projet
EDIS CONSULTING – GROUPE UBISIDE

http://www.ubiside.fr/