Share via


Abfangen eines Klicks auf eine Form oder auf ein Decorator-Element

Die folgenden Verfahren veranschaulichen, wie Sie einen Klick auf eine Form oder ein Symbol-Decorator-Element abfangen können. Sie können Klicks, Doppelklicks, Ziehbewegungen und andere Gesten abfangen und das Element reagieren lassen.

Abfangen von Klicks auf Formen

Schreiben Sie im DSL-Projekt in einer Codedatei, die von den generierten Codedateien getrennt ist, eine partielle Klassendefinition für die Formklasse. Überschreiben Sie OnDoubleClick() oder eine der anderen Methoden mit einem Namen, der mit On... beginnt. Beispiel:

public partial class MyShape // change
{
  public override void OnDoubleClick(DiagramPointEventArgs e)
  {
    base.OnDoubleClick(e);
    System.Windows.Forms.MessageBox.Show("Click");
    e.Handled = true;
  }
}

Hinweis

Legen Sie e.Handled auf true fest, es sei denn, dass das Ereignis an die enthaltende Form oder das Diagramm übergeben wird.

Abfangen von Klicks auf Decorator-Elemente

Bild-Decorator-Elemente werden auf einer Instanz der Klasse ImageField getragen, die über die Methode OnDoubleClick verfügt. Sie können die Klicks abfangen, wenn Sie eine ImageField-Unterklasse schreiben. Die Felder werden mit der Methode InitializeShapeFields eingerichtet. Daher müssen Sie diese Methode ändern, um Ihre Unterklasse anstelle des regulären ImageField zu instanziieren. Die InitializeShapeFields-Methode befindet sich im generierten Code der Formklasse. Sie können die Formklasse außer Kraft setzen, wenn Sie die Generates Double Derived-Eigenschaft wie in der folgenden Prozedur beschrieben festlegen.

Obwohl InitializeShapeFields eine Instanzmethode ist, wird sie nur einmal für jede Klasse aufgerufen. Daher gibt es nur eine Instanz von ClickableImageField für jedes Feld in jeder Klasse und nicht eine Instanz für jede Form im Diagramm. Wenn die Benutzer*innen doppelt auf eine Instanz klicken, müssen Sie feststellen, welche Instanz betroffen ist, wie im Code des Beispiels gezeigt wird.

Führen Sie die folgenden Schritte aus, um einen Klick auf ein Symbol-Decorator-Element abzufangen:

  1. Öffnen oder erstellen Sie eine DSL-Lösung.

  2. Wählen oder erstellen Sie eine Form mit einem Symbol-Decorator-Element, und ordnen Sie sie einer Domänenklasse zu.

  3. Erstellen Sie in einer Codedatei, die von den Dateien im Ordner GeneratedCode getrennt ist, die neue Unterklasse von ImageField:

    using Microsoft.VisualStudio.Modeling;
    using Microsoft.VisualStudio.Modeling.Design;
    using Microsoft.VisualStudio.Modeling.Diagrams;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace Fabrikam.MyDsl { // Change to your namespace
    internal class ClickableImageField : ImageField
    {
      // You can also override OnClick and so on.
      public override void OnDoubleClick(DiagramPointEventArgs e)
      {
        base.OnDoubleClick(e);
        // Work out which instance was hit.
        MyShape shapeHit = e.HitDiagramItem.Shape as MyShape;
        if (shapeHit != null)
        {
          MyDomainClass element =
              shapeHit.ModelElement as MyDomainClass;
          System.Windows.Forms.MessageBox.Show(
             "Double click on shape for " + element.Name);
          // If we do not set Handled, the event will
          // be passed to the containing shape:
          e.Handled = true;
        }
      }
    
       public ClickableImageField(string fieldName)
         : base(fieldName)
       { }
    }
    

    Sie sollten Handled auf true festlegen, wenn das Ereignis nicht an die enthaltende Form weitergegeben werden soll.

  4. Überschreiben Sie die Methode InitializeShapeFields in Ihrer Formklasse, indem Sie die folgende partielle Klassendefinition hinzufügen.

    public partial class MyShape // change
    {
     protected override void InitializeShapeFields
          (IList<ShapeField> shapeFields)
     {
      base.InitializeShapeFields(shapeFields);
      // You can see the above method in MyShapeBase
      // in the generated Shapes.cs
      // It has already added fields for the Icons.
      // So you will have to retrieve them and replace with your own.
      ShapeField unwantedField = shapeFields.First
          (field => field.Name == "IconDecorator1");
      shapeFields.Remove(unwantedField);
    
      // Now replicate the generated code from the base class
      // in Shape.cs, but with your own image constructor.
      ImageField field2 = new ClickableImageField("IconDecorator1");
      field2.DefaultImage = ImageHelper.GetImage(
        MyDslDomainModel.SingletonResourceManager
        .GetObject("MyShapeIconDecorator1DefaultImage"));
          shapeFields.Add(field2);
    }
    
  5. Erstellen Sie das Projekt, und führen Sie es aus.

  6. Klicken Sie doppelt auf das Symbol auf einer Instanz der Form. Ihre Testnachricht sollte angezeigt werden.

Abfangen von Klicks und Ziehen in „CompartmentShape“-Listen

Im folgenden Beispiel können Benutzer*innen Elemente in einer Depotform durch Ziehen neu anordnen.

  1. Erstellen Sie eine neue DSL-Lösung mithilfe der Lösungsvorlage Klassendiagramme.

    Sie können auch mit einer eigenen Lösung arbeiten, die Depotformen enthält. Dieser Code geht davon aus, dass eine Embedding Relationship zwischen den Modellelementen, die durch die Form dargestellt werden, und den Elementen, die in den Listenelementen des Depots dargestellt werden, besteht.

  2. Legen Sie die Eigenschaft Generates Double Derived der Depotform fest.

  3. Fügen Sie diesen Code einer Datei im DSL-Projekt hinzu.

  4. Passen Sie die Namen der Domänenklassen und Formen in diesem Code an Ihre eigene DSL an.

Der Code funktioniert wie folgt: In diesem Beispiel ist ClassShape der Name der Depotform.

  • Bei der Erstellung werden mehrere Mausereignishandler an jede Depotinstanz angefügt.

  • Das ClassShape.MouseDown-Ereignis speichert das aktuelle Element.

  • Wenn die Maus aus dem aktuellen Element verschoben wird, wird eine Instanz von MouseAction erstellt, die den Cursor fixiert und die Maus blockiert, bis sie losgelassen wird.

    Um andere Mausaktionen, wie beispielsweise die Auswahl des Textes eines Elements, nicht zu beeinträchtigen, wird die MouseAction erst erstellt, nachdem die Maus das ursprüngliche Element verlassen hat.

    Eine Alternative zur Erstellung einer MouseAction wäre, einfach auf MouseUp zu lauschen. Dieser Ansatz funktioniert jedoch nicht richtig, wenn die Benutzer*innen den Mauszeiger loslassen, nachdem sie ihn aus dem Bereich herausgezogen haben. Die MouseAction kann die entsprechende Aktion unabhängig davon ausführen, wo die Maus losgelassen wird.

  • Wenn die Maus losgelassen wird, ordnet MouseAction.MouseUp die Reihenfolge der Verbindungen zwischen den Modellelementen neu an.

  • Die Änderung der Rollenreihenfolge löst eine Regel aus, die die Anzeige aktualisiert. Dieses Verhalten ist bereits definiert, und es ist kein zusätzlicher Code erforderlich.

Der Beispielcode sieht wie folgt aus:

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
{
 /// <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.
  /// </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;
  }
 }
}