Esplorare e aggiornare i modelli nel codice del programma

È possibile scrivere codice per creare ed eliminare elementi del modello, impostarne le proprietà e creare ed eliminare collegamenti tra gli elementi. Tutte le modifiche devono essere apportate all'interno di una transazione. Se gli elementi vengono visualizzati in un diagramma, il diagramma verrà "corretto" automaticamente alla fine della transazione.

Definizione DSL di esempio

Questa è la parte principale di DslDefinition.dsl per gli esempi in questo argomento:

DSL Definition diagram - family tree model

Questo modello è un'istanza del linguaggio DSL:

Tudor Family Tree Model

Riferimenti e spazi dei nomi

Per eseguire il codice in questo argomento, è necessario fare riferimento a:

Microsoft.VisualStudio.Modeling.Sdk.11.0.dll

Il codice userà questo spazio dei nomi:

using Microsoft.VisualStudio.Modeling;

Inoltre, se si scrive il codice in un progetto diverso da quello in cui è definito il linguaggio DSL, è necessario importare l'assembly compilato dal progetto Dsl.

Proprietà

Le proprietà di dominio definite nella definizione DSL diventano proprietà a cui è possibile accedere nel codice del programma:

Person henry = ...;

if (henry.BirthDate < 1500) ...

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

Se si desidera impostare una proprietà, è necessario eseguire questa operazione all'interno di una transazione:

henry.Name = "Henry VIII";

Se nella definizione DSL, il tipo di una proprietà viene calcolato, non è possibile impostarlo. Per altre informazioni, vedere Proprietà Archiviazione calcolate e personalizzate.

Relazioni

Le relazioni di dominio definite nella definizione DSL diventano coppie di proprietà, una nella classe a ogni fine della relazione. I nomi delle proprietà vengono visualizzati nel diagramma DslDefinition come etichette sui ruoli in ogni lato della relazione. A seconda della molteplicità del ruolo, il tipo della proprietà è la classe all'altra estremità della relazione o una raccolta di tale classe.

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

FamilyTreeModel ftree = henry.FamilyTreeModel;

Le proprietà alle estremità opposte di una relazione sono sempre reciproche. Quando viene creato o eliminato un collegamento, le proprietà del ruolo in entrambi gli elementi vengono aggiornate. L'espressione seguente (che usa le estensioni di System.Linq) è sempre true per la relazione ParentsHaveChildren nell'esempio:

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

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

ElementLinks. Una relazione è rappresentata anche da un elemento del modello denominato collegamento, che è un'istanza del tipo di relazione di dominio. Un collegamento ha sempre un elemento di origine e un elemento di destinazione. L'elemento di origine e l'elemento di destinazione possono essere uguali.

È possibile accedere a un collegamento e alle relative proprietà:

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

// This is now true:

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

Per impostazione predefinita, non è consentita alcuna istanza di una relazione per collegare qualsiasi coppia di elementi del modello. Tuttavia, se nella definizione DSL, il Allow Duplicates flag è true per la relazione, potrebbe esserci più di un collegamento ed è necessario usare GetLinks:

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

Sono disponibili anche altri metodi per accedere ai collegamenti. Ad esempio:

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

Ruoli nascosti. Se nella definizione DSL, La proprietà generata è false per un determinato ruolo, non viene generata alcuna proprietà corrispondente a tale ruolo. Tuttavia, è comunque possibile accedere ai collegamenti e attraversare i collegamenti usando i metodi della relazione:

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

L'esempio più frequente è la PresentationViewsSubject relazione, che collega un elemento del modello alla forma che la visualizza in un diagramma:

PresentationViewsSubject.GetPresentation(henry)[0] as PersonShape

Directory degli elementi

È possibile accedere a tutti gli elementi nell'archivio usando la directory degli elementi:

store.ElementDirectory.AllElements

Esistono anche metodi per trovare elementi, ad esempio i seguenti:

store.ElementDirectory.FindElements(Person.DomainClassId);

store.ElementDirectory.GetElement(elementId);

Accesso alle informazioni sulla classe

È possibile ottenere informazioni sulle classi, le relazioni e altri aspetti della definizione DSL. Ad esempio:

DomainClassInfo personClass = henry.GetDomainClass();

DomainPropertyInfo birthProperty =

personClass.FindDomainProperty("BirthDate")

DomainRelationshipInfo relationship =

link.GetDomainRelationship();

DomainRoleInfo sourceRole = relationship.DomainRole[0];

Le classi predecessore degli elementi del modello sono le seguenti:

  • ModelElement: tutti gli elementi e le relazioni sono ModelElements

  • ElementLink: tutte le relazioni sono ElementLinks

Eseguire modifiche all'interno di una transazione

Ogni volta che il codice del programma cambia qualcosa nello Store, deve farlo all'interno di una transazione. Questo vale per tutti gli elementi del modello, le relazioni, le forme, i diagrammi e le relative proprietà. Per ulteriori informazioni, vedere Transaction.

Il metodo più pratico per gestire una transazione è costituito da un'istruzione using racchiusa in un'istruzione 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.
}

È possibile apportare un numero qualsiasi di modifiche all'interno di una transazione. È possibile aprire nuove transazioni all'interno di una transazione attiva.

Per rendere permanenti le modifiche, è necessario Commit che la transazione venga eliminata. Se si verifica un'eccezione che non viene intercettata all'interno della transazione, lo Store verrà reimpostato sullo stato prima delle modifiche.

Creazione di elementi del modello

Questo esempio aggiunge un elemento a un modello esistente:

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

Questo esempio illustra questi punti essenziali relativi alla creazione di un elemento:

  • Creare il nuovo elemento in una partizione specifica dello Store. Per gli elementi e le relazioni del modello, ma non per le forme, si tratta in genere della partizione predefinita.

  • Impostarla come destinazione di una relazione di incorporamento. Nella dslDefinition di questo esempio ogni persona deve essere la destinazione della relazione di incorporamento FamilyTreeHas Persone. A tale scopo, è possibile impostare la proprietà del ruolo FamilyTreeModel dell'oggetto Person oppure aggiungere Person alla proprietà del ruolo Persone dell'oggetto FamilyTreeModel.

  • Impostare le proprietà di un nuovo elemento, in particolare la proprietà per cui IsName è true in DslDefinition. Questo flag contrassegna la proprietà che serve per identificare l'elemento in modo univoco all'interno del relativo proprietario. In questo caso, la proprietà Name ha tale flag.

  • La definizione DSL di questo dsl deve essere stata caricata nello Store. Se si sta scrivendo un'estensione, ad esempio un comando di menu, questo in genere sarà già true. In altri casi, è possibile caricare in modo esplicito il modello nello Store o usare ModelBus per caricarlo. Per altre informazioni, vedere Procedura: Aprire un modello da file nel codice del programma.

    Quando si crea un elemento in questo modo, viene creata automaticamente una forma (se il linguaggio DSL ha un diagramma). Viene visualizzato in una posizione assegnata automaticamente, con forma, colore e altre caratteristiche predefinite. Per controllare dove e come viene visualizzata la forma associata, vedere Creazione di un elemento e della relativa forma.

Esistono due relazioni definite nella definizione DSL di esempio. Ogni relazione definisce una proprietà del ruolo nella classe a ogni fine della relazione.

Esistono tre modi in cui è possibile creare un'istanza di una relazione. Ognuno di questi tre metodi ha lo stesso effetto:

  • Impostare la proprietà del lettore del ruolo di origine. Ad esempio:

    • familyTree.People.Add(edward);

    • edward.Parents.Add(henry);

  • Impostare la proprietà del lettore ruolo di destinazione. Ad esempio:

    • edward.familyTreeModel = familyTree;

      La molteplicità di questo ruolo è 1..1, quindi si assegna il valore .

    • henry.Children.Add(edward);

      La molteplicità di questo ruolo è 0..*, quindi si aggiunge alla raccolta.

  • Costruire un'istanza della relazione in modo esplicito. Ad esempio:

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

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

    L'ultimo metodo è utile se si desidera impostare le proprietà sulla relazione stessa.

    Quando si crea un elemento in questo modo, viene creato automaticamente un connettore nel diagramma, ma ha una forma, un colore e altre funzionalità predefinite. Per controllare la modalità di creazione del connettore associato, vedere Creazione di un elemento e della relativa forma.

Eliminazione di elementi

Eliminare un elemento chiamando Delete():

henry.Delete();

Questa operazione eliminerà anche:

  • Collegamenti di relazione da e verso l'elemento . Ad esempio, edward.Parents non conterrà henrypiù .

  • Elementi nei ruoli per i quali il PropagatesDelete flag è true. Ad esempio, la forma che visualizza l'elemento verrà eliminata.

Per impostazione predefinita, ogni relazione di incorporamento ha PropagatesDelete true nel ruolo di destinazione. L'eliminazione henry non comporta l'eliminazione di , ma familyTree.Delete() comporta l'eliminazione familyTreedi tutti gli oggetti Persons.

Per impostazione predefinita, PropagatesDelete non è true per i ruoli delle relazioni di riferimento.

È possibile che le regole di eliminazione omettano propagazioni specifiche quando si elimina un oggetto. Ciò è utile se si sta sostituendo un elemento per un altro. Specificare il GUID di uno o più ruoli per cui l'eliminazione non deve essere propagata. Il GUID può essere ottenuto dalla classe di relazione:

henry.Delete(ParentsHaveChildren.SourceDomainRoleId);

Questo particolare esempio non avrà alcun effetto, perché PropagatesDelete è false per i ruoli della ParentsHaveChildren relazione.

In alcuni casi, l'eliminazione viene impedita dall'esistenza di un blocco, sull'elemento o su un elemento che verrebbe eliminato dalla propagazione. È possibile usare element.CanDelete() per verificare se l'elemento può essere eliminato.

È possibile eliminare un collegamento di relazione rimuovendo un elemento da una proprietà del ruolo:

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

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

È anche possibile eliminare il collegamento in modo esplicito:

edwardHenryLink.Delete();

Questi tre metodi hanno tutti lo stesso effetto. È necessario usare solo uno di essi.

Se il ruolo ha 0...1 o 1..1 molteplicità, è possibile impostarlo su nullo su un altro valore:

edward.FamilyTreeModel = null; O:

edward.FamilyTreeModel = anotherFamilyTree;

Riordinare i collegamenti di una relazione

I collegamenti di una determinata relazione che vengono originati o destinati a un particolare elemento del modello hanno una sequenza specifica. Vengono visualizzati nell'ordine in cui sono stati aggiunti. Ad esempio, questa istruzione restituirà sempre gli elementi figlio nello stesso ordine:

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

È possibile modificare l'ordine dei collegamenti:

ParentsHaveChildren link = GetLink(henry,edward);

ParentsHaveChildren nextLink = GetLink(henry, elizabeth);

DomainRoleInfo role =

link.GetDomainRelationship().DomainRoles[0];

link.MoveBefore(role, nextLink);

Locks

Le modifiche potrebbero essere impedite da un blocco. I blocchi possono essere impostati su singoli elementi, sulle partizioni e sull'archivio. Se uno di questi livelli ha un blocco che impedisce il tipo di modifica che si desidera apportare, è possibile che venga generata un'eccezione quando si tenta di farlo. È possibile determinare se i blocchi vengono impostati usando l'elemento . GetLocks(), che è un metodo di estensione definito nello spazio dei nomi Microsoft.VisualStudio.Modeling.Immutability.

Per altre informazioni, vedere Definizione di un criterio di blocco per creare segmenti di sola lettura.

Copiare e incollare

È possibile copiare elementi o gruppi di elementi in un IDataObjectoggetto :

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

Gli elementi vengono archiviati come gruppo di elementi serializzati.

È possibile unire elementi da un oggetto IDataObject a un modello:

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

Merge () può accettare un oggetto PresentationElement o un oggetto ModelElement. Se si assegna un oggetto PresentationElement, è anche possibile specificare una posizione nel diagramma di destinazione come terzo parametro.

Esplorazione e aggiornamento di diagrammi

In un linguaggio DSL, l'elemento del modello di dominio, che rappresenta un concetto come Person o Song, è separato dall'elemento shape, che rappresenta ciò che viene visualizzato nel diagramma. L'elemento del modello di dominio archivia le proprietà e le relazioni importanti dei concetti. L'elemento shape archivia le dimensioni, la posizione e il colore della visualizzazione dell'oggetto nel diagramma e il layout delle relative parti del componente.

Elementi presentazione

Class diagram of base shape and element types

Nella definizione DSL, ogni elemento specificato crea una classe derivata da una delle classi standard seguenti.

Tipo di elemento Classe base
Classe domain ModelElement
Relazione di dominio ElementLink
Forma NodeShape
Connector BinaryLinkShape
Diagramma Diagram

Un elemento in un diagramma rappresenta in genere un elemento del modello. In genere (ma non sempre), un rappresenta NodeShape un'istanza della classe di dominio e un BinaryLinkShape oggetto rappresenta un'istanza della relazione di dominio. La PresentationViewsSubject relazione collega un nodo o una forma di collegamento all'elemento del modello rappresentato.

Ogni nodo o forma di collegamento appartiene a un diagramma. Una forma di collegamento binario collega due forme di nodo.

Le forme possono avere forme figlio in due set. Una forma nel NestedChildShapes set è limitata al rettangolo di selezione del relativo elemento padre. Una forma nell'elenco può essere visualizzata all'esterno RelativeChildShapes o in parte all'esterno dei limiti dell'elemento padre, ad esempio un'etichetta o una porta. Un diagramma non RelativeChildShapes ha e nessun oggetto Parent.

Spostamento tra forme ed elementi

Gli elementi del modello di dominio e gli elementi della forma sono correlati dalla PresentationViewsSubject relazione.

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

La stessa relazione collega le relazioni ai connettori nel diagramma:

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

Questa relazione collega anche la radice del modello al diagramma:

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

Per ottenere l'elemento del modello rappresentato da una forma, utilizzare:

henryShape.ModelElement as Person

diagram.ModelElement as FamilyTreeModel

In generale, non è consigliabile spostarsi tra forme e connettori nel diagramma. È preferibile esplorare le relazioni nel modello, spostandosi tra le forme e i connettori solo quando è necessario lavorare sull'aspetto del diagramma. Questi metodi collegano connettori alle forme a ogni estremità:

personShape.FromRoleLinkShapes, personShape.ToRoleLinkShapes

connector.FromShape, connector.ToShape

Molte forme sono composte; sono costituiti da una forma padre e uno o più strati di elementi figlio. Le forme posizionate rispetto a un'altra forma sono dette elementi figlio. Quando la forma padre viene spostata, gli elementi figlio si spostano con esso.

I figli relativi possono essere visualizzati all'esterno del rettangolo di selezione della forma padre. Gli elementi figlio annidati vengono visualizzati rigorosamente all'interno dei limiti dell'elemento padre.

Per ottenere il primo set di forme in un diagramma, utilizzare:

Diagram.NestedChildShapes

Le classi predecessore di forme e connettori sono:

ModelElement

-- PresentationElement

-- ShapeElement

----- NodeShape

------- Diagram

------- YourShape

----- LinkShape

------- BinaryLinkShape

--------- Il tuo Connessione or

Proprietà di forme e Connessione or

Nella maggior parte dei casi, non è necessario apportare modifiche esplicite alle forme. Dopo aver modificato gli elementi del modello, le regole di correzione aggiornano le forme e i connettori. Per altre informazioni, vedere Risposta a e propagazione delle modifiche.

Tuttavia, è utile apportare alcune modifiche esplicite alle forme nelle proprietà indipendenti dagli elementi del modello. Ad esempio, è possibile modificare queste proprietà:

  • Size - determina l'altezza e la larghezza della forma.

  • Location - posizione relativa alla forma o al diagramma padre

  • StyleSet - set di penne e pennelli utilizzati per disegnare la forma o il connettore

  • Hide - rende invisibile la forma

  • Show - rende la forma visibile dopo un Hide()

Creazione di un elemento e della relativa forma

Quando si crea un elemento e lo si collega all'albero delle relazioni di incorporamento, viene creata e associata automaticamente una forma. Questa operazione viene eseguita dalle regole "fixup" eseguite alla fine della transazione. Tuttavia, la forma verrà visualizzata in una posizione assegnata automaticamente e la forma, il colore e altre caratteristiche avranno valori predefiniti. Per controllare la modalità di creazione della forma, è possibile utilizzare la funzione di unione. È innanzitutto necessario aggiungere gli elementi da aggiungere a elementgroup e quindi unire il gruppo nel diagramma.

Questo metodo:

  • Imposta il nome, se è stata assegnata una proprietà come nome dell'elemento.

  • Osserva tutte le direttive di merge degli elementi specificate nella definizione DSL.

In questo esempio viene creata una forma in corrispondenza della posizione del mouse, quando l'utente fa doppio clic sul diagramma. Nella definizione DSL per questo esempio la FillColor proprietà di ExampleShape è stata esposta.

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 si specificano più forme, impostare le relative posizioni usando .AbsoluteBounds

È anche possibile impostare il colore e altre proprietà esposte dei connettori usando questo metodo.

Usare le transazioni

Forme, connettori e diagrammi sono sottotipi di ModelElement e vivono nello Store. È quindi necessario apportare modifiche solo all'interno di una transazione. Per altre informazioni, vedere Procedura: Usare transazioni per aggiornare il modello.

Visualizzazione documento e dati documento

Class diagram of standard diagram types

Archiviare le partizioni

Quando viene caricato un modello, il diagramma adiacente viene caricato contemporaneamente. In genere, il modello viene caricato in Store.DefaultPartition e il contenuto del diagramma viene caricato in un'altra partizione. In genere, il contenuto di ogni partizione viene caricato e salvato in un file separato.