Navegar e atualizar um modelo no código do programa

Você pode gravar código para criar e excluir elementos de modelo, definir as propriedades e criar e excluir links entre elementos. Todas as alterações devem ser feitas em uma transação. Se os elementos forem exibidos em um diagrama, o diagrama será "corrigido" automaticamente no final da transação.

Um Exemplo de Definição de DSL

Esta é a parte principal do DslDefinition.dsl para os exemplos neste tópico:

DSL Definition diagram - family tree model

Esse modelo é uma instância dessa DSL:

Tudor Family Tree Model

Referências e Namespaces

Para executar o código neste tópico, você deve referenciar o seguinte:

Microsoft.VisualStudio.Modeling.Sdk.11.0.dll

Seu código usará este namespace:

using Microsoft.VisualStudio.Modeling;

Além disso, se você estiver gravando o código em um projeto diferente daquele em que a DSL está definida, importe o assembly compilado pelo projeto Dsl.

Propriedades

As propriedades de domínio determinadas na definição de DSL se tornam propriedades que você pode acessar no código do programa:

Person henry = ...;

if (henry.BirthDate < 1500) ...

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

Se você quiser definir uma propriedade, deverá fazer isso dentro de uma transação:

henry.Name = "Henry VIII";

Se, na definição de DSL, o Tipo de uma propriedade for Calculado, você não poderá defini-lo. Para obter mais informações, confira Propriedades de armazenamento calculado e personalizado.

Relações

Os relacionamento de domínio determinados na definição de DSL se tornam pares de propriedades, uma na classe em cada extremidade da relação. Os nomes das propriedades são exibidos no diagrama DslDefinition como rótulos nas funções em cada lado da relação. Dependendo da multiplicidade da função, o tipo da propriedade é a classe na outra extremidade da relação ou uma coleção dessa classe.

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

FamilyTreeModel ftree = henry.FamilyTreeModel;

As propriedades em extremidades opostas de uma relação são sempre recíprocas. Quando um link é criado ou excluído, as propriedades de função em ambos os elementos são atualizadas. A expressão a seguir (que usa as extensões de System.Linq) é sempre true para a relação ParentsHaveChildren no exemplo:

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

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

ElementLinks. Uma relação também é representada por um elemento de modelo chamado link, que é uma instância do tipo de relacionamento de domínio. Um link sempre tem um elemento de origem e um elemento de destino. O elemento de origem e o elemento de destino podem ser o mesmo.

Você pode acessar um link e suas propriedades:

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

// This is now true:

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

Por padrão, no máximo uma instância de uma relação tem permissão para vincular qualquer par de elementos de modelo. Mas se, na definição de DSL, o sinalizador Allow Duplicates for true para a relação, pode haver mais de um link e você deve usar GetLinks:

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

Também há outros métodos para acessar os links. Por exemplo:

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

Funções ocultas. Se na definição de DSL, Is Property Generated for false para uma função específica, nenhuma propriedade será gerada que corresponda a essa função. No entanto, você ainda pode acessar e percorrer os links usando os métodos da relação:

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

O exemplo usado com mais frequência é a relação PresentationViewsSubject, que vincula um elemento de modelo à forma que o exibe em um diagrama:

PresentationViewsSubject.GetPresentation(henry)[0] as PersonShape

O Diretório do Elemento

Você pode acessar todos os elementos no repositório usando o diretório de elementos:

store.ElementDirectory.AllElements

Também há métodos para localizar elementos, como o seguinte:

store.ElementDirectory.FindElements(Person.DomainClassId);

store.ElementDirectory.GetElement(elementId);

Como acessar as informações de classe

Você pode obter informações sobre as classes, as relações e outros aspectos da definição de DSL. Por exemplo:

DomainClassInfo personClass = henry.GetDomainClass();

DomainPropertyInfo birthProperty =

personClass.FindDomainProperty("BirthDate")

DomainRelationshipInfo relationship =

link.GetDomainRelationship();

DomainRoleInfo sourceRole = relationship.DomainRole[0];

As classes ancestrais dos elementos de modelo são as seguintes:

  • ModelElement – todos os elementos e relações são ModelElements

  • ElementLink – todas as relações são ElementLinks

Executar alterações dentro de uma transação

Sempre que o código do programa altera algo no Repositório, ele deve fazer isso dentro de uma transação. Isso se aplica a todos os elementos de modelo, relações, formas, diagramas e suas propriedades. Para obter mais informações, consulte Transaction.

O método mais conveniente de gerenciar uma transação é com uma instrução using embutida em uma instrução 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.
}

Você pode fazer qualquer número de alterações dentro de uma transação. Você pode abrir novas transações dentro de uma transação ativa.

Para tornar as alterações permanentes, você deve Commit a transação antes que ela seja descartada. Se ocorrer uma exceção que não seja capturada dentro da transação, o Repositório será redefinida para seu estado anterior às alterações.

Como criar elementos de modelo

Este exemplo adiciona um elemento a um 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!
}

Este exemplo ilustra estes pontos essenciais sobre como criar um elemento:

  • Crie o novo elemento em uma partição específica do Repositório. Para elementos e relações de modelo, mas não para formas, geralmente essa é a partição padrão.

  • Torne-a o destino de um relacionamento de incorporação. No DslDefinition deste exemplo, cada Pessoa deve ser o destino do relacionamento de incorporação FamilyTreeHasPeople. Para fazer isso, podemos definir a propriedade de função FamilyTreeModel do objeto Pessoa ou adicionar a Pessoa à propriedade de função Pessoas do objeto FamilyTreeModel.

  • Defina as propriedades de um novo elemento, especialmente a propriedade para a qual IsName é true no DslDefinition. Esse sinalizador marca a propriedade que serve para identificar o elemento exclusivamente dentro de seu proprietário. Nesse caso, a propriedade Nome tem esse sinalizador.

  • A definição dessa DSL deve ter sido carregada no Repositório. Se você estiver gravando uma extensão, como um comando de menu, normalmente ela já será true. Em outros casos, você pode carregar explicitamente o modelo no Repositório ou usar o ModelBus para carregá-lo. Para obter mais informações, confira Instruções: abrir um modelo do arquivo no código do programa.

    Quando você cria um elemento dessa maneira, uma forma é criada automaticamente (se a DSL tiver um diagrama). Ela é exibida em um local atribuído automaticamente, com forma, cor e outros recursos padrão. Se você quiser controlar onde e como a forma associada será exibida, confira Como criar um elemento e sua forma.

Há duas relações determinadas na definição de DSL de exemplo. Cada relação define uma propriedade de função na classe em cada extremidade da relação.

Há três maneiras pelas quais você pode criar uma instância de uma relação. Cada um desses três métodos tem o mesmo efeito:

  • Defina a propriedade do representante da função de origem. Por exemplo:

    • familyTree.People.Add(edward);

    • edward.Parents.Add(henry);

  • Defina a propriedade do representante da função de destino. Por exemplo:

    • edward.familyTreeModel = familyTree;

      A multiplicidade dessa função é 1..1. Portanto, atribuímos o valor.

    • henry.Children.Add(edward);

      A multiplicidade dessa função é 0..*. Portanto, adicionamos à coleção.

  • Construa uma instância da relação explicitamente. Por exemplo:

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

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

    O último método será útil se você quiser definir propriedades na própria relação.

    Quando você cria um elemento dessa maneira, um conector no diagrama é criado automaticamente, mas ele tem uma forma, cor e outros recursos padrão. Para controlar como o conector associado é criado, confira Como criar um elemento e sua forma.

Como excluir elementos

Exclua um elemento chamando Delete():

henry.Delete();

Essa operação também excluirá:

  • Links de relação no elemento. Por exemplo, edward.Parents não conterá mais henry.

  • Elementos nas funções em que o sinalizador PropagatesDelete é true. Por exemplo, a forma que exibe o elemento será excluída.

Por padrão, cada relacionamento de incorporação tem PropagatesDelete true na função de destino. A exclusão de henry não exclui o familyTree, mas familyTree.Delete() excluiria todos os Persons.

Por padrão, PropagatesDelete não é true para as funções de relações de referência.

Você pode fazer com que as regras de exclusão omitam propagações específicas ao excluir um objeto. Isso será útil se você estiver substituindo um elemento por outro. Você fornece o GUID de uma ou mais funções para as quais a exclusão não deve ser propagada. O GUID pode ser obtido da classe de relação:

henry.Delete(ParentsHaveChildren.SourceDomainRoleId);

(Este exemplo específico não teria efeito, pois PropagatesDelete é false para as funções da relação ParentsHaveChildren.)

Em alguns casos, a existência de um bloqueio impede a exclusão, seja no elemento ou em um elemento que seria excluído pela propagação. Você pode usar element.CanDelete() para verificar se o elemento pode ser excluído.

Você pode excluir um link de relação removendo um elemento de uma propriedade de função:

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

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

Você também pode excluir o link explicitamente:

edwardHenryLink.Delete();

Todos esses três métodos têm o mesmo efeito. Você só precisa usar um deles.

Se a função tiver multiplicidade 0..1 ou 1..1, você poderá defini-la como null ou como outro valor:

edward.FamilyTreeModel = null; // ou:

edward.FamilyTreeModel = anotherFamilyTree;

Como reordenar os links de uma relação

Os links de uma relação específica cuja origem e o destino a determinado elemento de modelo têm uma sequência específica. Eles são exibidos na ordem em que foram adicionados. Por exemplo, essa instrução sempre gerará os filhos na mesma ordem:

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

É possível alterar a ordem dos links:

ParentsHaveChildren link = GetLink(henry,edward);

ParentsHaveChildren nextLink = GetLink(henry, elizabeth);

DomainRoleInfo role =

link.GetDomainRelationship().DomainRoles[0];

link.MoveBefore(role, nextLink);

Locks

Um bloqueio pode impedir suas alterações. Os bloqueios podem ser definidos em elementos individuais, em partições e no repositório. Se qualquer um desses níveis tiver um bloqueio que impeça o tipo de alteração que você deseja fazer, uma exceção pode ser gerada quando você tentar fazer a alteração. Você pode descobrir se há bloqueios definidos usando o element.GetLocks(), que é um método de extensão definido no namespace Microsoft.VisualStudio.Modeling.Immutability.

Para obter mais informações, confira Como definir uma política de bloqueio para criar segmentos Somente Leitura.

Copiar e colar

Você pode copiar elementos ou grupos de elementos para um 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>());

Os elementos são armazenados como um Grupo de Elementos serializado.

Você pode mesclar elementos de um IDataObject em um modelo:

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

Merge () pode aceitar um PresentationElement ou um ModelElement. Se você fornecer um PresentationElement, também poderá especificar uma posição no diagrama de destino como um terceiro parâmetro.

Como navegar e atualizar diagramas

Em uma DSL, o elemento de modelo de domínio, que representa um conceito como Pessoa ou Música, é separado do elemento de forma, que representa o que você vê no diagrama. O elemento de modelo de domínio armazena as propriedades e relações importantes dos conceitos. O elemento de forma armazena o tamanho, a posição e a cor da exibição do objeto no diagrama e o layout das partes componentes.

Elemento da Apresentação

Class diagram of base shape and element types

Na definição de DSL, cada elemento especificado cria uma classe derivada de uma das classes padrão a seguir.

Tipo de elemento Classe base
Classe de domínio ModelElement
Relacionamento de domínio ElementLink
Forma NodeShape
Connector BinaryLinkShape
Diagrama Diagram

Um elemento em um diagrama geralmente representa um elemento de modelo. Normalmente (mas nem sempre), um NodeShape representa uma instância de classe de domínio e um BinaryLinkShape representa uma instância de relacionamento de domínio. A relação PresentationViewsSubject vincula um nó ou uma forma de vínculo ao elemento de modelo que representa.

Cada nó ou forma de link pertence a um diagrama. Uma forma de link binário conecta duas formas de nó.

As formas podem ter formas filho em dois conjuntos. Uma forma no conjunto NestedChildShapes é confinada à caixa delimitadora de seu pai. Uma forma na lista RelativeChildShapes pode ser exibida fora ou parcialmente fora dos limites do pai – por exemplo, um rótulo ou uma porta. Um diagrama não tem RelativeChildShapes nem Parent.

Como navegar entre formas e elementos

Elementos de modelo de domínio e elementos de forma estão relacionados pela relação PresentationViewsSubject.

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

A mesma relação vincula relações a conectores no diagrama:

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

Essa relação também vincula a raiz do modelo ao diagrama:

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

Para obter o elemento de modelo representado por uma forma, use:

henryShape.ModelElement as Person

diagram.ModelElement as FamilyTreeModel

Em geral, não é aconselhável navegar entre formas e conectores no diagrama. É melhor navegar pelas relações no modelo, movendo-se entre as formas e os conectores somente quando for necessário trabalhar na aparência do diagrama. Esses métodos vinculam conectores às formas em cada extremidade:

personShape.FromRoleLinkShapes, personShape.ToRoleLinkShapes

connector.FromShape, connector.ToShape

Muitas formas são composição. Elas são compostas por uma forma pai e uma ou mais camadas de filhos. As formas posicionadas em relação a outra forma são consideradas seus filhos. Quando a forma pai se move, os filhos se movem com ela.

Os filhos relativos podem ser exibidos fora da caixa delimitadora da forma pai. Os filhos aninhados são exibidos estritamente dentro dos limites do pai.

Para obter o conjunto superior de formas em um diagrama, use:

Diagram.NestedChildShapes

As classes ancestrais de formas e conectores são:

ModelElement

-- PresentationElement

-- ShapeElement

----- NodeShape

------- Diagram

------- YourShape

----- LinkShape

------- BinaryLinkShape

--------- YourConnector

Propriedades de formas e conectores

Na maioria dos casos, não é necessário fazer alterações explícitas nas formas. Quando você altera os elementos do modelo, as regras de "correção" atualizam as formas e os conectores. Para obter mais informações, confira Como responder e propagar alterações.

No entanto, é útil fazer algumas alterações explícitas nas formas em propriedades independentes dos elementos de modelo. Por exemplo, você pode alterar essas propriedades:

  • Size – determina a altura e a largura da forma.

  • Location – posição em relação à forma pai ou ao diagrama

  • StyleSet – o conjunto de canetas e pincéis usados para desenhar a forma ou o conector

  • Hide – torna a forma invisível

  • Show – torna a forma visível após um Hide()

Como criar um elemento e sua forma

Quando você cria um elemento e o vincula à árvore de relacionamentos de incorporação, uma forma é criada automaticamente e associada a ele. Isso é feito pelas regras de "correção" executadas no final da transação. No entanto, a forma será exibida em um local atribuído automaticamente e sua forma, cor e outros recursos terão valores padrão. Para controlar a maneira como a forma é criada, você pode usar a função de mesclagem. Primeiro, você deve adicionar os elementos desejados a um ElementGroup e, em seguida, mesclar o grupo no diagrama.

Este método:

  • Define o nome, se você atribuiu uma propriedade como o nome do elemento.

  • Observa as diretivas de mesclagem de elementos que você especificou na Definição de DSL.

Este exemplo cria uma forma na posição do mouse, quando o usuário clica duas vezes no diagrama. Na Definição de DSL para este exemplo, a propriedade FillColor de ExampleShape foi exposta.

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

Se você fornecer mais de uma forma, defina as posições relativas usando o AbsoluteBounds.

Você também pode definir a cor e outras propriedades expostas de conectores usando esse método.

Usar transações

Formas, conectores e diagramas são subtipos de ModelElement e estão localizados no Repositório. Portanto, você deve fazer alterações somente dentro de uma transação. Para obter mais informações, confira Instruções: usar transações para atualizar o modelo.

Exibição de Documento e Dados de Documento

Class diagram of standard diagram types

Partições do repositório

Quando um modelo é carregado, o diagrama que acompanha é carregado ao mesmo tempo. Normalmente, o modelo é carregado no Store.DefaultPartition e o conteúdo do diagrama é carregado em outra partição. Normalmente, o conteúdo de cada partição é carregado e salvo em um arquivo separado.