域属性值更改处理程序

在 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

  • 无法将更改处理程序添加到表示关系的角色的属性。 相反,可在关系类上定义 AddRuleDeleteRule。 当创建或更改链接时,将触发这些规则。 有关详细信息,请参阅模型中的规则传播更改

存储内外的更改

属性处理程序方法将在已启动更改的事务内部进行调用。 因此,你可以在存储内进行更多更改,而无需打开新事务。 所做的更改可能会导致其他处理程序调用。

当事务处于正在撤消、重做或回滚状态时,不应在存储中进行更改,即对模型元素、关系、形状、连接符、关系图或其属性进行更改。

此外,通常不应在从文件加载该模型时更新值。

因此,对模型所做的更改应通过测试进行保护,如下所示:

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() 来将值从一个域属性传播到另一个域属性。 每个属性都具有其自己的存储值。

相反,你可以考虑将派生属性定义为计算属性。 在这种情况下,该属性不具有其自己的存储,并且将定义每当需要其值时要计算的函数。 有关详细信息,请参阅计算和自定义存储属性

与上一示例不同,你可以在 DSL 定义中将 TextLengthCount 的“Kind”字段设置为“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) };
 }
}

示例

说明

以下示例将重写域属性的属性处理程序,并在 ExampleElement 域类的属性已发生更改时通知用户。

代码

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