规则在模型内部传播更改
可以创建存储规则,以在可视化和建模 SDK (VMSDK) 中的元素之间传播更改。 当存储区中任何元素发生更改时,通常会计划在提交最外层的事务时执行规则。 不同类型的事件(例如添加元素或删除元素)有不同类型的规则。 可以将规则附加到特定类型的元素、形状或关系图。 许多内置功能由规则定义:例如,规则可确保在模型发生更改时更新关系图。 可以通过添加自己的规则来自定义特定于域的语言。
存储规则特别适用于在存储内部传播更改,即模型元素、关系、形状或连接符及其域属性的更改。 当用户调用 Undo 或 Redo 命令时,规则不会运行。 相反,事务管理器确保存储内容还原到正确的状态。 如果要将更改传播到存储外部的资源,请使用“存储事件”。 有关详细信息,请参阅事件处理程序在模型外部传播更改。
例如,假设要指定每当用户(或代码)创建类型为 ExampleDomainClass 的新元素时,在模型的另一部分创建另一种类型的其他元素。 可以编写 AddRule 并将其与 ExampleDomainClass 关联。 你将在规则中编写代码以创建额外元素。
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.Modeling;
namespace ExampleNamespace
{
// Attribute associates the rule with a domain class:
[RuleOn(typeof(ExampleDomainClass), FireTime=TimeToFire.TopLevelCommit)]
// The rule is a class derived from one of the abstract rules:
class MyAddRule : AddRule
{
// Override the abstract method:
public override void ElementAdded(ElementAddedEventArgs e)
{
base.ElementAdded(e);
ExampleDomainClass element = e.ModelElement;
Store store = element.Store;
// Ignore this call if we're currently loading a model:
if (store.TransactionManager.CurrentTransaction.IsSerializing)
return;
// Code here propagates change as required - for example:
AnotherDomainClass echo = new AnotherDomainClass(element.Partition);
echo.Name = element.Name;
echo.Parent = element.Parent;
}
}
// The rule must be registered:
public partial class ExampleDomainModel
{
protected override Type[] GetCustomDomainModelTypes()
{
List<Type> types = new List<Type>(base.GetCustomDomainModelTypes());
types.Add(typeof(MyAddRule));
// If you add more rules, list them here.
return types.ToArray();
}
}
}
备注
规则的代码仅应更改存储中元素的状态;即,规则应仅更改模型元素、关系、形状、连接符、关系图或属性。 如果要将更改传播到存储外部的资源,请定义“存储事件”。 有关详细信息,请参阅事件处理程序在模型外部传播更改。
定义规则
将规则定义为以
RuleOn
属性为前缀的类。 该属性将规则与域类、关系或关系图元素之一关联。 规则将应用于此类的每一个实例,其中实例可能是抽象的。通过将规则添加到域模型类中的
GetCustomDomainModelTypes()
返回的集来注册规则。从抽象规则类之一派生规则类,并编写执行方法的代码。
以下各部分更详细地说明了这些步骤。
在域类上定义规则
在自定义代码文件中,定义一个类,并添加 RuleOnAttribute 属性作为其前缀:
[RuleOn(typeof(ExampleElement), // Usual value - but required, because it is not the default: FireTime = TimeToFire.TopLevelCommit)] class MyRule ...
第一个参数中的使用者类型可以是域类、域关系、形状、连接符或关系图。 通常,将规则应用于域类和关系。
FireTime
通常为TopLevelCommit
。 这可确保仅在对事务进行所有主要更改后再执行规则。 替代项为“内联”(其将在更改后立即执行规则)和 LocalCommit(其在当前事务(可能不在最外层)处理结束时执行规则)。 还可以设置规则的优先级,以影响规则在队列中的顺序,但这是实现你所需结果的不可靠方法。可以将抽象类指定为使用者类型。
该规则适用于使用者类的所有实例。
FireTime
的默认值为 TimeToFire.TopLevelCommit。 这将导致在提交最外层的事务时执行规则。 另一种替代方法是 TimeToFire.Inline。 这会导致规则在触发事件后立即执行。
注册规则
将规则类添加到域模型中的
GetCustomDomainModelTypes
返回的类型列表:public partial class ExampleDomainModel { protected override Type[] GetCustomDomainModelTypes() { List<Type> types = new List<Type>(base.GetCustomDomainModelTypes()); types.Add(typeof(MyAddRule)); // If you add more rules, list them here. return types.ToArray(); } }
如果不确定域模型类的名称,请查看文件 Dsl\GeneratedCode\DomainModel.cs
在 DSL 项目中的自定义代码文件中编写此代码。
编写规则的代码
从以下基类之一派生规则类:
基类 触发器 AddRule 添加了元素、链接或形状。
使用它来检测新关系以及新元素。ChangeRule 域属性值已更改。 方法参数提供新、旧值。
对于形状,如果移动了形状,则当内置AbsoluteBounds
属性更改时,将触发此规则。
在许多情况下,在属性处理程序中替代OnValueChanged
或OnValueChanging
是更方便的方法。 这些方法在更改之前和之后立即调用。 相比之下,规则通常在事务结束时运行。 有关详细信息,请参阅域属性值更改处理程序。 注意:创建或删除链接时不会触发此规则。 而是为域关系编写AddRule
和DeleteRule
。DeletingRule 在即将删除元素或链接时触发。 在事务结束之前,属性 ModelElement.IsDeleting 为 true。 DeleteRule 删除元素或链接时执行。 此规则将在执行所有其他规则(包括 DeletingRules)后执行。 ModelElement.IsDeleting 为 false,ModelElement.IsDeleted 为 true。 为了允许后续的撤消操作,实际上不会从内存中删除元素,而是从 Store.ElementDirectory 中删除该元素。 MoveRule 元素从一个存储分区移到另一个存储分区。
(请注意,此操作与形状的图形位置不相关。)RolePlayerChangeRule 此规则仅适用于域关系。 如果将模型元素显式分配给链接的任一端,则触发此规则。 RolePlayerPositionChangeRule 在链接上使用 MoveBefore 或 MoveToIndex 方法更改元素的链接排序时触发。 TransactionBeginningRule 创建事务时执行。 TransactionCommittingRule 事务即将提交时执行。 TransactionRollingBackRule 事务即将回滚时执行。 每个类都有一个替代的方法。 在类中键入
override
以发现它。 此方法的参数标识要更改的元素。请注意有关规则的以下几点:
事务中的一组更改可能会触发许多规则。 通常,在提交最外层的事务时执行规则。 它们以未指定的顺序执行。
规则始终在事务内执行。 因此,不需要创建新事务来进行更改。
回滚事务或执行撤消或重做操作时,不会执行规则。 这些操作将存储的所有内容重置为以前的状态。 因此,如果规则更改存储外部任何内容的状态,则可能不会与存储内容保持同步。 若要更新存储外部的状态,最好使用事件。 有关详细信息,请参阅事件处理程序在模型外部传播更改。
从文件加载模型时,将执行某些规则。 若要确定加载或保存是否正在进行,请使用
store.TransactionManager.CurrentTransaction.IsSerializing
。如果规则的代码创建了更多规则触发器,这些触发器将添加到触发列表的末尾,并且将在事务完成之前执行。 DeletedRules 在所有其他规则之后执行。 一个规则可以在事务中运行多次,每个更改一次。
若要向/从规则传递信息,可以在
TransactionContext
中存储信息。 这只是在事务期间维护的字典。 事务结束时,将释放该事务。 每个规则中的事件参数都提供对它的访问。 请记住,规则不按可预测的顺序执行。在考虑其他替代方法后,使用规则。 例如,如果要在值更改时更新属性,请考虑使用计算属性。 如果要约束形状的大小或位置,请使用
BoundsRule
。 如果要响应属性值中的更改,请向属性添加OnValueChanged
处理程序。 有关详细信息,请参阅响应和传播更改。
示例
以下示例在实例化域关系以链接两个元素时更新属性。 此规则不仅将在用户在关系图上创建链接时触发,而且当程序代码创建链接时也会触发。
若要测试此示例,请使用任务流解决方案模板创建 DSL,并将以下代码插入 Dsl 项目中的文件中。 生成并运行解决方案,并打开调试项目中的示例文件。 在注释形状和流元素之间绘制注释链接。 注释中的文本将更改,以报告已连接到的最新元素。
在实践中,通常会为每个 AddRule 编写一个 DeleteRule。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Modeling;
namespace Company.TaskRuleExample
{
[RuleOn(typeof(CommentReferencesSubjects))]
public class RoleRule : AddRule
{
public override void ElementAdded(ElementAddedEventArgs e)
{
base.ElementAdded(e);
CommentReferencesSubjects link = e.ModelElement as CommentReferencesSubjects;
Comment comment = link.Comment;
FlowElement subject = link.Subject;
Transaction current = link.Store.TransactionManager.CurrentTransaction;
// Don't want to run when we're just loading from file:
if (current.IsSerializing) return;
comment.Text = "Flow has " + subject.FlowTo.Count + " outgoing connections";
}
}
public partial class TaskRuleExampleDomainModel
{
protected override Type[] GetCustomDomainModelTypes()
{
List<Type> types = new List<Type>(base.GetCustomDomainModelTypes());
types.Add(typeof(RoleRule));
return types.ToArray();
}
}
}