Las reglas propagan los cambios dentro del modelo

Puede crear una regla de almacén para propagar un cambio de un elemento a otro en el SDK de visualización y modelado (VMSDK). Cuando se produce un cambio en cualquier elemento del almacén, las reglas se programan para ejecutarse, normalmente cuando se confirma la transacción más externa. Hay diferentes tipos de reglas para diferentes tipos de eventos, como agregar un elemento o eliminarlo. Puede adjuntar reglas a tipos específicos de elementos, formas o diagramas. Muchas características integradas se definen mediante reglas: por ejemplo, las reglas garantizan que un diagrama se actualice cuando cambie el modelo. Puede personalizar el idioma específico del dominio agregando sus propias reglas.

Las reglas de almacén son especialmente útiles para propagar los cambios dentro del almacén, es decir, los cambios en los elementos del modelo, las relaciones, las formas o conectores y sus propiedades de dominio. Las reglas no se ejecutan cuando el usuario invoca los comandos Deshacer o Rehacer. En su lugar, el administrador de transacciones se asegura de que el contenido del almacén se restaure al estado correcto. Si quiere propagar los cambios a los recursos fuera del almacén, utilice eventos del almacén. Para obtener más información, consulte Controladores de eventos Propagar cambios fuera del modelo.

Por ejemplo, supongamos que desea especificar que cada vez que el usuario (o el código) cree un nuevo elemento de tipo ExampleDomainClass, se crea un elemento adicional de otro tipo en otra parte del modelo. Puede escribir una regla de tipo AddRule y asociarla a ExampleDomainClass. Escribiría código en la regla para crear el elemento adicional.

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

Nota

El código de una regla debe cambiar el estado solo de los elementos dentro del almacén; es decir, la regla solo debe cambiar los elementos del modelo, las relaciones, las formas, los conectores, los diagramas o sus propiedades. Si quiere propagar los cambios a los recursos fuera del almacén, utilice eventos del almacén. Para obtener más información, consulte Controladores de eventos Propagar cambios fuera del modelo.

Para definir una regla

  1. Defina la regla como una clase prefijo con el atributo RuleOn. El atributo asocia la regla a uno de los elementos de diagrama, relaciones o clases de dominio. La regla se aplicará a cada instancia de esta clase, que puede ser abstracta.

  2. Registre la regla agregándola al conjunto devuelto por GetCustomDomainModelTypes() en la clase de modelo de dominio.

  3. Derive la clase de regla de una de las clases abstractas de tipo Rule y escriba el código del método de ejecución.

    En las siguientes secciones se describen estos pasos con más detalle.

Para definir una regla en una clase de dominio

  • En un archivo de código personalizado, defina una clase y prefijo con el atributo RuleOnAttribute:

    [RuleOn(typeof(ExampleElement),
         // Usual value - but required, because it is not the default:
         FireTime = TimeToFire.TopLevelCommit)]
    class MyRule ...
    
    
  • El tipo de asunto del primer parámetro puede ser una clase de dominio, una relación de dominio, una forma, un conector o un diagrama. Normalmente, se aplican reglas a las clases de dominio y las relaciones.

    FireTime es normalmente TopLevelCommit. Esto garantiza que la regla se ejecute solo después de que se hayan realizado todos los cambios principales de la transacción. Las alternativas son Inline, que ejecuta la regla poco después del cambio; y LocalCommit, que ejecuta la regla al final de la transacción actual (que podría no ser el más externo). También puede establecer la prioridad de una regla para que afecte a su orden en la cola, pero se trata de un método poco confiable para lograr el resultado que necesita.

  • Puede especificar una clase abstracta como el tipo de asunto.

  • La regla se aplica a todas las instancias de la clase subject.

  • El valor predeterminado de FireTime es TimeToFire.TopLevelCommit. Esto hace que la regla se ejecute cuando se confirma la transacción más externa. Una alternativa es TimeToFire.Inline. Esto hace que la regla se ejecute poco después del evento desencadenador.

Para registrar la regla

  • Agregue la clase de regla a la lista de tipos devueltos por GetCustomDomainModelTypes en el modelo de dominio:

    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 no está seguro del nombre de la clase de modelo de dominio, busque dentro del archivo Dsl\GeneratedCode\DomainModel.cs.

  • Escriba este código en un archivo de código personalizado en el proyecto DSL.

Para escribir el código de la regla

  • Derive la clase de regla de una de las siguientes clases base:

    Clase base Desencadenador
    AddRule Se agrega un elemento, un vínculo o una forma.

    Úselo para detectar nuevas relaciones, además de nuevos elementos.
    ChangeRule Se cambia un valor de propiedad de dominio. El argumento del método proporciona los valores antiguos y nuevos.

    En el caso de las formas, esta regla se desencadena cuando cambia la propiedad integrada AbsoluteBounds, si se mueve la forma.

    En muchos casos, es más conveniente invalidar OnValueChanged o OnValueChanging en el controlador de propiedades. Estos métodos se llaman inmediatamente antes y después del cambio. Por el contrario, la regla normalmente se ejecuta al final de la transacción. Para obtener más información, consulte Controladores de cambio de valor de propiedad de dominio. Nota: Esta regla no se desencadena cuando se crea o elimina un vínculo. En su lugar, escriba un AddRule y un DeleteRule para la relación de dominio.
    DeletingRule Se desencadena cuando un elemento o vínculo está a punto de eliminarse. La propiedad ModelElement.IsDeleting es true hasta el final de la transacción.
    DeleteRule Se realiza cuando se ha eliminado un elemento o vínculo. La regla se ejecuta después de ejecutar todas las demás reglas, incluido EletingRules. ModelElement.IsDeleting es false y ModelElement.IsDeleted es true. Para que se pueda deshacer posteriormente, el elemento no se quita realmente de la memoria, pero se quita de Store.ElementDirectory.
    MoveRule Un elemento se mueve de una partición de almacén a otra.

    (Observe que esto no está relacionado con la posición gráfica de una forma).
    RolePlayerChangeRule Esta regla solo se aplica a las relaciones de dominio. Se desencadena si asigna explícitamente un elemento de modelo a cualquier extremo de un vínculo.
    RolePlayerPositionChangeRule Se desencadena cuando se cambia el orden de los vínculos a o desde un elemento mediante los métodos MoveBefore o MoveToIndex en un vínculo.
    TransactionBeginningRule Se ejecuta cuando se crea una transacción.
    TransactionCommittingRule Se ejecuta cuando la transacción está a punto de confirmarse.
    TransactionRollingBackRule Se ejecuta cuando la transacción está a punto de revertirse.
  • Cada clase tiene un método que se invalida. Escriba override en la clase para detectarla. El parámetro de este método identifica el elemento que se está cambiando.

    Observe los siguientes aspectos sobre reglas:

  1. El conjunto de cambios en una transacción puede desencadenar muchas reglas. Normalmente, las reglas se ejecutan cuando se confirma la transacción más externa. Se ejecutan en un orden no especificado.

  2. Una regla siempre se ejecuta dentro de una transacción. Por lo tanto, no es necesario crear una nueva transacción para realizar cambios.

  3. Las reglas no se ejecutan cuando se revierte una transacción o cuando se realizan las operaciones Deshacer o Rehacer. Estas operaciones restablecen todo el contenido del almacén a su estado anterior. Por lo tanto, si la regla cambia el estado de cualquier cosa fuera del almacén, es posible que no se mantenga sincronizada con el contenido del almacén. Para actualizar el estado fuera del almacén, es mejor usar Eventos. Para obtener más información, consulte Controladores de eventos Propagar cambios fuera del modelo.

  4. Algunas reglas se ejecutan cuando se carga un modelo desde el archivo. Para determinar si la carga o el guardado están en curso, use store.TransactionManager.CurrentTransaction.IsSerializing.

  5. Si el código de la regla crea más desencadenadores de reglas, se agregarán al final de la lista de activación y se ejecutarán antes de que se complete la transacción. DeletedRules se ejecutan después de todas las demás reglas. Una regla se puede ejecutar muchas veces en una transacción, una vez por cada cambio.

  6. Para pasar información a reglas y desde ellas, puede almacenar información en TransactionContext. Se trata simplemente de un diccionario que se mantiene durante la transacción. Se elimina cuando finaliza la transacción. Los argumentos de evento de cada regla proporcionan acceso a él. Recuerde que las reglas no se ejecutan en un orden predecible.

  7. Use reglas después de considerar otras alternativas. Por ejemplo, si desea actualizar una propiedad cuando cambie un valor, considere la posibilidad de usar una propiedad calculada. Si desea restringir el tamaño o la ubicación de una forma, use BoundsRule. Si desea responder a un cambio en un valor de propiedad, agregue un controlador OnValueChanged a la propiedad . Para obtener más información, consulte Responder a los cambios y propagarlos.

Ejemplo

En el ejemplo siguiente se actualiza una propiedad cuando se crea una instancia de una relación de dominio para vincular dos elementos. La regla se desencadenará no solo cuando el usuario cree un vínculo en un diagrama, sino también si el código de programa crea un vínculo.

Para probar este ejemplo, cree un DSL mediante la plantilla de solución Flujo de tareas e inserte el siguiente código en un archivo del proyecto Dsl. Compile y ejecute la solución y abra el archivo de ejemplo en el proyecto Depuración. Dibuje un vínculo de comentario entre una forma de comentario y un elemento de flujo. El texto del comentario cambia para informar sobre el elemento más reciente al que se ha conectado.

En la práctica, normalmente escribiría un DeleteRule para cada 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();
    }
  }

}