Navigieren in und Aktualisieren von Modellen im Programmcode
Sie können Code schreiben, um Modellelemente zu erstellen und zu löschen, deren Eigenschaften festzulegen sowie 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:
Dieses Modell ist eine Instanz dieser DSL:
Verweise und Namespaces
Um den Code in diesem Thema auszuführen, sollten Sie auf Folgendes verweisen:
Microsoft.VisualStudio.Modeling.Sdk.11.0.dll
Ihr Code verwendet diesen Namespace:
using Microsoft.VisualStudio.Modeling;
Wenn Sie den Code außerdem in einem anderen Projekt schreiben als in dem, in dem Ihre DSL definiert ist, sollten Sie die Assembly importieren, die vom DSL-Projekt erstellt wird.
Navigieren im Modell
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, muss dies innerhalb einer Transaktion erfolgen:
henry.Name = "Henry VIII";
Wenn in der DSL-Definition Kind (Art) einer Eigenschaft Calculated (Berechnet) ist, 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 Eigenschaftenpaaren, eines für die Klasse auf jeder Seite 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 Multiplizität der Rolle ist der Typ der Eigenschaft entweder die Klasse auf der anderen Seite der Beziehung oder eine Sammlung dieser Klasse.
foreach (Person child in henry.Children) { ... }
FamilyTreeModel ftree = henry.FamilyTreeModel;
Die Eigenschaften an den gegenüberliegenden Seiten einer Beziehung beruhen immer auf Gegenseitigkeit. Wenn eine Verknüpfung 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 ParentsHaveChildren-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. Eine Verknüpfung verfügt immer über ein Quellelement und ein Zielelement. Das Quellelement und das Zielelement können identisch sein.
Sie können auf eine Verknüpfung und ihre Eigenschaften zugreifen:
ParentsHaveChildren link = ParentsHaveChildren.GetLink(henry, edward);
// This is now true:
link == null || link.Parent == henry && link.Child == edward
Standardmäßig ist nicht mehr als eine Instanz einer Beziehung erlaubt, um ein beliebiges Paar von Modellelementen zu verknüpfen. Wenn jedoch in der DSL-Definition das Flag Allow Duplicates
für die Beziehung TRUE ist, kann es mehrere Verknüpfung geben, und Sie müssen GetLinks
verwenden:
foreach (ParentsHaveChildren link in ParentsHaveChildren.GetLinks(henry, edward)) { ... }
Es gibt auch andere Methoden für den Zugriff auf Verknüpfungen. Beispiel:
foreach (ParentsHaveChildren link in ParentsHaveChildren.GetLinksToChildren(henry)) { ... }
Ausgeblendete Rollen. Wenn in der DSL-Definition Is Property Generated für eine bestimmte Rolle FALSE ist, wird keine Eigenschaft generiert, die dieser Rolle entspricht. Sie können jedoch weiterhin auf die Verknüpfungen zugreifen und die Verknüpfungen 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 der Form verknüpft, die 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 nach Elementen, z. B. die folgenden:
store.ElementDirectory.FindElements(Person.DomainClassId);
store.ElementDirectory.GetElement(elementId);
Zugreifen auf Klasseninformationen
Sie können Informationen zu den Klassen, Beziehungen und anderen Aspekten der DSL-Definition abrufen. Beispiel:
DomainClassInfo personClass = henry.GetDomainClass();
DomainPropertyInfo birthProperty =
personClass.FindDomainProperty("BirthDate")
DomainRelationshipInfo relationship =
link.GetDomainRelationship();
DomainRoleInfo sourceRole = relationship.DomainRole[0];
Die Vorgängerklassen von Modellelementen lauten wie folgt:
ModelElement: Alle Elemente und Beziehungen sind ModelElements.
ElementLink: Alle Beziehungen sind ElementLinks.
Ausführen von Änderungen innerhalb einer Transaktion
Wenn ihr Programmcode etwas im Speicher ändert, muss dies innerhalb einer Transaktion erfolgen. 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.
Um Ihre Änderungen dauerhaft zu machen, sollten Sie einen Commit
der Transaktion ausführen, bevor sie verworfen wird. Wenn eine Ausnahme auftritt, die nicht innerhalb der Transaktion abgefangen wird, wird der Speicher vor den Änderungen auf seinen Zustand 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 die folgenden wichtigen Punkte zum Erstellen eines Elements veranschaulicht:
Erstellen Sie das neue Element in einer bestimmten Partition des Speichers. Für Modellelemente und Beziehungen, nicht aber für Formen, ist dies in der Regel die Standardpartition.
Machen Sie sie zum Ziel einer Einbettungsbeziehung. In der DslDefinition dieses Beispiels muss jede Person das Ziel der Einbettungsbeziehung FamilyTreeHasPeople sein. Um dies zu erreichen, können wir entweder die FamilyTreeModel-Rolleneigenschaft des Person-Objekts festlegen oder die Person der People-Rolleneigenschaft des FamilyTreeModel-Objekts hinzufügen.
Legen Sie die Eigenschaften eines neuen Elements fest, insbesondere die Eigenschaft, für die
IsName
in der DslDefinition TRUE ist. Dieses Flag markiert die Eigenschaft, die dazu dient, das Element innerhalb seines Besitzers eindeutig zu identifizieren. In diesem Fall weist die Name-Eigenschaft dieses Flag auf.Die DSL-Definition dieser DSL muss in den Speicher geladen worden sein. Wenn Sie eine Erweiterung wie einen Menübefehl schreiben, ist dies in der Regel bereits TRUE. In anderen Fällen können Sie das Modell explizit in den Speicher laden oder ModelBus verwenden, um es zu laden. Weitere Informationen finden Sie unter Vorgehensweise: Öffnen eines Modells aus einer Datei im Programmcode.
Wenn Sie ein Element auf diese Weise erstellen, wird automatisch eine Form erstellt (wenn die 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 die zugeordnete Form angezeigt wird, finden Sie weitere Informationen unter Erstellen eines Elements und der zugehörigen Form.
Erstellen von Beziehungsverknüpfungen
In der DSL-Beispieldefinition sind zwei Beziehungen definiert. Jede Beziehung definiert eine Rolleneigenschaft für die Klasse auf jeder Seite der Beziehung.
Es gibt drei Möglichkeiten, wie Sie eine Instanz einer Beziehung erstellen können. Jede dieser drei Methoden hat die gleiche Wirkung:
Festlegen der Eigenschaft des Quellrolleninhabers. Beispiel:
familyTree.People.Add(edward);
edward.Parents.Add(henry);
Festlegen der Eigenschaft des Zielrolleninhabers. Beispiel:
edward.familyTreeModel = familyTree;
Die Multiplizität dieser Rolle ist
1..1
, sodass wir den Wert zuweisen.henry.Children.Add(edward);
Die Multiplizität dieser Rolle ist
0..*
, sodass wir zur Sammlung hinzufügen.
Explizites Erstellen einer Instanz der Beziehung. 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, der jedoch über eine Standardform, eine Standardfarbe und andere Features verfügt. Informationen zum Steuern der Erstellung des zugeordneten Verbinders finden Sie unter Erstellen eines Elements und seiner Form.
Löschen von Elementen
Löschen Sie ein Element, indem Sie Delete()
aufrufen:
henry.Delete();
Dieser Vorgang ist ebenfalls ein Löschvorgang:
Beziehungsverknüpfungen zum und vom Element.
edward.Parents
enthält z. B. nicht mehrhenry
.Elemente für Rollen, für die das
PropagatesDelete
-Flag TRUE ist. Beispielsweise wird die Form gelöscht, die das Element anzeigt.
Standardmäßig ist bei jeder Einbettungsbeziehung PropagatesDelete
in der Zielrolle TRUE. Durch das Löschen von henry
wird familyTree
nicht gelöscht, aber familyTree.Delete()
würde alle Persons
löschen.
PropagatesDelete
ist standardmäßig nicht TRUE für die Rollen von Verweisbeziehungen.
Sie können bewirken, dass die Löschregeln bestimmte Weitergaben auslassen, wenn Sie ein Objekt löschen. Dies ist nützlich, wenn Sie ein Element durch ein anderes ersetzen. Sie geben die GUID mindestens einer Rolle an, für die die Löschanweisung en nicht weitergegeben werden soll. Die GUID kann aus der Beziehungsklasse abgerufen werden:
henry.Delete(ParentsHaveChildren.SourceDomainRoleId);
(Dieses spezielle Beispiel hätte keine Auswirkung, weil PropagatesDelete
für die Rollen der ParentsHaveChildren
-Beziehung false
ist.)
In einigen Fällen wird das Löschen durch das Vorhandensein einer Sperre verhindert, entweder für das Element oder für ein Element, das durch Weitergabe gelöscht würde. Mit element.CanDelete()
können Sie überprüfen, ob das Element gelöscht werden kann.
Löschen von Beziehungsverknüpfungen
Sie können eine Beziehungsverknüpfung löschen, indem Sie ein Element aus einer Rolleneigenschaft entfernen:
henry.Children.Remove(edward); // or:
edward.Parents.Remove(henry); // or:
Sie können die Verknüpfung auch explizit löschen:
edwardHenryLink.Delete();
Diese drei Methoden haben alle die gleiche Wirkung. Sie müssen nur eine von ihnen verwenden.
Wenn die Rolle die Multiplizität 0..1 oder 1..1 aufweist, können Sie sie auf null
oder auf einen anderen Wert festlegen:
edward.FamilyTreeModel = null;
// oder:
edward.FamilyTreeModel = anotherFamilyTree;
Neusortieren der Verknüpfungen einer Beziehung
Die Verknüpfungen einer bestimmten Beziehung, die auf ein bestimmtes Modellelement bezogen (Quelle) oder ausgerichtet (Ziel) sind, weisen eine bestimmte Reihenfolge auf. Sie werden in der Reihenfolge angezeigt, in der sie hinzugefügt wurden. Diese Anweisung ergibt beispielsweise immer die untergeordneten Elemente in derselben Reihenfolge:
foreach (Person child in henry.Children) ...
Sie können die Reihenfolge der Verknüpfungen ändern:
ParentsHaveChildren link = GetLink(henry,edward);
ParentsHaveChildren nextLink = GetLink(henry, elizabeth);
DomainRoleInfo role =
link.GetDomainRelationship().DomainRoles[0];
link.MoveBefore(role, nextLink);
Locks
Ihre Änderungen werden möglicherweise durch eine Sperre verhindert. Sperren können für einzelne Elemente, für Partitionen und für den Speicher festgelegt werden. Wenn eine dieser Ebenen über eine Sperre verfügt, die die Art der Änderung verhindert, die Sie vornehmen möchten, wird beim Versuch möglicherweise eine Ausnahme ausgelöst. Sie können mit elements.GetLocks() ermitteln, ob Sperren festgelegt sind. Dies ist eine Erweiterungsmethode, die im Microsoft.VisualStudio.Modeling.Immutability-Namespace definiert ist.
Weitere Informationen finden Sie unter Definieren einer Sperrrichtlinie zum Erstellen von schreibgeschützten Segmenten.
Kopieren und Einfügen
Sie können Elemente oder Gruppen von Elementen 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 einem Modell zusammenführen:
using (Transaction t = targetDiagram.Store.
TransactionManager.BeginTransaction("paste"))
{
adopterShape.Diagram.ElementOperations.Merge(adopter, data);
}
Merge ()
kann ein PresentationElement
oder ein ModelElement
annehmen. Wenn Sie ein PresentationElement
übergeben, können Sie auch eine Position im Zieldiagramm als dritten Parameter angeben.
Navigieren in Diagrammen und Aktualisieren von Diagrammen
In einer DSL ist das Domänenmodellelement, das ein Konzept wie eine Person oder einen Song darstellt, vom Formelement getrennt, das das Objekt darstellt, das Sie im Diagramm sehen. Das Domänenmodellelement speichert die wichtigen Eigenschaften und Beziehungen der Konzepte. Das Formelement speichert die Größe, Position und Farbe der Ansicht des Objekts im Diagramm sowie das Layout seiner einzelnen Komponenten.
Präsentationselemente
In Ihrer DSL-Definition erstellt jedes Element, das Sie angeben, eine Klasse, die von einer der folgenden Standardklassen abgeleitet wird.
Art des Elements | Basisklasse |
---|---|
Domänenklasse | ModelElement |
Domänenbeziehung | ElementLink |
Form | NodeShape |
Connector | BinaryLinkShape |
Diagramm | Diagram |
Ein Element in einem Diagramm stellt in der Regel ein Modellelement dar. In der Regel (aber nicht immer) stellt eine NodeShape eine Domänenklasseninstanz und eine BinaryLinkShape eine Domänenbeziehungsinstanz dar. Die PresentationViewsSubject-Beziehung verknüpft einen Knoten oder eine Verknüpfungsform mit dem dargestellten Modellelement.
Jeder Knoten oder jede Verknüpfungsform gehört zu einem Diagramm. Eine binäre Verknüpfungsform verbindet zwei Knotenformen.
Formen können untergeordnete Formen in zwei Sätzen enthalten. Eine Form im NestedChildShapes
-Satz ist auf den Begrenzungsrahmens des übergeordneten Elements beschränkt. Eine Form in der RelativeChildShapes
-Liste kann außerhalb oder teilweise außerhalb der Grenzen des übergeordneten Elements angezeigt werden, z. B. eine Bezeichnung oder ein Port. Ein Diagramm enthält keine RelativeChildShapes
und kein Parent
-Element.
Navigieren zwischen Formen und Elementen
Domänenmodellelemente und Formelemente sind durch die 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;
Dieselbe 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;
Gehen Sie folgendermaßen vor, um das Modellelement abzurufen, das durch eine Form dargestellt wird:
henryShape.ModelElement as Person
diagram.ModelElement as FamilyTreeModel
Navigieren im Diagramm
Im Allgemeinen ist es nicht ratsam, zwischen Formen und Verbindern im Diagramm zu navigieren. Es ist besser, in den Beziehungen im Modell zu navigieren und nur dann zwischen den Formen und Verbindern zu wechseln, wenn an der Darstellung des Diagramms gearbeitet werden muss. Diese Methoden verknüpfen Verbinder mit den Formen auf jeder Seite:
personShape.FromRoleLinkShapes, personShape.ToRoleLinkShapes
connector.FromShape, connector.ToShape
Viele Formen sind zusammengesetzte Formen. Sie bestehen aus einer übergeordneten Form und mindestens einer untergeordneten Ebene. Formen, die relativ zu einer anderen Form positioniert sind, werden als untergeordnete Formen bezeichnet. Wenn die übergeordnete Form verschoben wird, werden die untergeordneten Elemente mit ihr verschoben.
Relative untergeordnete Elemente können außerhalb des Begrenzungsrahmens der übergeordneten Form angezeigt werden. Geschachtelte untergeordnete Elemente werden ausschließlich innerhalb der Grenzen des übergeordneten Elements angezeigt.
Verwenden Sie Folgendes, um die oberste Gruppe von Formen in einem Diagramm abzurufen:
Diagram.NestedChildShapes
Die Vorgängerklassen von Formen und Verbindern sind:
-- ShapeElement
----- NodeShape
------- Diagram
------- YourShape
----- LinkShape
------- BinaryLinkShape
--------- YourConnector
Eigenschaften von Formen und Verbindern
In den meisten Fällen ist es nicht erforderlich, explizite Änderungen an Formen vorzunehmen. Wenn Sie die Modellelemente geändert haben, aktualisieren die „Korrekturregeln“ die Formen und Verbinder. Weitere Informationen finden Sie unter Reagieren auf und Weitergeben von Änderungen.
Es ist jedoch hilfreich, einige explizite Änderungen an Formen in Eigenschaften vorzunehmen, die unabhängig von den Modellelementen sind. Sie können beispielsweise die folgenden Eigenschaften ändern:
Size: Bestimmt die Höhe und Breite der Form.
Location: Position relativ zur übergeordneten Form oder zum übergeordneten Diagramm.
StyleSet: Die Sammlung der Stifte und Pinsel, die zum Zeichnen der Form oder des Verbinders verwendet werden.
Hide: Macht die Form unsichtbar.
Show: Macht die Form nach
Hide()
sichtbar.
Erstellen eines Elements und seiner Form
Wenn Sie ein Element erstellen und es mit der Struktur der Einbettungsbeziehungen verknüpfen, wird automatisch eine Form erstellt und diesem zugeordnet. Dies erfolgt durch die „Korrekturregeln“, die am Ende der Transaktion ausgeführt werden. Die Form wird jedoch an einer automatisch zugewiesenen Position angezeigt, und für ihre Form, Farbe und andere Features werden Standardwerte verwendet. Um zu steuern, wie die Form erstellt wird, können Sie die Zusammenführungsfunktion verwenden. Sie müssen zuerst die Elemente hinzufügen, die Sie einer ElementGroup hinzufügen möchten, und dann die Gruppe mit dem Diagramm zusammenführen.
Diese Methode:
Legt den Namen fest, wenn Sie eine Eigenschaft als Elementnamen zugewiesen haben.
Beobachtet alle Elementmerge-Anweisungen, 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
verfügbar gemacht.
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 angeben, legen Sie ihre relativen Positionen mithilfe von AbsoluteBounds
fest.
Sie können auch die Farbe und andere verfügbar gemachte Eigenschaften von Verbindern mit dieser Methode festlegen.
Verwenden von Transaktionen
Formen, Verbinder und Diagramme sind Untertypen von ModelElement und befinden sich im Speicher. Sie müssen Änderungen an ihnen daher nur innerhalb einer Transaktion vornehmen. Weitere Informationen finden Sie unter Vorgehensweise: Verwenden von Transaktionen zum Aktualisieren des Modells.
Dokumentansicht und Dokumentdaten
Speicherpartitionen
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.