Работа с моделями и изменение их в коде программы

Можно написать код для создания и удаления элементов модели, задания их свойств и создания и удаления связей между элементами. Все изменения должны быть внесены в транзакцию. Если элементы отображаются на схеме, схема будет "исправлена" автоматически в конце транзакции.

Пример определения DSL

Это основная часть dslDefinition.dsl для примеров в этом разделе:

DSL Definition diagram - family tree model

Эта модель является экземпляром этого DSL:

Tudor Family Tree Model

Ссылки и пространства имен

Чтобы запустить код в этом разделе, следует ссылаться на следующее:

Microsoft.VisualStudio.Modeling.Sdk.11.0.dll

Код будет использовать это пространство имен:

using Microsoft.VisualStudio.Modeling;

Кроме того, при написании кода в другом проекте, в котором определен dsL, необходимо импортировать сборку, созданную проектом Dsl.

Свойства

Свойства домена, которые определяются в определении DSL, становятся свойствами, к которым можно получить доступ в коде программы:

Person henry = ...;

if (henry.BirthDate < 1500) ...

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

Если вы хотите задать свойство, необходимо сделать это внутри транзакции:

henry.Name = "Henry VIII";

Если в определении DSL тип свойства вычисляется, его нельзя задать. Дополнительные сведения см. в разделе "Вычисляемые и настраиваемые свойства служба хранилища".

Связи

Связи домена, которые определяются в определении DSL, становятся парами свойств, по одному в классе в каждом конце связи. Имена свойств отображаются на схеме DslDefinition в виде меток ролей на каждой стороне связи. В зависимости от кратности роли тип свойства является классом в другом конце связи или коллекцией этого класса.

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

FamilyTreeModel ftree = henry.FamilyTreeModel;

Свойства в противоположных концах отношения всегда взаимно. При создании или удалении ссылки свойства роли обоих элементов обновляются. Следующее выражение (которое использует расширения System.Linq) всегда верно для отношения ParentsHaveChildren в примере:

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

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

ElementLinks. Связь также представлена элементом модели, который называется ссылкой, которая является экземпляром типа связи домена. Ссылка всегда имеет один исходный элемент и один целевой элемент. Исходный элемент и целевой элемент могут совпадать.

Вы можете получить доступ к ссылке и ее свойствам:

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

// This is now true:

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

По умолчанию не более одного экземпляра связи разрешено связать любую пару элементов модели. Но если в определении Allow Duplicates DSL флаг имеет значение true для связи, то может быть несколько ссылок, и необходимо использовать GetLinks:

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

Существуют также другие методы для доступа к ссылкам. Например:

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

Скрытые роли. Если в определении DSL созданное свойство имеет значение false для определенной роли, свойство не создается, соответствующее этой роли. Однако вы по-прежнему можете получить доступ к ссылкам и пройти по ссылкам с помощью методов связи:

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

Наиболее часто используемым примером является PresentationViewsSubject связь, которая связывает элемент модели с фигурой, отображающей ее на схеме:

PresentationViewsSubject.GetPresentation(henry)[0] as PersonShape

Каталог элементов

Доступ ко всем элементам в хранилище можно получить с помощью каталога элементов:

store.ElementDirectory.AllElements

Существуют также методы поиска элементов, например следующие:

store.ElementDirectory.FindElements(Person.DomainClassId);

store.ElementDirectory.GetElement(elementId);

Доступ к сведениям о классе

Вы можете получить сведения о классах, отношениях и других аспектах определения DSL. Например:

DomainClassInfo personClass = henry.GetDomainClass();

DomainPropertyInfo birthProperty =

personClass.FindDomainProperty("BirthDate")

DomainRelationshipInfo relationship =

link.GetDomainRelationship();

DomainRoleInfo sourceRole = relationship.DomainRole[0];

Классы предков элементов модели приведены следующим образом:

  • ModelElement — все элементы и связи — ModelElements

  • ElementLink — все связи — ElementLinks

Выполнение изменений внутри транзакции

Всякий раз, когда код программы изменяет что-либо в Магазине, он должен сделать это внутри транзакции. Это относится ко всем элементам модели, связям, фигурам, схемам и их свойствам. Дополнительные сведения см. в разделе Transaction.

Самый удобный способ управления транзакцией — это инструкция, using заключенная в 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.
}

Вы можете внести любое количество изменений внутри одной транзакции. Вы можете открывать новые транзакции внутри активной транзакции.

Чтобы внести изменения, необходимо Commit выполнить транзакцию перед удалением. Если возникает исключение, которое не поймано внутри транзакции, магазин будет сбрасываться в состояние перед изменениями.

Создание элементов модели

В этом примере элемент добавляется в существующую модель:

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

В этом примере показаны следующие основные моменты создания элемента:

  • Создайте новый элемент в определенной секции Store. Для элементов и связей модели, но не фигур, обычно это секция по умолчанию.

  • Сделайте его целевым объектом связи внедрения. В этом примере DslDefinition каждый пользователь должен быть целью внедрения отношений FamilyTreeHas Люди. Для этого можно задать свойство роли FamilyTreeModel объекта Person или добавить Person в свойство роли Люди объекта FamilyTreeModel.

  • Задайте свойства нового элемента, особенно для свойства, для которого IsName задано значение true в dslDefinition. Этот флаг помечает свойство, которое служит для идентификации элемента уникальным образом внутри своего владельца. В этом случае свойство Name имеет этот флаг.

  • Определение DSL этого DSL должно быть загружено в Магазин. Если вы пишете расширение, например команду меню, это обычно будет верно. В других случаях можно явно загрузить модель в Магазин или использовать ModelBus для его загрузки. Дополнительные сведения см. в разделе "Практическое руководство. Открытие модели из файла в коде программы".

    При создании элемента таким образом создается фигура автоматически (если DSL имеет схему). Он отображается в автоматически назначенном расположении с фигурой, цветом и другими функциями по умолчанию. Если вы хотите контролировать, где и как отображается связанная фигура, см. статью "Создание элемента и его фигуры".

В примере определения DSL определены две связи. Каждая связь определяет свойство роли в классе в каждом конце связи.

Существует три способа создания экземпляра связи. Каждый из этих трех методов имеет одинаковый эффект:

  • Задайте свойство исходного проигрывателя ролей. Например:

    • familyTree.People.Add(edward);

    • edward.Parents.Add(henry);

  • Задайте свойство целевого проигрывателя ролей. Например:

    • edward.familyTreeModel = familyTree;

      Кратность этой роли имеет значение 1..1, поэтому мы назначаем это значение.

    • henry.Children.Add(edward);

      Кратность этой роли заключается в 0..*том, что мы добавим в коллекцию.

  • Создайте экземпляр связи явным образом. Например:

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

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

    Последний метод полезен, если вы хотите задать свойства для самой связи.

    При создании элемента таким образом соединитель на схеме создается автоматически, но он имеет фигуру, цвет и другие функции по умолчанию. Сведения о создании связанного соединителя см. в статье "Создание элемента и его фигуры".

Удаление элементов

Удаление элемента путем вызова Delete():

henry.Delete();

Эта операция также удаляет:

  • Связи с элементом и из нее. Например, edward.Parents больше не будет содержать henry.

  • Элементы ролей, для которых PropagatesDelete флаг имеет значение true. Например, фигура, отображающая элемент, будет удалена.

По умолчанию каждая связь внедрения имеет PropagatesDelete значение true в целевой роли. Удаление henry не удаляет familyTree, но familyTree.Delete() удаляет все Persons.

По умолчанию PropagatesDelete не соответствует ролям ссылочных связей.

Правила удаления могут опустить определенные распространения при удалении объекта. Это полезно, если вы подстановите один элемент для другого. Вы предоставляете GUID одной или нескольких ролей, для которых удаление не должно распространяться. GUID можно получить из класса отношений:

henry.Delete(ParentsHaveChildren.SourceDomainRoleId);

(Этот конкретный пример не будет иметь эффекта, так как PropagatesDelete предназначен false для ролей ParentsHaveChildren связи.)

В некоторых случаях удаление предотвращается наличием блокировки либо в элементе, либо в элементе, который будет удален путем распространения. Можно использовать element.CanDelete() для проверка, можно ли удалить элемент.

Связь можно удалить, удалив элемент из свойства роли:

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

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

Вы также можете явно удалить ссылку:

edwardHenryLink.Delete();

Эти три метода имеют одинаковый эффект. Вам нужно использовать только один из них.

Если роль имеет 0.1 или 1.1, можно задать для нее nullзначение или другое значение:

edward.FamilyTreeModel = null; Или:

edward.FamilyTreeModel = anotherFamilyTree;

Перезапорядочение ссылок связи

Ссылки определенной связи, исходной или целевой для определенного элемента модели, имеют определенную последовательность. Они отображаются в том порядке, в котором они были добавлены. Например, эта инструкция всегда будет давать дочерние элементы в одном порядке:

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

Вы можете изменить порядок ссылок:

ParentsHaveChildren link = GetLink(henry,edward);

ParentsHaveChildren nextLink = GetLink(henry, elizabeth);

DomainRoleInfo role =

link.GetDomainRelationship().DomainRoles[0];

link.MoveBefore(role, nextLink);

Блокировки

Изменения могут быть запрещены блокировкой. Блокировки можно задать для отдельных элементов, секций и в хранилище. Если любой из этих уровней имеет блокировку, которая предотвращает изменение, которое требуется внести, при попытке его может возникнуть исключение. Вы можете определить, задаются ли блокировки с помощью элемента. GetLocks(), который является методом расширения, определенным в пространстве Microsoft.VisualStudio.Modeling.Immutabilityимен.

Дополнительные сведения см. в разделе "Определение политики блокировки" для создания сегментов только для чтения.

Копирование и вставка

Элементы или группы элементов можно скопировать в 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>());

Элементы хранятся в виде сериализованной группы элементов.

Элементы из IDataObject можно объединить в модель:

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

Merge () может принять либо a PresentationElement , либо a ModelElement. Если дать его PresentationElement, можно также указать позицию на целевой схеме в качестве третьего параметра.

Навигация и обновление схем

В DSL элемент модели домена, представляющий концепцию, например Person или Song, отделен от элемента фигуры, который представляет то, что вы видите на схеме. Элемент модели домена сохраняет важные свойства и связи понятий. Элемент фигуры сохраняет размер, положение и цвет представления объекта на схеме и макет его компонентов.

Элементы презентации

Class diagram of base shape and element types

В определении DSL каждый элемент, указанный вами, создает класс, производный от одного из следующих стандартных классов.

Тип элемента Базовый класс
Класс домена ModelElement
Связь домена ElementLink
Фигура NodeShape
Соединитель BinaryLinkShape
Схема Diagram

Элемент на схеме обычно представляет элемент модели. Обычно (но не всегда) представляет NodeShape экземпляр класса домена и BinaryLinkShape представляет экземпляр связи домена. Связь PresentationViewsSubject связывает узел или фигуру ссылки на элемент модели, который он представляет.

Каждая фигура узла или канала принадлежит одной схеме. Двоичная фигура канала соединяет две фигуры узла.

Фигуры могут иметь дочерние фигуры в двух наборах. Фигура в NestedChildShapes наборе ограничена ограничивающим полем родительского элемента. Фигура в RelativeChildShapes списке может отображаться вне или частично вне границ родительского элемента, например метка или порт. Схема не RelativeChildShapes имеет и нет Parent.

Навигация между фигурами и элементами

Элементы модели домена и элементы фигуры связаны связью PresentationViewsSubject .

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

Те же связи связывают связи с соединителями на схеме:

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

Эта связь также связывает корень модели с схемой:

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

Чтобы получить элемент модели, представленный фигурой, используйте следующую команду:

henryShape.ModelElement as Person

diagram.ModelElement as FamilyTreeModel

Как правило, не рекомендуется перемещаться между фигурами и соединителями на схеме. Лучше перемещаться по связям в модели, перемещаясь между фигурами и соединителями, только если необходимо работать над внешним видом схемы. Эти методы связывают соединители с фигурами в каждом конце:

personShape.FromRoleLinkShapes, personShape.ToRoleLinkShapes

connector.FromShape, connector.ToShape

Многие фигуры являются составными; они состоят из родительской фигуры и одного или нескольких слоев дочерних элементов. Фигуры, расположенные относительно другой фигуры, как говорят, являются его дочерними. При перемещении родительской фигуры дочерние элементы перемещаются вместе с ним.

Относительные дочерние элементы могут отображаться вне ограничивающего поля родительской фигуры. Вложенные дочерние элементы отображаются строго внутри границ родительского элемента.

Чтобы получить верхний набор фигур на схеме, используйте следующую команду:

Diagram.NestedChildShapes

Классы предков фигур и соединителей:

ModelElement

-- PresentationElement

-- ShapeElement

----- NodeShape

------- Diagram

------- YourShape

----- LinkShape

------- BinaryLinkShape

--------- Ваш Подключение or

Свойства фигур и Подключение or

В большинстве случаев не требуется вносить явные изменения в фигуры. При изменении элементов модели правила "исправить" обновляют фигуры и соединители. Дополнительные сведения см. в статье "Реагирование на изменения" и "Распространение изменений".

Однако полезно внести некоторые явные изменения в фигуры в свойствах, которые не зависят от элементов модели. Например, можно изменить следующие свойства:

  • Size — определяет высоту и ширину фигуры.

  • Location — положение относительно родительской фигуры или схемы

  • StyleSet — набор перьев и кистей, используемых для рисования фигуры или соединителя

  • Hide — делает фигуру невидимой

  • Show — делает фигуру видимой после Hide()

Создание элемента и его фигуры

При создании элемента и связывании его с деревом связей внедрения фигуры автоматически создается и связывается с ним. Это делается правилами исправления, которые выполняются в конце транзакции. Однако фигура будет отображаться в автоматически назначенном расположении, а ее фигура, цвет и другие функции будут иметь значения по умолчанию. Чтобы управлять созданием фигуры, можно использовать функцию слияния. Сначала необходимо добавить элементы, которые нужно добавить в ElementGroup, а затем объединить группу на схему.

Этот метод выполняет следующее:

  • Задает имя, если вы назначили свойство в качестве имени элемента.

  • Наблюдает все директивы слияния элементов, указанные в определении DSL.

В этом примере создается фигура на позиции мыши, когда пользователь дважды щелкает схему. В определении DSL для этого примера FillColorExampleShape свойство было предоставлено.

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

Если вы предоставляете несколько фигур, задайте их относительные позиции с помощью .AbsoluteBounds

Вы также можете задать цвет и другие доступные свойства соединителей с помощью этого метода.

Использование транзакций

Фигуры, соединители и схемы являются подтипами ModelElement и живут в Магазине. Поэтому необходимо внести изменения только внутри транзакции. Дополнительные сведения см. в разделе "Практическое руководство. Использование транзакций для обновления модели".

Представление документов и данные документа

Class diagram of standard diagram types

Хранение секций

При загрузке модели сопровождающая схема загружается одновременно. Как правило, модель загружается в Store.DefaultPartition, а содержимое схемы загружается в другую секцию. Обычно содержимое каждой секции загружается и сохраняется в отдельный файл.