在 Visual Studio 的领域特定语言中,当域属性的值发生更改时,OnValueChanging() 和 OnValueChanged() 方法会在域属性处理程序中被调用。 若要响应更改,可以重写这些方法。
重写属性处理程序方法
特定于域的语言的每个域属性由嵌套在其父域类中的类进行处理。 其名称遵循 PropertyNamePropertyHandler 格式。 可以在文件 Dsl\Generated Code\DomainClasses.cs 中检查此属性处理程序类。 在类中, OnValueChanging() 在值更改之前立即调用,并在 OnValueChanged() 值更改后立即调用。
例如,假设你有一个名为Comment的域类,该类具有一个名为Text的字符串域属性和一个名为TextLengthCount的整数属性。 若要使 TextLengthCount 始终包含 Text 字符串的长度,可以在 Dsl 项目中的独立文件中编写如下代码:
// Domain Class "Comment":
public partial class Comment
{
// Domain Property "Text":
partial class TextPropertyHandler
{
protected override void OnValueChanging(CommentBase element, string oldValue, string newValue)
{
base.OnValueChanging(element, oldValue, newValue);
// To update values outside the Store, write code here.
// Let the transaction manager handle undo:
Store store = element.Store;
if (store.InUndoRedoOrRollback || store.InSerializationTransaction) return;
// Update values in the Store:
this.TextLengthCount = newValue.Length;
}
}
}
请注意有关属性处理程序的以下几点:
当用户更改域属性时,以及程序代码向属性分配不同的值时,将调用属性处理程序方法。
仅当值实际更改时,才会调用这些方法。 如果程序代码分配的值等于当前值,则不会调用处理程序。
计算和自定义存储域的属性不包含 OnValueChanged 和 OnValueChanging 方法。
不能使用更改处理程序修改新值。 如果要执行此作,例如,若要将值限制为特定范围,请定义一个
ChangeRule。不能将更改处理程序添加到表示关系角色的属性。 而是在关系类上定义
AddRule和DeleteRule。 创建或更改链接时会触发这些规则。 有关详细信息,请参阅 规则在模型中传播更改。
商店内外的变更
属性处理程序方法在启动更改的事务内调用。 因此,你可以在商店中进行更多更改,而无需打开新事务。 您的更改可能会导致额外的处理程序调用。
当事务被撤消、重做或回滚时,不应在存储中进行更改,即对模型元素、关系、形状、连接线关系图或其属性的更改。
此外,从文件加载模型时,通常不会更新值。
因此,模型的更改应通过类似这样的测试来保护。
if (!store.InUndoRedoOrRollback && !store. InSerializationTransaction)
{
this.TextLength = ...; // in-store changes
}
相比之下,如果属性处理程序将更改传播到存储区外(例如文件、数据库或非存储变量),则应始终进行这些更改,以便在用户调用撤消或重做时更新外部值。
取消更改
如果要阻止更改,可以回滚当前事务。 例如,你可能希望确保属性保留在特定范围内。
if (newValue > 10)
{
store.TransactionManager.CurrentTransaction.Rollback();
System.Windows.Forms.MessageBox.Show("Value must be less than 10");
}
替代技术:计算属性
前面的示例演示如何使用 OnValueChanged()将值从一个域属性传播到另一个域属性。 每个属性都有自己的存储值。
相反,可以考虑将派生属性定义为计算属性 (Calculated property)。 在这种情况下,该属性没有自己的存储,并且定义函数是在需要函数值时计算的。 有关详细信息,请参阅 “计算”和“自定义存储属性”。
在 DSL 定义中,可以将TextLengthCountKind字段设置为Calculated,而不是采用之前的示例。 你将为此域属性提供自己的 Get 方法。
Get 方法将返回字符串的Text当前长度。
但是,计算属性的潜在缺点是每次使用值时都会计算表达式,这可能会导致性能问题。 此外,计算属性上没有 OnValueChanging() 和 OnValueChanged()。
替代技术:更改规则
如果定义了 ChangeRule,那么它将在属性值更改的事务结束时执行。 有关详细信息,请参阅 规则在模型中传播更改。
如果在一个事务中进行了多个更改,则 ChangeRule 会在全部完成时执行。 相反,当某些更改尚未执行时,将执行 OnValue... 方法。 根据要实现的目标,这可能会使 ChangeRule 更合适。
还可以使用 ChangeRule 调整属性的新值,使其保持在特定范围内。
警告
如果规则对存储内容进行更改,可能会触发其他规则和属性处理程序。 如果规则更改触发它的属性,则会再次调用它。 必须确保规则定义不会导致无限触发。
using Microsoft.VisualStudio.Modeling;
...
// Change rule on the domain class Comment:
[RuleOn(typeof(Comment), FireTime = TimeToFire.TopLevelCommit)]
class MyCommentTrimRule : ChangeRule
{
public override void
ElementPropertyChanged(ElementPropertyChangedEventArgs e)
{
base.ElementPropertyChanged(e);
Comment comment = e.ModelElement as Comment;
if (comment.Text.StartsWith(" ") || comment.Text.EndsWith(" "))
comment.Text = comment.Text.Trim();
// If changed, rule will trigger again.
}
}
// Register the rule:
public partial class MyDomainModel
{
protected override Type[] GetCustomDomainModelTypes()
{ return new Type[] { typeof(MyCommentTrimRule) };
}
}
Example
Description
以下示例重写了一个域属性的属性处理程序,并在 ExampleElement 域类的属性发生更改时通知用户。
Code
using DslModeling = global::Microsoft.VisualStudio.Modeling;
using DslDesign = global::Microsoft.VisualStudio.Modeling.Design;
namespace msft.FieldChangeSample
{
public partial class ExampleElement
{
internal sealed partial class NamePropertyHandler
{
protected override void OnValueChanged(ExampleElement element,
string oldValue, string newValue)
{
if (!this.Store.InUndoRedoOrRollback)
{
// make in-store changes here...
}
// This part is called even in undo:
System.Windows.Forms.MessageBox.Show("Value Has Changed");
base.OnValueChanged(element, oldValue, newValue);
}
}
}
}