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

在可视化和建模 SDK 中,可以定义存储事件处理程序以将更改传播到存储外部的资源,例如非存储变量、文件、其他存储中的模型或其他 Visual Studio 扩展。 存储事件处理程序在发生触发事件的事务结束时执行。 它们也会在撤消或重做操作中执行。 因此,与存储规则不同,存储事件最适用于更新存储区外部的值。 与 .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);
    }
  }
}

使用事件在应用商店中进行可撤消的调整

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

但是,如果希望用户能够独立于原始事件撤消其他更新,则可以使用事件处理程序对存储进行其他更新。 例如,假设小写字符是专辑标题的常规约定。 可以编写一个存储事件处理程序,该处理程序在用户用大写输入标题后将其更正为小写。 但用户可以使用“撤消”命令取消更正,还原大写字符。 第二个撤销操作将删除用户所做的更改。

相比之下,如果你编写了一个存储规则来执行相同的操作,用户的更改和你的更正将位于同一交易中,这样用户无法撤消调整,否则会丢失原始更改。

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 避免对撤消中的模型元素进行更改。 事务管理器会将存储中的所有内容重置为其原始状态。

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

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

存储事件类型

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

EventManagerDirectory 属性名称 当...时执行
ElementAdded 创建域类、域关系、形状、连接线或关系图的实例。
元素已删除 模型元素已从存储的元素目录中删除,不再是任何关系的源或目标。 该元素实际上不会从内存中删除,但在将来撤消时会保留该元素。
ElementEventsBegun 在外部事务处理的末尾调用。
元素事件结束 在处理完所有其他事件后调用。
ElementMoved 模型元素已从一个存储分区移动到另一个存储分区。

这与图表上形状的位置无关。
ElementPropertyChanged 域属性的值已更改。 仅当旧值和新值不相等时才执行。
角色玩家变更 关系的两个角色(端)之一引用了一个新元素。
角色玩家顺序已更改 在多重性大于 1 的角色中,链接的顺序已更改。
事务开始
事务已提交
TransactionRolledBack

注释

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