Freigeben über


Navigieren und Aktualisieren eines Modells im Programmcode

Sie können Code schreiben, um Modellelemente zu erstellen und zu löschen, deren Eigenschaften festzulegen und Verknüpfungen zwischen Elementen zu erstellen und zu löschen. Alle Änderungen müssen innerhalb einer Transaktion vorgenommen werden. Wenn die Elemente in einem Diagramm angezeigt werden, wird das Diagramm am Ende der Transaktion automatisch korrigiert.

Beispiel für eine DSL-Definition

Dies ist der Hauptteil von DslDefinition.dsl für die Beispiele in diesem Thema:

DSL-Definitionsdiagramm - Familienbaummodell

Dieses Modell ist eine Instanz dieses DSL:

Tudor-Familienbaummodell

Verweise und Namespaces

Um den Code in diesem Thema auszuführen, sollten Sie sich auf Folgendes beziehen:

Microsoft.VisualStudio.Modeling.Sdk.11.0.dll

Ihr Code verwendet diesen Namespace:

using Microsoft.VisualStudio.Modeling;

Wenn Sie den Code in einem anderen Projekt schreiben als das Projekt, in dem Ihr DSL definiert ist, sollten Sie außerdem die Assembly importieren, die vom Dsl-Projekt erstellt wird.

Eigenschaften

Domäneneigenschaften, die Sie in der DSL-Definition definieren, werden zu Eigenschaften, auf die Sie im Programmcode zugreifen können:

Person henry = ...;

if (henry.BirthDate < 1500) ...

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

Wenn Sie eine Eigenschaft festlegen möchten, müssen Sie dies innerhalb einer Transaktion tun:

henry.Name = "Henry VIII";

Wenn in der DSL-Definition die Art einer Eigenschaft berechnet wird, können Sie sie nicht festlegen. Weitere Informationen finden Sie unter Berechnete und benutzerdefinierte Speichereigenschaften.

Beziehungen

Domänenbeziehungen, die Sie in der DSL-Definition definieren, werden zu Eigenschaftspaaren, eines an jedem Ende der Beziehung. Die Namen der Eigenschaften werden im DslDefinition-Diagramm als Bezeichnungen für die Rollen auf jeder Seite der Beziehung angezeigt. Abhängig von der Multiplikation der Rolle ist der Typ der Eigenschaft entweder die Klasse am anderen Ende der Beziehung oder eine Auflistung dieser Klasse.

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

FamilyTreeModel ftree = henry.FamilyTreeModel;

Die Eigenschaften an gegenüberliegenden Enden einer Beziehung sind immer gegenseitig. Wenn ein Link erstellt oder gelöscht wird, werden die Rolleneigenschaften für beide Elemente aktualisiert. Der folgende Ausdruck, der die Erweiterungen von System.Linq verwendet, gilt immer für die ElternHabenKinder-Beziehung im Beispiel:

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

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

ElementLinks. Eine Beziehung wird auch durch ein Modellelement dargestellt, das als Link bezeichnet wird. Dabei handelt es sich um eine Instanz des Domänenbeziehungstyps. Ein Link verfügt immer über ein Quellelement und ein Zielelement. Das Quellelement und das Zielelement können identisch sein.

Sie können auf einen Link und dessen Eigenschaften zugreifen:

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

// This is now true:

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

Standardmäßig darf nicht mehr als eine Instanz einer Beziehung ein Paar von Modellelementen verknüpfen. Aber wenn in der DSL-Definition die Allow Duplicates Kennzeichnung für die Beziehung gilt, kann es mehr als einen Link geben, und Sie müssen folgendes verwenden GetLinks:

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

Es gibt auch andere Methoden für den Zugriff auf Links. Beispiel:

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

Ausgeblendete Rollen. Wenn in der DSL-Definition "Is Property Generated " für eine bestimmte Rolle falsch ist, wird keine Eigenschaft generiert, die dieser Rolle entspricht. Sie können jedoch weiterhin auf die Links zugreifen und die Links mithilfe der Methoden der Beziehung durchlaufen:

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

Das am häufigsten verwendete Beispiel ist die PresentationViewsSubject Beziehung, die ein Modellelement mit dem Shape verknüpft, das es in einem Diagramm anzeigt:

PresentationViewsSubject.GetPresentation(henry)[0] as PersonShape

Das Elementverzeichnis

Sie können über das Elementverzeichnis auf alle Elemente im Speicher zugreifen:

store.ElementDirectory.AllElements

Es gibt auch Methoden zum Suchen von Elementen, z. B. die folgenden:

store.ElementDirectory.FindElements(Person.DomainClassId);

store.ElementDirectory.GetElement(elementId);

Zugreifen auf Klasseninformationen

Sie erhalten Informationen zu den Klassen, Beziehungen und anderen Aspekten der DSL-Definition. Beispiel:

DomainClassInfo personClass = henry.GetDomainClass();

DomainPropertyInfo birthProperty =

personClass.FindDomainProperty("BirthDate")

DomainRelationshipInfo relationship =

link.GetDomainRelationship();

DomainRoleInfo sourceRole = relationship.DomainRole[0];

Die Vorgängerklassen von Modellelementen sind wie folgt:

  • ModelElement – alle Elemente und Beziehungen sind ModelElements

  • ElementLink – alle Beziehungen sind ElementLinks

Durchführen von Änderungen innerhalb einer Transaktion

Wenn sich der Programmcode im Store ändert, muss er dies innerhalb einer Transaktion tun. Dies gilt für alle Modellelemente, Beziehungen, Formen, Diagramme und deren Eigenschaften. Weitere Informationen finden Sie unter Transaction.

Die bequemste Methode zum Verwalten einer Transaktion ist eine using anweisung, die in eine try...catch Anweisung eingeschlossen ist:

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

Sie können eine beliebige Anzahl von Änderungen innerhalb einer Transaktion vornehmen. Sie können neue Transaktionen innerhalb einer aktiven Transaktion öffnen.

Wenn Sie Ihre Änderungen dauerhaft vornehmen möchten, sollten Commit Sie die Transaktion vor dem Löschen vornehmen. Wenn eine Ausnahme auftritt, die nicht innerhalb der Transaktion abgefangen wird, wird der Store auf seinen Zustand vor den Änderungen zurückgesetzt.

Erstellen von Modellelementen

In diesem Beispiel wird einem vorhandenen Modell ein Element hinzugefügt:

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

In diesem Beispiel werden diese wesentlichen Punkte zum Erstellen eines Elements veranschaulicht:

  • Erstellen Sie das neue Element in einer bestimmten Partition des Store. Bei Modellelementen und Beziehungen, jedoch nicht bei Shapes, ist dies in der Regel die Standardpartition.

  • Legen Sie es als Ziel einer Einbettungsbeziehung fest. In der DslDefinition dieses Beispiels muss jede Person das Ziel der Einbettungsbeziehung FamilyTreeHasPeople sein. Dazu können wir entweder die FamilyTreeModel-Rolleneigenschaft des Person-Objekts festlegen oder die Person zur Personenrolleneigenschaft des FamilyTreeModel-Objekts hinzufügen.

  • Legen Sie die Eigenschaften eines neuen Elements fest, insbesondere die Eigenschaft, für die IsName in der DslDefinition wahr ist. Dieses Kennzeichen markiert die Eigenschaft, die das Element eindeutig innerhalb seines Eigentümers identifiziert. In diesem Fall weist die Name-Eigenschaft dieses Flag auf.

  • Die DSL-Definition dieses DSL muss in den Store geladen worden sein. Wenn Sie eine Erweiterung wie einen Menübefehl schreiben, ist dies in der Regel bereits wahr. In anderen Fällen können Sie das Modell explizit in den Store laden oder ModelBus verwenden, um es zu laden. Weitere Informationen finden Sie unter Vorgehensweise: Ein Modell in Programmcodes aus einer Datei öffnen.

    Wenn Sie ein Element auf diese Weise erstellen, wird automatisch ein Shape erstellt (wenn das DSL über ein Diagramm verfügt). Sie wird an einer automatisch zugewiesenen Position mit Standardform, Farbe und anderen Features angezeigt. Wenn Sie steuern möchten, wo und wie das zugeordnete Shape angezeigt wird, lesen Sie das Erstellen eines Elements und dessen Form.

In der Beispiel-DSL-Definition sind zwei Beziehungen definiert. Jede Beziehung definiert eine Rolleneigenschaft für die Klasse am ende der Beziehung.

Es gibt drei Möglichkeiten, wie Sie eine Instanz einer Beziehung erstellen können. Jede dieser drei Methoden hat die gleiche Wirkung:

  • Legen Sie die Eigenschaft des Quellrollenplayers fest. Beispiel:

    • familyTree.People.Add(edward);

    • edward.Parents.Add(henry);

  • Legen Sie die Eigenschaft des Zielrollenplayers fest. Beispiel:

    • edward.familyTreeModel = familyTree;

      Die Multiplikation dieser Rolle ist 1..1, sodass wir den Wert zuweisen.

    • henry.Children.Add(edward);

      Die Vielfältigkeit dieser Rolle ist 0..*, sodass wir zur Sammlung hinzufügen.

  • Erstellen Sie eine Instanz der Beziehung explizit. Beispiel:

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

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

    Die letzte Methode ist nützlich, wenn Sie Eigenschaften für die Beziehung selbst festlegen möchten.

    Wenn Sie ein Element auf diese Weise erstellen, wird automatisch ein Verbinder im Diagramm erstellt, verfügt jedoch über eine Standardform, Farbe und andere Features. Informationen zum Steuern, wie der zugeordnete Verbinder erstellt wird, finden Sie unter Erstellen eines Elements und seiner Form.

Löschen von Elementen

Löschen eines Elements durch Aufrufen von :

henry.Delete();

Bei diesem Vorgang wird auch gelöscht:

  • Beziehungs-Verknüpfungen zu und vom Element. Zum Beispiel enthält edward.Parents nicht mehr henry.

  • Elemente in Rollen, für die die PropagatesDelete Kennzeichnung wahr ist. Beispielsweise wird das Shape, das das Element anzeigt, gelöscht.

Standardmäßig hat jede Einbettungsbeziehung PropagatesDelete "true" an der Zielrolle. Das Löschen von henry löscht nicht das familyTree, aber familyTree.Delete() würde alle Persons löschen.

Standardmäßig gilt PropagatesDelete nicht für die Rollen von Referenzbeziehungen.

Sie können dazu führen, dass die Löschregeln bestimmte Verteilungen weglassen, wenn Sie ein Objekt löschen. Dies ist nützlich, wenn Sie ein Element für ein anderes ersetzen. Sie geben die GUID einer oder mehrerer Rollen an, für die der Löschvorgang nicht weitergegeben werden soll. Die GUID kann aus der Beziehungsklasse abgerufen werden:

henry.Delete(ParentsHaveChildren.SourceDomainRoleId);

(Dieses Beispiel hätte keine Auswirkungen, weil PropagatesDeletefalse für die Rollen der ParentsHaveChildren-Beziehung ist.)

In einigen Fällen wird das Löschen durch das Vorhandensein einer Sperre verhindert, entweder auf dem Element oder auf einem Element, das durch Verteilung gelöscht würde. Mit element.CanDelete() können Sie überprüfen, ob das Element gelöscht werden kann.

Sie können einen Beziehungslink löschen, indem Sie ein Element aus einer Rolleneigenschaft entfernen:

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

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

Sie können den Link auch explizit löschen:

edwardHenryLink.Delete();

Diese drei Methoden haben alle den gleichen Effekt. Sie müssen nur eine davon verwenden.

Wenn die Rolle eine Multiplikation von 0..1 oder 1..1 aufweist, können Sie sie auf nulloder auf einen anderen Wert festlegen:

edward.FamilyTreeModel = null; oder:

edward.FamilyTreeModel = anotherFamilyTree;

Erneutes Sortieren der Verknüpfungen einer Beziehung

Die Verknüpfungen einer bestimmten Beziehung, die an ein bestimmtes Modellelement stammen oder als Ziel dienen, weisen eine bestimmte Sequenz auf. Sie werden in der Reihenfolge angezeigt, in der sie hinzugefügt wurden. Diese Anweisung liefert beispielsweise immer die Kinder in derselben Reihenfolge:

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

Sie können die Reihenfolge der Links ändern:

ParentsHaveChildren link = GetLink(henry,edward);

ParentsHaveChildren nextLink = GetLink(henry, elizabeth);

DomainRoleInfo role =

link.GetDomainRelationship().DomainRoles[0];

link.MoveBefore(role, nextLink);

Schlösser

Ihre Änderungen werden möglicherweise durch eine Sperre verhindert. Sperren können für einzelne Elemente, partitionen und im Speicher festgelegt werden. Wenn eine dieser Ebenen eine Sperre aufweist, die die Art der Änderung verhindert, die Sie vornehmen möchten, wird beim Versuch eine Ausnahme ausgelöst. Sie können ermitteln, ob Sperren mithilfe des Elements festgelegt werden. GetLocks(), eine Erweiterungsmethode, die im Namespace Microsoft.VisualStudio.Modeling.Immutabilitydefiniert ist.

Weitere Informationen finden Sie unter Definieren einer Sperrrichtlinie zum Erstellen von Read-Only-Segmenten.

Kopieren und Einfügen

Sie können Elemente oder Elementgruppen in ein IDataObject kopieren.

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

Die Elemente werden als serialisierte Elementgruppe gespeichert.

Sie können Elemente aus einem IDataObject in ein Modell zusammenführen:

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

Merge () kann entweder ein PresentationElement oder ein ModelElement. Wenn Sie ihm eine PresentationElementGabe geben, können Sie auch eine Position im Zieldiagramm als dritten Parameter angeben.

Navigieren und Aktualisieren von Diagrammen

In einem DSL ist das Domänenmodellelement, das ein Konzept wie "Person" oder "Song" darstellt, vom Shape-Element getrennt, das die im Diagramm angezeigten Elemente darstellt. Das Domänenmodellelement speichert die wichtigen Eigenschaften und Beziehungen der Konzepte. Das Shape-Element speichert die Größe, Position und Farbe der Ansicht des Objekts im Diagramm sowie das Layout seiner Komponententeile.

Präsentationselemente

Klassendiagramm von Basisformen und Elementtypen

In Ihrer DSL-Definition erstellt jedes von Ihnen angegebene Element eine Klasse, die von einer der folgenden Standardklassen abgeleitet wird.

Art des Elements Basisklasse
Domänenklasse ModelElement
Domänenbeziehung ElementLink
Form NodeShape
Verbinder BinaryLinkShape
Diagramm Diagram

Ein Element in einem Diagramm stellt in der Regel ein Modellelement dar. In der Regel (aber nicht immer) stellt eine NodeShape Domänenklasseninstanz dar, und eine BinaryLinkShape stellt eine Domänenbeziehungsinstanz dar. Die PresentationViewsSubject Beziehung verknüpft einen Knoten oder eine Verknüpfungsform mit dem Modellelement, das es darstellt.

Jedes Knoten- oder Verbindungselement gehört zu einem Diagramm. Eine binäre Verknüpfungsform verbindet zwei Knotenformen.

Shapes können untergeordnete Formen in zwei Gruppen enthalten. Ein Shape im NestedChildShapes Satz ist auf das umgebende Feld des übergeordneten Elements beschränkt. Ein Shape in der RelativeChildShapes Liste kann außerhalb oder teilweise außerhalb der Grenzen des übergeordneten Objekts erscheinen, wie beispielsweise ein Beschriftungsfeld oder ein Port. Ein Diagramm hat keine RelativeChildShapes und keine Parent.

Navigieren zwischen Formen und Elementen

Domänenmodellelemente und Shape-Elemente sind mit der PresentationViewsSubject Beziehung verknüpft.

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

Die gleiche Beziehung verknüpft Beziehungen mit Verbindern im Diagramm:

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

Diese Beziehung verknüpft auch den Stamm des Modells mit dem Diagramm:

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

Verwenden Sie Folgendes, um das Modellelement abzurufen, das durch ein Shape dargestellt wird:

henryShape.ModelElement as Person

diagram.ModelElement as FamilyTreeModel

Im Allgemeinen ist es nicht ratsam, zwischen Shapes und Verbindern im Diagramm zu navigieren. Es ist besser, in den Beziehungen des Modells zu navigieren und zwischen den Formen und Verbindern nur dann zu wechseln, wenn es notwendig ist, an der Darstellung des Diagramms zu arbeiten. Diese Methoden verknüpfen Verbinder mit den Formen an jedem Ende.

personShape.FromRoleLinkShapes, personShape.ToRoleLinkShapes

connector.FromShape, connector.ToShape

Viele Formen sind Zusammengesetzte; sie bestehen aus einem übergeordneten Shape und einer oder mehreren Ebenen von untergeordneten Elementen. Shapes, die relativ zu einer anderen Form positioniert sind, werden als Untergeordnete Formen bezeichnet. Wenn das übergeordnete Shape verschoben wird, werden die untergeordneten Elemente mit ihr verschoben.

Relative untergeordnete Elemente können außerhalb des Begrenzungsrahmens des übergeordneten Elements angezeigt werden. Geschachtelte Elemente werden streng innerhalb der Grenzen des übergeordneten Elements angezeigt.

Um den oberen Satz von Shapes in einem Diagramm abzurufen, verwenden Sie Folgendes:

Diagram.NestedChildShapes

Die Basisklassen von Formen und Verbindern sind:

ModelElement

-- PresentationElement

-- ShapeElement

----- NodeShape

------- Diagram

------- Ihre Form

----- LinkShape

------- BinaryLinkShape

--------- IhrConnector

Eigenschaften von Shapes und Verbindern

In den meisten Fällen ist es nicht erforderlich, explizite Änderungen an Shapes vorzunehmen. Wenn Sie die Modellelemente geändert haben, aktualisieren die Regeln zur Fehlerbehebung die Formen und Verbinder. Weitere Informationen finden Sie unter Reagieren auf und Weitergeben von Änderungen.

Es ist jedoch hilfreich, explizite Änderungen an Shapes in Eigenschaften vorzunehmen, die unabhängig von den Modellelementen sind. Sie können z. B. diese Eigenschaften ändern:

  • Size - bestimmt die Höhe und Breite der Form.

  • Location - Position relativ zur übergeordneten Form oder zum übergeordneten Diagramm

  • StyleSet - die Gruppe von Stiften und Pinsel, die zum Zeichnen der Form oder Verbindung verwendet werden

  • Hide - macht die Form unsichtbar

  • Show - macht die Form nach einem Hide()

Erstellen eines Elements und seiner Form

Wenn Sie ein Element erstellen und in die Struktur der Einbettungsbeziehungen verknüpfen, wird automatisch eine Form erstellt und damit verknüpft. Dies erfolgt durch die "Fixup"-Regeln, die am Ende der Transaktion ausgeführt werden. Das Shape wird jedoch an einer automatisch zugewiesenen Position angezeigt, und seine Form, Farbe und andere Features weisen Standardwerte auf. Um zu steuern, wie das Shape erstellt wird, können Sie die Zusammenführungsfunktion verwenden. Sie müssen zuerst die Elemente hinzufügen, die Sie einer ElementGruppe hinzufügen möchten, und dann die Gruppe in das Diagramm zusammenführen.

Diese Methode:

  • Legt den Namen fest, wenn Sie eine Eigenschaft als Name des Elements zugewiesen haben.

  • Beachtet alle Elementzusammenführungsdirektiven, die Sie in der DSL-Definition angegeben haben.

In diesem Beispiel wird eine Form an der Mausposition erstellt, wenn der Benutzer auf das Diagramm doppelklickt. In der DSL-Definition für dieses Beispiel wurde die FillColor Eigenschaft von ExampleShape freigelegt.

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

Wenn Sie mehrere Formen bereitstellen, legen Sie ihre relativen Positionen mithilfe der AbsoluteBounds fest.

Sie können auch die Farbe und andere freigegebene Eigenschaften von Verbindern mit dieser Methode festlegen.

Verwenden von Transaktionen

Shapes, Verbinder und Diagramme sind Untertypen von ModelElement und befinden sich im Store. Sie müssen daher nur innerhalb einer Transaktion Änderungen daran vornehmen. Weitere Informationen finden Sie unter So verwenden Sie Transaktionen, um das Modell zu aktualisieren.

Dokumentansicht und Dokumentdaten

Klassendiagramm von Standarddiagrammtypen

Speichern von Partitionen

Wenn ein Modell geladen wird, wird das zugehörige Diagramm gleichzeitig geladen. In der Regel wird das Modell in Store.DefaultPartition geladen, und der Diagramminhalt wird in eine andere Partition geladen. Normalerweise wird der Inhalt jeder Partition geladen und in einer separaten Datei gespeichert.