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:
Esse modelo é uma instância dessa DSL:
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.
Como navegar no modelo
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.
Como criar links de relação
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á maishenry
.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.
Como excluir links de relação
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
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
Como navegar pelo diagrama
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:
-- 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
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.