Navegar por un modelo en el código del programa y actualizarlo

Puede escribir código para crear y eliminar elementos del modelo, establecer sus propiedades y crear y eliminar vínculos entre elementos. Todos los cambios se deben realizar dentro de una transacción. Si los elementos se ven en un diagrama, el diagrama se "corregirá" automáticamente al final de la transacción.

Una definición de DSL de ejemplo

Esta es la parte principal de DslDefinition.dsl para los ejemplos de este tema:

DSL Definition diagram - family tree model

Este modelo es una instancia de este DSL:

Tudor Family Tree Model

Referencias y espacios de nombres

Para ejecutar el código de este tema, debe hacer referencia a:

Microsoft.VisualStudio.Modeling.Sdk.11.0.dll

El código usará este espacio de nombres:

using Microsoft.VisualStudio.Modeling;

Además, si va a escribir el código en un proyecto distinto de aquel en el que se define el DSL, debe importar el ensamblado compilado por el proyecto de DSL.

Propiedades

Las propiedades de dominio que defina en la definición de DSL se convierten en propiedades a las que puede acceder en el código del programa:

Person henry = ...;

if (henry.BirthDate < 1500) ...

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

Si desea establecer una propiedad, debe hacerlo dentro de una transacción:

henry.Name = "Henry VIII";

Si en la definición de DSL, el tipo de una propiedad es Calculado, no se puede establecer. Para obtener más información, consulte Propiedades de almacenamiento calculadas y personalizadas.

Relaciones

Las relaciones de dominio que defina en la definición de DSL se convierten en pares de propiedades, una en la clase en cada extremo de la relación. Los nombres de las propiedades aparecen en el diagrama DslDefinition como etiquetas en los roles a cada lado de la relación. Dependiendo de la multiplicidad del rol, el tipo de la propiedad es la clase en el otro extremo de la relación o una colección de esa clase.

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

FamilyTreeModel ftree = henry.FamilyTreeModel;

Las propiedades en extremos opuestos de una relación siempre son recíprocas. Cuando se crea o elimina un vínculo, se actualizan las propiedades de rol de ambos elementos. La siguiente expresión (que usa las extensiones de System.Linq) siempre es true para la relación ParentsHaveChildren del ejemplo:

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

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

ElementLinks. Una relación también se representa mediante un elemento de modelo denominado vínculo, que es una instancia del tipo de relación de dominio. Un vínculo siempre tiene un elemento de origen y un elemento de destino. El elemento de origen y el elemento de destino pueden ser los mismos.

Puede acceder a un vínculo y a sus propiedades:

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

// This is now true:

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

De forma predeterminada, no se permite que más de una instancia de una relación vincule ningún par de elementos del modelo. Pero si en la definición de DSL la marca Allow Duplicates es true para la relación, puede haber más de un vínculo, y debe usar GetLinks:

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

También hay otros métodos para acceder a vínculos. Por ejemplo:

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

Roles ocultos. Si en la definición de DSL Is Property Generated es false para un rol determinado, no se genera ninguna propiedad que corresponda a ese rol. Sin embargo, todavía puede acceder a los vínculos y recorrerlos mediante los métodos de la relación:

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

El ejemplo más usado es la relación PresentationViewsSubject, que vincula un elemento de modelo a la forma que lo muestra en un diagrama:

PresentationViewsSubject.GetPresentation(henry)[0] as PersonShape

Directorio de elementos

Puede acceder a todos los elementos del almacén mediante el directorio de elementos:

store.ElementDirectory.AllElements

También hay métodos para buscar elementos, como los siguientes:

store.ElementDirectory.FindElements(Person.DomainClassId);

store.ElementDirectory.GetElement(elementId);

Acceso a la información de clase

Puede obtener información sobre las clases, las relaciones y otros aspectos de la definición de DSL. Por ejemplo:

DomainClassInfo personClass = henry.GetDomainClass();

DomainPropertyInfo birthProperty =

personClass.FindDomainProperty("BirthDate")

DomainRelationshipInfo relationship =

link.GetDomainRelationship();

DomainRoleInfo sourceRole = relationship.DomainRole[0];

Las clases antecesoras de los elementos del modelo son las siguientes:

  • ModelElement: todos los elementos y las relaciones son ModelElements.

  • ElementLink: todas las relaciones son ElementLinks.

Realización de cambios dentro de una transacción

Siempre que el código del programa cambie cualquier elemento de Store, debe hacerlo dentro de una transacción. Esto se aplica a todos los elementos, las relaciones, las formas y los diagramas del modelo, así como a sus propiedades. Para obtener más información, vea Transaction.

El método más recomendable para administrar una transacción es incluir una instrucción using en una instrucción 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.
}

Puede realizar cualquier número de cambios en una transacción. Puede abrir nuevas transacciones dentro de una transacción activa.

Para hacer que los cambios sean permanentes, debe ejecutar Commit en la transacción antes de eliminarla. Si se produce una excepción que no se detecta dentro de la transacción, el almacén se restablecerá a su estado antes de los cambios.

Creación de elementos de modelo

En este ejemplo se agrega un elemento a un modelo existente:

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

En este ejemplo se muestran estos puntos esenciales sobre la creación de un elemento:

  • Cree el elemento en una partición específica de Store. En el caso de los elementos y las relaciones del modelo, pero no las formas, suele ser la partición predeterminada.

  • Convertirlo en el destino de una relación de inclusión. En DslDefinition de este ejemplo, cada persona debe ser el destino de la relación de inclusión de FamilyTreeHasPeople. Para lograrlo, podemos establecer la propiedad de rol FamilyTreeModel del objeto Person o agregar la persona a la propiedad de rol Personas del objeto FamilyTreeModel.

  • Establezca las propiedades de un nuevo elemento, especialmente la propiedad para la que IsName es true en DslDefinition. Esta marca identifica la propiedad que sirve para identificar el elemento de forma única dentro de su propietario. En este caso, la propiedad Name tiene esa marca.

  • La definición de DSL de este DSL debe haberse cargado en Store. Si va a escribir una extensión como un comando de menú, normalmente ya será true. En otros casos, puede cargar explícitamente el modelo en Store o usar ModelBus para cargarlo. Para más información, vea Apertura de un modelo desde un archivo en el código del programa.

    Al crear un elemento de esta manera, se crea automáticamente una forma (si el DSL tiene un diagrama). Aparece en una ubicación asignada automáticamente, con forma, color y otras características predeterminados. Si desea controlar dónde y cómo aparece la forma asociada, vea Creación de un elemento y su forma.

Hay dos relaciones definidas en la definición de DSL de ejemplo. Cada relación define una propiedad de rol en la clase en cada extremo de la relación.

Hay tres maneras de crear una instancia de una relación. Cada uno de estos tres métodos tiene el mismo efecto:

  • Establezca la propiedad del encargado de rol de origen. Por ejemplo:

    • familyTree.People.Add(edward);

    • edward.Parents.Add(henry);

  • Establezca la propiedad del encargado de rol de destino. Por ejemplo:

    • edward.familyTreeModel = familyTree;

      La multiplicidad de este rol es 1..1, por lo que asignamos el valor.

    • henry.Children.Add(edward);

      La multiplicidad de este rol es 0..*, por lo que lo agregamos a la colección.

  • Construya una instancia de la relación explícitamente. Por ejemplo:

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

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

    El último método es útil si desea establecer propiedades en la propia relación.

    Al crear un elemento de esta manera, se crea automáticamente un conector en el diagrama, pero tiene una forma, un color y otras características predeterminados. Para controlar cómo se crea el conector asociado, consulte Creación de un elemento y su forma.

Eliminación de elementos

Elimine un elemento; para ello, llame a Delete():

henry.Delete();

Esta operación también eliminará:

  • La relación se vincula a y desde el elemento. Por ejemplo, edward.Parents ya no contendrá henry.

  • Elementos en roles para los que la marca PropagatesDelete es true. Por ejemplo, se eliminará la forma que muestra el elemento.

De forma predeterminada, cada relación de inclusión tiene PropagatesDelete como true en el rol de destino. Al eliminar henry no se elimina familyTree, pero familyTree.Delete() eliminaría todos los elementos Persons.

De forma predeterminada, PropagatesDelete no es true para los roles de relaciones de referencia.

Puede hacer que las reglas de eliminación omitan propagaciones específicas al eliminar un objeto. Esto resulta útil si sustituye un elemento por otro. Proporcione el GUID de uno o varios roles para los que no se debe propagar la eliminación. El GUID se puede obtener de la clase de relación:

henry.Delete(ParentsHaveChildren.SourceDomainRoleId);

(Este ejemplo en particular no tendría ningún efecto, porque PropagatesDelete es false para los roles de la relación ParentsHaveChildren).

En algunos casos, la eliminación se impide mediante la existencia de un bloqueo, ya sea en el elemento o en un elemento que se eliminaría por propagación. Puede usar element.CanDelete() para comprobar si el elemento se puede eliminar.

Puede eliminar un vínculo de relación quitando un elemento de una propiedad de rol:

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

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

También puede eliminar el vínculo explícitamente:

edwardHenryLink.Delete();

Estos tres métodos tienen el mismo efecto. Solo tiene que usar uno de ellos.

Si el rol tiene multiplicidad 0..1 o 1..1, puede establecerlo en null o en otro valor:

edward.FamilyTreeModel = null; // o:

edward.FamilyTreeModel = anotherFamilyTree;

Reordenación de los vínculos de una relación

Los vínculos de una relación determinada que tienen como origen o destino un elemento de modelo determinado tienen una secuencia específica. Aparecen en el orden en que se agregaron. Por ejemplo, esta instrucción siempre producirá los elementos secundarios en el mismo orden:

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

Puede cambiar el orden de los vínculos:

ParentsHaveChildren link = GetLink(henry,edward);

ParentsHaveChildren nextLink = GetLink(henry, elizabeth);

DomainRoleInfo role =

link.GetDomainRelationship().DomainRoles[0];

link.MoveBefore(role, nextLink);

Bloqueos

Puede que un bloqueo impida los cambios. Los bloqueos se pueden establecer en elementos individuales, en particiones y en el almacén. Si alguno de estos niveles tiene un bloqueo que impide el tipo de cambio que desea realizar, se podría producir una excepción al intentarlo. Puede detectar si los bloqueos se establecen mediante el elemento element.GetLocks(), que es un método de extensión definido en el espacio de nombres Microsoft.VisualStudio.Modeling.Immutability.

Para obtener más información, vea Definir una directiva de bloqueo para crear segmentos de solo lectura.

Copiar y pegar

Puede copiar elementos o grupos de elementos en un objeto 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>());

Los elementos se almacenan como un grupo de elementos serializado.

Puede combinar elementos de un IDataObject en un modelo:

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

Merge () puede aceptar PresentationElement o ModelElement. Si le asigna un elemento PresentationElement, también puede especificar una posición en el diagrama de destino como tercer parámetro.

Navegar por los diagramas y actualizarlos

En un DSL, el elemento de modelo de dominio, que representa un concepto como Person o Song, es independiente del elemento shape, que representa lo que ve en el diagrama. El elemento de modelo de dominio almacena las propiedades y relaciones importantes de los conceptos. El elemento shape almacena el tamaño, la posición y el color de la vista del objeto en el diagrama y el diseño de sus partes componentes.

Elementos de presentación

Class diagram of base shape and element types

En la definición de DSL, cada elemento que especifique crea una clase derivada de una de las siguientes clases estándar.

Tipo de elemento Clase base
Clase de dominio ModelElement
Relación de dominio ElementLink
Forma NodeShape
Conector BinaryLinkShape
Diagrama Diagram

Un elemento de un diagrama suele representar un elemento de modelo. Normalmente (pero no siempre), NodeShape representa una instancia de clase de dominio y BinaryLinkShape representa una instancia de relación de dominio. La relación PresentationViewsSubject vincula un nodo o una forma de vínculo al elemento de modelo que representa.

Cada nodo o forma de vínculo pertenece a un diagrama. Una forma de vínculo binario conecta dos formas de nodo.

Las formas pueden tener formas secundarias en dos conjuntos. Una forma del conjunto NestedChildShapes se limita al rectángulo delimitador de su elemento primario. Una forma de la lista RelativeChildShapes puede aparecer fuera o parcialmente fuera de los límites del elemento primario; por ejemplo, una etiqueta o un puerto. Un diagrama no tiene RelativeChildShapes ni Parent.

Navegar entre formas y elementos

Los elementos del modelo de dominio y los elementos de forma están relacionados mediante PresentationViewsSubject.

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

La misma relación vincula las relaciones a los conectores en el diagrama:

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

Esta relación también vincula la raíz del modelo al diagrama:

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

Para obtener el elemento de modelo representado por una forma, use:

henryShape.ModelElement as Person

diagram.ModelElement as FamilyTreeModel

En general, no es aconsejable navegar entre formas y conectores en el diagrama. Es mejor navegar por las relaciones del modelo, moviéndose entre las formas y los conectores solo cuando es necesario trabajar en la apariencia del diagrama. Estos métodos vinculan conectores a las formas en cada extremo:

personShape.FromRoleLinkShapes, personShape.ToRoleLinkShapes

connector.FromShape, connector.ToShape

Muchas formas son compuestos; se componen de una forma primaria y una o varias capas de elementos secundarios. Se dice que las formas que están colocadas en relación con otra forma son sus elementos secundarios. Cuando se mueve la forma primaria, los elementos secundarios se mueven con ella.

Los elementos secundarios relativos pueden aparecer fuera del rectángulo delimitador de la forma primaria. Los elementos secundarios anidados aparecen estrictamente dentro de los límites del elemento primario.

Para obtener el conjunto superior de formas en un diagrama, use:

Diagram.NestedChildShapes

Las clases antecesoras de formas y conectores son:

ModelElement

-- PresentationElement

-- ShapeElement

----- NodeShape

------- Diagram

------- YourShape

----- LinkShape

------- BinaryLinkShape

--------- YourConnector

Propiedades de formas y conectores

En la mayoría de los casos, no es necesario realizar cambios explícitos en las formas. Cuando haya cambiado los elementos del modelo, las reglas de "corrección" actualizan las formas y los conectores. Para obtener más información, consulte Responder a los cambios y propagarlos.

Sin embargo, resulta útil realizar algunos cambios explícitos en las formas de las propiedades que son independientes de los elementos del modelo. Por ejemplo, puede cambiar estas propiedades:

  • Size: determina el alto y el ancho de la forma.

  • Location: posición relativa a la forma o el diagrama primarios.

  • StyleSet: el conjunto de lápices y pinceles usados para dibujar la forma o el conector.

  • Hide: hace que la forma sea invisible.

  • Show: hace que la forma sea visible después de Hide().

Creación de un elemento y su forma

Cuando se crea un elemento y se vincula al árbol de relaciones de inclusión, se crea automáticamente una forma y se asocia a él. Esto se realiza mediante las reglas de "corrección" que se ejecutan al final de la transacción. Sin embargo, la forma aparecerá en una ubicación asignada automáticamente y su forma, color y otras características tendrán valores predeterminados. Para controlar cómo se crea la forma, puede usar la función merge. Primero debe agregar los elementos que desea agregar a un ElementoGroup y, a continuación, combinar el grupo en el diagrama.

Este método:

  • Establece el nombre, si ha asignado una propiedad como el nombre de elemento.

  • Observa las directivas de fusión de elementos mediante combinación que especificó en la definición de DSL.

En este ejemplo se crea una forma en la posición del mouse, cuando el usuario hace doble clic en el diagrama. En la definición de DSL de este ejemplo, se ha expuesto la propiedad FillColor de ExampleShape.

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 proporciona más de una forma, establezca sus posiciones relativas mediante AbsoluteBounds.

También puede establecer el color y otras propiedades expuestas de conectores mediante este método.

Uso de transacciones

Las formas, los conectores y los diagramas son subtipos de ModelElement y se encuentran en Store. Por lo tanto, debe realizar cambios en ellos solo dentro de una transacción. Para obtener más información, vea Uso de transacciones para actualizar el modelo.

Vista de documentos y datos de documento

Class diagram of standard diagram types

Almacenamiento de particiones

Cuando se carga un modelo, el diagrama adjunto se carga al mismo tiempo. Normalmente, el modelo se carga en Store.DefaultPartition y el contenido del diagrama se carga en otra partición. Normalmente, el contenido de cada partición se carga y se guarda en un archivo independiente.