Udostępnij za pośrednictwem


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:

DSL Definition diagram - family tree model

Ten model jest wystąpieniem tego rozszerzenia DSL:

Tudor Family Tree Model

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.

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.

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ąć.

Łą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

Class diagram of base shape and element types

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

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:

ModelElement

-- PresentationElement

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

Class diagram of standard diagram types

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.