自定义复制行为

在创建 Visual Studio 可视化和建模 SDK 的域特定语言 (DSL) (dsl),可以修改会发生什么,当用户复制和粘贴元素。

标准复制和粘贴行为

若要启用副本,请设置 编辑 节点的 启用副本粘贴 属性在 DSL 资源管理器中。

默认情况下,那么,当用户元素复制到剪贴板时,下面的元素也会复制:

  • 所选元素的所有子代。(即嵌入关系的目标是源将复制的组件。) 的元素

  • 复制的组件之间的关系链接。

此递归规则适用于复制的组件和链接。

复制和粘贴的元素

复制的组件和链接。 ElementGroupPrototype (EGP) 序列化并存储,随即被放到剪贴板上。

复制的组件的图像在剪贴板还放置。这允许用户粘贴到其他应用程序 (如 Word。

用户可以粘贴到可以根据 DSL 定义接受元素的目标上的复制的组件。例如,从该元素解决方案模板生成的 DSL,用户可以粘贴端口在元素,该元素,但不在关系图上;可以将元素粘贴在关系图上,,而在其他元素上。

自定义复制和粘贴行为

有关自定义使用程序代码模型的更多信息,请参见 在程序代码中导航和更新模型

  • 启用或禁用复制, " 剪切 ",并粘贴。
    在 DSL 资源管理器中,设置 编辑 节点的 启用副本粘贴 属性。

  • **复制指向同一目标。**例如,具有相同主题元素所链接到的一个复制的注释框。
    将角色的 传播复制 属性设置为 仅链接的传播复制。有关更多信息,请参见 自定义链接复制行为。

  • 复制链接的元素。例如,那么,当您将一个新元素时,所有链接的注释框的副本。
    将角色的 传播复制 属性设置为 对链接和对方角色的扮演者的传播复制。有关更多信息,请参见 自定义链接复制行为。

  • **请通过复制和粘贴快速地重复元素。**通常,您要复制的项仍处于选中状态,因此,您不能粘贴上来相同的元素。
    添加一个元素合并指令添加到域类,并将其设置为向前合并到父类。这将对拖动操作的同一角色。有关更多信息,请参见 自定义元素创建和移动

    - 或 -

    在将元素粘贴选择关系图,通过重写的 ClipboardCommandSet.ProcessOnPasteCommand()。将自定义文件的此代码在 DslPackage 项目:

    namespace Company.MyDsl {
    using System.Linq;
    using Microsoft.VisualStudio.Modeling.Diagrams; 
    using Microsoft.VisualStudio.Modeling.Shell;
    partial class MyDslClipboardCommandSet
    {
      protected override void ProcessOnMenuPasteCommand()
      {
     // Deselect the current selection after copying:
     Diagram diagram = (this.CurrentModelingDocView as SingleDiagramDocView).Diagram;
        this.CurrentModelingDocView
         .SelectObjects(1, new object[] { diagram }, 0);
      }
    } }
    
  • **创建额外的链接,当一个选定的目标上的用户粘贴。**例如,那么,当注释框中粘贴在元素上时,链接创建在它们之间。
    添加一个元素合并指令到目标域类,并将其设置为通过添加链接处理为合并。这将对拖动操作的同一角色。有关更多信息,请参见 自定义元素创建和移动

    - 或 -

    重写 ClipboardCommandSet.ProcessOnPasteCommand() 在调用基方法之后创建其他的链接。

  • 自定义组件可以复制 到外部应用程序 ),添加边框更改为位图窗体的布局 例如。
    重写在 DslPackage 项目的 MyDslClipboardCommandSet.ProcessOnMenuCopyCommand() 。

  • 自定义组件如何复制到剪贴板由复制命令,但是,不在拖动操作。
    重写在 DslPackage 项目的 MyDslClipboardCommandSet.CopyModelElementsIntoElementGroupPrototype() 。

  • 保留形状格式进行复制和粘贴。
    当用户复制多个形状时,可以保留它们的相对位置,并粘贴时。此方法由示例演示。 VMSDK: Circuit Diagrams sample

    若要获得效果,请添加形状并连接到复制的 ElementGroupPrototype。重写的最简便的方法是 ElementOperations.CreateElementGroupPrototype()。为此,请将以下代码添加到 DSL 项目:

    public class MyElementOperations : DesignSurfaceElementOperations
    {
      // Create an EGP to add to the clipboard.
      // Called when the elements to be copied have been
      // collected into an ElementGroup.
     protected override ElementGroupPrototype CreateElementGroupPrototype(ElementGroup elementGroup, ICollection<ModelElement> elements, ClosureType closureType)
      {
     // Add the shapes and connectors:
     // Get the elements already in the group:
        ModelElement[] mels = elementGroup.ModelElements
            .Concat(elementGroup.ElementLinks) // Omit if the paste target is not the diagram.
            .ToArray();
     // Get their shapes:
        IEnumerable<PresentationElement> shapes = 
           mels.SelectMany(mel => 
                PresentationViewsSubject.GetPresentation(mel));
        elementGroup.AddRange(shapes);
    
     return base.CreateElementGroupPrototype
               (elementGroup, elements, closureType);
      }
    
     public MyElementOperations(IServiceProvider serviceProvider, ElementOps1Diagram diagram)
          : base(serviceProvider, diagram)
      { }
    }
    
    // Replace the standard ElementOperations
    // singleton with your own:
    partial class MyDslDiagram // EDIT NAME
    {
     /// <summary>
     /// Singleton ElementOperations attached to this diagram.
     /// </summary>
     public override DesignSurfaceElementOperations ElementOperations
      {
     get
        {
     if (singleton == null)
          {
            singleton = new MyElementOperations(this.Store as IServiceProvider, this);
          }
     return singleton;
        }
      }
     private MyElementOperations singleton = null;
    }
    
  • 在所选位置粘贴形状,如当前光标位置。
    当用户复制多个形状时,可以保留它们的相对位置,并粘贴时。此方法由示例演示。 VMSDK: Circuit Diagrams sample

    若要获得效果,请重写 ClipboardCommandSet.ProcessOnMenuPasteCommand() 使用 ElementOperations.Merge()的位置特定版本。为此,请将 DslPackage 项目中的以下代码:

    
    partial class MyDslClipboardCommandSet // EDIT NAME
    {
       /// <summary>
        /// This method assumes we only want to paste things onto the diagram
        /// - not onto anything contained in the diagram.
        /// The base method pastes in a free space on the diagram.
        /// But if the menu was used to invoke paste, we want to paste in the cursor position.
        /// </summary>
        protected override void ProcessOnMenuPasteCommand()
        {
    
      NestedShapesSampleDocView docView = this.CurrentModelingDocView as NestedShapesSampleDocView;
    
          // Retrieve data from clipboard:
          System.Windows.Forms.IDataObject data = System.Windows.Forms.Clipboard.GetDataObject();
    
          Diagram diagram = docView.CurrentDiagram;
          if (diagram == null) return;
    
          if (!docView.IsContextMenuShowing)
          {
            // User hit CTRL+V - just use base method.
    
            // Deselect anything that's selected, otherwise
            // pasted item will be incompatible:
            if (!this.IsDiagramSelected())
            {
              docView.SelectObjects(1, new object[] { diagram }, 0);
            }
    
            // Paste into a convenient spare space on diagram:
        base.ProcessOnMenuPasteCommand();
          }
          else
          {
            // User right-clicked - paste at mouse position.
    
            // Utility class:
            DesignSurfaceElementOperations op = diagram.ElementOperations;
    
            ShapeElement pasteTarget = diagram;
    
            // Check whether what's in the paste buffer is acceptable on the target.
            if (pasteTarget != null && op.CanMerge(pasteTarget, data))
            {
    
            // Although op.Merge would be a no-op if CanMerge failed, we check CanMerge first
              // so that we don't create an empty transaction (after which Undo would be no-op).
              using (Transaction t = diagram.Store.TransactionManager.BeginTransaction("paste"))
              {
                PointD place = docView.ContextMenuMousePosition;
                op.Merge(pasteTarget, data, PointD.ToPointF(place));
                t.Commit();
              }
            }
          }
        }
      }
    
  • 让用户拖放元素。
    请参见 如何:添加拖放处理程序

自定义链接复制行为

当用户将一个组件时,标准行为是所有嵌入元素也被复制。可以修改标准复制的行为。在 DSL 定义,选择一个角色关系中的一端,并在 " 属性 " 窗口中设置 传播复制 值。

传播域角色的“复制”属性

有三个值:

  • 不要将复制

  • 将传播仅复制链接 ),以便组粘贴时,此链接的新副本将引用为现有元素在链接的另一端。

  • 传播复制链接和对方角色的扮演者 - 复制的组包含该元素的副本在链接的另一端。

使用 PropagateCopyToLinkOnly 进行复制的效果

您所做的更改将影响元素和复制的图像。

编程复制和粘贴行为

DSL 的行为的许多方面有关复制、粘贴、对象的创建和删除的是已与关系图 ElementOperations 的实例管理。您可以通过派生拥有类从 ElementOperations 并重写关系图类的 ElementOperations 属性修改 DSL 的行为。

提示提示

有关自定义使用程序代码模型的更多信息,请参见 在程序代码中导航和更新模型

复制操作的序列图粘贴操作的序列图

定义拥有 ElementOperations

  1. 在 DSL 项目的新文件,创建从 DesignSurfaceElementOperations派生的类。

  2. 添加关系图类的分部类定义。此类的名称可以在 Dsl\GeneratedCode\Diagrams.cs找到。

    在关系图类,请重写 ElementOperations 返回 ElementOperations 子类的实例。您应返回同一个实例在每次调用。

将自定义代码文件的此代码在 DslPackage 项目:

using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Diagrams.ExtensionEnablement;

  public partial class MyDslDiagram
  {
    public override DesignSurfaceElementOperations ElementOperations
    {
      get
      {
        if (this.elementOperations == null)
        {
          this.elementOperations = new MyElementOperations(this.Store as IServiceProvider, this);
        }
        return this.elementOperations;
      }
    }
    private MyElementOperations elementOperations = null;
  }

  public class MyElementOperations : DesignSurfaceElementOperations
  {
    public MyElementOperations(IServiceProvider serviceProvider, MyDslDiagram diagram)
      : base(serviceProvider, diagram)
    { }
    // Overridden methods follow
  }

接收来自其他模型拖动项

ElementOperations 还可用于定义复制、移动、删除和拖放行为。作为使用的演示 ElementOperations,给出的示例此处定义自定义拖放行为。但是,在中,为该目的在 如何:添加拖放处理程序考虑替代方法,将介绍更为可扩展的。

定义在 ElementOperations 类的两个方法:

  • 确定 的CanMerge(ModelElement targetElement, System.Windows.Forms.IDataObject data) 源元素是否可以拖动到目标形状、链接或关系图上。

  • 合并 源元素为目标的MergeElementGroupPrototype(ModelElement targetElement, ElementGroupPrototype sourcePrototype) 。

Ff521398.collapse_all(zh-cn,VS.110).gifCanMerge()

[CanMerge()] 调用来确定应为用户的反馈,当鼠标位于关系图之间移动。对方法的参数为鼠标悬停的有关拖动操作的源的元素和数据。用户可以从任何位置屏幕拖动。因此,源对象是许多不同类型,并且可以序列化使用不同的布局。如果源是 DSL 或 UML 模型,数据参数是 ElementGroupPrototype的序列化。拖动、复制和工具箱操作使用 ElementGroupPrototypes 表示模型的片段。

元素组原型可以包含任意数量的元素和链接。元素类型可由其 GUID 标识。GUID 拖动的是形状,而不是基础模型元素。在下面的示例中,因此,如果从 UML 关系图的类形状拖动此关系图上, CanMerge() 返回 true。

public override bool CanMerge(ModelElement targetShape, System.Windows.Forms.IDataObject data)
 {
  // Extract the element prototype from the data.
  ElementGroupPrototype prototype = ElementOperations.GetElementGroupPrototype(this.ServiceProvider, data);
  if (targetShape is MyTargetShape && prototype != null &&
        prototype.RootProtoElements.Any(rootElement => 
          rootElement.DomainClassId.ToString() 
          ==  "3866d10c-cc4e-438b-b46f-bb24380e1678")) // Guid of UML Class shapes
          // or SourceClass.DomainClassId
        return true;
   return base.CanMerge(targetShape, data);
 }

MergeElementGroupPrototype()

,当用户拖放到关系图、形状或连接上时,的元素调用此方法。它应将被拖动的内容添加到目标元素。在此示例中,代码以确定它是否识别目标和原型类型的组合;如果存在,则方法将被拖动的元素转换应添加到设计组件的原型。该基方法调用执行合并,转换的或不重元素。

    public override void MergeElementGroupPrototype(ModelElement targetShape, ElementGroupPrototype sourcePrototype)
    {
      ElementGroupPrototype prototypeToMerge = sourcePrototype;
      MyTargetShape pel = targetShape as MyTargetShape;
      if (pel != null)
      {
        prototypeToMerge = ConvertDraggedTypeToLocal(pel, sourcePrototype);
      }
      if (prototypeToMerge != null)
        base.MergeElementGroupPrototype(targetShape, prototypeToMerge);
    }

此示例处理 UML 从 UML 类关系图中拖动的类元素。DSL 并非旨在直接存储 UML 类,,相反,我们创建每个拖动 UML 类的一个 DSL 元素。,如果 DSL 是实例关系图,例如,这会很有用。用户可以拖动到关系图上的类来创建这些类的实例。

    private ElementGroupPrototype ConvertDraggedTypeToLocal (MyTargetShape snapshot, ElementGroupPrototype prototype)
    {
      // Find the UML project:
      EnvDTE.DTE dte = snapshot.Store.GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
      foreach (EnvDTE.Project project in dte.Solution.Projects)
      {
        IModelingProject modelingProject = project as IModelingProject;
        if (modelingProject == null) continue; // not a modeling project
        IModelStore store = modelingProject.Store;
        if (store == null) continue;
        // Look for the shape that was dragged:
        foreach (IDiagram umlDiagram in store.Diagrams())
        {
          // Get modeling diagram that implements UML diagram:
          Diagram diagram = umlDiagram.GetObject<Diagram>();
          Guid elementId = prototype.SourceRootElementIds.FirstOrDefault();
          ShapeElement shape = diagram.Partition.ElementDirectory.FindElement(elementId) as ShapeElement;
          if (shape == null) continue;
          IClass classElement = shape.ModelElement as IClass;
          if (classElement == null) continue;
          
          // Create a prototype of elements in my DSL, based on the UML element:
          Instance instance = new Instance(snapshot.Store);
          instance.Type = classElement.Name;
          // Pack them into a prototype:
          ElementGroup group = new ElementGroup(instance);
          return group.CreatePrototype();
        }
      }
      return null;
    }

标准复制行为

本节中的代码显示为的方法可以重写更改复制的行为。为了帮助您了解如何实现拥有自定义项,本节说明在复制涉及的方法的代码,但是,不更改标准行为。

当用户按 CTRL+C 或使用复制菜单命令时,方法 ProcessOnMenuCopyCommand 调用。可以查看此如何在 DslPackage\Generated Code\CommandSet.cs设置为。有关如何安装的更多信息,请参见 如何:向快捷菜单中添加命令

可以通过添加 MyDslClipboardCommandSet 的分部类定义将重写 ProcessOnMenuCopyCommand 在 DslPackage 项目的。

using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;

partial class MyDslClipboardCommandSet
{
  /// <summary>
  /// Override ProcessOnMenuCopyCommand() to copy elements to the
  /// clipboard in different formats, or to perform additional tasks
  /// before or after copying – for example deselect the copied elements.
  /// </summary>
  protected override void ProcessOnMenuCopyCommand()
  {
    IList<ModelElement> selectedModelElements = this.SelectedElements;
    if (selectedModelElements.Count == 0) return;

    // System container for clipboard data.
    // The IDataObject can contain data in several formats.
    IDataObject dataObject = new DataObject();
      
    Bitmap bitmap = null; // For export to other programs.
    try
    {
      #region Create EGP for copying to a DSL.
      this.CopyModelElementsIntoElementGroupPrototype
                     (dataObject, selectedModelElements);
      #endregion
      
      #region Create bitmap for copying to another application. 
      // Find all the shapes associated with this selection:
      List<ShapeElement> shapes = new List<ShapeElement>(
        this.ResolveExportedShapesForClipboardImages
              (dataObject, selectedModelElements));

      bitmap = this.CreateBitmapForClipboard(shapes);
      if (bitmap != null)
      {
        dataObject.SetData(DataFormats.Bitmap, bitmap);
      }
      #endregion 
     
      // Add the data to the clipboard:
      Clipboard.SetDataObject(dataObject, true, 5, 100);
    }
    finally
    {
      // Dispose bitmap after SetDataObject:
      if (bitmap != null) bitmap.Dispose();
    }
  }
/// <summary>
/// Override this to customize the element group that is copied to the clipboard.
/// </summary>
protected override void CopyModelElementsIntoElementGroupPrototype(IDataObject dataObject, IList<ModelElement> selectedModelElements)
{
  return this.ElementOperations.Copy(dataObject, selectedModelElements);
}
}

每个关系图都具有 ElementOperations 一个实例。可以提供拥有的派生对象。此文件,在 DSL 项目可以放在节点中,行为方式与它重写的代码相同:

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

namespace Company.MyDsl
{
  partial class MyDslDiagram
  {
    /// <summary>
    /// Singleton ElementOperations attached to this diagram.
    /// </summary>
    public override DesignSurfaceElementOperations ElementOperations
    {
      get
      {
        if (this.elementOperations == null)
        {
          this.elementOperations = new MyElementOperations(this.Store as IServiceProvider, this);
        }
        return this.elementOperations;
      }
    }
    private MyElementOperations elementOperations = null;
  }

  // Our own version of ElementOperations so that we can override:
  public class MyElementOperations : DesignSurfaceElementOperations
  {
    public MyElementOperations(IServiceProvider serviceProvider, ElementOps1Diagram diagram)
      : base(serviceProvider, diagram)
    { }


     
    /// <summary>
    /// Copy elements to the clipboard data.
    /// Provides a hook for adding custom data.
    /// </summary>
    public override void Copy(System.Windows.Forms.IDataObject data, 
      ICollection<ModelElement> elements, 
      ClosureType closureType, 
      System.Drawing.PointF sourcePosition)
    {
      if (CanAddElementGroupFormat(elements, closureType))
      {
        AddElementGroupFormat(data, elements, closureType); 
      }

      // Override these to store additional data:
      if (CanAddCustomFormat(elements, closureType))
      {
        AddCustomFormat(data, elements, closureType, sourcePosition);
      }
    }
     
    
    protected override void AddElementGroupFormat(System.Windows.Forms.IDataObject data, ICollection<ModelElement> elements, ClosureType closureType)
    {
      // Add the selected elements and those implied by the propagate copy rules:
      ElementGroup elementGroup = this.CreateElementGroup(elements, closureType);

      // Mark all the elements that are not embedded under other elements:
      this.MarkRootElements(elementGroup, elements, closureType);

      // Store in the clipboard data:
      ElementGroupPrototype elementGroupPrototype = this.CreateElementGroupPrototype(elementGroup, elements, closureType);
      data.SetData(ElementGroupPrototype.DefaultDataFormatName, elementGroupPrototype);
    }

    /// <summary>
    /// Override this to store additional elements in the element group:
    /// </summary>
    protected override ElementGroupPrototype CreateElementGroupPrototype(ElementGroup elementGroup, ICollection<ModelElement> elements, ClosureType closureType)
    {
      ElementGroupPrototype prototype = new ElementGroupPrototype(this.Partition, elementGroup.RootElements, elementGroup);
      return prototype;
    }

    /// <summary>
    /// Create an element group from the given starting elements, using the 
    /// copy propagation rules specified in the DSL Definition.
    /// By default, this includes all the embedded descendants of the starting elements,
    /// and also includes reference links where both ends are already included.
    /// </summary>
    /// <param name="startElements">model elements to copy</param>
    /// <param name="closureType"></param>
    /// <returns></returns>
    protected override ElementGroup CreateElementGroup(ICollection<ModelElement> startElements, ClosureType closureType)
    {
      // ElementClosureWalker finds all the connected elements, 
      // according to the propagate copy rules specified in the DSL Definition:
      ElementClosureWalker walker = new ElementClosureWalker(this.Partition, 
        closureType, // Normally ClosureType.CopyClosure
        startElements, 
        true, // Do not load other models.
        null, // Optional list of domain roles not to traverse.
        true); // Include relationship links where both ends are already included.
      
      walker.Traverse(startElements);
      IList<ModelElement> closureList = walker.ClosureList;
      Dictionary<object, object> closureContext = walker.Context;

      // create a group for this closure
      ElementGroup group = new ElementGroup(this.Partition);
      group.AddRange(closureList, false);

      // create the element group prototype for the group
      foreach (object key in closureContext.Keys)
      {
        group.SourceContext.ContextInfo[key] = closureContext[key];
      }

      return group;
    }
  }
}

请参见

概念

自定义元素创建和移动

如何:添加拖放处理程序

自定义删除行为

其他资源

Sample: VMSDK Circuit Diagrams sample