事件处理程序在模型外部传播更改

在可视化和建模 SDK 中,可以定义存储事件处理程序,以将更改传播到存储外部的资源,例如非存储变量、文件、其他存储中的模型或其他 Visual Studio 扩展。 存储事件处理程序在触发事件的事务结束之后执行。 它们也会在 Undo 或 Redo 操作中执行。 因此,与存储规则不同,存储事件最适用于更新存储外部的值。 与 .NET 事件不同,存储事件处理程序注册为侦听类:不需要为每个实例注册单独的处理程序。 若要详细了解如何在处理更改的不同方式之间做出选择,请参阅响应和传播更改

图形图面和其他用户界面控件是可由存储事件处理的外部示例资源。

定义存储事件

  1. 选择要监视的事件类型。 如需完整列表,请查看 EventManagerDirectory 的属性。 每个属性对应于一种类型的事件。 最常用的事件类型包括:

    • ElementAdded - 创建模型元素、关系链接、形状或连接器时触发。

    • ElementPropertyChanged - 更改 Normal 域属性的值时触发。 仅在新值和旧值不相等时,才触发该事件。 该事件不能应用于计算和自定义存储属性。

      它不能应用于与关系链接对应的角色属性。 请改用 ElementAdded 来监视域关系。

    • ElementDeleted - 删除模型元素、关系、形状或连接器后触发。 你仍可以访问元素的属性值,但它与其他元素没有任何关系。

  2. 在 DslPackage 项目的单独代码文件中为 YourDslDocData 添加分部类定义 。

  3. 将事件的代码编写为方法,如以下示例所示。 它可以是 static,除非你想要访问 DocData

  4. 重写 OnDocumentLoaded() 以注册处理程序。 如果具有多个处理程序,可以在同一位置注册所有处理程序。

注册代码的位置并不重要。 DocView.LoadView() 是一个可选位置。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Modeling;

namespace Company.MusicLib
{
  partial class MusicLibDocData
  {
    // Register store events here or in DocView.LoadView().
    protected override void OnDocumentLoaded()
    {
      base.OnDocumentLoaded(); // Don't forget this.

      #region Store event handler registration.
      Store store = this.Store;
      EventManagerDirectory emd = store.EventManagerDirectory;
      DomainRelationshipInfo linkInfo = store.DomainDataDirectory
          .FindDomainRelationship(typeof(ArtistAppearsInAlbum));
      emd.ElementAdded.Add(linkInfo,
          new EventHandler<ElementAddedEventArgs>(AddLink));
      emd.ElementDeleted.Add(linkInfo,
          new EventHandler<ElementDeletedEventArgs>(RemoveLink));

      #endregion Store event handlers.
    }

    private void AddLink(object sender, ElementAddedEventArgs e)
    {
      ArtistAppearsInAlbum link = e.ModelElement as ArtistAppearsInAlbum;
      if (link != null)
            ExternalDatabase.Add(link.Artist.Name, link.Album.Title);
    }
    private void RemoveLink(object sender, ElementDeletedEventArgs e)
    {
      ArtistAppearsInAlbum link = e.ModelElement as ArtistAppearsInAlbum;
      if (link != null)
            ExternalDatabase.Delete(link.Artist.Name, link.Album.Title);
    }
  }
}

使用事件在存储中进行可撤消的调整

存储事件通常不用于传播存储中的更改,因为事件处理程序在提交事务后执行。 相反,将使用存储规则。 有关详细信息,请参阅模型中的规则传播更改

但是,如果希望用户能够独立于原始事件撤消其他更新,可以使用事件处理程序对存储进行其他更新。 例如,假设小写字符是发行集标题的常用约定。 可以编写一个存储事件处理程序,在用户以大写键入标题后将标题更正为小写。 但用户可以使用 Undo 命令取消更正,从而还原大写字符。 第二次 Undo 操作将删除用户的更改。

相反,如果你编写了一个存储规则来执行相同的操作,则用户的更改和你的更正将在同一事务中,这样用户就无法在不丢失原始更改的情况下撤消调整。

partial class MusicLibDocView
{
    // Register store events here or in DocData.OnDocumentLoaded().
    protected override void LoadView()
    {
      /* Register store event handler for Album Title property. */
      // Get reflection data for property:
      DomainPropertyInfo propertyInfo =
        this.DocData.Store.DomainDataDirectory
        .FindDomainProperty(Album.TitleDomainPropertyId);
      // Add to property handler list:
      this.DocData.Store.EventManagerDirectory
        .ElementPropertyChanged.Add(propertyInfo,
        new EventHandler<ElementPropertyChangedEventArgs>
             (AlbumTitleAdjuster));

      /*
      // Alternatively, you can set one handler for
      // all properties of a class.
      // Your handler has to determine which property changed.
      DomainClassInfo classInfo = this.Store.DomainDataDirectory
           .FindDomainClass(typeof(Album));
      this.Store.EventManagerDirectory
          .ElementPropertyChanged.Add(classInfo,
        new EventHandler<ElementPropertyChangedEventArgs>
             (AlbumTitleAdjuster));
       */
      return base.LoadView();
    }

// Undoable adjustment after a property is changed.
// Method can be static since no local access.
private static void AlbumTitleAdjuster(object sender,
         ElementPropertyChangedEventArgs e)
{
  Album album = e.ModelElement as Album;
  Store store = album.Store;

  // We mustn't update the store in an Undo:
  if (store.InUndoRedoOrRollback
      || store.InSerializationTransaction)
      return;

  if (e.DomainProperty.Id == Album.TitleDomainPropertyId)
  {
    string newValue = (string)e.NewValue;
    string lowerCase = newValue.ToLowerInvariant();
    if (!newValue.Equals(lowerCase))
    {
      using (Transaction t = store.TransactionManager
            .BeginTransaction("adjust album title"))
      {
        album.Title = lowerCase;
        t.Commit();
      } // Beware! This could trigger the event again.
    }
  }
  // else other properties of this class.
}

如果编写用于更新存储的事件:

  • 使用 store.InUndoRedoOrRollback 可避免在 Undo 中更改模型元素。 事务管理器会将存储中的所有内容设置回其原始状态。

  • 使用 store.InSerializationTransaction 可避免在从文件加载模型时进行更改。

  • 更改将导致触发更多事件。 请确保避免出现无限循环。

存储事件类型

每个事件类型对应于 Store.EventManagerDirectory 中的一个集合。 你可以随时添加或删除事件处理程序,但通常是在加载文档时添加它们。

EventManagerDirectory 属性名称 执行时间
ElementAdded 创建域类、域关系、形状、连接器或关系图的实例时。
ElementDeleted 模型元素已从存储的元素目录中删除,不再是任何关系的源或目标。 该元素实际上并未从内存中删除,而是保留以备将来执行 Undo 时使用。
ElementEventsBegun 在外部事务结束时调用。
ElementEventsEnded 在处理完所有其他事件后调用。
ElementMoved 模型元素已从一个存储分区移到另一个存储分区。

这与关系图上形状的位置无关。
ElementPropertyChanged 域属性的值已更改。 仅在旧值和新值不相等时,才执行此操作。
RolePlayerChanged 关系的两个角色(端点)之一引用了新元素。
RolePlayerOrderChanged 在多重大于 1 的角色中,链接序列已更改。
TransactionBeginning
TransactionCommitted
TransactionRolledBack

注意

“文本模板转换”组件将作为“Visual Studio 扩展开发”工作负载的一部分自动安装 。 还可以从 Visual Studio 安装程序的“SDK、库和框架”类别下的“单个组件”选项卡进行安装 。 从“单个组件”选项卡安装“建模 SDK”组件 。