如何:添加拖放处理程序

可以添加拖放事件的处理程序添加到 DSL,因此,用户可以拖动到关系图上的项从其他关系图或从 Visual Studio的其他部分。还可以添加事件的处理程序 (如双击。同时,拖放以及双击处理程序称为 笔势处理程序。

本主题讨论在其他关系图给定的拖放操作。对于在一个关系图中的移动和复制操作,请考虑定义 ElementOperations子类替换。有关更多信息,请参见 如何:程序复制和粘贴行为 - 重定向。也可以自定义 DSL 定义。

主题内容

  • 最后两节描述定义笔势处理程序替换方法:

    • 定义笔势处理程序通过重写的方法 ShapeElement。OnDragDrop、 OnDoubleClick、 OnDragOver和其他方法可被重写。

    • 通过使用 MEF 的笔势处理程序。,使第三方开发人员可定义自己的处理程序添加到 DSL,请使用此方法。,这些安装了 DSL 之后,用户可以选择安装第三方扩展。

  • 如何解密拖动的项。可以将组件从拖到任何窗口或从桌面,以及从 DSL。

  • 如何获取原始的拖动的项。如果已拖动项是 DSL 元素,可以打开源模型和访问元素。

  • 使用鼠标事件:拖动的隔离舱项目。此示例演示截获到形状的字段的鼠标事件的底部处理程序。该示例让用户通过拖动重新排列在隔离舱的项与鼠标。

定义笔势处理程序通过重写的方法 ShapeElement

添加新代码文件添加到 DSL 项目。对于笔势处理程序,则通常必须至少有 using 以下语句:

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

在新文件中,应定义响应拖动操作的形状或关系图类的分部类。重写以下方法:

  • OnDragOver- 此方法,则当鼠标指针在拖动操作过程时输入形状。方法应检查用户拖动的项,并将影响属性指示用户是否可以将此形状的项目。影响属性确定光标的外观,则此形状,并确定 OnDragDrop() 是否将调用,当用户松开鼠标按钮时。

    partial class MyShape // MyShape generated from DSL Definition.
    {
        public override void OnDragOver(DiagramDragEventArgs e)
        {
          base.OnDragOver(e);
          if (e.Effect == System.Windows.Forms.DragDropEffects.None 
               && IsAcceptableDropItem(e)) // To be defined
          {
            e.Effect = System.Windows.Forms.DragDropEffects.Copy;
          }
        }
    
  • OnDragDrop –调用此方法,如果用户释放鼠标按钮时,此形状或关系图时将鼠标指针方式,以及 None外,因此,如果 OnDragOver(DiagramDragEventArgs e) 之前设置 e.Effect 设置为值。

    public override void OnDragDrop(DiagramDragEventArgs e)
        {
          if (!IsAcceptableDropItem(e))
          {
            base.OnDragDrop(e);
          }
          else 
          { // Process the dragged item, for example merging a copy into the diagram
            ProcessDragDropItem(e); // To be defined
          }  
        }
    
  • OnDoubleClick –此方法,则当用户双击形状或关系图时。

    有关更多信息,请参见 如何:截获对形状或修饰器的单击

定义 IsAcceptableDropItem(e) 确定所拖动的项是否可接受和 ProcessDragDropItem (e) 更新模型,在中删除时。这些方法必须从事件参数首先提取该项目。有关如何执行此操作的信息,请参见 如何获取对拖动的项的引用。

通过使用 MEF 的笔势处理程序

MEF (managed extensibility framework) 可以定义可随最小配置的元素。有关更多信息,请参见 Managed Extensibility Framework (MEF)

定义 MEF 笔势处理程序

  1. 添加到 Dsl ,并 DslPackage 投影到 使用 MEF 扩展 DSL中描述的 MefExtension 文件。

  2. 现在可以定义笔势处理程序为 MEF 组件:

      // This attribute is defined in the generated file
      // DslPackage\MefExtension\DesignerExtensionMetaDataAttribute.cs:
      [MyDslGestureExtension]
      public class MyGestureHandlerClassName : IGestureExtension
      {
        /// <summary>
        /// Called to determine whether a drag onto the diagram can be accepted.
        /// </summary>
        /// <param name="diagramDragEventArgs">Contains a link to the item that is being dragged</param>
        /// <param name="targetMergeElement">The shape or connector that the mouse is over</param>
        /// <returns>True if the item can be accepted on the targetMergeElement.</returns>
        public bool CanDragDrop(ShapeElement targetMergeElement, DiagramDragEventArgs diagramDragEventArgs)
        {
          MyShape target = targetMergeElement as MyShape;
          if (target == null) return false;
          if (target.IsAcceptableDropItem(diagramDragEventArgs)) return true; 
          return false;
        }
        public void OnDragDrop(ShapeElement targetDropElement, DiagramDragEventArgs diagramDragEventArgs)
        {
          MyShape target = targetMergeElement as MyShape;
          if (target == null || ! target.IsAcceptableDropItem(diagramDragEventArgs)) return;
          // Process the dragged item, for example merging a copy into the diagram:
          target.ProcessDragDropItem(diagramDragEventArgs);
       }
    
    

    ,则所拖动的对象时,的不同类型可以创建多个笔势处理程序元素,例如。

  3. 添加目标形状、链接或关系图类的分部类定义,并定义方法 IsAcceptableDropItem() 和 ProcessDragDropItem()。这些方法必须通过展开拖动的项从开始事件参数。有关更多信息,请参见 如何获取对拖动的项的引用。

如何解密拖动的项

当用户拖动一个项目在关系图上,或从关系图的一部分到另一页时,有关拖动的项的信息可在 [DiagramDragEventArgs]。由于拖动操作可能已开始在屏幕上的所有对象,数据可以在中可用的任何一个各种格式中。代码必须识别它能够处理的格式。

若要查看拖动源信息可用的格式,请运行代码在调试模式下,将断点设置为项。 OnDragOver() 或 CanDragDrop()。检查 DiagramDragEventArgs 参数的值。将信息提供以两种形式:

  • IDataObjectData –此特性具有源对象的序列化版本,通常是为了多个格式。其最有用功能包括:

    • 关系图 EventArgs.Data.GetDataFormats() –列表可以解密已拖动的对象的格式。例如,在中,如果用户将从桌面的文件,可用的格式包括文件名 ("FileNameW")。

    • diagramEventArgs.Data.GetData(format) –解密已拖动的对象与所指定的格式。转换为相应类型的对象。例如:

      string fileName = diagramEventArgs.Data.GetData("FileNameW") as string;

      还可以传输对象 (例如模型总线从的源引用拥有自定义格式。有关更多信息,请参见 如何发送模型总线在拖放引用。

  • ,如果您希望用户从拖动 DSL 或 UML 模型的项目中,ElementGroupPrototypePrototype –使用此属性。元素组原型包含一个或多个对象、链接和它们的属性。还用于粘贴操作,所以,当您从工具箱添加一个元素。在原型中,对象及其类型由 GUID 标识。例如,此代码允许用户从拖动到 UML 关系图或 UML 模型资源管理器中的类元素:

    private bool IsAcceptableDropItem(DiagramDragEventArgs e)
    {
      return e.Prototype != null && e.Prototype.RootProtoElements.Any(element => 
            element.DomainClassId.ToString() 
            == "3866d10c-cc4e-438b-b46f-bb24380e1678"); // Accept UML class shapes.
     // Or, from another DSL: SourceNamespace.SourceShapeClass.DomainClassId
    }
    

    若要接受 UML 形状,通过实验确定 UML 形状类的 GUID。确保通常具有多元素的一种类型的任意关系图上。还确保从 DSL 或 UML 关系图拖动的对象是形状,而不是模型元素。

DiagramDragEventArgs 还有一个指示当前鼠标指针位置的属性,并且该用户按 CTRL 或 shift、 ALT 键。

如何获取一个拖动的元素的初始

事件参数的 Data 和 Prototype 属性只包含对要拖动的形状。通常,因此,如果在目标 DSL 创建从原型以某种方式派生的对象,需要获取对原始项的访问,例如,读取文件目录或导航到模型元素的可由形状。您可以使用 Visual Studio 模型总线做到这一点。

准备 DSL 为模型总线项目

  • 使源 DSL 访问受 Visual Studio 模型总线:

    1. ,如果尚未安装,请下载并安装 Visual Studio 模型总线扩展它。有关更多信息,请参见 Visualization and Modeling SDK

    2. 打开源 DSL 的 DSL 定义文件在 DSL 设计器中。右击该设计图面然后单击 启用 Modelbus。在对话框中,选择一个或两个选项。单击**“确定”**。一个新项 “ModelBus”添加到 DSL 解决方案。

    3. 单击 转换所有模板 并重新生成解决方案。

从源 DSL 发送对象

  • 在 ElementOperations 子类,重写 Copy() ,以便输入一辆模型总线对 IDataObject (MBR)。,当用户开始从源关系图,拖动此方法将调用。,当用户在目标关系图,删除编码 MBR 然后将在 IDataObject。

    
    
    using Microsoft.VisualStudio.Modeling;
    using Microsoft.VisualStudio.Modeling.Shell;
    using Microsoft.VisualStudio.Modeling.Diagrams;
    using Microsoft.VisualStudio.Modeling.Integration;
    using Microsoft.VisualStudio.Modeling.Integration.Shell;
    using System.Drawing; // PointF
    using  System.Collections.Generic; // ICollection
    using System.Windows.Forms; // for IDataObject
    ...
    public class MyElementOperations : DesignSurfaceElementOperations
    {
        public override void Copy(System.Windows.Forms.IDataObject data, System.Collections.Generic.ICollection<ModelElement> elements, ClosureType closureType, System.Drawing.PointF sourcePosition)
        {
          base.Copy(data, elements, closureType, sourcePosition);
    
          // Get the ModelBus service:
          IModelBus modelBus =
              this.Store.GetService(typeof(SModelBus)) as IModelBus;
          DocData docData = ((VSDiagramView)this.Diagram.ActiveDiagramView).DocData;
          string modelFile = docData.FileName;
          // Get an adapterManager for the target DSL:
          ModelBusAdapterManager manager =
              (modelBus.FindAdapterManagers(modelFile).First());
          ModelBusReference modelReference = manager.CreateReference(modelFile);
          ModelBusReference elementReference = null;
          using (ModelBusAdapter adapter = modelBus.CreateAdapter(modelReference))
          {
            elementReference = adapter.GetElementReference(elements.First());
          }
    
          data.SetData("ModelBusReference", elementReference);
        }
    ...}
    

若要接收到此模型总线请从目标 DSL 或 UML 项目中的一个 DSL 引用

  1. 在目标 DSL 项目中,添加项目引用:

    • 源 DSL 项目。

    • 源 ModelBus 项目。

  2. 在笔势处理程序代码文件中,添加以下命名空间引用:

    using Microsoft.VisualStudio.Modeling;
    using Microsoft.VisualStudio.Modeling.ExtensionEnablement;
    using Microsoft.VisualStudio.Modeling.Diagrams;
    using Microsoft.VisualStudio.Modeling.Diagrams.ExtensionEnablement;
    using Microsoft.VisualStudio.Modeling.Integration;
    using SourceDslNamespace;
    using SourceDslNamespace.ModelBusAdapters;
    
  3. 下面的示例演示如何对源模型元素的 get 访问:

      partial class MyTargetShape // or diagram or connector 
      {
        internal void ProcessDragDropItem(DiagramDragEventArgs diagramDragEventArgs)
        {
          // Verify that we're being passed an Object Shape.
          ElementGroupPrototype prototype = diagramDragEventArgs.Prototype;
          if (prototype == null) return;
          if (Company.InstanceDiagrams.ObjectShape.DomainClassId
            != prototype.RootProtoElements.First().DomainClassId)
            return;
          // - This is an ObjectShape.
          // - We need to access the originating Store, find the shape, and get its object.
    
          IModelBus modelBus = targetDropElement.Store.GetService(typeof(SModelBus)) as IModelBus;
    
          // Unpack the MBR that was packed in Copy:
          ModelBusReference reference = diagramDragEventArgs.Data.GetData("ModelBusReference") as ModelBusReference;
          using (SourceDslAdapter adapter = modelBus.CreateAdapter(reference) as SourceDslAdapter)
          {
            using (ILinkedUndoTransaction t = LinkedUndoContext.BeginTransaction("doing things"))
            {
              // Quickest way to get the shape from the MBR:
              ObjectShape firstShape = adapter.ResolveElementReference<ObjectShape>(reference);
    
              // But actually there might be several shapes - so get them from the prototype instead:
              IElementDirectory remoteDirectory = adapter.Store.ElementDirectory;
              foreach (Guid shapeGuid in prototype.SourceRootElementIds)
              {
                PresentationElement pe = remoteDirectory.FindElement(shapeGuid) as PresentationElement;
                if (pe == null) continue;
                SourceElement instance = pe.ModelElement as SourceElement;
                if (instance == null) continue;
    
                // Do something with the object:
            instance...
              }
              t.Commit();
            }
          }
      }
    

若要接受源元素从 UML 模型中

  • 下面的代码示例接受从 UML 关系图删除的对象。

      using Microsoft.VisualStudio.ArchitectureTools.Extensibility;
      using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml;
      using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Presentation;
      using Microsoft.VisualStudio.Modeling;
      using Microsoft.VisualStudio.Modeling.Diagrams;
      using Microsoft.VisualStudio.Modeling.Diagrams.ExtensionEnablement;
      using Microsoft.VisualStudio.Uml.Classes;
      using System;
      using System.ComponentModel.Composition;
      using System.Linq;
    ...
    partial class TargetShape
    {
      internal void ProcessDragDropItem(DiagramDragEventArgs diagramDragEventArgs)
      {
            EnvDTE.DTE dte = this.Store.GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
            // Find the UML project
            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) return;
    
              foreach (IDiagram dd in store.Diagrams())
              {
                  // Get Modeling.Diagram that implements UML.IDiagram:
                  Diagram diagram = dd.GetObject<Diagram>(); 
    
                  foreach (Guid elementId in e.Prototype.SourceRootElementIds)
                  {
                    ShapeElement shape = diagram.Partition.ElementDirectory.FindElement(elementId) as ShapeElement;
                    if (shape == null) continue;
                    // This example assumes the shape is a UML class:
                    IClass classElement = shape.ModelElement as IClass;
                    if (classElement == null) continue;
    
                    // Now do something with the UML class element ...
                  }
            }
          break; // don't try any more projects 
    }  }  }
    

使用鼠标事件:拖动的隔离舱项目

您可以编写截获到形状的字段的鼠标事件的处理程序。下面的示例让用户通过拖动重新排列在隔离舱的项与鼠标。

使用 类图 解决方案模板,若要生成此示例,请创建一个解决方案。添加一个代码文件并添加以下代码。调整该命名空间与拥有。

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

// This sample allows users to re-order items in a compartment shape by dragging.

// This example is built on the "Class Diagrams" solution template of VMSDK (DSL Tools).
// You will need to change the following ___domain class names to your own:
// ClassShape = a compartment shape
// ClassModelElement = the ___domain class displayed using a ClassShape
// This code assumes that the embedding relationships displayed in the compartments
// don't use inheritance (don't have base or derived ___domain relationships).

namespace Company.CompartmentDrag  // EDIT.
{
 /// <summary>
 /// Manage the mouse while dragging a compartment item.
 /// </summary>
 public class CompartmentDragMouseAction : MouseAction
 {
  private ModelElement sourceChild;
  private ClassShape sourceShape;
  private RectangleD sourceCompartmentBounds;

  public CompartmentDragMouseAction(ModelElement sourceChildElement, ClassShape sourceParentShape, RectangleD bounds)
   : base (sourceParentShape.Diagram)
  {
   sourceChild = sourceChildElement;
   sourceShape = sourceParentShape;
   sourceCompartmentBounds = bounds; // For cursor.
  }
   
  /// <summary>
  /// Call back to the source shape to drop the dragged item.
  /// </summary>
  /// <param name="e"></param>
  protected override void OnMouseUp(DiagramMouseEventArgs e)
  {
   base.OnMouseUp(e);
   sourceShape.DoMouseUp(sourceChild, e);
   this.Cancel(e.DiagramClientView);
   e.Handled = true;
  }

  /// <summary>
  /// Ideally, this shouldn't happen. This action should only be active
  /// while the mouse is still pressed. However, it can happen if you
  /// move the mouse rapidly out of the source shape, let go, and then 
  /// click somewhere else in the source shape. Yuk.
  /// </summary>
  /// <param name="e"></param>
  protected override void OnMouseDown(DiagramMouseEventArgs e)
  {
   base.OnMouseDown(e);
   this.Cancel(e.DiagramClientView);
   e.Handled = false;
  }

  /// <summary>
  /// Display an appropriate cursor while the drag is in progress:
  /// Up-down arrow if we are inside the original compartment.
  /// No entry if we are elsewhere.
  /// </summary>
  /// <param name="currentCursor"></param>
  /// <param name="diagramClientView"></param>
  /// <param name="mousePosition"></param>
  /// <returns></returns>
  public override System.Windows.Forms.Cursor GetCursor(System.Windows.Forms.Cursor currentCursor, DiagramClientView diagramClientView, PointD mousePosition)
  {
   // If the cursor is inside the original compartment, show up-down cursor.
   return sourceCompartmentBounds.Contains(mousePosition) 
    ? System.Windows.Forms.Cursors.SizeNS // Up-down arrow.
    : System.Windows.Forms.Cursors.No;
  }
 }

 /// <summary>
 /// Override some methods of the compartment shape.
 /// *** GenerateDoubleDerived must be set for this shape in DslDefinition.dsl. ****
 /// </summary>
 public partial class ClassShape
 {
  /// <summary>
  /// Model element that is being dragged.
  /// </summary>
  private static ClassModelElement dragStartElement = null;
  /// <summary>
  /// Absolute bounds of the compartment, used to set the cursor.
  /// </summary>
  private static RectangleD compartmentBounds;

  /// <summary>
  /// Attach mouse listeners to the compartments for the shape.
  /// This is called once per compartment shape.
  /// The base method creates the compartments for this shape.
  /// </summary>
  public override void EnsureCompartments()
  {
   base.EnsureCompartments();
   foreach (Compartment compartment in this.NestedChildShapes.OfType<Compartment>())
   {
    compartment.MouseDown += new DiagramMouseEventHandler(compartment_MouseDown);
    compartment.MouseUp += new DiagramMouseEventHandler(compartment_MouseUp);
    compartment.MouseMove += new DiagramMouseEventHandler(compartment_MouseMove);
   }
  }


  /// <summary>
  /// Remember which item the mouse was dragged from.
  /// We don't create an Action immediately, as this would inhibit the
  /// inline text editing feature. Instead, we just remember the details
  /// and will create an Action when/if the mouse moves off this list item.
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  void compartment_MouseDown(object sender, DiagramMouseEventArgs e)
  {
   dragStartElement = e.HitDiagramItem.RepresentedElements.OfType<ClassModelElement>().FirstOrDefault();
   compartmentBounds = e.HitDiagramItem.Shape.AbsoluteBoundingBox;
  }

  /// <summary>
  /// When the mouse moves away from the initial list item, but still inside the compartment,
  /// create an Action to supervise the cursor and handle subsequent mouse events.
  /// Transfer the details of the initial mouse position to the Action.
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  void compartment_MouseMove(object sender, DiagramMouseEventArgs e)
  {
   if (dragStartElement != null)
   {
    if (dragStartElement != e.HitDiagramItem.RepresentedElements.OfType<ClassModelElement>().FirstOrDefault())
    {
     e.DiagramClientView.ActiveMouseAction = new CompartmentDragMouseAction(dragStartElement, this, compartmentBounds);
     dragStartElement = null;
    }
   }
  }

  /// <summary>
  /// User has released the mouse button. 
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  void compartment_MouseUp(object sender, DiagramMouseEventArgs e)
  {
    dragStartElement = null;
  }

  /// <summary>
  /// Forget the source item if mouse up occurs outside the
  /// compartment.
  /// </summary>
  /// <param name="e"></param>
  public override void OnMouseUp(DiagramMouseEventArgs e)
  {
   base.OnMouseUp(e);
   dragStartElement = null;
  }


  /// <summary>
  /// Called by the Action when the user releases the mouse.
  /// If we are still on the same compartment but in a different list item,
  /// move the starting item to the position of the current one.
  /// </summary>
  /// <param name="dragFrom"></param>
  /// <param name="e"></param>
  public void DoMouseUp(ModelElement dragFrom, DiagramMouseEventArgs e)
  {
   // Original or "from" item:
   ClassModelElement dragFromElement = dragFrom as ClassModelElement;
   // Current or "to" item:
   ClassModelElement dragToElement = e.HitDiagramItem.RepresentedElements.OfType<ClassModelElement>().FirstOrDefault();
   if (dragFromElement != null && dragToElement != null)
   {
    // Find the common parent model element, and the relationship links:
    ElementLink parentToLink = GetEmbeddingLink(dragToElement);
    ElementLink parentFromLink = GetEmbeddingLink(dragFromElement);
    if (parentToLink != parentFromLink && parentFromLink != null && parentToLink != null)
    {
     // Get the static relationship and role (= end of relationship):
     DomainRelationshipInfo relationshipFrom = parentFromLink.GetDomainRelationship();
     DomainRoleInfo parentFromRole = relationshipFrom.DomainRoles[0];
     // Get the node in which the element is embedded, usually the element displayed in the shape:
     ModelElement parentFrom = parentFromLink.LinkedElements[0];

     // Same again for the target:
     DomainRelationshipInfo relationshipTo = parentToLink.GetDomainRelationship();
     DomainRoleInfo parentToRole = relationshipTo.DomainRoles[0];
     ModelElement parentTo = parentToLink.LinkedElements[0];

     // Mouse went down and up in same parent and same compartment:
     if (parentTo == parentFrom && relationshipTo == relationshipFrom)
     {
      // Find index of target position:
      int newIndex = 0;
      var elementLinks = parentToRole.GetElementLinks(parentTo);
      foreach (ElementLink link in elementLinks)
      {
       if (link == parentToLink) { break; }
       newIndex++;
      }

      if (newIndex < elementLinks.Count)
      {
       using (Transaction t = parentFrom.Store.TransactionManager.BeginTransaction("Move list item"))
       {
        parentFromLink.MoveToIndex(parentFromRole, newIndex);
        t.Commit();
       }
      }
     }
    }
   }
  }

  /// <summary>
  /// Get the embedding link to this element.
  /// Assumes there is no inheritance between embedding relationships.
  /// (If there is, you need to make sure you've got the relationship
  /// that is represented in the shape compartment.)
  /// </summary>
  /// <param name="child"></param>
  /// <returns></returns>
  ElementLink GetEmbeddingLink(ClassModelElement child)
  {
   foreach (DomainRoleInfo role in child.GetDomainClass().AllEmbeddedByDomainRoles)
   {
    foreach (ElementLink link in role.OppositeDomainRole.GetElementLinks(child))
    {
     // Just the assume the first embedding link is the only one.
     // Not a valid assumption if one relationship is derived from another.
     return link;
    }
   }
   return null;
  }
 }
}

请参见

概念

自定义复制行为

如何:程序复制和粘贴行为 - 重定向