Nawigowanie po modelu i aktualizowanie go w kodzie programu
Możesz napisać kod, aby tworzyć i usuwać elementy modelu, ustawiać ich właściwości oraz tworzyć i usuwać łącza między elementami. Wszystkie zmiany muszą zostać wprowadzone w ramach transakcji. Jeśli elementy są wyświetlane na diagramie, diagram zostanie automatycznie naprawiony na końcu transakcji.
Przykładowa definicja DSL
Jest to główna część pliku DslDefinition.dsl dla przykładów w tym temacie:
Ten model jest wystąpieniem tego rozszerzenia DSL:
Odwołania i przestrzenie nazw
Aby uruchomić kod w tym temacie, należy się odwołać:
Microsoft.VisualStudio.Modeling.Sdk.11.0.dll
Twój kod będzie używać tej przestrzeni nazw:
using Microsoft.VisualStudio.Modeling;
Ponadto, jeśli piszesz kod w innym projekcie niż ten, w którym zdefiniowano rozszerzenie DSL, należy zaimportować zestaw utworzony przez projekt Dsl.
Nawigowanie po modelu
Właściwości
Właściwości domeny zdefiniowane w definicji DSL stają się właściwościami, do których można uzyskać dostęp w kodzie programu:
Person henry = ...;
if (henry.BirthDate < 1500) ...
if (henry.Name.EndsWith("VIII")) ...
Jeśli chcesz ustawić właściwość, musisz to zrobić wewnątrz transakcji:
henry.Name = "Henry VIII";
Jeśli w definicji DSL właściwość Jest obliczana, nie można go ustawić. Aby uzyskać więcej informacji, zobacz Właściwości obliczeniowe i niestandardowego magazynu.
Relacje
Relacje domeny zdefiniowane w definicji DSL stają się parami właściwości, po jednym na klasie na każdym końcu relacji. Nazwy właściwości są wyświetlane na diagramie DslDefinition jako etykiety na rolach po każdej stronie relacji. W zależności od wielokrotności roli typ właściwości jest klasą na drugim końcu relacji lub kolekcją tej klasy.
foreach (Person child in henry.Children) { ... }
FamilyTreeModel ftree = henry.FamilyTreeModel;
Właściwości na przeciwnych końcach relacji są zawsze wzajemne. Po utworzeniu lub usunięciu łącza właściwości roli w obu elementach zostaną zaktualizowane. Następujące wyrażenie (które używa rozszerzeń System.Linq
) jest zawsze prawdziwe dla relacji ParentsHaveChildren w przykładzie:
(Person p) => p.Children.All(child => child.Parents.Contains(p))
&& p.Parents.All(parent => parent.Children.Contains(p));
ElementLinks. Relacja jest również reprezentowana przez element modelu nazywany łączem, który jest wystąpieniem typu relacji domeny. Łącze zawsze ma jeden element źródłowy i jeden element docelowy. Element źródłowy i element docelowy mogą być takie same.
Możesz uzyskać dostęp do linku i jego właściwości:
ParentsHaveChildren link = ParentsHaveChildren.GetLink(henry, edward);
// This is now true:
link == null || link.Parent == henry && link.Child == edward
Domyślnie nie więcej niż jedno wystąpienie relacji może łączyć dowolną parę elementów modelu. Jeśli jednak w definicji DSL flaga Allow Duplicates
ma wartość true dla relacji, może istnieć więcej niż jeden link i należy użyć polecenia GetLinks
:
foreach (ParentsHaveChildren link in ParentsHaveChildren.GetLinks(henry, edward)) { ... }
Istnieją również inne metody uzyskiwania dostępu do linków. Na przykład:
foreach (ParentsHaveChildren link in ParentsHaveChildren.GetLinksToChildren(henry)) { ... }
Ukryte role. Jeśli w definicji DSL właściwość jest generowana jako fałsz dla określonej roli, nie zostanie wygenerowana żadna właściwość odpowiadająca tej roli. Jednak nadal możesz uzyskać dostęp do linków i przejść przez łącza przy użyciu metod relacji:
foreach (Person p in ParentsHaveChildren.GetChildren(henry)) { ... }
Najczęściej używanym przykładem jest PresentationViewsSubject relacja, która łączy element modelu z kształtem, który wyświetla go na diagramie:
PresentationViewsSubject.GetPresentation(henry)[0] as PersonShape
Katalog elementów
Dostęp do wszystkich elementów w magazynie można uzyskać przy użyciu katalogu elementów:
store.ElementDirectory.AllElements
Istnieją również metody znajdowania elementów, takie jak:
store.ElementDirectory.FindElements(Person.DomainClassId);
store.ElementDirectory.GetElement(elementId);
Uzyskiwanie dostępu do informacji o klasie
Możesz uzyskać informacje o klasach, relacjach i innych aspektach definicji DSL. Na przykład:
DomainClassInfo personClass = henry.GetDomainClass();
DomainPropertyInfo birthProperty =
personClass.FindDomainProperty("BirthDate")
DomainRelationshipInfo relationship =
link.GetDomainRelationship();
DomainRoleInfo sourceRole = relationship.DomainRole[0];
Klasy elementów modelu są następujące:
ModelElement — wszystkie elementy i relacje to ModelElements
ElementLink — wszystkie relacje to ElementLinks
Wykonywanie zmian wewnątrz transakcji
Za każdym razem, gdy kod programu zmieni wszystko w Sklepie, musi to zrobić wewnątrz transakcji. Dotyczy to wszystkich elementów modelu, relacji, kształtów, diagramów i ich właściwości. W celu uzyskania więcej informacji, zobacz następujący temat: Transaction.
Najwygodniejszym sposobem zarządzania transakcją jest using
instrukcja ujęta w instrukcji 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.
}
Możesz wprowadzić dowolną liczbę zmian wewnątrz jednej transakcji. Nowe transakcje można otworzyć wewnątrz aktywnej transakcji.
Aby wprowadzić zmiany na stałe, należy Commit
dokonać transakcji przed jej likwidacją. Jeśli wystąpi wyjątek, który nie zostanie przechwycony wewnątrz transakcji, sklep zostanie zresetowany do stanu przed zmianami.
Tworzenie elementów modelu
W tym przykładzie dodano element do istniejącego modelu:
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!
}
W tym przykładzie przedstawiono te podstawowe kwestie dotyczące tworzenia elementu:
Utwórz nowy element w określonej partycji magazynu. W przypadku elementów modelu i relacji, ale nie kształtów, jest to zwykle partycja domyślna.
Ustaw ją na element docelowy relacji osadzania. W przykładzie DslDefinition każda osoba musi być obiektem docelowym osadzania relacji FamilyTreeHas Osoby. Aby to osiągnąć, możemy ustawić właściwość roli FamilyTreeModel obiektu Person lub dodać właściwość Person do właściwości roli Osoby obiektu FamilyTreeModel.
Ustaw właściwości nowego elementu, szczególnie właściwość, dla której
IsName
wartość true znajduje się w definicji dslDefinition. Ta flaga oznacza właściwość, która służy do identyfikowania elementu unikatowo w obrębie jego właściciela. W tym przypadku właściwość Name ma tę flagę.Definicja DSL tego rozszerzenia DSL musi zostać załadowana do magazynu. Jeśli piszesz rozszerzenie, takie jak polecenie menu, zwykle będzie to już prawdziwe. W innych przypadkach można jawnie załadować model do sklepu lub załadować go za pomocą modelu ModelBus . Aby uzyskać więcej informacji, zobacz How to: Open a Model from File in Program Code (Jak otworzyć model z pliku w kodzie programu).
Podczas tworzenia elementu w ten sposób kształt jest tworzony automatycznie (jeśli rozszerzenie DSL ma diagram). Jest ona wyświetlana w automatycznie przypisanej lokalizacji z domyślnym kształtem, kolorem i innymi funkcjami. Jeśli chcesz kontrolować miejsce i sposób wyświetlania skojarzonego kształtu, zobacz Tworzenie elementu i jego kształtu.
Tworzenie linków relacji
Istnieją dwie relacje zdefiniowane w przykładowej definicji DSL. Każda relacja definiuje właściwość roli w klasie na każdym końcu relacji.
Istnieją trzy sposoby tworzenia wystąpienia relacji. Każda z tych trzech metod ma taki sam efekt:
Ustaw właściwość odtwarzacza roli źródłowej. Na przykład:
familyTree.People.Add(edward);
edward.Parents.Add(henry);
Ustaw właściwość docelowego gracza roli. Na przykład:
edward.familyTreeModel = familyTree;
Wielokrotność tej roli to
1..1
, więc przypisujemy wartość .henry.Children.Add(edward);
Wielokrotność tej roli to
0..*
, dlatego dodajemy do kolekcji.
Skonstruuj jawnie wystąpienie relacji. Na przykład:
FamilyTreeHasPeople edwardLink = new FamilyTreeHasPeople(familyTreeModel, edward);
ParentsHaveChildren edwardHenryLink = new ParentsHaveChildren(henry, edward);
Ostatnia metoda jest przydatna, jeśli chcesz ustawić właściwości na samej relacji.
Po utworzeniu elementu w ten sposób łącznik na diagramie jest tworzony automatycznie, ale ma on domyślny kształt, kolor i inne funkcje. Aby kontrolować sposób tworzenia skojarzonego łącznika, zobacz Tworzenie elementu i jego kształtu.
Usuwanie elementów
Usuń element, wywołując polecenie Delete()
:
henry.Delete();
Ta operacja spowoduje również usunięcie:
Linki relacji do i z elementu . Na przykład
edward.Parents
nie będzie już zawieraćhenry
.Elementy w rolach, dla których flaga
PropagatesDelete
ma wartość true. Na przykład kształt, który wyświetla element, zostanie usunięty.
Domyślnie każda relacja osadzania ma PropagatesDelete
wartość true w roli docelowej. Usunięcie henry
nie powoduje usunięcia obiektu familyTree
, ale familyTree.Delete()
spowoduje usunięcie wszystkich elementów Persons
.
Domyślnie PropagatesDelete
nie dotyczy ról relacji referencyjnych.
Reguły usuwania mogą spowodować pominięcie określonych propagacji podczas usuwania obiektu. Jest to przydatne, jeśli podstawisz jeden element dla innego. Należy podać identyfikator GUID co najmniej jednej roli, dla której usunięcie nie powinno być propagowane. Identyfikator GUID można uzyskać z klasy relacji:
henry.Delete(ParentsHaveChildren.SourceDomainRoleId);
(Ten konkretny przykład nie miałby żadnego wpływu, ponieważ PropagatesDelete
dotyczy false
ról ParentsHaveChildren
relacji).
W niektórych przypadkach usunięcie jest blokowane przez istnienie blokady na elemecie lub na elemecie, który zostanie usunięty przez propagację. Możesz użyć element.CanDelete()
polecenia , aby sprawdzić, czy element można usunąć.
Usuwanie łączy relacji
Łącze relacji można usunąć, usuwając element z właściwości roli:
henry.Children.Remove(edward); // or:
edward.Parents.Remove(henry); // or:
Możesz również jawnie usunąć link:
edwardHenryLink.Delete();
Te trzy metody mają ten sam efekt. Wystarczy użyć jednego z nich.
Jeśli rola ma 0..1 lub 1..1 mnożenie, możesz ustawić ją na null
, lub na inną wartość:
edward.FamilyTreeModel = null;
Lub:
edward.FamilyTreeModel = anotherFamilyTree;
Ponowne porządkowanie łączy relacji
Linki określonej relacji, która jest źródłowa lub ukierunkowana na określony element modelu, mają określoną sekwencję. Są one wyświetlane w kolejności, w której zostały dodane. Na przykład ta instrukcja zawsze zwraca elementy podrzędne w tej samej kolejności:
foreach (Person child in henry.Children) ...
Kolejność łączy można zmienić:
ParentsHaveChildren link = GetLink(henry,edward);
ParentsHaveChildren nextLink = GetLink(henry, elizabeth);
DomainRoleInfo role =
link.GetDomainRelationship().DomainRoles[0];
link.MoveBefore(role, nextLink);
Blokady
Zmiany mogą być blokowane przez blokadę. Blokady można ustawiać na poszczególnych elementach, na partycjach i w sklepie. Jeśli którykolwiek z tych poziomów ma blokadę, która uniemożliwia zmianę, którą chcesz wprowadzić, podczas próby może zostać zgłoszony wyjątek. Możesz dowiedzieć się, czy blokady są ustawiane przy użyciu elementu . GetLocks(), która jest metodą rozszerzenia zdefiniowaną w przestrzeni nazw Microsoft.VisualStudio.Modeling.Immutability.
Aby uzyskać więcej informacji, zobacz Definiowanie zasad blokowania w celu tworzenia segmentów tylko do odczytu.
Kopiowanie i wklejanie
Elementy lub grupy elementów można skopiować do elementu 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>());
Elementy są przechowywane jako serializowana grupa elementów.
Elementy można scalić z obiektu IDataObject z modelem:
using (Transaction t = targetDiagram.Store.
TransactionManager.BeginTransaction("paste"))
{
adopterShape.Diagram.ElementOperations.Merge(adopter, data);
}
Merge ()
może zaakceptować element PresentationElement
lub .ModelElement
Jeśli nadasz mu wartość PresentationElement
, możesz również określić pozycję na diagramie docelowym jako trzeci parametr.
Nawigowanie i aktualizowanie diagramów
W języku DSL element modelu domeny, który reprezentuje koncepcję, taką jak Osoba lub Piosenka, jest oddzielony od elementu kształtu, który reprezentuje to, co widzisz na diagramie. Element modelu domeny przechowuje ważne właściwości i relacje pojęć. Element kształtu przechowuje rozmiar, położenie i kolor widoku obiektu na diagramie oraz układ jego części składowych.
Elementy prezentacji
W definicji DSL każdy określony element tworzy klasę pochodzącą z jednej z następujących klas standardowych.
Rodzaj elementu | Klasa bazowa |
---|---|
Klasa domeny | ModelElement |
Relacja domeny | ElementLink |
Kształt | NodeShape |
Łącznik | BinaryLinkShape |
Diagram | Diagram |
Element na diagramie zwykle reprezentuje element modelu. Zazwyczaj (ale nie zawsze) NodeShape reprezentuje wystąpienie klasy domeny i BinaryLinkShape reprezentuje wystąpienie relacji domeny. Relacja PresentationViewsSubject łączy węzeł lub kształt łącza z elementem modelu, który reprezentuje.
Każdy węzeł lub kształt łącza należy do jednego diagramu. Kształt łącza binarnego łączy dwa kształty węzłów.
Kształty mogą mieć kształty podrzędne w dwóch zestawach. Kształt w NestedChildShapes
zestawie jest ograniczony do pola ograniczenia elementu nadrzędnego. Kształt na RelativeChildShapes
liście może pojawić się poza lub częściowo poza granicami elementu nadrzędnego — na przykład etykietą lub portem. Diagram nie RelativeChildShapes
ma i nie Parent
.
Nawigowanie między kształtami i elementami
Elementy modelu domeny i elementy kształtu są powiązane przez relację PresentationViewsSubject .
// using Microsoft.VisualStudio.Modeling;
// using Microsoft.VisualStudio.Modeling.Diagrams;
// using System.Linq;
Person henry = ...;
PersonShape henryShape =
PresentationViewsSubject.GetPresentation(henry)
.FirstOrDefault() as PersonShape;
Ta sama relacja łączy relacje z łącznikami na diagramie:
Descendants link = Descendants.GetLink(henry, edward);
DescendantConnector dc =
PresentationViewsSubject.GetPresentation(link)
.FirstOrDefault() as DescendantConnector;
// dc.FromShape == henryShape && dc.ToShape == edwardShape
Ta relacja łączy również katalog główny modelu z diagramem:
FamilyTreeDiagram diagram =
PresentationViewsSubject.GetPresentation(familyTree)
.FirstOrDefault() as FamilyTreeDiagram;
Aby uzyskać element modelu reprezentowany przez kształt, użyj:
henryShape.ModelElement as Person
diagram.ModelElement as FamilyTreeModel
Poruszanie się po diagramie
Ogólnie rzecz biorąc, nie zaleca się nawigowania między kształtami i łącznikami na diagramie. Lepiej jest nawigować po relacjach w modelu, przechodząc między kształtami i łącznikami tylko wtedy, gdy konieczne jest, aby pracować nad wyglądem diagramu. Te metody łączą łączniki z kształtami na każdym końcu:
personShape.FromRoleLinkShapes, personShape.ToRoleLinkShapes
connector.FromShape, connector.ToShape
Wiele kształtów jest złożonych; składają się one z kształtu nadrzędnego i co najmniej jednej warstwy elementów podrzędnych. Mówi się, że kształty umieszczone względem innego kształtu są jego dziećmi. Gdy kształt nadrzędny zostanie przeniesiony, elementy podrzędne przeniosą się z nim.
Względne elementy podrzędne mogą pojawić się poza polem ograniczenia kształtu nadrzędnego. Zagnieżdżone elementy podrzędne pojawiają się ściśle wewnątrz granic elementu nadrzędnego.
Aby uzyskać górny zestaw kształtów na diagramie, użyj:
Diagram.NestedChildShapes
Klasy przodków kształtów i łączników to:
-- ShapeElement
----- NodeShape
------- Diagram
------- Twój kształt
----- LinkShape
------- BinaryLinkShape
--------- Twój Połączenie or
Właściwości kształtów i Połączenie or
W większości przypadków nie jest konieczne wprowadzanie jawnych zmian w kształtach. Po zmianie elementów modelu reguły "naprawy" aktualizują kształty i łączniki. Aby uzyskać więcej informacji, zobacz Odpowiadanie na zmiany i propagowanie ich.
Warto jednak wprowadzić pewne jawne zmiany kształtów we właściwościach, które są niezależne od elementów modelu. Można na przykład zmienić następujące właściwości:
Size - określa wysokość i szerokość kształtu.
Location — położenie względem kształtu lub diagramu nadrzędnego
StyleSet - zestaw piór i pędzli używanych do rysowania kształtu lub łącznika
Hide - sprawia, że kształt jest niewidoczny
Show - sprawia, że kształt jest widoczny po
Hide()
Tworzenie elementu i jego kształtu
Podczas tworzenia elementu i łączenia go z drzewem osadzania relacji kształt jest tworzony automatycznie i skojarzony z nim. Jest to wykonywane przez reguły "fixup", które są wykonywane na końcu transakcji. Jednak kształt pojawi się w automatycznie przypisanej lokalizacji, a jego kształt, kolor i inne funkcje będą miały wartości domyślne. Aby kontrolować sposób tworzenia kształtu, możesz użyć funkcji scalania. Najpierw należy dodać elementy, które chcesz dodać do grupy Elementów, a następnie scalić grupę z diagramem.
Ta metoda:
Ustawia nazwę, jeśli przypisano właściwość jako nazwę elementu.
Obserwuje wszystkie dyrektywy scalania elementów określone w definicji DSL.
W tym przykładzie zostanie utworzony kształt w pozycji myszy, gdy użytkownik kliknie dwukrotnie diagram. W definicji DSL dla tego przykładu FillColor
uwidoczniono właściwość .ExampleShape
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();
}
}
}
Jeśli podasz więcej niż jeden kształt, ustaw ich względne pozycje przy użyciu elementu AbsoluteBounds
.
Można również ustawić kolor i inne uwidocznione właściwości łączników przy użyciu tej metody.
Korzystanie z transakcji
Kształty, łączniki i diagramy są podtypami ModelElement i działają w Sklepie. W związku z tym należy wprowadzić zmiany tylko wewnątrz transakcji. Aby uzyskać więcej informacji, zobacz How to: Use Transactions to Update the Model (Instrukcje: używanie transakcji do aktualizowania modelu).
Widok dokumentu i dane dokumentu
Partycje magazynu
Po załadowaniu modelu towarzyszący diagram jest ładowany w tym samym czasie. Zazwyczaj model jest ładowany do pliku Store.DefaultPartition, a zawartość diagramu jest ładowana do innej partycji. Zazwyczaj zawartość każdej partycji jest ładowana i zapisywana w osobnym pliku.