Compartilhar via


Navegar e atualizar um modelo no código do programa

Você pode escrever código para criar e excluir elementos de modelo, definir suas 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.

Uma definição de DSL de exemplo

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

Diagrama de definição de DSL – modelo de árvore genealógica

Esse modelo é uma instância desta DSL:

Modelo de Árvore Familiar Tudor

Referências e namespaces

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

Microsoft.VisualStudio.Modeling.Sdk.11.0.dll

Seu código usará este namespace:

using Microsoft.VisualStudio.Modeling;

Além disso, se você estiver escrevendo o código em um projeto diferente daquele em que sua DSL está definida, importe o assembly criado pelo projeto DsL.

Propriedades

As propriedades de domínio definidas na definição de DSL tornam-se propriedades que podem ser acessadas 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-la. Para obter mais informações, consulte Propriedades de Armazenamento Calculadas e Personalizadas.

Relationships

As relações de domínio definidas na definição de DSL tornam-se pares de propriedades, uma na classe em cada extremidade da relação. Os nomes das propriedades aparecem 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 verdadeira 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 relação 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 os mesmos.

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, não mais de uma instância de uma relação tem permissão para vincular qualquer par de elementos de modelo. No entanto, se na definição de DSL, o Allow Duplicates sinalizador for verdadeiro para a relação, poderá haver mais de um link e você deverá usar GetLinks:

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

Há também outros métodos para acessar links. Por exemplo:

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

Funções ocultas. Se na definição de DSL, É Propriedade Gerada for falsa para um papel específico, nenhuma propriedade será gerada que corresponda a esse papel. No entanto, você ainda pode acessar os links 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 PresentationViewsSubject relação, 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 do elemento:

store.ElementDirectory.AllElements

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

store.ElementDirectory.FindElements(Person.DomainClassId);

store.ElementDirectory.GetElement(elementId);

Acessando 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 na Loja, 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 using declaração colocada em uma try...catch declaração:

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 suas 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, a Loja será redefinida para seu estado antes das alterações.

Criando 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 da Loja. Para elementos de modelo e relações, mas não formas, essa geralmente é a partição padrão.

  • Torne-o o destino de uma relação de inserção. No DslDefinition deste exemplo, cada Pessoa deve ser o destino da inserção da relação FamilyTreeHasPeople. Para isso, podemos definir a propriedade de função FamilyTreeModel do objeto Person ou adicionar a pessoa à propriedade de função People do objeto FamilyTreeModel.

  • Defina as propriedades de um novo elemento, particularmente a propriedade para a qual IsName é verdadeira no DslDefinition. Este indicador marca a propriedade que serve para identificar o elemento exclusivamente dentro de sua propriedade. Nesse caso, a propriedade Name tem esse sinalizador.

  • A definição desta DSL deve ter sido carregada no repositório. Se você estiver escrevendo uma extensão, como um comando de menu, isso normalmente já será verdadeiro. Em outros casos, você pode carregar explicitamente o modelo na Loja ou usar o ModelBus para carregá-lo. Para obter mais informações, consulte Como abrir um modelo do arquivo no código do programa.

    Quando você cria um elemento dessa forma, uma forma é criada automaticamente (se a DSL tiver um diagrama). Ele aparece em um local atribuído automaticamente, com forma, cor e outros recursos padrão. Se você quiser controlar onde e como a forma associada aparece, consulte Criando um elemento e sua forma.

Há duas relações definidas 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 participante de função de origem. Por exemplo:

    • familyTree.People.Add(edward);

    • edward.Parents.Add(henry);

  • Defina a propriedade do jogador-alvo de função. Por exemplo:

    • edward.familyTreeModel = familyTree;

      A multiplicidade dessa função é 1..1, portanto, atribuimos 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 forma, um conector no diagrama é criado automaticamente, mas ele tem uma forma, cor e outros recursos padrão. Para controlar como o conector associado é criado, consulte Criando um elemento e sua forma.

Excluindo elementos

Exclua um elemento chamando Delete():

henry.Delete();

Essa operação também excluirá:

  • Links de relacionamento para e do elemento. Por exemplo, edward.Parents não conterá henrymais .

  • Elementos em funções para as quais o PropagatesDelete flag é verdadeiro. Por exemplo, a forma que exibe o elemento será excluída.

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

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

Você pode fazer com que as regras de exclusão omita 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 nenhum efeito, porque PropagatesDelete é false para as funções da ParentsHaveChildren relação.)

Em alguns casos, a exclusão é impedida pela existência de um bloqueio, 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 para outro valor:

edward.FamilyTreeModel = null; ou:

edward.FamilyTreeModel = anotherFamilyTree;

Reordenar os Links de uma Relação

Os links de uma relação específica que são originados ou direcionados a um determinado elemento de modelo têm uma sequência específica. Eles aparecem na ordem em que foram adicionados. Por exemplo, essa instrução sempre produzirá os filhos na mesma ordem:

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

Você pode 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

Suas alterações podem ser impedidas por um bloqueio. 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 poderá ser gerada quando você tentar. Você pode descobrir se os bloqueios são definidos usando o elemento. GetLocks(), que é um método de extensão definido no namespace Microsoft.VisualStudio.Modeling.Immutability.

Para obter mais informações, consulte Definindo uma política de bloqueio para criar segmentos de Read-Only.

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ê der um PresentationElement, também poderá especificar uma posição no diagrama de destino como um terceiro parâmetro.

Navegando e atualizando diagramas

Em uma DSL, o elemento de modelo de domínio, que representa um conceito como Pessoa ou Canção, é 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 shape armazena o tamanho, a posição e a cor da exibição do objeto no diagrama e o layout de suas partes componentes.

Elementos de apresentação

Diagrama de Classe de Tipos Básicos de Formas e Elementos

Em sua Definição de DSL, cada elemento que você especificar cria uma classe derivada de uma das seguintes classes padrão.

Tipo de elemento Classe base
Classe de domínio ModelElement
Relação de domínio ElementLink
Forma NodeShape
Connector BinaryLinkShape
Diagramar 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 relação de domínio. A PresentationViewsSubject relação vincula um nó ou uma forma de vínculo ao elemento de modelo que ele representa.

Cada forma de nó ou link pertence a um único diagrama. Uma forma de ligação binária conecta duas formas de nó.

As formas podem ter formas filho em dois conjuntos. Uma forma no conjunto NestedChildShapes está confinada à caixa delimitadora de seu elemento pai. Uma forma na RelativeChildShapes lista pode aparecer fora ou em parte fora dos limites do elemento pai - por exemplo, um rótulo ou uma porta. Um diagrama não tem nenhum RelativeChildShapes e nenhum Parent.

Navegando entre formas e elementos

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

// 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 compostas; elas são compostas por uma forma principal e uma ou mais camadas subordinadas. As formas posicionadas em relação a outra forma são consideradas seus filhos. Quando a forma principal se move, as formas filhas se movem com ela.

Os filhos relativos podem aparecer fora da caixa delimitadora da forma pai. Crianças aninhadas aparecem 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

--------- SeuConector

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 ajuste atualizam as formas e os conectores. Para mais informações, consulte Responder e Propagar Alterações.

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

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

  • Location - posição relativa à forma ou diagrama pai

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

Criando um elemento e sua forma

Quando você cria um elemento e o vincula à árvore de relações de inserção, uma forma é criada automaticamente e associada a ele. ** Isso é feito pelas regras de "ajuste" que são executadas no final da transação. No entanto, a forma aparecerá em um local atribuído automaticamente, e sua forma, cor e outros recursos terão valores padrão. Para controlar como a forma é criada, você pode usar a função de mesclagem. Primeiro você deve adicionar os elementos que deseja adicionar 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 especificadas 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 FillColor propriedade 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 suas 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 armazenados no Repositório. Portanto, você deve fazer alterações neles somente dentro de uma transação. Para obter mais informações, consulte Como usar transações para atualizar o modelo.

Exibição do documento e dados do documento

Diagrama de classe de tipos de diagrama padrão

Armazenar partições

Quando um modelo é carregado, o diagrama que acompanha é carregado ao mesmo tempo. Normalmente, o modelo é carregado em 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.