Overriding SpliceIntoLink Behaviour

I’ve got different stroke colors set up for pipes in my diagram, but when I splice nodes into the link the new links have the default color instead of the color of the original link… How can I handle the creation of the new links myself?

Ryan

Is the color of the link data-bound? If so, then the issue is how to make sure the multiple new link data objects in the model have the desired properties.

The DraggingTool just calls GraphLinksModel.RemoveLink(NodeType, PortKey, NodeType, PortKey) and then GraphLinksModel.AddLink(NodeType, PortKey, NodeType, PortKey).

So you could override the corresponding DeleteLink(LinkType) and InsertLink(LinkType) methods on the model. You could keep a reference to the deleted link data and then look at it and copy the desired properties when inserting.

But how would the model know that those RemoveLink and AddLink calls are due to the DropOnto behavior rather than just deleting an existing link or drawing a new one? Override the DraggingTool.DropOnto method to set a flag on the model, call the base method, and then clear the flag. Remember to forget the saved reference to the deleted link data when clearing the flag, to avoid retaining any garbage. Install an instance of your custom dragging tool by replacing the standard Diagram.DraggingTool.

Thanks Walter, that works great… But now that I’ve got that working I’m running into a problem trying to splice nodes into links which are not connected on 1 or both ends.

I’ve overridden the DropOnto method in my DraggingTool in order to remove the original link and add the new links, but now the ends of the original link that were not connected go to (0,0) instead of the original location of the endpoints on the original link.

The LinkData being passed to the InsertLink method appears to be the same when drawing a link connected at 1 end (which works properly) as when splicing into a link connected on 1 end, but the new link from the splice doesn’t seem to be receiving some information as to where to end the disconnected link. What am I missing?

Thanks
Ryan

If you set the Link.Route.Points, it should keep using one or both end points that are not connected to a port/node.

I’m not sure how/where I would go about doing that? My InsertLink looks like this:


protected override void InsertLink(PipeData linkdata)
        {
            if (this.isSplicing)
            {
                linkdata.PipeColor = splicingLinkData.PipeColor;

                if (this.doneSplicing)
                {
                    this.isSplicing = false;
                    this.splicingLinkData = null;
                }
                else
                    this.doneSplicing = this.isSplicing;
            }

            base.InsertLink(linkdata);
        }

How do I set the link’s points within that? and how do I determine which points to use from the original link… would it be enough to take the first and last points and use those?

I suppose you could copy the Points collection from splicingLinkData.

You might want to call Route.InvalidateRoute() afterwards, so that the link’s actual route is reasonable after being connected to a new node.

But I haven’t tried any of this.

I updated the routes like this:


model.AddLink(new PipeData() { From = linkdata.From, To = ((ItemData)draggingNode.Data).Key, Points = linkAPoints });
                        Link linkA = this.Diagram.Links.Last();
                        linkA.Route.InvalidateRoute();
<span ="Apple-tab-span" style="white-space:pre">			</span>//linkA.Route.Points = linkAPoints;

Which still draws the endpoints to (0,0) until I move the spliced in node, at which point the endpoints magically snap to where they were supposed to be. Is there something I can invoke on the diagram to make that automatically happen?

Having to move the node brought up another strange behaviour… when I move a node that is connected to a link which has nodes on both ends, the end of the link attached to the moving node does not move with the node. I tried overriding the MoveParts method in my dragging tool, but it seems to be changing the links routes after I have manually set them… Any idea for that one?


public override void MoveParts(Dictionary<Part, DraggingTool.Info> parts, Point offset)
        {
            base.MoveParts(parts, offset);

            if (parts.Keys.OfType<Node>().Count() == 1)
            {
                Node node = parts.Keys.OfType<Node>().First();
                foreach (Link link in node.LinksInto)
                {
                    PipeData pipeData = (PipeData)link.Data;
                    Point start = pipeData.Points.First();
                    Point end = node.Location;

                    pipeData.Points = makeOrthogonal(start, end);
                    link.Route.Points = pipeData.Points.ToList(); //.InvalidateRoute(); //
                }

                foreach (Link link in node.LinksOutOf)
                {
                    PipeData pipeData = (PipeData)link.Data;
                    Point start = node.Location;
                    Point end = pipeData.Points.Last();

                    pipeData.Points = makeOrthogonal(start, end);
                    link.Route.Points = pipeData.Points.ToList(); //.InvalidateRoute(); //
                }
            }
        }

Thanks again
Ryan

Oops, sorry about that – instead of calling InvalidateRoute(), try calling RecomputePoints().

I’m not sure I understand your other situation.
Why are you overriding MoveParts?

I was mistaken about the InvalidateRoute method working after moving the node, it was actually the MoveParts implementation I had that was correcting the placement… I tried RecomputePoints on both links but that seems to have the same effect of drawing the unconnected endpoint to (0,0), which is fine as setting the route points to the data points works just fine.

As for overriding MoveParts, the reason I did that is because of a problem when I have 2 nodes connected by a link. Say the first node is at (10,10) and the second node is at (50, 50), if I move the second node to (50,10) the endpoint of the link that was connected to the second node stays at (50, 50) instead of moving.

Well, that’s pretty odd. Clearly that’s not the normal behavior as you can see in practically every sample. I guess I would start by checking that the model thinks that the link is really connected to that node, and vice-versa. Check the value returned by GraphLinksModel.GetToNodeForLink and GetFromLinksForNode.

I checked out GraphLinksModel.GetToNodeForLink<span =“Apple-style-span” style=": rgb248, 248, 252; "> and GetFromLinksForNode while moving the nodes, and it seems that everything is staying connected. I did some further testing to find the conditions when the disconnected links are happening, and can report:

Link from node to nothing -> Link stays connected to node when moving
Disconnect from link, connect to link to node -> <span =“Apple-style-span” style="text-align: -webkit-auto; ">Link stays connected to node when moving
<span =“Apple-style-span” style="text-align: -webkit-auto; ">Connect from link, to link stays connected -> Link stays connected when to link node moves, doesn’t move when from link node moves
<span =“Apple-style-span” style="text-align: -webkit-auto; ">Swap nodes on each end of link -> Now the from link node moving stays connected, to link node doesn’t
<span =“Apple-style-span” style="text-align: -webkit-auto; ">
<span =“Apple-style-span” style="text-align: -webkit-auto; ">Create link between 2 nodes -> Link doesn’t move when either node moves
<span =“Apple-style-span” style="text-align: -webkit-auto; ">Disconnect to link -> Link doesn’t move when connected node moves
Disconnect from link, reconnect to link -> Link doesn’t move when connected node moves

Also, for my own clarity… What (if any) is the relationship between the Link.Data.Points and Link.Route.Points?

You might want to check all of your method overrides again.

Link.Data.Points might not even exist – it depends on how the model link data class is implemented.

Link.Route.Points is a copy of the array of Points that is actually used to construct the Geometry for the LinkShape – the Shape that “shows” the route.

Route.Points is not a DependencyProperty, so it cannot be data-bound. However, several of the samples demonstrate how to save and load the routing information (i.e. the Points collection) by copying the information from the Route to the link data and having the link data points turned into XML, and vice-versa when loading.

The only methods of the DraggingTool I have overridden are ConsiderDragOver, DropOnto, and MoveParts. I have also overridden UpdateRouteDataPoints in the PartManager in order to update the Data.Points.

With what I currently have, while I am dragging the node the link is staying attached, but once I let go of the left mouse button the node stays put but the link goes back to its previous position. Can you tell me which methods are called when moving a node? It seems the Route is being set again after base.DoMouseUp is called.

I’d start by assuming that I did not want to override MoveParts.

If I comment out my overridden MoveParts then the link doesn’t move at all with the node.

The only time that you set the Route.Points is when splicing into a link happens, yes? There is no other code that sets it, nor at any other time, other than loading from a database/file, right?

Yes, the only time I am setting Route.Points is when loading a link or splicing into the link (which isn’t happening in these cases). I’m not sure if there’s any possibility of the xaml I have effecting anything, but this is what I have…

Node Template:


<DataTemplate x:Key="Oil">
        <Grid go:Part.SelectionAdorned="True"
              go:Part.Resizable="True"
              go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}"
              Height="{Binding Path=Data.Height, Mode=TwoWay}" Width="{Binding Path=Data.Width, Mode=TwoWay}">
            <FrameworkElement.ToolTip>
                <TextBlock Text="{Binding Path=Data.Text}" />
            </FrameworkElement.ToolTip>
            <Viewbox Stretch="Uniform">
                <Grid Background="White"
                      go:Node.PortId="" Cursor="Hand"
                      go:Node.FromSpot="AllSides" go:Node.ToSpot="AllSides"
                      go:Node.LinkableFrom="True" go:Node.LinkableTo="True">
                    <Path Fill="#000000" Data="F1 M 94.246,47.123 C 94.246,73.148 73.148,94.247 47.123,94.247 C 21.098,94.247 0.000,73.148 0.000,47.123 C 0.000,21.098 21.098,0.000 47.123,0.000 C 73.148,0.000 94.246,21.098 94.246,47.123 Z"/>
                </Grid>
            </Viewbox>
            <Rectangle Fill="Transparent" Margin="12" />
        </Grid>
    </DataTemplate>

Pipe Template:


<DataTemplate x:Key="PipeTemplate">
            <go:LinkPanel go:Part.Reshapable="True"
                          go:Part.DropOntoBehavior="SplicesIntoLink">
                <go:Link.Route>
                    <go:Route Routing="AvoidsNodes" Curve="JumpOver" Corner="5"
                              FromEndSegmentDirection="RotatedNodeOrthogonal"
                              ToEndSegmentDirection="RotatedNodeOrthogonal"
                              RelinkableFrom="True" RelinkableTo="True"
                              ToShortLength="5" /> <!-- -->
                </go:Link.Route>
                
                <go:LinkShape Name="linkBase" Stroke="{Binding Path=Data.PipeColor}" StrokeThickness="3" />
                <Path go:LinkPanel.ToArrow="Triangle" Fill="Black" />
                
                <go:LinkingTool PortGravity="25" />
                <go:RelinkingTool PortGravity="25" />
                 
            </go:LinkPanel>            
        </DataTemplate>

Diagram:


<go:Diagram x:Name="myDiagram"
                    HorizontalContentAlignment="Stretch"
                    VerticalContentAlignment="Stretch"
                    NodeTemplateDictionary="{DynamicResource NodeTemplateDictionary}"
                    LinkTemplate="{StaticResource PipeTemplate}"
                    LinkDrawn="myDiagram_LinkDrawn"       
                    AllowDrop="True"
                    GridVisible="True" 
                    GridSnapEnabled="True"
                    AllowDelete="True">
            
            <go:Diagram.LinkingTool>
                <go:LinkingTool PortGravity="25" />
            </go:Diagram.LinkingTool>
            <go:Diagram.RelinkingTool>
                <go:RelinkingTool PortGravity="25" />
            </go:Diagram.RelinkingTool>
            
            <go:Diagram.DraggingTool>
                <local:CustomDraggingTool DropOntoEnabled="True" FromPortId="Out" ToPortId="In" />
            </go:Diagram.DraggingTool>
            <go:Diagram.ResizingTool>
                <local:CustomResizingTool />
            </go:Diagram.ResizingTool>
            <go:Diagram.RotatingTool>
                <go:RotatingTool SnapAngleMultiple="90" SnapAngleEpsilon="45" />
            </go:Diagram.RotatingTool>
        </go:Diagram>        

If you specify the geometry of a Path using Data="…", it’s treated as a rectangular shape.
There is no reasonable programmatic way to figure out what figures it has, to determine the nearest intersection points.
Although it wouldn’t matter anyway, since there’s a filled Grid surrounding the Path.

What are two Tools doing inside your LinkPanel?
Maybe we should signal an error if we detect anything like that.
I don’t know if that would explain the weird routing behavior, but I suspect not.
I don’t see anything else amiss, but it’s sometimes hard to tell with a glance.

The tools in the LinkPanel were there when I was trying to figure out the snap-to distance, and just forgot to remove them after I got it working by setting them in the diagram.