共用方式為


HOW TO:加入拖放處理常式

您可以將拖放事件的處理常式新增到您的 DSL,以便使用者可以拖曳項目拖曳到您的圖表中從其他的圖表或其他部分的Visual Studio。您也可以加入處理常式,例如連按兩下事件。拖放,然後按兩下滑鼠的處理常式在一起,即為 「 筆勢的處理常式。

本主題討論其他圖表上的拖放筆勢的威脅。在單一圖表中移動和複製事件,請考慮定義子類別的另一種ElementOperations。如需詳細資訊,請參閱 HOW TO:程式複製和貼上行為 - 重新導向。您也可以自訂 DSL 定義。

本主題內容

  • 前兩個的各節會說明另一種方式,定義動作處理常式:

    • 定義動作處理常式,可以覆寫的 ShapeElement 方法。OnDragDropOnDoubleClick, 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– 這個方法會呼叫,如果使用者放開滑鼠按鈕時滑鼠指標停在這個圖形或圖表中,如果OnDragOver(DiagramDragEventArgs e)先前設定e.Effect以外的值為None。

    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– 當使用者按兩下圖表的圖形時,會呼叫這個方法。

    如需詳細資訊,請參閱 HOW TO:攔截圖案或 Decorator 上的點選

定義IsAcceptableDropItem(e)來判斷拖曳的項目是否可以接受的以及 ProcessDragDropItem(e) 来捨棄這個項目時,更新您的模型。這些方法從事件引數,必須先解壓縮此項目。如何執行這些作業的相關資訊,請參閱如何取得拖曳的項目參考。

定義動作處理常式,可以使用 MEF

MEF (管理擴充性架構) 可讓您定義以最小組態可安裝的元件。如需詳細資訊,請參閱 Managed Extensibility Framework (MEF)

若要定義 MEF 動作處理常式

  1. 新增至您DslDslPackage專案MefExtension檔案中所描述的使用 MEF 擴充您的 DSL

  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參數。以兩種形式,所提供的資訊:

  • IDataObject Data– 此屬性通常是執行序列化的來源物件版本,會以一個以上的格式。它最有用的功能如下:

    • diagramEventArgs.Data.GetDataFormats() – 列出您可以在此解碼被拖曳的物件的格式。比方說,如果使用者從桌面,拖曳檔案,可使用的格式將包含檔案名稱 ("FileNameW")。

    • diagramEventArgs.Data.GetData(format)– 將解碼拖曳的物件,以指定的格式。將物件轉換成適當的型別。例如:

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

      您也可以自己自訂的格式傳輸從來源模型匯流排參考的物件。如需詳細資訊,請參閱如何傳送模型匯流排參考拖放在。

  • ElementGroupPrototypePrototype– 若要讓使用者從 DSL 或 UML 模型] 拖曳項目,請使用這個屬性。項目群組原型包含一個或多個物件、 連結及它們的屬性值。它也用在貼上作業,以及當您要新增項目,從工具箱] 中。在原型中,物件和其型別會由 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、 alt 鍵或 SHIFT 鍵。

如何恢復成原始的拖曳的項目

Data和Prototype的事件引數屬性包含只拖曳圖案的參照。通常,如果您想要建立的物件中的目標衍生自以某種方式原型的 DSL 時,您需要取得存取權,比方說,讀取檔案內容,或瀏覽至圖案所代表的模型項目。您可以使用 Visual Studio 模型匯流排來替代此作業。

若要準備模型匯流排的 DSL 專案

  • 使來源 DSL 可供存取Visual Studio模型匯流排:

    1. 下載並安裝 Visual Studio 模型匯流排擴充功能,如果尚未安裝。如需詳細資訊,請參閱視覺化和模型的 SDK。

    2. DSL 設計工具中開啟來源 DSL DSL 定義檔。在設計介面上按一下滑鼠右鍵,然後按一下 [ 啟用 Modelbus。在對話方塊中,選擇一或兩個選項。按一下 [確定]。"ModelBus"的新專案加入至 DSL 方案。

    3. 按一下 [ 轉換所有的範本 ,並重建方案。

若要讓物件從來源 DSL

  • 在您的 ElementOperations 子類別中覆寫Copy() ,讓它將模型匯流排參考 (MBR) 編碼成 IDataObject。當使用者開始拖曳來源圖表中,會呼叫這個方法。編碼的 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. 下列範例會示範如何取得來源模型項目的存取權:

      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;
  }
 }
}

請參閱

概念

自訂複製行為

HOW TO:程式複製和貼上行為 - 重新導向