共用方式為


HOW TO:回應 UML 模型中的變更

更新:2011 年 3 月

您可以撰寫程式碼,以便在 Visual Studio 的 UML 模型發生變更時執行。 此程式碼將會同樣回應使用者直接做的變更,以及其他 Visual Studio 擴充功能所做的變更。

警告

UML Extension API 不會直接支援這項功能。 因此,您必須使用與傳統稍微不同的技巧。 本主題提供必要的程式碼。 不過在某些情況下,可能會發生未預期的效果。 未來的 Visual Studio UML 實作變更也可能使本主題描述的技巧失效。

若要使用本主題描述的技巧,熟悉用來實作 UML 工具的 Visualization and Modeling SDK (VMSDK) 會有幫助。 如需詳細資訊,請參閱 Visualization and Modeling SDK - 網域指定的語言

本主題內容:

  • 建立 UML 擴充功能專案

  • 事件和規則

  • 定義事件

  • 範例:使用事件,依據造型設定類別的色彩

  • 定義規則

  • 範例:使用規則,依據造型設定類別的色彩

  • 範例:預設為雙向的關聯

  • 核心和檢視模型

建立 UML 擴充功能專案

在許多情況下,您會在已實作命令或軌跡處理常式的擴充功能中加入事件處理常式。 在這種情況下,可以在相同的 Visual Studio 專案中加入此處描述的程式碼。 如需詳細資訊,請參閱HOW TO:在模型圖表上定義功能表命令HOW TO:在模型圖表上定義 Drop 和 Double-Click 處理常式

如果您想要在不同的 Visual Studio 擴充功能中建立事件處理常式,一開始要建立新的 UML 驗證專案。 在 [新增專案] 對話方塊中,按一下 [模型專案],然後選取 [模型驗證擴充功能]。 或者,您可以依照HOW TO:定義 UML 模型的驗證條件約束中的「定義驗證擴充功能」程序進行。

您必須將下列參考加入至專案:

  • \Program Files\Microsoft Visual Studio 10.0\Common7\IDE\PrivateAssemblies\Microsoft.VisualStudio.Uml.dll

  • Microsoft.VisualStudio.ArchitectureTools.Extensibility.dll

  • Microsoft.VisualStudio.Modeling.Sdk.10.0dll

  • Microsoft.VisualStudio.Modeling.Sdk.Diagrams.10.0.dll

  • Microsoft.VisualStudio.Uml.Interfaces.dll

事件和規則

VMSDK 提供兩個偵測存放區變更的主要方法:

  • 在發生變更的交易結束後,事件處理常式會回應該變更。 事件處理常式通常用來將變更傳播至模型外、至使用者介面、檔案或資料庫。 您也可以撰寫事件處理常式,讓它在新交易中對模型進行其他變更。

    規則在發生變更的交易內回應該變更。 規則通常用來在模型內傳播變更,以便維護模型之兩個組件間的一致性。 您也可以撰寫可藉由取消交易來防止無效變更的規則。

如需交易的詳細資訊,請參閱HOW TO:使用交易連結模型更新

定義事件處理常式

若要在發生變更時叫用事件處理常式,您必須註冊事件處理常式。 您必須為要監視的每個項目類別 (例如 UseCase 或 Activity) 註冊處理常式。 您不必為每個執行個體註冊它。

下列範例會根據使用者套用的造型,設定 UML 類別的色彩。 事件處理常式註冊後,會在造型執行個體建立或刪除時觸發。 因為這個範例使用事件處理常式而不是規則,所以在變更造型的交易完成之後會呼叫處理常式。 因為色彩也是 VMSDK 存放區的變更,所以必須在另一個交易中執行色彩變更。

您也可以使用事件處理常式,在存放區外執行變更。

您可以改寫下列範例程式碼,以回應您所選擇的事件。 此程式碼的重點如下:

  • Validation API 用來註冊事件處理常式。 驗證架構提供了一個方便的方法,可以在開啟模型時執行程式碼。 但程式碼實際上不會執行驗證,使用者不必為了執行更新而叫用驗證。

  • 事件處理常式是加入至 Store.EventManagerDirectory 的方法。 這是基礎 VMSDK (DSL) 實作的 Store,而不是 UML ModelStore。 EventManagerDirectory 針對不同的事件型別 (例如 ElementAdded 和 ElementDeleted) 有一組固定的字典。

  • 若要註冊事件,您必須知道要監視的實作類別或關聯性名稱。 這些類別是定義在 Microsoft.VisualStudio.Modeling.Uml.dll 中,當您在偵錯工具中監看屬性時會看到類別名稱。 您可以將這些類別成員轉型為適當的介面型別,例如 IClass、IStereotype。 如需介面型別的清單,請參閱模型項目型別

    未來版本中的實作類別名稱可能不同。

  • 當使用者叫用 [復原] 和 [取消復原] 命令時,都會呼叫事件處理常式。 例如,在 Add 事件之後,[復原] 會引發 Delete 事件。 如果要將變更傳播至存放區外,您的事件處理常式應該回應這些事件。 但它不應該在存放區內變更以回應 [復原] 或 [取消復原],也不應該在從檔案讀取模型時變更。 您可以使用 if (!store.InUndoRedoOrRollback && !store.InSerializationTransaction)...。

  • 此範例示範加入及刪除模型物件的事件處理常式。 您也可以為屬性值的變更來建立事件處理常式。 如需詳細資訊,請參閱事件處理常式傳播模型外的變更

  • 如需事件的詳細資訊,請參閱事件處理常式傳播模型外的變更

範例:使用事件,依據造型設定類別的色彩

在這個範例中,您也必須將專案參考加入至 System.Drawing.dll

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Drawing;
using System.Linq;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Presentation;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Validation;
using Microsoft.VisualStudio.Uml.AuxiliaryConstructs;
using Microsoft.VisualStudio.Uml.Classes;
using Microsoft.VisualStudio.Uml.Profiles;
using Microsoft.VisualStudio.Uml.UseCases;

using Microsoft.VisualStudio.Uml.ModelStore; // in private assembly. Used for Get|IsElementDefinition()

namespace UmlEvents  // <<<< EDIT
{
/// <summary>
/// Wraps a UML model to add stereotype coloring.
/// </summary>
public partial class ColoringModelAdapter
{
  // This is the underlying DSL store, not the wrapping UML ModelStore:
  private Store store;

  /// <summary>
  /// This isn't actually validation. It's to couple this adapter to the model before we start.
  /// The validation package creates an instance of this class and then calls this method.
  /// See "Validation": https://msdn.microsoft.com/library/bb126413.aspx
  /// </summary>
  /// <param name="vcontext"></param>
  /// <param name="model"></param>
  [Export(typeof(System.Action<ValidationContext, object>))]
  [ValidationMethod(ValidationCategories.Open)]
  public void ConnectAdapterToModel(ValidationContext vcontext, IModel model)
  {
    // This is the underlying DSL store, not the wrapping UML ModelStore:
    store = (model as ModelElement).Store;

    // Add an event that triggers on creating a stereotype instance.
    // See "Event handlers": https://msdn.microsoft.com/library/bb126250.aspx
    DomainClassInfo siClass = store.DomainDataDirectory.FindDomainClass
      ("Microsoft.VisualStudio.Uml.Classes.StereotypeInstance");
    store.EventManagerDirectory.ElementAdded.Add(siClass,
      new EventHandler<ElementAddedEventArgs>(StereotypeInstanceAdded));

    // For the deletion, we need to trigger from the deleted link
    // between the stereotype instance and the model element - 
    // because after deletion we can't find the element from the stereotype instance.
    DomainRelationshipInfo linkToStereotypeClass = store.DomainDataDirectory.FindDomainRelationship
      ("Microsoft.VisualStudio.Uml.Classes.ElementHasAppliedStereotypeInstances");
    store.EventManagerDirectory.ElementDeleted.Add(linkToStereotypeClass,
      new EventHandler<ElementDeletedEventArgs>(StereotypeInstanceDeleted));

    // Add here handlers for other events.
  }

  /// <summary>
  /// Event handler called whenever a stereotype instance is linked to a uml model element.
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  private void StereotypeInstanceAdded(object sender, ElementAddedEventArgs e)
  {
    // Don't handle changes in undo or load from file:
    if (store.InUndoRedoOrRollback || store.InSerializationTransaction) return;

    IStereotypeInstance si = e.ModelElement as IStereotypeInstance;
    IElement umlElement = si.Element;

     // Work only with the core model, not the views:
     if (!umlElement.IsElementDefinition()) return;

    // I'm only interested in coloring classes and interfaces:
    if (!(umlElement is IType)) return;

    Color? color = ColorForStereotype(si.Name);
    if (color.HasValue)
    {
      SetColorOfShapes(si.Element, color.Value);
    }
  }

  /// <summary>
  /// Called whenever a stereotype instance is deleted - well, actually, 
  /// when the link between the stereotype instance and the uml model element is deleted.
  /// Triggering on the link deletion allows us to get both ends.
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  private void StereotypeInstanceDeleted(object sender, ElementDeletedEventArgs e)
  {
    // Don't handle changes in undo or load from file:
    if (store.InUndoRedoOrRollback || store.InSerializationTransaction) return;

    // Use the generic link type to avoid unearthing the UML implementation DLL:
    ElementLink elementToStereotypeLink = e.ModelElement as ElementLink;
    IElement umlElement = elementToStereotypeLink.LinkedElements[0] as IElement;
    IStereotypeInstance si = elementToStereotypeLink.LinkedElements[1] as IStereotypeInstance;

     // Work only with the core model, not the views:
     if (!umlElement.IsElementDefinition()) return;

    // We're here either because a stereotype is being un-applied,
    // or because the uml element is being deleted.
    // Don't bother if the element is being deleted:
    if ((umlElement as ModelElement).IsDeleting) return;

    // We're only interested in classes and interfaces:
    if (!(umlElement is IType)) return;

    // Because more than one stereotype can be applied to an element,
    // we should check to see if there are any remaining:
    Color newColor = Color.WhiteSmoke; // Default if there aren't.
    foreach (IStereotypeInstance remainingSi in umlElement.AppliedStereotypes)
    {
      Color? color = ColorForStereotype(remainingSi.Name);
      if (color.HasValue)
      {
        newColor = color.Value;
        break;
      }
    }
    SetColorOfShapes(umlElement, newColor);
  }

  private void SetColorOfShapes(IElement element, Color color)
  {
    foreach (IShape shape in element.Shapes())
    {
      shape.Color = color;
    }
  }

  /// <summary>
  /// This example deals only with a subset of the standard stereotypes.
  /// </summary>
  private Color? ColorForStereotype(string name)
  {
    switch (name)
    {
      case "focus": return Color.AliceBlue;
      case "auxiliary": return Color.Bisque;
      case "specification": return Color.OliveDrab;
      case "realization": return Color.LightSeaGreen;
      case "implementationClass": return Color.PaleGoldenrod;
    }
    return null;
  } 
}}

定義規則

您可以定義規則,在 VMSDK 存放區內傳播變更。 觸發變更和規則都是在相同的交易中執行。 當使用者叫用 [復原] 時,這兩項變更會一起復原。

上述範例的缺點是範例使用事件處理常式來變更圖案色彩。 色彩也會附加至 VMSDK 存放區的欄位,因此必須在交易中執行。 因此,如果使用者在套用造型之後叫用 [復原] 命令,則會復原色彩變更,但造型保持已套用狀態。 需要另一個 [復原],才能還原造型應用。 在某些情況下,這可能是預期的效果,否則您可以藉由定義規則,將這些變更全部在一個交易內傳播。

規則比較不適合將變更傳播至存放區外,因為當使用者執行 [復原] 或 [取消復原] 命令時不會叫用規則。

規則是在規則管理員中註冊的類別。 通常當您撰寫 VMSDK 程式碼時,可藉由將屬性附加至類別,並將類別包含在載入擴充功能程式碼時所載入的清單中,來註冊規則。 但因為 UML 實作已編譯,所以您必須動態將規則加入至規則管理員。 範例中提供的程式碼強烈相依於目前的實作規則管理,在未來版本中可能會變更。

若要加入規則,您必須知道實作類別的名稱。 這些名稱在未來版本中可能會變更。 您應該盡可能將項目轉型為 UML API 型別,例如 IClass、IProfile。

此範例示範處理 UML 模型物件加入及刪除的規則。 您也可以建立可回應物件屬性變更的規則。 如需詳細資訊,請參閱規則傳播模型內的變更

範例:使用規則,依據造型設定類別的色彩

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Drawing;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Presentation;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Validation;
using Microsoft.VisualStudio.Uml.AuxiliaryConstructs;
using Microsoft.VisualStudio.Uml.Classes;
using Microsoft.VisualStudio.Uml.UseCases; 

using Microsoft.VisualStudio.Uml.ModelStore; // in private assembly. Used for Get|IsElementDefinition()


namespace UmlRules
{
  class ColorByStereotype
  {
    /// <summary>
    /// Singleton wrappers: one per model.
    /// </summary>
    private static Dictionary<IPackage, ColorByStereotype > modelAdapters = 
        new Dictionary<IPackage, ColorByStereotype >();

    private class Wrapper
    {
      /// <summary>
      /// This isn't actually validation. 
      /// It sets up some store rules.
      /// The validation package creates an instance of this class and then calls this method.
      /// See "Validation": https://msdn.microsoft.com/library/bb126413.aspx
      /// </summary>
      /// <param name="vcontext"></param>
      /// <param name="model"></param>
      [Export(typeof(System.Action<ValidationContext, object>))]
      [ValidationMethod(ValidationCategories.Open)]
      private void ConnectAdapterToModel(ValidationContext vcontext, IModel model)
      {
        modelAdapters.Add(model, new ColorByStereotype (model));
      }
    }

    private IModel model;
    private Store store;
    private ColorByStereotype (IModel model)
    {
      this.model = model;
      // This is the underlying DSL store, not the wrapping UML ModelStore:
      store = (model as ModelElement).Store;

      SetRule<StereotypeInstanceAddedRule>(
        store.DomainDataDirectory.FindDomainClass(
          "Microsoft.VisualStudio.Uml.Classes.StereotypeInstance"));
      
      // For the deletion, we need to trigger from the deleted link
      // between the stereotype instance and the model element - 
      // because after deletion we can't find the element from the stereotype instance.
      
      SetRule<StereotypeInstanceDeletedRule>(
        store.DomainDataDirectory.FindDomainRelationship(
        "Microsoft.VisualStudio.Uml.Classes.ElementHasAppliedStereotypeInstances"));
    }

    /// <summary>
    /// Register a rule. 
    /// Normally, you set a rule by prefixing the rule class with 
    /// [RuleOn(typeof(TargetClass))]
    /// but we are setting up the rule at runtime, so must add
    /// the rules to the relevant dictionaries.
    /// </summary>
    /// <typeparam name="T">Rule class</typeparam>
    /// <param name="classInfo">Class or relationship to which to attach the rule.</param>
    private void SetRule<T>(DomainClassInfo classInfo) where T : Rule, new()
    {
      T rule = new T();
      rule.FireTime = TimeToFire.TopLevelCommit;

      System.Type tt = typeof(T);
      string ruleSet = (typeof(AddRule).IsAssignableFrom(tt)) ? "AddRules" :
        (typeof(ChangeRule).IsAssignableFrom(tt)) ? "ChangeRules" :
        (typeof(DeleteRule).IsAssignableFrom(tt)) ? "DeleteRules" :
        (typeof(DeletingRule).IsAssignableFrom(tt)) ? "DeletingRules" : "";

      // The rest of this method uses reflection to achieve the following:
      // store.RuleManager.RegisterRule(rule);
      // classInfo.AddRules.Add(rule);

      System.Reflection.BindingFlags privateBinding = 
          System.Reflection.BindingFlags.Instance 
        | System.Reflection.BindingFlags.NonPublic;
      System.Reflection.MethodInfo mi = 
        typeof(RuleManager).GetMethod("RegisterRule", privateBinding);
      mi.Invoke(store.RuleManager, new object[] { rule });

      store.RuleManager.EnableRule(typeof(T));

      System.Reflection.PropertyInfo pi = 
        typeof(DomainClassInfo).GetProperty(ruleSet, privateBinding);
      dynamic rules = pi.GetValue(classInfo, null);
      System.Type ruleListType = rules.GetType();
      System.Reflection.FieldInfo listpi = 
        ruleListType.GetField("list", privateBinding);
      dynamic list = listpi.GetValue(rules);
      System.Type listType = list.GetType();
      System.Reflection.MethodInfo addmi = listType.GetMethod("Add");
      addmi.Invoke(list, new object[] { rule });


      System.Reflection.MethodInfo resetRulesCache = 
        typeof(DomainClassInfo).GetMethod("ResetRulesCache", privateBinding);
      resetRulesCache.Invoke(classInfo, null);

    }

    #region Rules.
    private class StereotypeInstanceAddedRule : AddRule
    {
      public override void ElementAdded(ElementAddedEventArgs e)
      {
        base.ElementAdded(e);
        Store store = e.ModelElement.Store;
        // Don't handle load from file:
        if (store.InSerializationTransaction)
          return;

        IStereotypeInstance si = e.ModelElement as IStereotypeInstance;
        IElement umlElement = si.Element;
        
         // Work only with the core model, not the views:
         if (!umlElement.IsElementDefinition()) return;

        // I'm only interested in coloring classes and interfaces:
        if (!(umlElement is IType)) return;

        Color? color = ColorForStereotype(si.Name);
        if (color.HasValue)
        {
          SetColorOfShapes(si.Element, color.Value);
        }
      }
    }
    private class StereotypeInstanceDeletedRule : DeleteRule
    {
      public override void ElementDeleted(ElementDeletedEventArgs e)
      {
        base.ElementDeleted(e);
        Store store = e.ModelElement.Store;


        // Use the generic link type to avoid using the UML implementation DLL:
        ElementLink elementToStereotypeLink = e.ModelElement as ElementLink;
        IElement umlElement = elementToStereotypeLink.LinkedElements[0] as IElement;

         // Work only with the core model, not the views:
         if (!umlElement.IsElementDefinition()) return;

        // We're here either because a stereotype is being un-applied,
        // or because the uml element is being deleted.
        // Don't bother if the element is being deleted:
        if ((umlElement as ModelElement).IsDeleting) return;

        // We're only interested in classes and interfaces:
        if (!(umlElement is IType)) return;

        // Because more than one stereotype can be applied to an element,
        // we should check to see if there are any remaining:
        Color newColor = Color.WhiteSmoke; // Default if there aren't.
        foreach (IStereotypeInstance remainingSi in umlElement.AppliedStereotypes)
        {
          Color? color = ColorForStereotype(remainingSi.Name);
          if (color.HasValue)
          {
            newColor = color.Value;
            break;
          }
        }
        SetColorOfShapes(umlElement, newColor);
      }
    }

    /// <summary>
    /// Set the color of the shapes that display an element.
    /// </summary>
    /// <param name="element"></param>
    /// <param name="color"></param>
    private static void SetColorOfShapes(IElement element, Color color)
    {
      foreach (IShape shape in element.Shapes())
      {
        shape.Color = color;
      }
    }

    /// <summary>
    /// For this sample, we just deal with some of the standard stereotypes.
    /// </summary>
    /// <param name="name">Stereotype name</param>
    /// <returns></returns>
    private static Color? ColorForStereotype(string name)
    {
      switch (name)
      {
        case "focus": return Color.AliceBlue;
        case "auxiliary": return Color.Bisque;
        case "specification": return Color.OliveDrab;
        case "realization": return Color.LightSeaGreen;
        case "implementationClass": return Color.PaleGoldenrod;
      }
      return null;
    }
    #endregion
  }
}

範例:預設為雙向的關聯

根據預設,當您在類別圖表中繪製關聯時,只能以某一方向巡覽新關聯。 箭頭指向某一端。 基於某些用途,繪製沒有箭頭的雙向關聯會比較便利。 您可以藉由加入下列規則,讓雙向關聯成為預設。

/// <summary>
/// Rule invoked when an Association is created.
/// This rule sets both ends navigable, which is convenient for representing requirements.
/// </summary>
private class AssociationAddRule : AddRule
{
  public override void ElementAdded(ElementAddedEventArgs e)
  {
    Store store = e.ModelElement.Store;
    IAssociation association = e.ModelElement as IAssociation;

    // Do not apply the rule if we are reading from file or undoing a deletion:
    if (association.MemberEnds.Count() == 0 
       || store.InSerializationTransaction || store.InUndoRedoOrRollback) return;

    // Do not apply the rule unless a containing package or model has imported 
    // a profile that defines the stereotype "Default Binary Associations" for associations:
    // if (!association.ApplicableStereotypes.Any
    //      (s => s.DisplayName == "Default Binary Associations")) return;

    // Don’t apply the rule to use cases:
    if (!(association.SourceElement is IUseCase && association.TargetElement is IUseCase))
    {
      association.OwnedEnds.First().SetNavigable(true);
      association.OwnedEnds.Last().SetNavigable(true);
    }
  }
}

若要註冊規則,您必須使用定義規則所描述的 SetRule 方法:

SetRule<AssociationAddRule>(store.DomainDataDirectory.
      FindDomainRelationship("Microsoft.VisualStudio.Uml.Classes.Association"));

如果您想要能夠啟用或停用這個規則,一個做法是定義設定檔 (其中定義某個造型)。 您可以在規則中加入程式碼,以驗證包含的封裝或模型中已啟用此設定檔。 如需詳細資訊,請參閱HOW TO:定義要擴充 UML 的設定檔

核心和檢視模型

UML 模型由多個 VMSDK (DSL) 模型所組成:

  • 核心模型包含所有 UML 模型項目的表示。 使用者可以在 [UML 模型總管] 視窗中看到這些項目,而且您可以透過 UML ModelStore API 存取它們。 核心模型佔用一個 VMSDK 存放區分割。

  • UML 專案中的每個 UML 圖表都有一個檢視模型。 每個檢視模型中的物件是核心模型物件的 Proxy。 UML 圖表上的每個顯示項目都有一個檢視模型物件。 每個檢視模型佔用一個 VMSDK 存放區分割。

  • 圖表上的每個顯示項目都有一個 VMSDK 圖案物件。 檢視模型項目和圖案之間有一對一關聯性。

當您定義規則或事件處理常式時,在核心和檢視物件變更時都會呼叫它們。 您應該只處理核心物件的變更。 範例中的處理常式使用 element.IsElementDefinition(),以判斷是否正在處理核心物件。

若要使用這個方法,您必須將專案參考加入至:

%ProgramFiles%\Microsoft Visual Studio 10.0\Common7\IDE\PrivateAssemblies\Microsoft.VisualStudio.Uml.dll

警告

IsElementDefinition 和私用組件中定義的其他方法在未來版本中可能會變更。

using Microsoft.VisualStudio.Uml.ModelStore; 
  // in private assembly. Used for GetElementDefinition()
...
  // Determine whether an element is view or core:
  if (anElement.IsElementDefinition()) 
  { /* core */ }
  else
  { /* view */ }
...
  // If shapeElement is a shape on a diagram -
  // The VMSDK element connected to the shape is in the view:
  IElement viewModelElement = shapeElement.ModelElement as IElement;
  // Get the core element for which the view is a proxy:
  IElement coreModelElement = viewModelElement.GetElementDefinition();
...

請參閱

工作

事件處理常式傳播模型外的變更

其他資源

HOW TO:巡覽 UML 模型

範例:依造型加上色彩

變更記錄

日期

記錄

原因

2011 年 3 月

建立主題。

客戶回函。