在程序代码中导航和更新模型

可以编写代码来创建和删除模型元素、设置其属性以及创建和删除元素之间的链接。 所有更改都必须在事务中进行。 如果在关系图上查看元素,则关系图将在事务结束时自动“固定”。

示例 DSL 定义

在本主题的示例中,这是 DslDefinition.dsl 的主要部分:

DSL 定义图 - 家庭树模型

此模型是此 DSL 的实例:

Tudor 家庭树模型

引用和命名空间

若要运行本主题中的代码,应引用:

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 定义中,某个属性的 KindCalculated,则无法设置它。 有关详细信息,请参阅 “计算”和“自定义存储属性”。

Relationships

在 DSL 定义中定义的域关系将转换为属性对,该属性对位于关系两端的类中。 属性的名称在 DslDefinition 图中显示为关系两端角色上的标签。 根据角色的多重性属性,属性的类型是关系另一端的对象类,或者是该对象类的集合。

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

FamilyTreeModel ftree = henry.FamilyTreeModel;

关系相反端的属性始终是相互的。 创建或删除链接时,将更新这两个元素上的角色属性。 在以下示例中,对于 ParentsHaveChildren 关系,使用 System.Linq 扩展功能的以下表达式始终为 true:

(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

默认情况下,不允许多个关系实例链接任何一对模型元素。 但是,如果在 DSL 定义中, Allow Duplicates 该标志对关系是真实的,则可能存在多个链接,并且必须使用 GetLinks

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

还有其他访问链接的方法。 例如:

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

隐藏的角色。 如果在 DSL 定义中,特定角色的 Is Property Generatedfalse,则不会生成对应于该角色的属性。 但是,你仍然可以使用关系的方法访问链接并遍历链接:

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 - 所有关系都是 ElementLink

在事务中执行更改

每当程序代码更改应用商店中的任何内容时,它都必须在事务中执行此作。 这适用于所有模型元素、关系、形状、关系图及其属性。 有关详细信息,请参阅 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!
}

此示例说明了有关创建元素的以下要点:

  • 在应用商店的特定分区中创建新元素。 对于模型元素和关系,但不是形状,这通常是默认分区。

  • 使其成为嵌入关系的目标。 在此示例中的 DslDefinition 中,每个人必须是嵌入关系 FamilyTreeHasPeople 的目标。 为此,我们可以设置 Person 对象的 FamilyTreeModel 角色属性,或将 Person 添加到 FamilyTreeModel 对象的 People 角色属性。

  • 设置新元素的属性,尤其是 DslDefinition 中为 true 的属性 IsName 。 此标志标记用于在其所有者中唯一标识元素的属性。 在这种情况下,Name 属性具有该标志。

  • 此 DSL 的 DSL 定义必须已加载到应用商店中。 如果编写扩展(如菜单命令),这通常已经为 true。 在其他情况下,可以将模型显式加载到应用商店中,或使用 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);

(此特定示例不会产生任何效果,因为在关系 ParentsHaveChildren 中,PropagatesDeletefalse 的角色。)

在某些情况下,通过锁的存在防止删除,无论是在某个元素上,还是在因传播而被删除的元素上。 可用于 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);

Locks

锁可能会阻止更改。 可以在各个元素、分区和存储区上设置锁。 如果其中任何一个级别都有一个锁来阻止要做出的更改类型,则尝试更改时可能会引发异常。 可以通过使用 element.GetLocks() 来发现元素是否设置了锁。GetLocks() 是在命名空间 Microsoft.VisualStudio.Modeling.Immutability 中定义的扩展方法。

有关详细信息,请参阅 定义锁定策略以创建 Read-Only 段

复制和粘贴

可以将元素或元素组复制到 :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 () 可以接受一个 PresentationElement 或一个 ModelElement。 如果为其指定一个 PresentationElement位置,还可以将目标关系图上的位置指定为第三个参数。

导航和更新图表

在 DSL 中,表示 Person 或 Song 等概念的域模型元素独立于形状元素,该元素表示在关系图中看到的内容。 域模型元素存储概念的重要属性和关系。 形状元素将对象视图的大小、位置和颜色存储在关系图上,以及其组件部件的布局。

演示文稿元素

基形状和元素类型的类图

在 DSL 定义中,你指定的每个元素都会创建一个派生自以下标准类之一的类。

元素类型 基类
域类 ModelElement
域关系 ElementLink
形状 NodeShape
Connector 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

--------- YourConnector

形状和连接线的属性

在大多数情况下,不需要对形状进行显式更改。 更改模型元素后,“修复”规则将更新形状和连接线。 有关详细信息,请参阅 “响应和传播更改”。

但是,对独立于模型元素的属性中的形状进行一些显式更改非常有用。 例如,可以更改以下属性:

  • Size - 确定形状的高度和宽度。

  • Location - 相对于父形状或关系图的位置

  • StyleSet - 用于绘制形状或连接线的笔和画笔集

  • Hide - 使形状不可见

  • Show - 使形状在Hide()后可见

创建元素及其形状

创建元素并将其链接到嵌入关系树时,会自动创建形状并将其关联。 这是由在事务结束时执行的“修复”规则完成的。 但是,该形状将显示在自动分配的位置,其形状、颜色和其他功能将具有默认值。 若要控制形状的创建方式,可以使用合并函数。 首先,必须将您想要添加的元素加入到一个 ElementGroup 中,然后将该组合并到图中。

此方法:

  • 如果已将属性指定为元素名称,则设置名称。

  • 遵循在 DSL 定义中指定的任何元素合并指令。

当用户双击图表时,本示例在鼠标位置创建一个形状。 在此示例的 DSL 定义中,ExampleShapeFillColor 属性已被公开。

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形状设置其相对位置。

还可以使用此方法设置连接器的颜色和其他公开属性。

使用事务

形状、连接线和关系图是 Microsoft Store 中的 ModelElement 子类型,并且位于应用商店中。 因此,必须仅在事务内对它们进行修改。 有关详细信息,请参阅 如何:使用事务更新模型

文档视图和文档数据

标准关系图类型的类图

存储分区

加载模型时,同时加载随附的关系图。 通常,模型将加载到 Store.DefaultPartition 中,关系图内容将加载到另一个分区中。 通常,将加载每个分区的内容并将其保存到单独的文件中。