Partager via


Accéder à un modèle et le mettre à jour dans le code du programme

Vous pouvez écrire du code pour créer et supprimer des éléments de modèle, définir leurs propriétés et créer et supprimer des liens entre les éléments. Toutes les modifications doivent être apportées au sein d’une transaction. Si les éléments sont affichés dans un diagramme, le diagramme est « corrigé » automatiquement à la fin de la transaction.

Exemple de définition DSL

Il s’agit de la partie principale de DslDefinition.dsl pour les exemples de cette rubrique :

DSL Definition diagram - family tree model

Ce modèle est une instance de ce DSL :

Tudor Family Tree Model

Références et espaces de noms

Pour exécuter le code de cette rubrique, vous devez référencer :

Microsoft.VisualStudio.Modeling.Sdk.11.0.dll

Votre code utilisera cet espace de noms :

using Microsoft.VisualStudio.Modeling;

En outre, si vous écrivez le code dans un projet différent de celui dans lequel votre DSL est défini, vous devez importer l’assembly généré par le projet Dsl.

Propriétés

Les propriétés de domaine que vous définissez dans la définition DSL deviennent des propriétés accessibles dans le code du programme :

Person henry = ...;

if (henry.BirthDate < 1500) ...

if (henry.Name.EndsWith("VIII")) ...

Si vous voulez définir une propriété, vous devez le faire à l’intérieur d’une transaction :

henry.Name = "Henry VIII";

Si, dans la définition DSL, le Type d’une propriété est Calculé, vous ne pouvez pas la définir. Pour plus d’informations, consultez Propriétés de stockage calculées et personnalisées.

Relations

Les relations de domaine que vous définissez dans la définition DSL deviennent des paires de propriétés, chacune d’elles prenant place à chaque extrémité de la relation au niveau de la classe. Les noms de propriétés s’affichent dans le diagramme DslDefinition sous forme d’étiquettes au niveau des rôles, de part et d’autre de la relation. Selon la multiplicité du rôle, le type de la propriété est soit la classe à l’autre extrémité de la relation, soit une collection de cette classe.

foreach (Person child in henry.Children) { ... }

FamilyTreeModel ftree = henry.FamilyTreeModel;

Les propriétés situées aux extrémités opposées d’une relation sont toujours réciproques. Quand un lien est créé ou supprimé, les propriétés de rôle au niveau des deux éléments sont mises à jour. L’expression suivante (qui utilise les extensions de System.Linq) est toujours vraie pour la relation ParentsHaveChildren de l’exemple :

(Person p) => p.Children.All(child => child.Parents.Contains(p))

&& p.Parents.All(parent => parent.Children.Contains(p));

ElementLinks. Une relation est aussi représentée par un élément de modèle appelé lien, qui est une instance du type de relation de domaine. Un lien possède toujours un élément source et un élément cible. L’élément source et l’élément cible peuvent être identiques.

Vous pouvez accéder à un lien et à ses propriétés :

ParentsHaveChildren link = ParentsHaveChildren.GetLink(henry, edward);

// This is now true:

link == null || link.Parent == henry && link.Child == edward

Par défaut, seule une instance d’une relation est autorisée à lier une paire d’éléments de modèle. Mais si, dans la définition DSL, l’indicateur Allow Duplicates a la valeur true pour la relation, il peut exister plusieurs liens, et vous devez utiliser GetLinks :

foreach (ParentsHaveChildren link in ParentsHaveChildren.GetLinks(henry, edward)) { ... }

Il existe aussi d’autres méthodes pour accéder aux liens. Par exemple :

foreach (ParentsHaveChildren link in ParentsHaveChildren.GetLinksToChildren(henry)) { ... }

Rôles masqués. Si, dans la définition DSL, Propriété générée a la valeur false pour un rôle déterminé, aucune propriété correspondant à ce rôle n’est générée. Cependant, vous pouvez toujours accéder aux liens et parcourir les liens en employant les méthodes de la relation :

foreach (Person p in ParentsHaveChildren.GetChildren(henry)) { ... }

L’exemple le plus souvent utilisé est la relation PresentationViewsSubject, qui lie un élément de modèle à la forme qui l’affiche sur un diagramme :

PresentationViewsSubject.GetPresentation(henry)[0] as PersonShape

Répertoire d’éléments

Vous pouvez accéder à tous les éléments du Store via le répertoire d’éléments :

store.ElementDirectory.AllElements

Il existe aussi des méthodes qui permettent de rechercher des éléments, comme celles-ci :

store.ElementDirectory.FindElements(Person.DomainClassId);

store.ElementDirectory.GetElement(elementId);

Accéder aux informations sur les classes

Vous pouvez obtenir des informations sur les classes, les relations et d’autres aspects de la définition DSL. Par exemple :

DomainClassInfo personClass = henry.GetDomainClass();

DomainPropertyInfo birthProperty =

personClass.FindDomainProperty("BirthDate")

DomainRelationshipInfo relationship =

link.GetDomainRelationship();

DomainRoleInfo sourceRole = relationship.DomainRole[0];

Les classes ancêtres des éléments de modèle sont les suivantes :

  • ModelElement : tous les éléments et toutes les relations sont des ModelElements

  • ElementLink : toutes les relations sont des ElementLinks

Effectuer des modifications à l’intérieur d’une transaction

Chaque fois que le code de votre programme change quelque chose dans le Store, il doit le faire à l’intérieur d’une transaction. Cela s’applique à l’ensemble des éléments de modèle, des relations, des formes, diagrammes et de leurs propriétés. Pour plus d’informations, consultez Transaction.

La méthode la plus pratique pour gérer une transaction est d’utiliser une instruction using intercalée dans une instruction try...catch :

Store store; ...
try
{
  using (Transaction transaction =
    store.TransactionManager.BeginTransaction("update model"))
    // Outermost transaction must always have a name.
  {
    // Make several changes in Store:
    Person p = new Person(store);
    p.FamilyTreeModel = familyTree;
    p.Name = "Edward VI";
    // end of changes to Store

    transaction.Commit(); // Don't forget this!
  } // transaction disposed here
}
catch (Exception ex)
{
  // If an exception occurs, the Store will be
  // rolled back to its previous state.
}

Vous pouvez apporter autant de modifications que nécessaire à l’intérieur d’une transaction. Vous pouvez ouvrir de nouvelles transactions à l’intérieur d’une transaction active.

Pour rendre vos modifications permanentes, vous devez faire un Commit de la transaction avant qu’elle soit éliminée. S’il se produit une exception qui n’est pas interceptée à l’intérieur de la transaction, le Store reprend l’état qui était le sien avant les modifications.

Créer des éléments de modèle

Cet exemple ajoute un élément à un modèle existant :

FamilyTreeModel familyTree = ...; // The root of the model.
using (Transaction t =
    familyTree.Store.TransactionManager
    .BeginTransaction("update model"))
{
  // Create a new model element
  // in the same partition as the model root:
  Person edward = new Person(familyTree.Partition);
  // Set its embedding relationship:
  edward.FamilyTreeModel = familyTree;
          // same as: familyTree.People.Add(edward);
  // Set its properties:
  edward.Name = "Edward VII";
  t.Commit(); // Don't forget this!
}

Cet exemple illustre ces points essentiels dans la création d’un élément :

  • Créez le nouvel élément dans une partition spécifique du Store. Pour les éléments de modèle et les relations, mais pas pour les formes, il s’agit généralement de la partition par défaut.

  • Faites-en la cible d’une relation d’incorporation. Dans la DslDefinition de cet exemple, chaque personne doit être la cible de la relation d’incorporation FamilyTreeHasPeople. Pour parvenir à cela, nous pouvons définir la propriété de rôle FamilyTreeModel de l’objet Person ou ajouter la personne à la propriété de rôle People de l’objet FamilyTreeModel.

  • Définissez les propriétés d’un nouvel élément, en particulier la propriété pour laquelle IsName a la valeur true dans DslDefinition. Cet indicateur marque la propriété qui sert à identifier l’élément de manière unique au sein de son propriétaire. Dans ce cas, la propriété Name a cet indicateur.

  • La définition DSL de ce DSL doit avoir été chargée dans le Store. Si vous écrivez une extension telle qu’une commande de menu, celle-ci aura généralement déjà la valeur true. Dans les autres cas, vous pouvez charger explicitement le modèle dans le Store ou utiliser ModelBus pour le charger. Pour plus d’informations, consultez Guide pratique pour ouvrir un modèle à partir d’un fichier dans le code du programme.

    Quand vous créez un élément de cette façon, une forme est automatiquement créée (si le DSL présente un diagramme). Il apparaît dans un emplacement attribué automatiquement, avec la forme, la couleur et d’autres caractéristiques par défaut. Si vous voulez contrôler où et comment la forme associée apparaît, consultez Créer un élément et sa forme.

Deux relations sont définies dans l’exemple de définition DSL. Chaque relation définit une propriété de rôle au niveau de la classe à chaque extrémité de la relation.

Il existe trois façons de créer une instance d’une relation. Chacune de ces trois méthodes produit le même effet :

  • Définissez la propriété du joueur de rôle source. Par exemple :

    • familyTree.People.Add(edward);

    • edward.Parents.Add(henry);

  • Définissez la propriété du joueur de rôle cible. Par exemple :

    • edward.familyTreeModel = familyTree;

      La multiplicité de ce rôle étant de 1..1, nous attribuons la valeur.

    • henry.Children.Add(edward);

      La multiplicité de ce rôle étant de 0..*, nous l’ajoutons à la collection.

  • Construisez explicitement une instance de la relation. Par exemple :

    • FamilyTreeHasPeople edwardLink = new FamilyTreeHasPeople(familyTreeModel, edward);

    • ParentsHaveChildren edwardHenryLink = new ParentsHaveChildren(henry, edward);

    La dernière méthode est intéressante si vous voulez définir des propriétés au niveau de la relation proprement dite.

    Quand vous créez un élément de cette façon, un connecteur est créé automatiquement sur le diagramme, mais il présente une forme, une couleur et d’autres caractéristiques par défaut. Pour contrôler la façon dont le connecteur associé est créé, consultez Créer un élément et sa forme.

Supprimer des éléments

Supprimez un élément en appelant Delete() :

henry.Delete();

Cette opération supprime également :

  • Les liens de relation vers et à partir de l’élément. Par exemple, edward.Parents ne contient plus henry.

  • Les éléments au niveau des rôles pour lesquels l’indicateur PropagatesDelete a la valeur true. Par exemple, la forme qui affiche l’élément est supprimée.

Par défaut, l’indicateur PropagatesDelete de chaque relation d’incorporation a la valeur true au niveau du rôle cible. Si la suppression de henry n’entraîne pas la suppression de familyTree, familyTree.Delete() supprimerait tous les Persons.

Par défaut, PropagatesDelete n’a pas la valeur true pour les rôles de relations de référence.

Vous pouvez faire en sorte que les règles de suppression omettent certaines propagations au moment où vous supprimez un objet. Cela est utile si vous remplacez un élément par un autre. Vous fournissez le GUID d’un ou plusieurs rôles dont la suppression ne doit pas être propagée. Vous pouvez vous procurer le GUID à partir de la classe de la relation :

henry.Delete(ParentsHaveChildren.SourceDomainRoleId);

(Cet exemple particulier n’aurait aucun effet, car PropagatesDelete a la valeur false pour les rôles de la relation ParentsHaveChildren.)

Dans certains cas, la suppression est empêchée par l’existence d’un verrou, soit au niveau de l’élément, soit au niveau d’un élément qui serait supprimé par propagation. Vous pouvez utiliser element.CanDelete() pour vérifier si l’élément peut être supprimé.

Vous pouvez supprimer un lien de relation en supprimant un élément d’une propriété de rôle :

henry.Children.Remove(edward); // or:

edward.Parents.Remove(henry); // or:

Vous pouvez aussi supprimer le lien explicitement :

edwardHenryLink.Delete();

Ces trois méthodes produisent toutes le même effet. Vous n’avez besoin d’en utiliser qu’une seule d’elles.

Si le rôle présente une multiplicité de 0..1 ou 1..1, vous pouvez le définir sur null ou lui attribuer une autre valeur :

edward.FamilyTreeModel = null; // ou :

edward.FamilyTreeModel = anotherFamilyTree;

Réorganiser les liens d’une relation

Les liens d’une relation donnée qui sont sourcés ou ciblés au niveau d’un élément de modèle particulier présentent un ordre spécifique. Ils s’affichent dans l’ordre où ils ont été ajoutés. Par exemple, cette instruction produira toujours les enfants dans le même ordre :

foreach (Person child in henry.Children) ...

Vous pouvez changer l’ordre des liens :

ParentsHaveChildren link = GetLink(henry,edward);

ParentsHaveChildren nextLink = GetLink(henry, elizabeth);

DomainRoleInfo role =

link.GetDomainRelationship().DomainRoles[0];

link.MoveBefore(role, nextLink);

Verrous

Vos modifications peuvent être empêchées par un verrou. Les verrous peuvent être définis au niveau des éléments individuels, des partitions et du Store. Si un verrou est présent à l’un de ces niveaux et qu’il vous interdit d’apporter le type de modification voulu, vous risquez d’obtenir une exception pendant l’opération. Vous pouvez déterminer si des verrous sont définis à l’aide de element.GetLocks(), qui est une méthode d’extension définie dans l’espace de noms Microsoft.VisualStudio.Modeling.Immutability.

Pour plus d’informations, consultez Définition d’une stratégie de verrouillage pour créer des segments en lecture seule.

Copier et coller

Vous pouvez copier des éléments ou des groupes d’éléments dans un IDataObject :

Person person = personShape.ModelElement as Person;
Person adopter = adopterShape.ModelElement as Person;
IDataObject data = new DataObject();
personShape.Diagram.ElementOperations
      .Copy(data, person.Children.ToList<ModelElement>());

Les éléments sont stockés sous la forme d’un groupe d’éléments sérialisé.

Vous pouvez fusionner les éléments d’un IDataObject dans un modèle :

using (Transaction t = targetDiagram.Store.
        TransactionManager.BeginTransaction("paste"))
{
  adopterShape.Diagram.ElementOperations.Merge(adopter, data);
}

Merge () peut accepter un PresentationElement ou un ModelElement. Si vous lui attribuez un PresentationElement, vous pouvez aussi spécifier une position sur le diagramme cible en guise de troisième paramètre.

Parcourir et mettre à jour les diagrammes

Dans un DSL, l’élément de modèle de domaine, qui représente un concept tel que Person ou Song, est distinct de l’élément shape (forme), qui représente ce que vous voyez sur le diagramme. L’élément de modèle de domaine stocke les propriétés et les relations importantes des concepts. L’élément shape stocke la taille, la position et la couleur de l’objet affiché sur le diagramme ainsi que la disposition de ses composants.

Élément de présentation

Class diagram of base shape and element types

Dans votre définition DSL, chaque élément que vous spécifiez crée une classe qui est dérivée de l’une des classes standard suivantes.

Type d’élément Classe de base
Classe de domaine ModelElement
Relation de domaine ElementLink
Forme NodeShape
Connecteur BinaryLinkShape
Diagramme Diagram

Un élément figurant sur un diagramme représente généralement un élément de modèle. En règle générale (mais pas toujours), un NodeShape représente une instance de classe de domaine et un BinaryLinkShape représente une instance de relation de domaine. La relation PresentationViewsSubject lie une forme de nœud ou de lien à l’élément de modèle qu’elle représente.

Chaque forme de nœud ou de lien appartient à un diagramme. Une forme de lien binaire connecte deux formes de nœud.

Les formes peuvent avoir des formes enfants dans deux ensembles. Une forme dans l’ensemble NestedChildShapes est limitée au cadre englobant de son parent. Une forme dans la liste RelativeChildShapes peut se trouver entièrement ou partiellement en dehors des limites du parent, par exemple une étiquette ou un port. Un diagramme ne présente ni de RelativeChildShapes ni de Parent.

Naviguer entre les formes et les éléments

Les éléments de modèle de domaine et les éléments de forme sont liés par la relation PresentationViewsSubject.

// using Microsoft.VisualStudio.Modeling;
// using Microsoft.VisualStudio.Modeling.Diagrams;
// using System.Linq;
Person henry = ...;
PersonShape henryShape =
  PresentationViewsSubject.GetPresentation(henry)
    .FirstOrDefault() as PersonShape;

La même relation lie les relations à des connecteurs sur le diagramme :

Descendants link = Descendants.GetLink(henry, edward);
DescendantConnector dc =
   PresentationViewsSubject.GetPresentation(link)
     .FirstOrDefault() as DescendantConnector;
// dc.FromShape == henryShape && dc.ToShape == edwardShape

Cette relation lie aussi la racine du modèle au diagramme :

FamilyTreeDiagram diagram =
   PresentationViewsSubject.GetPresentation(familyTree)
      .FirstOrDefault() as FamilyTreeDiagram;

Pour obtenir l’élément de modèle représenté par une forme, utilisez :

henryShape.ModelElement as Person

diagram.ModelElement as FamilyTreeModel

En général, il n’est pas recommandé de naviguer entre les formes et les connecteurs sur le diagramme. Il est préférable de parcourir les relations dans le modèle et de se déplacer entre les formes et les connecteurs uniquement quand il est nécessaire de travailler sur l’apparence du diagramme. Ces méthodes lient les connecteurs aux formes à chaque extrémité :

personShape.FromRoleLinkShapes, personShape.ToRoleLinkShapes

connector.FromShape, connector.ToShape

De nombreuses formes sont composites ; elles sont constitués d’une forme parente et d’une ou plusieurs couches d’enfants. Les formes qui sont positionnées par rapport à une autre forme sont considérées comme leurs enfants. Quand la forme parente se déplace, les enfants se déplacent avec elle.

Des enfants relatifs peuvent apparaître en dehors du cadre englobant de la forme parente. Les enfants imbriqués apparaissent strictement à l’intérieur des limites du parent.

Pour obtenir l’ensemble supérieur de formes d’un diagramme, utilisez :

Diagram.NestedChildShapes

Les classes ancêtres des formes et des connecteurs sont les suivantes :

ModelElement

-- PresentationElement

-- ShapeElement

----- NodeShape

------- Diagram

------- VotreForme

----- LinkShape

------- BinaryLinkShape

--------- VotreConnecteur

Propriétés des formes et des connecteurs

Dans la plupart des cas, il n’est pas nécessaire d’apporter des modifications explicites aux formes. Une fois que vous avez modifié les éléments de modèle, les règles « correctives » mettent à jour les formes et les connecteurs. Pour plus d’informations, consultez Répondre aux modifications et les propager.

Cependant, il est utile d’apporter des modifications explicites aux formes dans les propriétés qui sont indépendantes des éléments de modèle. Par exemple, vous pouvez modifier ces propriétés :

  • Size : détermine la hauteur et la largeur de la forme.

  • Location : position par rapport à la forme parente ou au diagramme

  • StyleSet : ensemble de stylets et de pinceaux utilisés pour dessiner la forme ou le connecteur

  • Hide : rend la forme invisible

  • Show : rend la forme visible après un Hide()

Créer un élément et sa forme

Quand vous créez un élément et que vous le liez dans l’arborescence des relations d’incorporation, une forme est automatiquement créée et associée à celle-ci. Cette tâche assurée par les règles de « correctives » qui s’exécutent à la fin de la transaction. Cependant, la forme s’affiche à un emplacement affecté automatiquement ; la forme, sa couleur et ses autres caractéristiques disposent des valeurs par défaut. Pour contrôler la façon dont la forme est créée, vous pouvez utiliser la fonction de fusion. Vous devez d’abord ajouter les éléments que vous voulez ajouter dans un ElementGroup, puis fusionner le groupe dans le diagramme.

Cette méthode :

  • Définit le nom, si vous avez attribué une propriété comme nom d’élément.

  • Observe toutes les directives de fusion d’éléments que vous avez spécifiées dans la définition DSL.

Dans cet exemple, une forme est créée à l’emplacement de la souris quand l’utilisateur double-clique sur le diagramme. Dans la définition DSL de cet exemple, la propriété FillColor de ExampleShape a été exposée.

using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
partial class MyDiagram
{
  public override void OnDoubleClick(DiagramPointEventArgs e)
  {
    base.OnDoubleClick(e);

    using (Transaction t = this.Store.TransactionManager
        .BeginTransaction("double click"))
    {
      ExampleElement element = new ExampleElement(this.Store);
      ElementGroup group = new ElementGroup(element);

      { // To use a shape of a default size and color, omit this block.
        ExampleShape shape = new ExampleShape(this.Partition);
        shape.ModelElement = element;
        shape.AbsoluteBounds = new RectangleD(0, 0, 1.5, 1.0);
        shape.FillColor = System.Drawing.Color.Azure;
        group.Add(shape);
      }

      this.ElementOperations.MergeElementGroupPrototype(
        this,
        group.CreatePrototype(),
        PointD.ToPointF(e.MousePosition));
      t.Commit();
    }
  }
}

Si vous fournissez plusieurs formes, définissez leurs positions relatives à l’aide de AbsoluteBounds.

Vous pouvez aussi définir la couleur et les autres propriétés exposées des connecteurs à l’aide de cette méthode.

Utiliser des transactions

Les formes, les connecteurs et les diagrammes sont des sous-types de ModelElement et résident dans le Store. Vous devez donc y apporter des modifications uniquement à l’intérieur d’une transaction. Pour plus d’informations, consultez Guide pratique pour utiliser des transactions pour mettre à jour le modèle.

Vue de document et données de document

Class diagram of standard diagram types

Partitions du Store

Quand un modèle est chargé, le diagramme qui l’accompagne est chargé en même temps. En règle générale, le modèle est chargé dans Store.DefaultPartition, et le contenu du diagramme est chargé dans une autre partition. Habituellement, le contenu de chaque partition est chargé et enregistré dans un fichier distinct.