Drawing a link with no attached nodes

Just wondering if it’s possible to draw a link which is not attached to anything by just clicking and dragging? Obviously by default that behavior is for selection, but I would like to be able to draw unconnected links this way rather than dragging a predefined link onto the diagram and then moving and resizing that link (as in the draggable link demo).

Thanks
Ryan

[code] public class RoutingLinkTool : DiagramTool {

public override void DoActivate() {
  base.DoActivate();
  StartTransaction("New Routed Link");
  CaptureMouse();

#if SILVERLIGHT
this.Diagram.Cursor = Cursors.Stylus;
#else
this.Diagram.Cursor = Cursors.Cross;
#endif
}

public override void DoDeactivate() {
  base.DoDeactivate();
  this.Diagram.Cursor = null;
  ReleaseMouse();
  StopTransaction();
  this.CurrentData = null;
  this.CurrentLink = null;
}

MyLinkData CurrentData { get; set; }
Link CurrentLink { get; set; }
String IntendedLayerName { get; set; }

private void AddPoint(Point p) {
  // make sure there's a new MyLinkData in the model
  MyLinkData d = this.CurrentData;
  if (d == null) {
    // create and initialize the MyLinkData
    d = new MyLinkData();
    d.Points = new List<Point>();
    this.CurrentData = d;
    // add the MyLinkData to the model to produce a Link
    ((MyModel)this.Diagram.Model).AddLink(d);
    this.CurrentLink = this.Diagram.PartManager.FindLinkForData(d, this.Diagram.Model);
    // temporarily put into the temporary "Tool" layer, so it's in front of anything else
    this.IntendedLayerName = this.CurrentLink.LayerName;
    this.CurrentLink.LayerName = "Tool";
  }
  // add the Point to the data and to the PointCollection
  var pts = d.Points.ToList();
  pts.Add(p);
  d.Points = pts;
  this.CurrentLink.Route.Points = pts;
}

private void MoveLastPoint(Point p) {
  MyLinkData d = this.CurrentData;
  if (d == null) return;
  if (((List<Point>)d.Points).Count > 0) {
    // replace the last point in the data and in the PointCollection
    ((List<Point>)d.Points)[((List<Point>)d.Points).Count-1] = p;
  }
}

private void RemoveLastPoint() {
  MyLinkData d = this.CurrentData;
  if (d == null) return;
  if (((List<Point>)d.Points).Count > 0) {
    // replace the last point in the data and in the PointCollection
    ((List<Point>)d.Points).RemoveAt(((List<Point>)d.Points).Count-1);
  }
}

private void FinishShape() {
  MyLinkData d = this.CurrentData;
  if (d == null) return;
  // if there aren't enough points, discard it by cancelling
  if (((List<Point>)d.Points).Count < 2) {
    DoCancel();
  } else {
    // put into desired layer and update the diagram bounds
    this.CurrentLink.LayerName = this.IntendedLayerName;
    this.CurrentLink.Route.Points = (List<Point>)d.Points;
    this.Diagram.Panel.UpdateDiagramBounds();
    // select the new Link
    this.CurrentLink.IsSelected = true;
    // and tell the tool to commit the transaction
    this.TransactionResult = "New Routed Link";
    StopTool();
  }
}

public override void DoMouseDown() {
  if (!this.Active) {
    DoActivate();
    // the first point
    AddPoint(this.Diagram.LastMousePointInModel);
  }
  if (IsLeftButtonDown()) {
    // a temporary end point to be replaced by DoMouseMove
    AddPoint(this.Diagram.LastMousePointInModel);
  } else {  // e.g. right mouse down
    FinishShape();
  }
}

public override void DoMouseMove() {
  if (this.Active) {
    // the temporary last point follows the mouse
    MoveLastPoint(this.Diagram.LastMousePointInModel);
  }
}

public override void DoMouseUp() {
  // don't stop this tool (the default behavior is to call StopTool)
}

public override void DoKeyDown(KeyEventArgs e) {
  if (e.Key == Key.Enter) {  // accept
    // remove the temporary point, which is last
    RemoveLastPoint();
    FinishShape();
  } else if (e.Key == Key.Z) {  // undo
    // remove a point, and treat the last one as a temporary one
    RemoveLastPoint();
    MoveLastPoint(this.Diagram.LastMousePointInModel);
  } else {
    base.DoKeyDown(e);
  }
}

public override void DoCancel() {
  if (this.CurrentData != null) {
    ((MyModel)this.Diagram.Model).RemoveLink(this.CurrentData);
  }
  base.DoCancel();
}

}[/code]
Use this as a modal tool by setting Diagram.CurrentTool, such as:
myDiagram.CurrentTool = new RoutingLinkTool() { Diagram = myDiagram };

This was actually adapted from the PolylineDrawingTool that I provided in this forum, http://www.nwoods.com/forum/forum_posts.asp?TID=3485.

If you want to draw orthogonal routes, you’ll need to adapt this code to automatically put in extra points to ensure that the route is orthogonal.

Works great, thanks… For anyone interested I did make the lines orthogonal, made the drawing a click-drag-release instead of multiple clicks, and added a temporary line to show where the link was going. Here’s what I changed:


private void FinishShape()
        {
            PipeData d = this.CurrentData;
            if (d == null) return;
            // if there aren't enough points, discard it by cancelling
            if (((List<Point>)d.Points).Count < 2)
            {
                DoCancel();
            }
            else
            {
                Point start = d.Points.First();
                Point end = d.Points.Last();

                d.Points = makeOrthogonal(start, end);

                Brush pipeColor = ((Document)((FrameworkElement)this.Diagram.Parent).Parent).PipeColor;

                this.CurrentLink.Path.Stroke = pipeColor;
                this.CurrentLink.Path.StrokeThickness = 3.0;
                this.CurrentLink.DropOntoBehavior = DropOntoBehavior.SplicesIntoLink;
                this.CurrentLink.RelinkableTo = true;
                this.CurrentLink.RelinkableFrom = true;

                d.PipeColor = pipeColor;

                // put into desired layer and update the diagram bounds
                this.CurrentLink.LayerName = this.IntendedLayerName;
                this.CurrentLink.Route.Points = (List<Point>)d.Points;

                this.CurrentLink.Route.Routing = LinkRouting.AvoidsNodes;
                this.CurrentLink.Route.Curve = LinkCurve.JumpOver;
                this.CurrentLink.Route.Corner = 5;

                this.Diagram.Panel.UpdateDiagramBounds();
                // select the new Link
                this.CurrentLink.IsSelected = true;
                // and tell the tool to commit the transaction
                this.TransactionResult = "New Routed Link";
                
                StopTool();
            }
        }

        private List<Point> makeOrthogonal(Point start, Point end)
        {
            double midpoint1X;
            double midpoint2X;
            double midpoint1Y;
            double midpoint2Y;

            if (start.X < end.X)
                midpoint1X = start.X + 20;
            else
                midpoint1X = start.X - 20;

            midpoint2X = midpoint1X;
            midpoint1Y = start.Y;
            midpoint2Y = end.Y;

            List<Point> points = new List<Point>();
            
            points.Add(start);
            points.Add(new Point(midpoint1X, midpoint1Y));
            points.Add(new Point(midpoint2X, midpoint2Y));
            points.Add(end);

            return points;
        }

        public override void DoMouseDown()
        {
            if (!this.Active)
            {
                DoActivate();
                // the first point
                AddPoint(this.Diagram.LastMousePointInModel);
                AddPoint(this.Diagram.LastMousePointInModel);

                this.CurrentLink.Path.Stroke = Brushes.Blue;
                this.CurrentLink.Path.StrokeThickness = 1.0;
            }
        }

        public override void DoMouseMove()
        {
            if (this.Active)
            {
                // the temporary last point follows the mouse
                MoveLastPoint(this.Diagram.LastMousePointInModel);
                
                this.CurrentLink.Route.Points = (List<Point>)this.CurrentData.Points;
            }
        }

        public override void DoMouseUp()
        {
            FinishShape();
        }

How can I keep the link drawing tool active after completing a link? I would like to be able to draw multiple lines without having to click the button each time to re-set the current tool.

You’ll need to implement DoMouseUp completely, actually creating the appropriate link, because the default implementation calls StopTool.

Alternatively you could let it call StopTool and then explicitly restart the LinkingTool again by setting its LinkingTool.StartPort property and then setting Diagram.CurrentTool. That might be easier to implement.