可以编写代码来创建和删除模型元素、设置其属性以及创建和删除元素之间的链接。 所有更改都必须在事务中进行。 如果在关系图上查看元素,则关系图将在事务结束时自动“固定”。
示例 DSL 定义
在本主题的示例中,这是 DslDefinition.dsl 的主要部分:
此模型是此 DSL 的实例:
引用和命名空间
若要运行本主题中的代码,应引用:
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 定义中,某个属性的 Kind 是 Calculated,则无法设置它。 有关详细信息,请参阅 “计算”和“自定义存储属性”。
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 Generated 为 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 - 所有关系都是 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 中,PropagatesDelete 是 false 的角色。)
在某些情况下,通过锁的存在防止删除,无论是在某个元素上,还是在因传播而被删除的元素上。 可用于 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
形状和连接线的祖先类包括:
-- ShapeElement
----- NodeShape
------- Diagram
------- YourShape
----- LinkShape
------- BinaryLinkShape
--------- YourConnector
形状和连接线的属性
在大多数情况下,不需要对形状进行显式更改。 更改模型元素后,“修复”规则将更新形状和连接线。 有关详细信息,请参阅 “响应和传播更改”。
但是,对独立于模型元素的属性中的形状进行一些显式更改非常有用。 例如,可以更改以下属性:
Size - 确定形状的高度和宽度。
Location - 相对于父形状或关系图的位置
StyleSet - 用于绘制形状或连接线的笔和画笔集
Hide - 使形状不可见
Show - 使形状在
Hide()后可见
创建元素及其形状
创建元素并将其链接到嵌入关系树时,会自动创建形状并将其关联。 这是由在事务结束时执行的“修复”规则完成的。 但是,该形状将显示在自动分配的位置,其形状、颜色和其他功能将具有默认值。 若要控制形状的创建方式,可以使用合并函数。 首先,必须将您想要添加的元素加入到一个 ElementGroup 中,然后将该组合并到图中。
此方法:
如果已将属性指定为元素名称,则设置名称。
遵循在 DSL 定义中指定的任何元素合并指令。
当用户双击图表时,本示例在鼠标位置创建一个形状。 在此示例的 DSL 定义中,ExampleShape 的 FillColor 属性已被公开。
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 中,关系图内容将加载到另一个分区中。 通常,将加载每个分区的内容并将其保存到单独的文件中。