自定义复制行为
在使用 Visual Studio 可视化和建模 SDK 创建的域特定语言 (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:电路图示例上的示例演示了此方法。
若要获得此效果,请将形状和连接符添加到复制的 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:电路图示例上的示例演示了此方法。
为实现此效果,请重写 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 定义中,在关系的一边选择某个角色,然后在“属性”窗口中设置“传播复制”值。
有三个值:
不传播复制
仅将复制传播到链接 - 在粘贴该组时,此链接的新副本将引用链接另一端上的现有元素。
将复制传播到链接和相反的角色扮演者 - 复制的组包括位于链接另一端上的元素的副本。
所进行的更改将同时影响元素和复制的图像。
编程复制和粘贴行为
与复制、粘贴、创建和删除对象有关的 DSL 行为的许多方面都由耦合到关系图的 ElementOperations 的实例控制。 通过从 ElementOperations 中派生自己的类并重写关系图类的 ElementOperations 属性,可修改 DSL 的行为。
提示
有关使用程序代码自定义模型的详细信息,请参阅在程序代码中导航和更新模型。
定义自己的 ElementOperations
在 DSL 项目的新文件中,创建派生自 DesignSurfaceElementOperations 的类。
为关系图类添加分部类定义。 此类的名称可以在 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)
,用于将源元素合并到目标中。
CanMerge()
调用 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 中了解设置方法。 有关如何设置命令的详细信息,请参阅如何:向快捷菜单添加命令。
通过在 DslPackage 项目中添加 MyDslClipboardCommandSet
的分部类定义,可重写 ProcessOnMenuCopyCommand。
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;
}
}
}
相关内容
注意
“文本模板转换”组件将作为“Visual Studio 扩展开发”工作负载的一部分自动安装 。 还可以从 Visual Studio 安装程序的“SDK、库和框架”类别下的“单个组件”选项卡进行安装 。 从“单个组件”选项卡安装“建模 SDK”组件 。