Propagation de modifications dans le modèle par des règles

Vous pouvez créer une règle de magasin pour propager un changement d’un élément à un autre dans Visualization and Modeling SDK (VMSDK). Quand un changement se produit sur un élément du magasin, des règles sont planifiées pour être exécutées, généralement quand la transaction la plus externe est commitée. Il existe différents types de règles pour différents types d’événements, comme l’ajout d’un élément ou sa suppression. Vous pouvez attacher des règles à des types spécifiques d’éléments, de formes ou de diagrammes. De nombreuses fonctionnalités intégrées sont définies par des règles : par exemple, les règles garantissent qu’un diagramme est mis à jour quand le modèle change. Vous pouvez personnaliser votre langage dédié en ajoutant vos propres règles.

Les règles de magasin sont particulièrement utiles pour propager des changements à l’intérieur du magasin, c’est-à-dire les changements des éléments de modèle, des relations, des formes ou connecteurs et de leurs propriétés de domaine. Les règles ne s’exécutent pas quand l’utilisateur appelle les commandes Annuler ou Rétablir. À la place, le gestionnaire de transactions vérifie que le contenu du magasin est restauré à l’état correct. Si vous souhaitez propager des changements de ressources en dehors du magasin, utilisez Événements du magasin. Pour plus d’informations, consultez Les gestionnaires d’événements propagent les changements en dehors du modèle.

Par exemple, supposons que vous voulez spécifier que chaque fois que l’utilisateur (ou votre code) crée un élément de type ExampleDomainClass, un élément supplémentaire d’un autre type est créé dans une autre partie du modèle. Vous pouvez écrire un AddRule et l’associer à ExampleDomainClass. Vous devez écrire du code dans la règle pour créer l’élément supplémentaire.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.Modeling;

namespace ExampleNamespace
{
 // Attribute associates the rule with a domain class:
 [RuleOn(typeof(ExampleDomainClass), FireTime=TimeToFire.TopLevelCommit)]
 // The rule is a class derived from one of the abstract rules:
 class MyAddRule : AddRule
 {
  // Override the abstract method:
  public override void ElementAdded(ElementAddedEventArgs e)
  {
    base.ElementAdded(e);
    ExampleDomainClass element = e.ModelElement;
    Store store = element.Store;
    // Ignore this call if we're currently loading a model:
    if (store.TransactionManager.CurrentTransaction.IsSerializing)
       return;

    // Code here propagates change as required - for example:
      AnotherDomainClass echo = new AnotherDomainClass(element.Partition);
      echo.Name = element.Name;
      echo.Parent = element.Parent;
    }
  }
 // The rule must be registered:
 public partial class ExampleDomainModel
 {
   protected override Type[] GetCustomDomainModelTypes()
   {
     List<Type> types = new List<Type>(base.GetCustomDomainModelTypes());
     types.Add(typeof(MyAddRule));
     // If you add more rules, list them here.
     return types.ToArray();
   }
 }
}

Notes

Le code d’une règle doit changer l’état uniquement des éléments à l’intérieur du magasin. Autrement dit, la règle doit changer seulement les éléments de modèle, les relations, les formes, les connecteurs, les diagrammes ou leurs propriétés. Si vous souhaitez propager des changements de ressources en dehors du magasin, utilisez Événements du magasin. Pour plus d’informations, consultez Les gestionnaires d’événements propagent les changements en dehors du modèle.

Pour définir une règle

  1. Définissez la règle comme une classe préfixée avec l’attribut RuleOn. L’attribut associe la règle à une de vos classes de domaine, de vos relations ou un de vos éléments de diagramme. La règle est appliquée à chaque instance de cette classe, qui peut être abstraite.

  2. Inscrivez la règle en l’ajoutant à l’ensemble renvoyé par GetCustomDomainModelTypes() dans votre classe de modèle de domaine.

  3. Dérivez la classe de règle d’une des classes Rule abstraites et écrivez le code de la méthode d’exécution.

    Les sections suivantes décrivent ces étapes plus en détail.

Pour définir une règle sur une classe de domaine

  • Dans un fichier de code personnalisé, définissez une classe et préfixez-la avec l’attribut RuleOnAttribute :

    [RuleOn(typeof(ExampleElement),
         // Usual value - but required, because it is not the default:
         FireTime = TimeToFire.TopLevelCommit)]
    class MyRule ...
    
    
  • Le type d’objet dans le premier paramètre peut être une classe de domaine, une relation de domaine, une forme, un connecteur ou un diagramme. En règle générale, vous appliquez des règles aux classes et aux relations de domaine.

    FireTime est généralement TopLevelCommit. Cela garantit que la règle est exécutée seulement une fois que tous les changements principaux de la transaction ont été effectués. Les alternatives sont Inline, qui exécute la règle peu après le changement, et LocalCommit, qui exécute la règle à la fin de la transaction actuelle (qui n’est peut-être pas la plus externe). Vous pouvez également définir la priorité d’une règle pour affecter son classement dans la file d’attente, mais c’est une méthode peu fiable pour obtenir le résultat dont vous avez besoin.

  • Vous pouvez spécifier une classe abstraite comme type d’objet.

  • La règle s’applique à toutes les instances de la classe d’objet.

  • La valeur par défaut de FireTime est TimeToFire.TopLevelCommit. Cela entraîne l’exécution de la règle quand la transaction la plus externe est commitée. Une alternative est TimeToFire.Inline. Cela entraîne l’exécution de la règle peu après l’événement de déclenchement.

Pour inscrire la règle

  • Ajoutez votre classe de règle à la liste des types renvoyés par GetCustomDomainModelTypes dans votre modèle de domaine :

    public partial class ExampleDomainModel
     {
       protected override Type[] GetCustomDomainModelTypes()
       {
         List<Type> types = new List<Type>(base.GetCustomDomainModelTypes());
         types.Add(typeof(MyAddRule));
         // If you add more rules, list them here.
         return types.ToArray();
       }
     }
    
    
  • Si vous n’êtes pas sûr du nom de votre classe de modèle de domaine, recherchez dans le fichier Dsl\GeneratedCode\DomainModel.cs

  • Écrivez ce code dans un fichier de code personnalisé dans votre projet DSL.

Pour écrire le code de la règle

  • Dérivez la classe de règle de l’une des classes de base suivantes :

    Classe de base Déclencheur
    AddRule Un élément, un lien ou une forme est ajouté.

    Utilisez-le pour détecter les nouvelles relations, en plus des nouveaux éléments.
    ChangeRule Une valeur de propriété de domaine est changée. L’argument de méthode fournit les anciennes et les nouvelles valeurs.

    Pour les formes, cette règle est déclenchée quand la propriété intégrée AbsoluteBounds change, si la forme est déplacée.

    Dans de nombreux cas, il est plus pratique de remplacer OnValueChanged ou OnValueChanging dans le gestionnaire de propriétés. Ces méthodes sont appelées immédiatement avant et après le changement. En revanche, la règle s’exécute généralement à la fin de la transaction. Pour plus d’informations, consultez Gestionnaires de changement des valeurs de propriété de domaine. Remarque : Cette règle n’est pas déclenchée quand un lien est créé ou supprimé. À la place, écrivez un AddRule et un DeleteRule pour la relation de domaine.
    DeletingRule Déclenché quand un élément ou un lien est sur le point d’être supprimé. La propriété ModelElement.IsDeleting a la valeur true jusqu’à la fin de la transaction.
    DeleteRule Effectué quand un élément ou un lien a été supprimé. La règle est exécutée une fois que toutes les autres règles ont été exécutées, y compris DeletingRules. ModelElement.IsDeleting a la valeur false et ModelElement.IsDeleted a la valeur true. Pour autoriser une annulation ultérieure, l’élément n’est pas réellement supprimé de la mémoire, mais de Store.ElementDirectory.
    MoveRule Un élément est déplacé d’une partition de magasin vers une autre.

    (Notez que cela n’est pas lié à la position graphique d’une forme.)
    RolePlayerChangeRule Cette règle s’applique uniquement aux relations de domaine. Elle est déclenchée si vous attribuez explicitement un élément de modèle à l’une ou l’autre des extrémités d’un lien.
    RolePlayerPositionChangeRule Déclenché quand l’ordre des liens en provenance ou à destination d’un élément est changé avec les méthodes MoveBefore ou MoveToIndex sur un lien.
    TransactionBeginningRule Exécuté quand une transaction est créée.
    TransactionCommittingRule Exécuté quand la transaction est sur le point d’être commitée.
    TransactionRollingBackRule Exécuté quand la transaction est sur le point d’être restaurée.
  • Chaque classe a une méthode que vous remplacez. Tapez override dans votre classe pour la découvrir. Le paramètre de cette méthode identifie l’élément qui change.

    Notez les points suivants sur les règles :

  1. L’ensemble de changements d’une transaction peut déclencher de nombreuses règles. Généralement, les règles sont exécutées quand la transaction la plus externe est commitée. Elles sont exécutées dans un ordre non spécifié.

  2. Une règle est toujours exécutée à l’intérieur d’une transaction. Par conséquent, vous n’avez pas besoin de créer une nouvelle transaction pour faire des changements.

  3. Les règles ne sont pas exécutées quand une transaction est restaurée, ou quand les opérations Annuler ou Rétablir sont effectuées. Ces opérations réinitialisent tout le contenu du magasin à son état précédent. Par conséquent, si votre règle change l’état de quelque chose en dehors du magasin, elle peut ne pas conserver la synchronisation avec le contenu du magasin. Pour mettre à jour l’état en dehors du magasin, utilisez plutôt des événements. Pour plus d’informations, consultez Les gestionnaires d’événements propagent les changements en dehors du modèle.

  4. Certaines règles sont exécutées quand un modèle est chargé à partir d’un fichier. Pour déterminer si un chargement ou un enregistrement est en cours, utilisez store.TransactionManager.CurrentTransaction.IsSerializing.

  5. Si le code de votre règle crée d’autres déclencheurs de règle, ils sont ajoutés à la fin de la liste de déclenchement et sont exécutés avant la fin de la transaction. Les DeletedRules sont exécutés après toutes les autres règles. Une règle peut s’exécuter plusieurs fois dans une transaction, une fois pour chaque changement.

  6. Pour passer des informations en provenance et à destination des règles, vous pouvez stocker les informations dans TransactionContext. Il s’agit simplement d’un dictionnaire utilisé pendant la transaction. Il est supprimé à la fin de la transaction. Les arguments d’événement de chaque règle permettent d’y accéder. N’oubliez pas que les règles ne sont pas exécutées dans un ordre prévisible.

  7. Utilisez des règles après avoir envisagé d’autres alternatives. Par exemple, si vous souhaitez mettre à jour une propriété quand une valeur change, utilisez une propriété calculée. Si vous souhaitez limiter la taille ou l’emplacement d’une forme, utilisez un BoundsRule. Si vous souhaitez répondre à un changement de valeur d’une propriété, ajoutez un gestionnaire OnValueChanged à la propriété. Pour plus d’informations, consultez Répondre aux changements et les propager.

Exemple

L’exemple suivant met à jour une propriété quand une relation de domaine est instanciée pour lier deux éléments. La règle est déclenchée non seulement quand l’utilisateur crée un lien sur un diagramme, mais également si le code du programme crée un lien.

Pour tester cet exemple, créez un DSL à partir du modèle de solution Flux de tâches et insérez le code suivant dans un fichier du projet Dsl. Générez et exécutez la solution, puis ouvrez l’exemple de fichier dans le projet de débogage. Dessinez un lien de commentaire entre une forme Commentaire et un élément de flux. Le texte du commentaire change pour signaler l’élément le plus récent auquel vous l’avez connecté.

Dans la pratique, vous écrivez généralement un DeleteRule pour chaque AddRule.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Modeling;

namespace Company.TaskRuleExample
{

  [RuleOn(typeof(CommentReferencesSubjects))]
  public class RoleRule : AddRule
  {

    public override void ElementAdded(ElementAddedEventArgs e)
    {
      base.ElementAdded(e);
      CommentReferencesSubjects link = e.ModelElement as CommentReferencesSubjects;
      Comment comment = link.Comment;
      FlowElement subject = link.Subject;
      Transaction current = link.Store.TransactionManager.CurrentTransaction;
      // Don't want to run when we're just loading from file:
      if (current.IsSerializing) return;
      comment.Text = "Flow has " + subject.FlowTo.Count + " outgoing connections";
    }

  }

  public partial class TaskRuleExampleDomainModel
  {
    protected override Type[] GetCustomDomainModelTypes()
    {
      List<Type> types = new List<Type>(base.GetCustomDomainModelTypes());
      types.Add(typeof(RoleRule));
      return types.ToArray();
    }
  }

}