如何:使用 UML API 编辑序列图

交互是一组生命线之间的消息序列。 交互显示在序列图中。

有关 API 的完整详细信息,请参见Microsoft.VisualStudio.Uml.Interactions

有关为 UML 关系图编写命令和笔势处理程序的更常规的介绍,请参见如何:在建模图上定义菜单命令

基本代码

命名空间导入

必须包括以下 using 语句:

using Microsoft.VisualStudio.Uml.Classes;
   // for basic UML types such as IPackage
using Microsoft.VisualStudio.Uml.Interactions;
   // for interaction types
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml;
   // to create elements and use additional functions

如果您创建的是菜单命令和笔势处理程序,则还需要:

using System.ComponentModel.Composition; 
   // for Import and Export
using Microsoft.VisualStudio.Modeling.ExtensionEnablement;
   // for ICommandExtension
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Presentation;
   // for diagrams and context

有关更多信息,请参见如何:在建模图上定义菜单命令

获取上下文

如果要将交互作为序列图中的命令或笔势处理程序的一部分来编辑,则可获取对上下文的引用。 例如:

    [SequenceDesignerExtension]
    [Export(typeof(ICommandExtension))]  
    public class MySequenceDiagramCommand : ICommandExtension
    {
        [Import]
        public IDiagramContext Context { get; set; }
        public void QueryStatus (IMenuCommand command)
        {
          ISequenceDiagram sequenceDiagram = 
              Context.CurrentDiagram as ISequenceDiagram;
             ...

生成的序列图和 UML 序列图

有两种序列图:一种是在 UML 建模项目中手动创建的,另一种是从程序代码生成的。 可以使用 UmlMode 属性来查找正在使用的序列图的类型。

例如,如果需要生成仅在 UML 序列图上可见的菜单命令,QueryStatus() 方法可以包含以下语句:

    command.Enabled = command.Visible = 
          sequenceDiagram != null && sequenceDiagram.UmlMode;

生成的序列图上的生命线、消息和其他元素与 UML 序列图上的大致相同。 UML 模型中的模型存储区具有一个包含所有其他元素的根模型;而其自身的模型存储区(具有一个 null 根)中存在生成的交互:

    IModel rootModel = sequenceDiagram.ModelStore.Root;
    // !sequenceDiagram.UmlMode == (rootModel == null)

创建和显示交互

创建交互作为包或模型的子级。

例如,如果您开发的是可能对空白序列图执行的命令,则应始终首先检查是否存在交互。

public void Execute (IMenuCommand command)
{
    ISequenceDiagram sequenceDiagram = 
         Context.CurrentDiagram as ISequenceDiagram;
    if (sequenceDiagram == null) return;
    // Get the diagram's interaction:
    IInteraction interaction = sequenceDiagram.Interaction;
    // A new sequence diagram might have no interaction:
    if (interaction == null)
    {
       // Get the home package or model of the diagram:
       IPackage parentPackage = sequenceDiagram.GetObject<IPackage>();
       interaction = parentPackage.CreateInteraction();
       // Display the interaction on the sequence diagram:
       sequenceDiagram.Bind(interaction);
    } 

更新交互及其布局

在更新交互时,始终通过使用下列方法之一更新交互的布局来结束操作:

  • ISequenceDiagram.UpdateShapePositions() 可调整最近已插入或移动的形状的位置,以及这些形状周围的形状的位置。

  • ISequenceDiagram.Layout([SequenceDiagramLayoutKinds]) 可重新绘制整个关系图。 使用此参数可指定对生命线和/或消息进行重新定位。

在插入新元素或移动现有元素时,这一点尤其重要。 在您执行这些操作中的某个操作之前,这些元素在关系图中将不会处于适当位置。 您只需要在完成一系列更改后调用这些操作中的某个操作一次即可。

为了使在您的命令之后执行撤消操作的用户不会感到困惑,可以使用 ILinkedUndoTransaction 封装您所做的更改和最后的 Layout() 或 UpdateShapePositions() 操作。 例如:

using (ILinkedUndoTransaction transaction = LinkedUndoContext.BeginTransaction("create loop"))
{
  Interaction.CreateCombinedFragment(InteractionOperatorKind.Loop, messages);
  Diagram.UpdateShapePositions();
  transaction.Commit();
}

若要使用 ILinkedUndoTransaction,您必须在类中做出此声明:

[Import] ILinkedUndoContext LinkedUndoContext { get; set; }

有关更多信息,请参见如何:使用事务链接模型更新

生成交互

创建生命线

ILifeline lifeline = interaction.CreateLifeline();

生命线表示一个可连接的元素,即类型的实例。 例如,如果交互用于显示组件如何将传入消息委托给自己的内部部件,则生命线可以表示该组件的端口和部件:

foreach (IConnectableElement part in 
            component.Parts
           .Concat<IConnectableElement>(component.OwnedPorts))
{
   ILifeline lifeline = interaction.CreateLifeline();
   lifeline.Represents = part;
}

或者,如果交互显示任意一组对象,则可在交互自身中创建属性或其他 IConnectableElement:

ILifeline lifeline = interaction.CreateLifeline();
IProperty property1 = interaction.CreateProperty();
property1.Type = model.CreateInterface();
property1.Type.Name = "Type 1";
lifeline.Represents = property1;

作为进一步的替代方法,可以设置生命线的名称和类型,而不将其链接到某个可连接的元素:

ILifeline lifeline = interaction.CreateLifeline();
lifeline.Name = "c1";
lifeline.SetInstanceType("Customer");
System.Diagnostics.Debug.Assert(
           lifeline.GetDisplayName() == "c1:Customer"  );

创建消息

若要创建消息,则必须在源生命线和目标生命线上标识插入点。 例如:

interaction.CreateMessage( sourceInsertionPoint, 
                           targetInsertionPoint, 
                           MessageKind.Complete, 
                           MessageSort.ASynchCall)

创建具有未定义源或未定义目标的消息:

interaction.CreateLostFoundMessage(MessageKind.Found, insertionPoint);

可用于在生命线上的所有关键点标识插入点的消息有多个:

用于 ILifeline 的方法

使用该方法在此点插入

FindInsertionPointAtTop()

生命线的顶部。

FindInsertionPointAtBottom()

生命线的底部。

FindInsertionPointAfterMessage

(IMessage previous)

紧跟在指定消息之后的点。

FindInsertionPointAfterExecutionSpecification

(IExecutionSpecification previous)

可在生命线上或在父执行规范块上的点。

FindInsertionPointAfterInteractionUse

(IInteractionUse previous)

交互使用之后的点。

FindInsertionPointAfterCombinedFragment

(ICombinedFragment previous)

组合片段之后的点。

FindInsertionPoint(IExecutionSpecification block)

执行块的顶部。

FindInsertionPoint(IInteractionOperand fragment)

组合片段的操作数顶部。

在创建消息时,应注意不要定义将跨其他消息的消息。

创建组合片段和交互使用

可以通过在元素必须涉及的每条生命线上指定一个插入点来创建组合片段和交互使用。 注意不要指定一组将跨现有消息或片段的点。

Interaction.CreateCombinedFragment(InteractionOperatorKind.Loop, 
  Interaction.Lifelines.Select(lifeline => lifeline.FindInsertionPointAtTop()));
Interaction.CreateInteractionUse(
  Interaction.Lifelines.Select(lifeline => lifeline.FindInsertionPointAtTop()));

也可以创建涉及一组现有消息的组合片段。 所有这些消息都必须源于同一条生命线或同一个执行块。

ICombinedFragment cf = Interaction.CreateCombinedFragment(
  InteractionOperatorKind.Loop,
  Interaction.Lifelines.First().GetAllOutgoingMessages());

组合片段始终是利用单个操作数创建的。 若要创建新操作数,您必须指定要在其前或其后进行插入的现有操作数,并指定是要在其前还是其后进行插入:

// Create an additional operand before the first
cf.CreateInteractionOperand(cf.Operands.First(), false);
// Create an additional operand after the last:
cf.CreateInteractionOperand(cf.Operands.Last(), true);

疑难解答

如果尚未完成使用 UpdateShapePositions() 或 Layout() 操作进行的更改,则形状将出现在错误的位置。

大多数其他问题都是由于插入点未对齐导致的,从而使新的消息或片段必须跨其他消息或片段。 出现的症状可能指示未执行更改或引发了异常。 在执行 UpdateShapePositions() 或 Layout() 操作之前,可能不会引发异常。

请参见

参考

Microsoft.VisualStudio.Uml.Interactions

概念

扩展 UML 模型和关系图

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

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

使用 UML API 编程

其他资源

如何:定义自定义建模工具箱项