Adding adornment to a link


#1

Hi,

As you can see in the below screenshot, I have a connection where it has only two adornments. Using this I’m only able to re-route the link in one direction. How do I add more adornments to the link ?

image

Thanks.


#2

Assuming you want the user to maintain orthogonality, you’ll need to add more points to the route. That can be done by overriding Route.AddOrthoPoints:

  public class ExtraRoute : Route {
    protected override void AddOrthoPoints(Point startFrom, double fromDir, Point endTo, double toDir, Node fromnode, Node tonode) {
      AddPoint(startFrom);
      base.AddOrthoPoints(startFrom, fromDir, endTo, toDir, fromnode, tonode);
      AddPoint(endTo);
    }
  }

Use it in your Link template:

    <DataTemplate x:Key="LinkTemplate">
      <go:LinkPanel . . .>
        <go:Link.Route>
          <local:ExtraRoute Routing="Orthogonal" . . . />
        </go:Link.Route>
  . . .

#3

Thanks. This works fine.
But , the two points nearest to the ports(StartFrom and endTo points) can only be moved along one direction.
image
Is it possible to make all points more flexible in the sense that one can move them in both directions?


#4

So you have added Points to the route (i.e. Link.Points). Are there in fact two Points with the same value for points 1 and 2, and similarly two same points at points n-2 and n-3?

The default LinkReshapingTool behavior is to maintain orthogonality by only allowing one direction freedom of movement to the first and last reshape handles. When there are only 6 points, that means there is only one direction allowed for the middle 2 reshape handles. But if you have 8 points now in the route, there should be full freedom to the middle 2 reshape handles, at points 3 and 4, limited freedom to the reshape handles at points 2 and 5, and no reshape handle at all at points 1 and 6.

If you want full freedom of movement for the reshape handles at points 2 and 5, I suppose you could customize the LinkReshapingTool even further by having it automatically insert points when the user starts to drag the reshape handle at points 2 and 5. But only the first time when the drag starts – not on each mouse movement!

If I have time I can see if I can come with a sample this afternoon.


#5

I haven’t tested this thoroughly, but this custom LinkReshapingTool only inserts a point at the time of the first call to DoReshape.

  public class CustomLinkReshapingTool : LinkReshapingTool {
    public const String ToolCategory = "ReshapeLink";

    private bool _AlreadyAddedPoint = false;

    public override void DoActivate() {
      base.DoActivate();
      _AlreadyAddedPoint = false;
    }

    public override void UpdateAdornments(Part part) {
      Link link = part as Link;
      if (link == null) return;  // no Nodes

      Adornment adornment = null;
      if (link.IsSelected) {
        FrameworkElement selelt = link.Path;
        if (selelt != null && link.CanReshape() && Part.IsVisibleElement(selelt)) {
          adornment = link.GetAdornment(ToolCategory);
          if (adornment == null) {
            Route route = link.Route;
            IEnumerable<Point> pts = route.Points;
            int numpts = route.PointsCount;
            if (numpts < 2) return;
            bool ortho = route.Orthogonal;

            // this Adornment consists of a LinkPanel holding a bunch of ToolHandles
            LinkPanel panel = new LinkPanel();
            int firstindex = route.FirstPickIndex;
            int lastindex = route.LastPickIndex;
            int orthadjust = (ortho ? 1 : 0);
            if (/*route.Resegmentable &&*/ route.Curve != LinkCurve.Bezier) {
              for (int i = firstindex+orthadjust; i < lastindex-orthadjust; i++) {
                Point a = route.GetPoint(i);
                Point b = route.GetPoint(i + 1);
                if (IsApprox(a, b)) continue;
                FrameworkElement mh = null;
                // now the handle at the middle of each segment:
                if (IsApprox(a.X, b.X)) {
                  mh = new ToolHandle() {
                    Width = 6,
                    Height = 16,
                    Fill = Brushes.Yellow,
                    Stroke = Brushes.Black,
                    StrokeThickness = 1
                  };
                }
                else {
                  mh = new ToolHandle() {
                    Width = 16,
                    Height = 6,
                    Fill = Brushes.Yellow,
                    Stroke = Brushes.Black,
                    StrokeThickness = 1
                  };
                }
                NodePanel.SetFigure(mh, NodeFigure.Rectangle);
                // identify this particular handle within the LinkPanel
                LinkPanel.SetIndex(mh, i);  // the segment
                LinkPanel.SetFraction(mh, 0.5);  // how far along the segment
                if (IsApprox(a.X, b.X)) {
                  SetReshapeBehavior(mh, ReshapeBehavior.Horizontal);
                  mh.Cursor = Cursors.SizeWE;
                } else {
                  SetReshapeBehavior(mh, ReshapeBehavior.Vertical);
                  mh.Cursor = Cursors.SizeNS;
                }
                panel.Children.Add(mh);
              }
            }
            // don't bother creating handles for firstindex or lastindex
            for (int i = firstindex + 1; i < lastindex; i++) {
              // the handle at each vertex point:
              FrameworkElement h = new ToolHandle() {
                Width = 6,
                Height = 6,
                Fill = Brushes.Yellow,
                Stroke = Brushes.Black,
                StrokeThickness = 1
              };
              NodePanel.SetFigure(h, NodeFigure.Rectangle);
              // identify the segment for this particular handle
              LinkPanel.SetIndex(h, i);
              // now determines its reshape behavior and cursor, depending on whether Orthogonal et al.
              //if (i == firstindex + 1 && ortho) {
              //  Point a = route.GetPoint(firstindex);
              //  Point b = route.GetPoint(firstindex + 1);
              //  if (IsApprox(a, b)) {
              //    b = route.GetPoint(firstindex - 1);
              //    if (IsApprox(a.X, b.X)) {
              //      SetReshapeBehavior(h, ReshapeBehavior.Vertical);
              //      h.Cursor = Cursors.SizeNS;
              //    } else if (IsApprox(a.Y, b.Y)) {
              //      SetReshapeBehavior(h, ReshapeBehavior.Horizontal);
              //      h.Cursor = Cursors.SizeWE;
              //    }
              //  } else if (IsApprox(a.X, b.X)) {
              //    SetReshapeBehavior(h, ReshapeBehavior.Vertical);
              //    h.Cursor = Cursors.SizeNS;
              //  } else if (IsApprox(a.Y, b.Y)) {
              //    SetReshapeBehavior(h, ReshapeBehavior.Horizontal);
              //    h.Cursor = Cursors.SizeWE;
              //  }
              //} else if (i == lastindex - 1 && ortho) {
              //  Point a = route.GetPoint(lastindex - 1);
              //  Point b = route.GetPoint(lastindex);
              //  if (IsApprox(a, b)) {
              //    a = route.GetPoint(lastindex + 1);
              //    if (IsApprox(a.X, b.X)) {
              //      SetReshapeBehavior(h, ReshapeBehavior.Vertical);
              //      h.Cursor = Cursors.SizeNS;
              //    } else if (IsApprox(a.Y, b.Y)) {
              //      SetReshapeBehavior(h, ReshapeBehavior.Horizontal);
              //      h.Cursor = Cursors.SizeWE;
              //    }
              //  } else if (IsApprox(a.X, b.X)) {
              //    SetReshapeBehavior(h, ReshapeBehavior.Vertical);
              //    h.Cursor = Cursors.SizeNS;
              //  } else if (IsApprox(a.Y, b.Y)) {
              //    SetReshapeBehavior(h, ReshapeBehavior.Horizontal);
              //    h.Cursor = Cursors.SizeWE;
              //  }
              //} else {
                SetReshapeBehavior(h, ReshapeBehavior.All);
                h.Cursor = Cursors.SizeAll;
              //}
              panel.Children.Add(h);
            }

            adornment = new Adornment();
            adornment.AdornedElement = selelt;
            adornment.Category = ToolCategory;
            adornment.Content = panel;  // just provide the FrameworkElement as the Content and as the Visual Child
            adornment.LocationSpot = Spot.TopLeft;
          }
        }
        if (adornment != null) {
          Point loc = link.GetElementPoint(selelt, Spot.TopLeft);
          adornment.Position = loc;
          adornment.Remeasure();
        }
      }
      link.SetAdornment(ToolCategory, adornment);
    }

    protected override void DoReshape(Point newPoint) {
      Link link = this.AdornedLink;
      Route route = link.Route;
      int index = this.HandleIndex;
      ReshapeBehavior behavior = GetReshapeBehavior(this.Handle);
      if (behavior == ReshapeBehavior.None) return;
      if (route.Orthogonal) {  // need to adjust adjacent points as well
        if (!_AlreadyAddedPoint && LinkPanel.GetFraction(this.Handle) == 0.0) {
          _AlreadyAddedPoint = true;
          if (index == route.FirstPickIndex + 1) {
            System.Diagnostics.Debug.WriteLine("first " + index.ToString());
            route.InsertPoint(index, route.GetPoint(index));
            link.SetAdornment(ToolCategory, null);
            UpdateAdornments(link);

            return;
          } else if (index == route.LastPickIndex - 1) {
            System.Diagnostics.Debug.WriteLine("last " + index.ToString());
            route.InsertPoint(index, route.GetPoint(index));
            link.SetAdornment(ToolCategory, null);
            UpdateAdornments(link);
            return;
          }
        }
        if (index == route.FirstPickIndex+1) {
          int midfirst = route.FirstPickIndex+1;
          if (behavior == ReshapeBehavior.Vertical) {
            // move segment vertically
            route.SetPoint(midfirst, new Point(route.GetPoint(midfirst-1).X, newPoint.Y));
            route.SetPoint(midfirst+1, new Point(route.GetPoint(midfirst+2).X, newPoint.Y));
          } else if (behavior == ReshapeBehavior.Horizontal) {
            // move segment horizontally
            route.SetPoint(midfirst, new Point(newPoint.X, route.GetPoint(midfirst-1).Y));
            route.SetPoint(midfirst+1, new Point(newPoint.X, route.GetPoint(midfirst+2).Y));
          }
        } else if (index == route.LastPickIndex-1) {
          int midlast = route.LastPickIndex-1;
          if (behavior == ReshapeBehavior.Vertical) {
            // move segment vertically
            route.SetPoint(midlast-1, new Point(route.GetPoint(midlast-2).X, newPoint.Y));
            route.SetPoint(midlast, new Point(route.GetPoint(midlast+1).X, newPoint.Y));
          } else if (behavior == ReshapeBehavior.Horizontal) {
            // move segment horizontally
            route.SetPoint(midlast-1, new Point(newPoint.X, route.GetPoint(midlast-2).Y));
            route.SetPoint(midlast, new Point(newPoint.X, route.GetPoint(midlast+1).Y));
          }
        } else {
          // can move anywhere, but need to keep adjacent segments orthogonal
          int i = index;
          Point oldpt = route.GetPoint(i);
          Point before = route.GetPoint(i-1);
          Point after = route.GetPoint(i+1);
          if (IsApprox(before.X, oldpt.X) && IsApprox(oldpt.Y, after.Y)) {
            if (IsApprox(before.X, route.GetPoint(i-2).X) && !IsApprox(before.Y, route.GetPoint(i-2).Y)) {
              route.InsertPoint(i, new Point(newPoint.X, before.Y));
              index++;
              i++;
            } else route.SetPoint(i-1, new Point(newPoint.X, before.Y));
            if (IsApprox(after.Y, route.GetPoint(i+2).Y) && !IsApprox(after.X, route.GetPoint(i+2).X)) {
              route.InsertPoint(i+1, new Point(after.X, newPoint.Y));
            } else route.SetPoint(i+1, new Point(after.X, newPoint.Y));
          } else if (IsApprox(before.Y, oldpt.Y) && IsApprox(oldpt.X, after.X)) {
            if (IsApprox(before.Y, route.GetPoint(i-2).Y) && !IsApprox(before.X, route.GetPoint(i-2).X)) {
              route.InsertPoint(i, new Point(before.X, newPoint.Y));
              index++;
              i++;
            } else route.SetPoint(i-1, new Point(before.X, newPoint.Y));
            if (IsApprox(after.X, route.GetPoint(i+2).X) && !IsApprox(after.Y, route.GetPoint(i+2).Y)) {
              route.InsertPoint(i+1, new Point(newPoint.X, after.Y));
            } else route.SetPoint(i+1, new Point(newPoint.X, after.Y));
          } else if (IsApprox(before.X, oldpt.X) && IsApprox(oldpt.X, after.X)) {
            if (IsApprox(before.X, route.GetPoint(i-2).X) && !IsApprox(before.Y, route.GetPoint(i-2).Y)) {
              route.InsertPoint(i, new Point(newPoint.X, before.Y));
              index++;
              i++;
            } else route.SetPoint(i-1, new Point(newPoint.X, before.Y));
            if (IsApprox(after.X, route.GetPoint(i+2).X) && !IsApprox(after.Y, route.GetPoint(i+2).Y)) {
              route.InsertPoint(i+1, new Point(newPoint.X, after.Y));
            } else route.SetPoint(i+1, new Point(newPoint.X, after.Y));
          } else if (IsApprox(before.Y, oldpt.Y) && IsApprox(oldpt.Y, after.Y)) {
            if (IsApprox(before.Y, route.GetPoint(i-2).Y) && !IsApprox(before.X, route.GetPoint(i-2).X)) {
              route.InsertPoint(i, new Point(before.X, newPoint.Y));
              index++;
              i++;
            } else route.SetPoint(i-1, new Point(before.X, newPoint.Y));
            if (IsApprox(after.Y, route.GetPoint(i+2).Y) && !IsApprox(after.X, route.GetPoint(i+2).X)) {
              route.InsertPoint(i+1, new Point(after.X, newPoint.Y));
            } else route.SetPoint(i+1, new Point(after.X, newPoint.Y));
          }
          route.SetPoint(index, newPoint);
        }
      } else {  // no Orthogonal constraints, just set the new point
        route.SetPoint(this.HandleIndex, newPoint);
        // also adjust the end point if there is no spot for the port
        var fromNode = link.FromNode;
        var fromPort = link.FromPort;
        var fromSpot = Node.GetFromSpot(fromPort);
        var toNode = link.ToNode;
        var toPort = link.ToPort;
        var toSpot = Node.GetToSpot(toPort);
        if (this.HandleIndex == 1 && fromSpot.IsNoSpot) {
          var endpt = route.GetLinkPoint(fromNode, fromPort, Spot.None, true, false, toNode, toPort);
          route.SetPoint(0, endpt);
        }
        if (this.HandleIndex == route.PointsCount - 2 && toSpot.IsNoSpot) {
          var endpt = route.GetLinkPoint(toNode, toPort, Spot.None, false, false, fromNode, fromPort);
          route.SetPoint(route.PointsCount - 1, endpt);
        }
      }
    }

    public static bool IsApprox(double x, double y) {
      double d = x - y;
      return d < 0.5 && d > -0.5;
    }
    public static bool IsApprox(Point a, Point b) {
      return IsApprox(a.X, b.X) && IsApprox(a.Y, b.Y);
    }
  }