Compartir vía


Navegar y actualizar un modelo en código de programa

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 deben realizarse 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:

Diagrama de definición de DSL: modelo de árbol de familia

Este modelo es una instancia de este DSL:

Modelo de árbol familiar de Tudor

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 está escribiendo el código en un proyecto diferente del en el que se define el DSL, debe importar el ensamblado compilado por el proyecto 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 de 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.

Relationships

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 de 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 utiliza las extensiones de System.Linq) es siempre verdadera en 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 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 vincular ningún par de elementos de modelo a más de una instancia de una relación. Pero si en la definición de DSL, el indicador Allow Duplicates es verdadero para la relación, podría haber más de un enlace y usted 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 recorrer los vínculos mediante los métodos de la relación:

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

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

PresentationViewsSubject.GetPresentation(henry)[0] as PersonShape

El 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);

Obtener 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 elementos de modelo son las siguientes:

  • ModelElement: todos los elementos y relaciones son ModelElements

  • ElementLink: todas las relaciones son ElementLinks

Realizar cambios dentro de una transacción

Siempre que el código del programa cambie algo en la Tienda, debe hacerlo dentro de una transacción. Esto se aplica a todos los elementos del modelo, relaciones, formas, diagramas y sus propiedades. Para obtener más información, consulte Transaction.

El método más conveniente para gestionar una transacción es con una using sentencia incluida en una try...catch sentencia:

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 dentro de una transacción. Puede abrir nuevas transacciones dentro de una transacción activa.

Para realizar los cambios permanentes, debe Commit realizar la transacción antes de eliminarla. Si se produce una excepción que no se detecta dentro de la transacción, la Tienda 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 nuevo elemento en una partición específica de Store. En el caso de los elementos y relaciones del modelo, pero no las formas, suele ser la partición predeterminada.

  • Establézcalo como el objetivo de una relación de integración. En dslDefinition de este ejemplo, cada persona debe ser el destino de la inserción de relaciones FamilyTreeHasPeople. Para lograrlo, podemos establecer la propiedad de rol FamilyTreeModel del objeto Person o agregar la propiedad Person al rol People del objeto FamilyTreeModel.

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

  • La definición de DSL de este DSL debe haberse cargado en la Tienda. Si está escribiendo una extensión como un comando de menú, normalmente ya se cumplirá. En otros casos, puede cargar explícitamente el modelo en store o usar ModelBus para cargarlo. Para obtener más información, vea Cómo: Abrir un modelo desde un archivo en código de 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 predeterminada, color y otras características. Si desea controlar dónde y cómo aparece la forma asociada, vea Crear 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 al final 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 jugador de rol de fuente. Por ejemplo:

    • familyTree.People.Add(edward);

    • edward.Parents.Add(henry);

  • Establezca la propiedad del jugador 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 se agrega a la colección.

  • Construya una instancia de la relación de manera explícita. 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, color y otras características predeterminadas. Para controlar cómo se crea el conector asociado, consulte Creación de un elemento y su forma.

Eliminar elementos

Para eliminar un elemento, llame a Delete():

henry.Delete();

Esta operación también eliminará:

  • Vínculos de relación hacia y desde el elemento. Por ejemplo, edward.Parents ya no contendrá henry.

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

De forma predeterminada, cada relación de inserción tiene PropagatesDelete true en el rol de destino. Al eliminar henry no se elimina familyTree, pero familyTree.Delete() eliminaría todas las Persons.

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

Puede hacer que las reglas de eliminación omitan propagaciones específicas al eliminar un objeto. Esto es ú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 puede obtenerse de la clase de relación:

henry.Delete(ParentsHaveChildren.SourceDomainRoleId);

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

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 se puede eliminar el elemento.

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 tienes 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 hijos 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);

Locks

Es posible que los cambios se impidan mediante un bloqueo. 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, es posible que se produzca una excepción al intentarlo. Puede detectar si los bloqueos se establecen mediante el elemento . 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, consulte Definición de una directiva de bloqueo para crear segmentos de Read-Only.

Copiar y pegar

Puede copiar elementos o grupos de elementos en un IDataObject objeto:

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 ya sea un PresentationElement o un ModelElement. Si le asigna un PresentationElement, también puede especificar una posición en el diagrama de destino como tercer parámetro.

Navegación y actualización de diagramas

En un DSL, el elemento de modelo de dominio, que representa un concepto como Persona o Canción, es independiente del elemento de forma, que representa lo que se 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, así como la disposición de sus componentes.

Elementos de presentación

Diagrama de clases de tipos de elementos y formas base

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 normalmente representa un elemento de modelo. Normalmente (pero no siempre), NodeShape representa una instancia de clase de dominio y un BinaryLinkShape objeto representa una instancia de relación de dominio. La PresentationViewsSubject relación vincula un nodo o forma de vínculo al elemento de modelo que representa.

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

Las formas pueden tener formas hijas en dos conjuntos. Una forma del NestedChildShapes conjunto se limita al rectángulo delimitador de su elemento primario. Una forma de la RelativeChildShapes lista 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 con la PresentationViewsSubject relación.

// 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 en el modelo, moverse 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 compositas; están formadas por una forma principal y una o varias capas de formas hijas. Se dice que las formas que se colocan 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 cuadro de límite 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, vea 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, podría cambiar estas propiedades:

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

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

  • StyleSet : 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()

Crear un elemento y su forma

Al crear un elemento y vincularlo al árbol de relaciones de inserció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 elementGroup y, a continuación, combinar el grupo en el diagrama.

Este método:

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

  • Observa las directivas de combinación de elementos que se especificaron en la definición del 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 FillColor propiedad 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 el AbsoluteBounds.

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

Usar transacciones

Las formas, los conectores y los diagramas son subtipos de ModelElement y viven en la Tienda. Por lo tanto, debe realizar cambios en ellos solo dentro de una transacción. Para obtener más información, vea Cómo: Usar transacciones para actualizar el modelo.

Vista de documento y datos de documento

Diagrama de clases de tipos de diagrama estándar

Almacenar 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.