如何:在建模图上定义放置和双击处理程序

在 Visual Studio 旗舰版中,可以定义当用户双击项或将项拖动到 UML 关系图上时将执行的命令。 可以将这些扩展打包到 Visual Studio 集成扩展 (VSIX) 中,并将其分发给其他 Visual Studio 旗舰版用户。

如果已存在针对要拖动的关系图类型和元素类型的内置行为,则可能无法添加或重写此行为。

要求

创建笔势处理程序

若要为 UML 设计器定义一个笔势处理程序,必须创建一个定义该笔势处理程序的行为的类,并将此类嵌入到 Visual Studio 集成扩展 (VSIX) 中。 VSIX 充当可安装处理程序的容器。 以下是定义笔势处理程序的两种替换方法:

  • **使用项目模板在笔势处理程序自己的 VSIX 中创建它。**这是一种快速方法。 如果您不希望将处理程序与其他类型的扩展(如验证扩展、自定义工具箱项或菜单命令)合并,则可以使用此方法。

  • **创建单独的笔势处理程序和 VSIX 项目。**如果将多个类型的扩展并入同一个 VSIX 中,则请使用此方法。 例如,如果笔势处理程序需要模型遵守特定约束,则可以将笔势处理程序嵌入到验证方法所在的 VSIX 中。

在笔势处理程序自己的 VSIX 中创建它

  1. 在**“新建项目”对话框中的“建模项目”下,选择“笔势扩展”**。

  2. 在新项目中打开 .cs 文件,并修改 GestureExtension 类来实现您的笔势处理程序。

    有关更多信息,请参见实现笔势处理程序。

  3. 按 F5 测试笔势处理程序。 有关更多信息,请参见执行笔势处理程序。

  4. 通过复制您的项目所生成的 bin\*\*.vsix 文件,将笔试处理程序安装到其他计算机上。 有关更多信息,请参见安装笔势处理程序。

为笔势处理程序创建类库 (DLL) 项目

  1. 在新的 Visual Studio 解决方案或现有解决方案中创建类库项目。

    1. 在**“文件”菜单上指向“新建”,然后单击“项目”**。

    2. 在**“已安装的模板”下,单击“Visual C#”“Visual Basic”,然后在中间列中单击“类库”**。

    3. 设置**“解决方案”**以指示您是希望创建新的解决方案,还是希望向已打开的 VSIX 解决方案添加组件。

    4. 设置项目的名称和位置,然后单击“确定”。

  2. 将下列引用添加到项目中。

    Microsoft.VisualStudio.Modeling.Sdk.10.0

    Microsoft.VisualStudio.Modeling.Sdk.Diagrams.10.0

    Microsoft.VisualStudio.ArchitectureTools.Extensibility

    Microsoft.VisualStudio.Uml.Interfaces

    System.ComponentModel.Composition

    System.Windows.Forms

    Microsoft.VisualStudio.ArchitectureTools.Extensibility.Layer – 只在扩展层关系图时才需要。 有关更多信息,请参见创建层关系图的扩展

  3. 将类文件添加到项目中,并将其内容设置为下面的代码。

    提示

    根据您的喜好更改命名空间和类名。

    using System.ComponentModel.Composition;
    using System.Linq;
    using System.Collections.Generic;
    using Microsoft.VisualStudio.Modeling.Diagrams;
    using Microsoft.VisualStudio.Modeling.Diagrams.ExtensionEnablement;
    using Microsoft.VisualStudio.Modeling.ExtensionEnablement;
    using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml;
    using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Presentation;
    using Microsoft.VisualStudio.Uml.AuxiliaryConstructs;
    using Microsoft.VisualStudio.Modeling;
    using Microsoft.VisualStudio.Uml.Classes;
    // ADD other UML namespaces if required
    
    namespace MyGestureHandler // CHANGE
    {
      // DELETE any of these attributes if the handler
      // should not work with some types of diagram.
      [ClassDesignerExtension]
      [ActivityDesignerExtension]
      [ComponentDesignerExtension]
      [SequenceDesignerExtension]
      [UseCaseDesignerExtension]
      // [LayerDesignerExtension]
    
      // Gesture handlers must export IGestureExtension:
      [Export(typeof(IGestureExtension))]
      // CHANGE class name
      public class MyGesture1 : IGestureExtension
      {
        [Import]
        public IDiagramContext DiagramContext { get; set; }
    
        /// <summary>
        /// Called when the user double-clicks on the diagram
        /// </summary>
        /// <param name="targetElement"></param>
        /// <param name="diagramPointEventArgs"></param>
        public void OnDoubleClick(ShapeElement targetElement, DiagramPointEventArgs diagramPointEventArgs)
        {
          // CHANGE THIS CODE FOR YOUR APPLICATION.
    
          // Get the target shape, if any. Null if the target is the diagram.
          IShape targetIShape = targetElement.CreateIShape();
    
          // Do something...
        }
    
        /// <summary>
        /// Called repeatedly when the user drags from anywhere on the screen.
        /// Return value should indicate whether a drop here is allowed.
        /// </summary>
        /// <param name="targetMergeElement">References the element to be dropped on.</param>
        /// <param name="diagramDragEventArgs">References the element to be dropped.</param>
        /// <returns></returns>
        public bool CanDragDrop(ShapeElement targetMergeElement, DiagramDragEventArgs diagramDragEventArgs)
        {
          // CHANGE THIS CODE FOR YOUR APPLICATION.
    
          // Get the target element, if any. Null if the target is the diagram.
          IShape targetIShape = targetMergeElement.CreateIShape();
    
          // This example allows drag of any UML elements.
          return GetModelElementsFromDragEvent(diagramDragEventArgs).Count() > 0;
        }
    
    
        /// <summary>
        /// Execute the action to be performed on the drop.
        /// </summary>
        /// <param name="targetDropElement"></param>
        /// <param name="diagramDragEventArgs"></param>
        public void OnDragDrop(ShapeElement targetDropElement, DiagramDragEventArgs diagramDragEventArgs)
        {
          // CHANGE THIS CODE FOR YOUR APPLICATION.
        }
    
        /// <summary>
        /// Retrieves UML IElements from drag arguments.
        /// Works for drags from UML diagrams.
        /// </summary>
        private IEnumerable<IElement> GetModelElementsFromDragEvent
                (DiagramDragEventArgs dragEvent)
        {
          //ElementGroupPrototype is the container for
          //dragged and copied elements and toolbox items.
          ElementGroupPrototype prototype =
             dragEvent.Data.
             GetData(typeof(ElementGroupPrototype))
                  as ElementGroupPrototype;
          // Locate the originals in the implementation store.
          IElementDirectory implementationDirectory =
             dragEvent.DiagramClientView.Diagram.Store.ElementDirectory;
    
          return prototype.ProtoElements.Select(
            prototypeElement =>
            {
              ModelElement element = implementationDirectory
                .FindElement(prototypeElement.ElementId);
              ShapeElement shapeElement = element as ShapeElement;
              if (shapeElement != null)
              {
                // Dragged from a diagram.
                return shapeElement.ModelElement as IElement;
              }
              else
              {
                // Dragged from UML Model Explorer.
                return element as IElement;
              }
            });
        }
    
      }
    }
    

    有关要在方法中放置的内容的更多信息,请参见实现笔势处理程序。

您必须将菜单命令添加到一个充当命令安装容器的 VSIX 项目中。 如果需要,可以将其他组件包括在同一 VSIX 中。

将单独的笔势处理程序添加到 VSIX 项目

  1. 如果您已使用菜单命令自己的 VSIX 创建了它,则不需要此过程。

  2. 创建一个 VSIX 项目(如果解决方案中已有一个 VSIX 项目,则无需执行此步骤)。

    1. 在**“解决方案资源管理器”中,右击该解决方案,指向“添加”,然后单击“新建项目”**。

    2. 在**“已安装的模板”下,展开“Visual C#”“Visual Basic”,然后单击“扩展性”。 在中间列中,单击“VSIX 项目”**。

  3. 将 VSIX 项目设置为解决方案的启动项目。

    • 在解决方案资源管理器中,右击该 VSIX 项目,然后单击**“设为启动项目”**。
  4. source.extension.vsixmanifest 中的**“内容”**下,将笔势处理程序类库项目添加为 MEF 组件。

    1. 打开 source.extension.vsixmanifest

    2. 单击**“添加内容”**。

    3. 在**“选择内容类型”处,选择“MEF 组件”**。

    4. 在**“选择源”处,单击“项目”**,并选择笔势处理程序类库项目的名称。

  5. 单击**“选择版本”**,并选择要运行扩展的 Visual Studio 版本。

  6. 设置 VSIX 的名称字段和描述性字段。 保存该文件。

执行笔势处理程序

仅为测试目的在调试模式下执行笔势处理程序。

测试笔势处理程序

  1. F5,或在**“调试”菜单上单击“开始调试”**。

    这将启动 Visual Studio 的实验实例。

    疑难解答:如果新的 Visual Studio 未启动:

    • 如果您有多个项目,请确保将 VSIX 项目设置为解决方案的启动项目。

    • 在解决方案资源管理器中,右击启动项目,然后单击“属性”。 在项目属性编辑器中,单击**“调试”选项卡。 确保“启动外部程序”**字段中的字符串是 Visual Studio 的完整路径名,通常为:

      C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\devenv.exe

  2. 在实验性 Visual Studio 中,打开或创建一个建模项目,然后打开或创建一个建模图。 使用属于笔势处理程序类的特性中列出的某个类型的关系图。

  3. 在关系图上的任意位置双击。 应调用您的双击处理程序。

  4. 将一个元素从 UML 资源管理器拖动到该关系图上。 应调用您的拖动处理程序。

疑难解答:如果笔势处理程序未运行,请确保:

  • 该笔势处理程序项目作为一个 MEF 组件在 VSIX 项目的 source.extensions.manifest 中的**“内容”**列表中列出。

  • 所有 Import 和 Export 特性的参数都有效。

  • CanDragDrop 方法未返回 false。

  • 正在使用的模型关系图的类型(UML 类、序列等)作为笔势处理程序类特性(如 [ClassDesignerExtension]、[SequenceDesignerExtension] 等)之一列出。

  • 尚未为此类型的目标和放置的元素定义内置功能。

实现笔势处理程序

笔势处理程序方法

笔势处理程序类实现并导出 IGestureExtension。 以下是您需要定义的方法:

bool CanDragDrop (ShapeElement target, DiagramDragEventArgs dragEvent)

返回 true 将允许在此目标上放置在 dragEvent 中引用的源元素。

此方法不应更改模型。 由于此方法是用于在用户移动鼠标时确定箭头状态,因此它应快速工作。

void OnDragDrop (ShapeElement target, DiagramDragEventArgs dragEvent)

基于目标和在 dragEvent 中引用的源对象来更新模型。

当用户在拖动后释放鼠标按键时调用。

void OnDoubleClick (ShapeElement target, DiagramPointEventArgs pointEvent)

target 是用户双击的形状。

您可以编写可接受 UML 以及各种其他项(如文件、.NET 类视图中的节点、体系结构资源管理器节点等)的处理程序。 用户可将这些项中的任意项拖至 UML 关系图,前提是编写了可对这些项的序列化格式进行解码的 OnDragDrop 方法。 解码方法因项类型而异。

这些方法的参数为:

  • ShapeElement target. 用户已将某项拖至其上的形状或关系图。

    ShapeElement 是作为 UML 建模工具的基础的实现中的一个类。 为降低使 UML 模型和关系图处于不一致状态的风险,建议您不要直接使用此类的方法。 而是在 IShape 中包装元素,然后使用如何:在关系图上显示模型中所述的方法。

    • 获取 IShape:

      IShape targetIShape = target.CreateIShape(target);
      
    • 获取拖动或双击操作的目标模型元素:

      IElement target = targetIShape.Element;
      

      可将此元素强制转换为一个更明确的元素类型。

    • 获取包含 UML 模型的 UML 模型存储区:

      IModelStore modelStore = 
        targetIShape.Element.GetModelStore(); 
      
    • 获取对宿主和服务提供程序的访问权限:

      target.Store.GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE
      
  • DiagramDragEventArgs eventArgs. 此参数传送拖动操作的序列化格式的源对象:

    System.Windows.Forms.IDataObject data = eventArgs.Data;  
    

    可以从 Visual Studio 的不同部分或从 Windows 桌面将许多不同类型的元素拖动到关系图上。 在 IDataObject 中按照不同的方式对不同类型的元素进行编码。 若要从其中提取元素,请参考适当类型对象的文档。

    如果您的源对象是从 UML 模型资源管理器或其他 UML 关系图中拖动的 UML 元素,请参考如何:从 IDataObject 获取 UML 模型元素

编写方法的代码

有关编写代码以读取和更新模型的更多信息,请参见使用 UML API 编程

有关在拖动操作中访问模型信息的信息,请参见如何:从 IDataObject 获取 UML 模型元素

如果您处理的是序列图,请参见如何:使用 UML API 编辑序列图

除了方法参数外,您还可以在提供对当前关系图和模型访问的类中声明一个导入属性。

[Import] public IDiagramContext DiagramContext { get; set; }

IDiagramContext 的声明允许您在方法中编写可访问关系图、当前选定内容和模型的代码:

IDiagram diagram = this.DiagramContext.CurrentDiagram;
foreach (IShape<IElement> shape in diagram.GetSelectedShapes<IElement>)
{ IElement element = shape.Element; ... }
IModelStore modelStore = diagram.ModelStore;
IModel model = modelStore.Root;
foreach (IDiagram diagram in modelStore.Diagrams) {...}
foreach (IElement element in modelStore.AllInstances<IUseCase>) {...}

有关更多信息,请参见如何:导航 UML 模型

安装和卸载扩展

既可以在您自己的计算机上也可以在其他计算机上安装 Visual Studio 扩展。

安装扩展

  1. 在您的计算机中,查找由 VSIX 项目生成的 .vsix 文件。

    1. 在**“解决方案资源管理器”中,右击 VSIX 项目,然后单击“在 Windows 资源管理器中打开文件夹”**。

    2. 找到文件 bin\*\您的项目.vsix

  2. .vsix 文件复制到要安装扩展的目标计算机上。 该计算机可为您自己的计算机或其他计算机。

    目标计算机必须装有 source.extension.vsixmanifest 中指定的 Visual Studio 版本之一。

  3. 在目标计算机上,双击 .vsix 文件。

    **“Visual Studio Extension Installer”**将会打开并安装扩展。

  4. 启动或重新启动 Visual Studio。

卸载扩展

  1. 在**“工具”菜单上,单击“扩展管理器”**。

  2. 展开**“已安装的扩展”**。

  3. 选择扩展,然后单击**“卸载”**。

在极少数情况下,有错误的扩展无法加载并在错误窗口中创建报告,但不显示在扩展管理器中。 在这种情况下,可以通过从以下位置删除文件来移除扩展:

%LocalAppData%\Local\Microsoft\VisualStudio\10.0\Extensions

示例

下面的示例演示如何在序列图中基于从组件图中拖动的组件的部件和端口来创建生命线。

若要对其进行测试,请按 F5。 这将打开一个 Visual Studio 实验实例。 在此实例中,打开 UML 模型并在组件图上创建一个组件。 向该组件添加一些接口和内部组件部件。 选择接口和部件。 然后,将接口和部件拖动到序列图上。 (从组件图向上拖动到序列图的选项卡,然后向下拖动到序列图中。)将为每个接口和部件显示一条生命线。

有关将交互绑定到序列图的更多信息,请参见如何:使用 UML API 编辑序列图

using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Diagrams.ExtensionEnablement;
using Microsoft.VisualStudio.Modeling.ExtensionEnablement;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Presentation;
using Microsoft.VisualStudio.Uml.AuxiliaryConstructs;
using Microsoft.VisualStudio.Uml.Classes;
using Microsoft.VisualStudio.Uml.Interactions;
using Microsoft.VisualStudio.Uml.CompositeStructures;
using Microsoft.VisualStudio.Uml.Components;

/// <summary>
/// Creates lifelines from component ports and parts.
/// </summary>
[Export(typeof(IGestureExtension))]
[SequenceDesignerExtension]
public class CreateLifelinesFromComponentParts : IGestureExtension
{
  [Import]
  public IDiagramContext Context { get; set; }

  /// <summary>
  /// Called by the modeling framework when
  /// the user drops something on a target.
  /// </summary>
  /// <param name="target">The target shape or diagram </param>
  /// <param name="dragEvent">The item being dragged</param>
  public void OnDragDrop(ShapeElement target,
           DiagramDragEventArgs dragEvent)
  {
    ISequenceDiagram diagram = Context.CurrentDiagram
            as ISequenceDiagram;
    IInteraction interaction = diagram.Interaction;
    if (interaction == null)
    {
      // Sequence diagram is empty: create an interaction.
      interaction = diagram.ModelStore.Root.CreateInteraction();
      interaction.Name = Context.CurrentDiagram.Name;
      diagram.Bind(interaction);
    }
    foreach (IConnectableElement connectable in
       GetConnectablesFromDrag(dragEvent))
    {
      ILifeline lifeline = interaction.CreateLifeline();
      lifeline.Represents = connectable;
      lifeline.Name = connectable.Name;
    }
  }

  /// <summary>
  /// Called by the modeling framework to determine whether
  /// the user can drop something on a target.
  /// Must not change anything.
  /// </summary>
  /// <param name="target">The target shape or diagram</param>
  /// <param name="dragEvent">The item being dragged</param>
  /// <returns>true if this item can be dropped on this target</returns>
  public bool CanDragDrop(ShapeElement target,
           DiagramDragEventArgs dragEvent)
  {
    IEnumerable<IConnectableElement> connectables = GetConnectablesFromDrag(dragEvent);
    return connectables.Count() > 0;
  }

  ///<summary>
  /// Get dragged parts and ports of an IComponent.
  ///</summary>
  private IEnumerable<IConnectableElement>
    GetConnectablesFromDrag(DiagramDragEventArgs dragEvent)
  {
    foreach (IElement element in
      GetModelElementsFromDragEvent(dragEvent))
    {
      IConnectableElement part = element as IConnectableElement;
      if (part != null)
      {
        yield return part;
      }
    }
  }

  /// <summary>
  /// Retrieves UML IElements from drag arguments.
  /// Works for drags from UML diagrams.
  /// </summary>
  private IEnumerable<IElement> GetModelElementsFromDragEvent
          (DiagramDragEventArgs dragEvent)
  {
    //ElementGroupPrototype is the container for
    //dragged and copied elements and toolbox items.
    ElementGroupPrototype prototype =
       dragEvent.Data.
       GetData(typeof(ElementGroupPrototype))
            as ElementGroupPrototype;
    // Locate the originals in the implementation store.
    IElementDirectory implementationDirectory =
       dragEvent.DiagramClientView.Diagram.Store.ElementDirectory;

    return prototype.ProtoElements.Select(
      prototypeElement =>
      {
        ModelElement element = implementationDirectory
          .FindElement(prototypeElement.ElementId);
        ShapeElement shapeElement = element as ShapeElement;
        if (shapeElement != null)
        {
          // Dragged from a diagram.
          return shapeElement.ModelElement as IElement;
        }
        else
        {
          // Dragged from UML Model Explorer.
          return element as IElement;
        }
      });
  }

  public void OnDoubleClick(ShapeElement targetElement, DiagramPointEventArgs diagramPointEventArgs)
  {
  }
}

如何:从 IDataObject 获取 UML 模型元素中介绍了 GetModelElementsFromDragEvent() 的代码。

请参见

概念

扩展 UML 模型和关系图

如何:在建模图上定义菜单命令

如何:为 UML 模型定义验证约束

使用 UML API 编程

其他资源

如何:定义和安装建模扩展