Dynamic Ports Not Extending Past Edge of Node

I am working on implementing dynamic ports for our nodes, and am having some issues with the display of the nodes. The static ports display over the edge of the node, but for whatever reason the dynamic ports in the ItemsControl don’t display past the edge. Any suggestion for how to get them to display show up?


<span style="font-size: 12px; line-height: 1.4;">       </span><span style="font-size: 12px; line-height: 1.4;"> </span><go:SpotPanel
            go:Part.SelectionAdorned="False"
            go:Node.Location="{Binding Path=Data.Location, Mode=OneWay}"            
            go:Part.Resizable="False"
            go:Part.ResizeElementName="Container"
            go:Part.ResizeAdornmentTemplate="{DynamicResource NodeResizeAdornmentTemplate}"
            go:Part.Rotatable="{Binding Path=Data.Rotatable, Mode=OneWay}"
            go:Node.RotationAngle="{Binding Path=Data.Angle, Mode=TwoWay}" 
            go:Part.RotateAdornmentTemplate="{DynamicResource NodeRotationAdornmentTemplate}"
            go:Part.Reshapable="False">

            <ContextMenuService.ContextMenu>
                <ContextMenu ItemsSource="{DynamicResource NodeContextMenuTemplate}" />
            </ContextMenuService.ContextMenu>

            <Grid Name="Container"
                  Height="{Binding Path=Node.Diagram.NodeHeight, Mode=OneWay}"
                  Width="{Binding Path=Node.Diagram.NodeWidth, Mode=OneWay}"
                  RenderTransformOrigin ="0.5 0.5">
                <Grid.RenderTransform>
                    <ScaleTransform ScaleX="{Binding Path=Data.ScaleX, Mode=OneWay}" />
                </Grid.RenderTransform>
                <Grid>
                    <FrameworkElement.ToolTip>
                        <TextBlock Text="{Binding Path=Data.Category}" />
                    </FrameworkElement.ToolTip>

                    <Viewbox Stretch="Fill">
                        <Grid UseLayoutRounding="True">
                            ...
                        </Grid>
                    </Viewbox>

                    <Grid UseLayoutRounding="True">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="7.5*"/>
                            <RowDefinition Height="45*"/>
                            <RowDefinition Height="47.5*"/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="72*"/>
                            <ColumnDefinition Width="10*"/>
                            <ColumnDefinition Width="18*"/>
                        </Grid.ColumnDefinitions>

                        <Viewbox Stretch="Uniform" Grid.Column="1" Grid.Row="1" RenderTransformOrigin="0.5 0.5">
                            <Viewbox.RenderTransform>
                                <ScaleTransform ScaleX="{Binding Path=Data.ScaleX, Mode=OneWay}" />
                            </Viewbox.RenderTransform>                            
                            <Path HorizontalAlignment="Center" VerticalAlignment="Center"                                   
                                          Fill="Black" 
                                          Data="{Binding Path=Data.TagPoints, Mode=OneWay}"/>
                        </Viewbox>
                    </Grid>
                </Grid>
            </Grid>
            
            <Rectangle Width="7" Height="7" Fill="Red" go:SpotPanel.Alignment="Center"
                       StrokeThickness="1" Stroke="Red" go:SpotPanel.Spot="0 0.2857 0 0" />

            <Rectangle Width="7" Height="7" Fill="Red" go:SpotPanel.Alignment="Center"
                       StrokeThickness="1" Stroke="Red" go:SpotPanel.Spot="0 0.7143 0 0" />

            <Rectangle Width="7" Height="7" Fill="Red" go:SpotPanel.Alignment="Center"
                       StrokeThickness="1" Stroke="Red" go:SpotPanel.Spot="0.25 0.08 0 0" />

            <Rectangle Width="7" Height="7" Fill="Red" go:SpotPanel.Alignment="Center"
                       StrokeThickness="1" Stroke="Red" go:SpotPanel.Spot="0.95 0.2857 0 0" />

            <Rectangle Width="7" Height="7" Fill="Red" go:SpotPanel.Alignment="Center"
                       StrokeThickness="1" Stroke="Red" go:SpotPanel.Spot="0.25 1 0 0" />

            <ItemsControl ItemsSource="{Binding Path=Data.DynamicAnchors}">
                <ItemsControl.ItemContainerStyle>
                    <Style TargetType="ContentPresenter">
                        <Setter Property="go:SpotPanel.Spot" Value="{Binding Path=AnchorSpot, Mode=OneWay}" />
                        <Setter Property="go:SpotPanel.Alignment" Value="Center" />
                        <Setter Property="Width" Value="{Binding ElementName=Container, Path=ActualWidth, Mode=OneWay}" />
                        <Setter Property="Height" Value="{Binding ElementName=Container, Path=ActualHeight, Mode=OneWay}" />
                    </Style>
                </ItemsControl.ItemContainerStyle>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Rectangle Width="7" Height="7" Fill="Green"
                                   StrokeThickness="1"
                                   Stroke="Green"
                                   go:Node.PortId="{Binding Path=ID, Mode=OneWay}" />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <go:SpotPanel VerticalAlignment="Center" HorizontalAlignment="Center" 
                                      Width="{Binding ElementName=Container, Path=Width, Mode=OneWay}" 
                                      Height="{Binding ElementName=Container, Path=Height, Mode=OneWay}" />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
            </ItemsControl>
        </go:SpotPanel>  

Here’s what the ports look like:

The SpotPanel expects to position its immediate elements relative to the main element. Are you sure that the Rectangles created for the items in the ItemsControl really are the immediate children of the SpotPanel?

I’ve fixed the issue of the with the ports being cut off by adding a margin around the object. Now my object template looks like this:


<Grid Name="Outer"
            go:Part.SelectionAdorned="False" go:Part.SelectionElementName="Container"
            go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}"  go:Node.LocationElementName="Container"             
            go:Part.Resizable="{Binding Path=Data.Resizable, Mode=OneTime}"
            go:Part.ResizeElementName="Container"
            go:Part.ResizeAdornmentTemplate="{DynamicResource NodeResizeAdornmentTemplate}"
            go:Part.Rotatable="{Binding Path=Data.Rotatable, Mode=OneWay}"
            go:Node.RotationAngle="{Binding Path=Data.Angle, Mode=TwoWay}" 
            go:Part.RotateAdornmentTemplate="{DynamicResource NodeRotationAdornmentTemplate}"
            go:Part.Reshapable="False">


            <go:SpotPanel Margin="3.5">
                <Grid Name="Container" go:SpotPanel.Spot="0.5 0.5 0 0" go:SpotPanel.Alignment="Center" 
                      Height="{Binding Path=Data.Height, Mode=TwoWay}"
                      Width="{Binding Path=Data.Width, Mode=TwoWay}"
                      RenderTransformOrigin ="0.5 0.5">
                                                                                                                                                                                                                                    
                    <Grid>
                    <span style="white-space: pre;">	</span><Viewbox Stretch="Fill">
<span style="white-space: pre;">				</span>...
<span style="white-space: pre;">			</span></Viewbox>
                    </Grid>

<div style='font-family: "Courier New", Courier, mono; white-space: pre;'>            <Rectangle Width="7" Height="7" Fill="Red" go:SpotPanel.Alignment="Center"<div style='font-family: "Courier New", Courier, mono; white-space: pre;'>                       StrokeThickness="1" Stroke="Red" go:SpotPanel.Spot="0 0.2857 0 0" /><div style='font-family: "Courier New", Courier, mono; white-space: pre;'>
<div style='font-family: "Courier New", Courier, mono; white-space: pre;'>            <Rectangle Width="7" Height="7" Fill="Red" go:SpotPanel.Alignment="Center"<div style='font-family: "Courier New", Courier, mono; white-space: pre;'>                       StrokeThickness="1" Stroke="Red" go:SpotPanel.Spot="0 0.7143 0 0" /><div style='font-family: "Courier New", Courier, mono; white-space: pre;'>
<div style='font-family: "Courier New", Courier, mono; white-space: pre;'>            <Rectangle Width="7" Height="7" Fill="Red" go:SpotPanel.Alignment="Center"<div style='font-family: "Courier New", Courier, mono; white-space: pre;'>                       StrokeThickness="1" Stroke="Red" go:SpotPanel.Spot="0.25 0.08 0 0" /><div style='font-family: "Courier New", Courier, mono; white-space: pre;'>
<div style='font-family: "Courier New", Courier, mono; white-space: pre;'>            <Rectangle Width="7" Height="7" Fill="Red" go:SpotPanel.Alignment="Center"<div style='font-family: "Courier New", Courier, mono; white-space: pre;'>                       StrokeThickness="1" Stroke="Red" go:SpotPanel.Spot="0.95 0.2857 0 0" /><div style='font-family: "Courier New", Courier, mono; white-space: pre;'>
<div style='font-family: "Courier New", Courier, mono; white-space: pre;'>            <Rectangle Width="7" Height="7" Fill="Red" go:SpotPanel.Alignment="Center"<div style='font-family: "Courier New", Courier, mono; white-space: pre;'>                       StrokeThickness="1" Stroke="Red" go:SpotPanel.Spot="0.25 1 0 0" /><div style='font-family: "Courier New", Courier, mono; white-space: pre;'>
<span style="line-height: 1.4; font-size: 12px;">           </span><span style="line-height: 1.4; font-size: 12px;"> </span><font face="Courier New, Courier, mono"><span style="white-space: pre;"></go:SpotPanel></span></font>
<font face="Courier New, Courier, mono"><span style="white-space: pre;">
</span></font>
<font face="Courier New, Courier, mono"><span style="white-space: pre;"><span style="white-space: pre;">	</span><ItemsControl Name="DynamicPorts"
</span></font>
<font face="Courier New, Courier, mono"><span style="white-space: pre;">                          ItemsSource="{Binding Path=Data.DynamicAnchors}"
</span></font>
<font face="Courier New, Courier, mono"><span style="white-space: pre;">                          Style="{DynamicResource DynamicPortListDiagramStyle}"
</span></font>
<font face="Courier New, Courier, mono"><span style="white-space: pre;">                          Width="{Binding ElementName=Outer, Path=ActualWidth, Mode=OneWay}"</span></font>
<font face="Courier New, Courier, mono"><span style="white-space: pre;">                          Height="{Binding ElementName=Outer, Path=ActualHeight, Mode=OneWay}"/></span></font>
<font face="Courier New, Courier, mono"><span style="white-space: pre;"></span></font>
<font face="Courier New, Courier, mono"><span style="white-space: pre;"></Grid></span></font>

Which has created a couple more issues…

The first issue is how to handle the margin for positioning the dynamic ports. I want to ensure that the furthest out the ports can be is to have their center on the path of the object, but I’m not sure how to make that happen. If I were to try and position the port at the far left of the object, the Spot X value could be 0 as with the static ports already in the template, but that would not take into account the margin and the port would be to the left of the existing ports. I could calculate the proper X value for the spot based on the width & margin, but that would only work for the object at that size and would need to be re-calculated if it was resized. Lastly I could add an XOffset value for the Spot, but that would only work for the edges of the object, any spots inside the object would need a calculated value that would also need to be recalculated as the object is resized. I guess my question for this is are there anything I am missing that would take care of these issues? Or is there a better way to nest my controls to avoid these problems?

The second issue is with resizing… Once the size of the object is increased, the outer container and in turn the ItemsControl with the dynamic ports can’t be made smaller. I know this is because the ItemsControl is growing with the root element and preventing it from shrinking… But along with the first issue, any suggestion for how to best position these controls to get this working?

Thanks
Ryan

The problem appears to be that you really want a combination of a SpotPanel and an ItemsControl. Can you specify the ItemsControl.ItemsPanel to be a SpotPanel? Then the ItemTemplate (a DataTemplate) can bind or set the SpotPanel attached properties.

I haven’t tried this, but I think that is the way to go. Presumably you could get rid of all of those explicit Rectangles too.

Regarding the positioning of objects “on” the edge of a Shape, it depends on how the Geometry of the Shape is defined. If you just set its Data using the Path markup syntax, it’s basically a rectangular box and there’s no way to figure out where the edges really are. But if it’s defined as a real PathGeometry, then we can compute the intersection points with an infinite line and find such a point that is closest to a given point. That is what Route does when calculating a link connection point. If you really want to pursue this course of action, I can help.

We are actually using a SpotPanel as the panel for the control already, though as you suggested above the Items are not direct descendants of the panel. The hierarchy looks like:

ItemsControl -> Panel -> ContentPresenter -> Item

So we’ve had to set the SpotPanel properties on the content presenter. This is what our styles/templates look like for the controls…

ItemsControl:


    <Style x:Key="DynamicPortListStyle" TargetType="ItemsControl">
        <Setter Property="ItemTemplate" Value="{DynamicResource DynamicPortItemTemplate}" />
        <Setter Property="ItemContainerStyle" Value="{DynamicResource DynamicPortContainerStyle}" />
        <Setter Property="ItemsPanel" Value="{DynamicResource DynamicPortPanelTemplate}" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ItemsControl">
                    <ItemsPresenter Height="{TemplateBinding Height}" Width="{TemplateBinding Width}" />
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

Panel:


    <ItemsPanelTemplate x:Key="DynamicPortPanelTemplate">
        <go:SpotPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
    </ItemsPanelTemplate>

ContentPresenter:


    <Style x:Key="DynamicPortContainerStyle" TargetType="ContentPresenter">
        <Setter Property="go:SpotPanel.Spot" Value="{Binding Path=AnchorSpot, Mode=OneWay}" />
        <Setter Property="go:SpotPanel.Alignment" Value="Center" />
        <Setter Property="Width" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ItemsControl}, Path=Width}" />
        <Setter Property="Height" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ItemsControl}, Path=Height}" />
    </Style>

Item:


    <DataTemplate x:Key="DynamicPortItemTemplate">
        <Rectangle Width="7" Height="7" Fill="Green" 
                   HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                   StrokeThickness="1"
                   Stroke="Green"
                   go:Node.PortId="{Binding Path=ID, Mode=OneWay}" />
    </DataTemplate>

Also, we want to keep the explicit Rectangles as they are the default ports for the objects that can’t be removed.

As for positioning the ports on the edge of the shape, its less about having them position on the edge as it is having them position where we want them and not having them cut off at the edge of the shape.

This is the code I am using to add new ports:


        public string AddPort()
        {
            if (!CurrentNode.IsMouseOver)
                return "";

            ItemData data = CurrentNode.Data as ItemData;

            if (data == null) return "";

            Rect bounds = CurrentNode.Bounds;
            Point position = Diagram.LastMousePointInModel;

            double XOffset = position.X - bounds.X;
            double YOffset = position.Y - bounds.Y;

            // Ensure values between 0-1
            double X = RestrictValue(XOffset / bounds.Width);
            double Y = RestrictValue(YOffset / bounds.Height);

            // Determine direction links should draw from the new port (whichever edge closest to)
            Spot ToFromSpot = GetToFromSpot(X, Y);
            string direction = GetDirectionFromSpot(ToFromSpot);
            string id = "x" + direction + data.DynamicAnchors.Where(x => x.ID.Contains(direction)).Count().ToString();

            DynamicAnchor newAnchor = new DynamicAnchor(new Spot(X, Y, 0, 0), ToFromSpot, id);
            data.DynamicAnchors.Add(newAnchor);
            return id;
        }

This code works if there isn’t a margin around the object, the new ports position as expect, but then ports on the edge of the object are cut off as above. With the margin added, the ports no longer get cut off at the edge of the object, but they no longer position correctly with the added width of the panel.

In the image below I’ve set the SpotPanel background to yellow, and you can see that the ports will display past the edge of the panels edge on the left (seems that extra width is added on the right).

But for whatever reason, with the ItemsControl it just cuts those ports off at the edge of the panel.

I’m not sure I follow how switching to PathGeometries for the ports would help but if the method you are envisioning would allow us to accurately position the ports so that their centers can be constrained inside the object we would definitely be open to exploring that.

Thanks

I’m just guessing now. Do you need to bind the Width and Height of the ItemsPresenter or of the ItemsControl itself?

I’ve managed to fix the positioning & cut-off issues by rearranging the controls, and utilizing viewboxes.

This is a sample of an updated object template:


<span style="font-size: 12px; line-height: 1.4;"><span ="Apple-tab-span" style="white-space:pre">	</span><Grid</span>
            go:Part.SelectionAdorned="False"
            go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}"   
            go:Part.Resizable="{Binding Path=Data.Resizable, Mode=OneTime}"
            go:Part.ResizeElementName="Container"
            go:Part.ResizeAdornmentTemplate="{DynamicResource NodeResizeAdornmentTemplate}"
            go:Part.Rotatable="{Binding Path=Data.Rotatable, Mode=OneWay}"
            go:Node.RotationAngle="{Binding Path=Data.Angle, Mode=TwoWay}" 
            go:Part.RotateAdornmentTemplate="{DynamicResource NodeRotationAdornmentTemplate}"
            go:Part.Reshapable="False">

            <Grid Name="Container"
                  Height="{Binding Path=Data.Height, Mode=TwoWay}"
                  Width="{Binding Path=Data.Width, Mode=TwoWay}"<span style="font-size: 12px; line-height: 1.4;">></span>
                <Grid>
                    <Viewbox Stretch="UniformToFill">
                        <Grid>
                            <Viewbox Stretch="UniformToFill">
                                <Viewbox Stretch="Fill" Height="{Binding Path=Data.TempHeight, Mode=OneTime}" Width="{Binding Path=Data.TempWidth, Mode=OneTime}" Margin="3.5">
                                    <Grid UseLayoutRounding="True" Name="PathContainer"><span style="font-size: 12px; line-height: 1.4;">                                        <span ="Apple-tab-span" style="white-space:pre">				</span>...</span>
                                    </Grid>
                                </Viewbox>
                            </Viewbox>

                            <Viewbox Stretch="Uniform">
                                <ItemsControl Height="{Binding Path=Data.Height, Mode=OneTime}"
                                          Width="{Binding Path=Data.Width, Mode=OneTime}"
                                          Name="DynamicPorts" ItemsSource="{Binding Path=Data.DynamicAnchors}"
                                          Style="{DynamicResource DynamicPortListDiagramStyle}"/>
                            </Viewbox>
                        </Grid>
                    </Viewbox>
                </Grid>
            </Grid>

            <Rectangle Name="PortValues" Visibility="Collapsed" 
                       StrokeThickness="{Binding Path=Node.IsMouseOver, Converter={StaticResource AnchorThicknessChooser}}"
                       Stroke="{Binding Path=Data.HasError, Converter={StaticResource AnchorStrokeChooser}}" />
        </Grid>
    </DataTemplate>
</ResourceDictionary>

And the templates for the ItemsControl & Ports:


<Style x:Key="DynamicPortContainerDiagramStyle" TargetType="ContentPresenter">
        <Setter Property="go:SpotPanel.Spot" Value="{Binding Path=AnchorSpot, Mode=OneWay}" />
        <Setter Property="go:SpotPanel.Alignment" Value="Center" />
        <Setter Property="Width" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ItemsControl}, Path=Width}" />
        <Setter Property="Height" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ItemsControl}, Path=Height}" />
    </Style>

    <DataTemplate x:Key="DynamicPortItemDiagramTemplate">
        <Rectangle Width="7" Height="7" Cursor="Hand" Fill="Transparent"
                   go:Node.PortId="{Binding Path=ID, Mode=OneWay}"
                   go:Node.LinkableFrom="True" go:Node.LinkableTo="True"
                   go:Node.FromSpot="{Binding Path=ToFromSpot, Mode=OneWay}" go:Node.ToSpot="{Binding Path=ToFromSpot, Mode=OneWay}"
                   StrokeThickness="{Binding ElementName=PortValues, Path=StrokeThickness}"
                   Stroke="{Binding ElementName=PortValues, Path=Stroke}" />
    </DataTemplate>

    <ItemsPanelTemplate x:Key="DynamicPortPanelDiagramTemplate">
        <go:SpotPanel />
    </ItemsPanelTemplate>

    <Style x:Key="DynamicPortListDiagramStyle" TargetType="ItemsControl">
        <Setter Property="ItemTemplate" Value="{DynamicResource DynamicPortItemDiagramTemplate}" />
        <Setter Property="ItemContainerStyle" Value="{DynamicResource DynamicPortContainerDiagramStyle}" />
        <Setter Property="ItemsPanel" Value="{DynamicResource DynamicPortPanelDiagramTemplate}" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ItemsControl">
                    <ItemsPresenter  />                       
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

All of the previously static ports have been moved into the dynamic port list, and the problem I am running into with these objects now is that links are being drawn from the center of the object like this:

The link above was drawn from the top-right port… While the link was being drawn the temporary link was routing correctly, but as soon as the actual link was added it rerouted and drew itself from the center of the object. We do have customized routing code, but I have checked and in this case the base code is being used and the results aren’t being altered. Any suggestions as to what the cause may be?


Thanks
Ryan

Are there any elements in the visual tree of the Node that have go:Node.PortId set to the empty string?

Are you sure that the link data in the GraphLinksModel is actually storing the port id’s?

And that if you are not using the GraphLinksModelLinkData class, that you have set the GraphLinksModel.LinkFromParameterPath and .LinkToParameterPath properties?

I’m just wondering what other differences there might be between your app and sample apps such as the Dynamic Ports sample.

Are there any elements in the visual tree of the Node that have go:Node.PortId set to the empty string?

I don’t believe so - Assuming Node.Ports shows all ports that have a PortID set, there are none set to an empty string

Are you sure that the link data in the GraphLinksModel is actually storing the port id’s?
I have checked the link after it has been added to the diagram, and its “FromPort” is set to the correct PortID value

And that if you are not using the GraphLinksModelLinkData class, that you have set the GraphLinksModel.LinkFromParameterPath and .LinkToParameterPath properties?
Not applicable

I'm just wondering what other differences there might be between your app and sample apps such as the Dynamic Ports sample.
We have made a lot of changes, but it is only the nodes that have the dynamic ports that are experiencing this issue. Our other nodes without the dynamic ports are still linking correctly.

Are you creating a new port (i.e. FrameworkElement) around the time that a new link has been drawn? I’m wondering if the timing isn’t working out right for that. Remember that everything in WPF and Silverlight is asynchronous, even if it is all single-threaded.

I don’t believe so… the static ports in the collection are added during Data.Clone, so they should be added to the template as soon as the node enters the diagram, shouldnt they?

Your screenshot makes me guess that the routing of the Link thinks that the FromSpot is Spot.Center. You might want to check that all of your ports not only have the right PortId but also the right FromSpot and ToSpot values.

Yes the FromSpot is Spot.Center, it was my understanding that the FromSpot just determines where in the port FrameworkElement (in this case Rectangle’s) the link starts… Is that not the case?

Yes, that’s right. OK, so then it’s doing the right thing then, if you want the link to start at the center of that Rectangle.

I did some testing before your last response and set the FromSpot to the same value as the SpotPanel.Spot, which seems to have fixed the problem I was having… May be something going on in the visual tree that was applying the FromSpot to the Node/ItemsControl/whatever instead of the Rectangle, even though the FrameworkElement being shown as the Port was the Rectangle.

I’m not sure if this is related to the above changes, but I am seeing some links draw in the wrong direction for certain objects.

Here is an example of what is happening:

I would like to force the link to draw upwards from the port instead of to the left. Depending on the position of the node the direction changes between the left and top. Is there any way to control the direction the link is drawn from the port? Or does it just draw in the direction of the closest edge?

If the port is a separate small (perhaps transparent) FrameworkElement, then it’s just a matter of setting the go:Node.FromSpot (or go:Node.ToSpot).

If the port is the whole Node, then yes, it tries to do the “smart” thing, which might not be what you want. You could override Route.GetLinkDirection to do whatever you want. Note that you can also override Route.GetLinkPoint and GetLinkPointFromPoint for even more flexibility.

I’ve implemented GetLinkDirection:


protected override double GetLinkDirection(Node node, FrameworkElement port, Point linkpoint, Spot spot, bool from, bool ortho, Node othernode, FrameworkElement otherport)
        {
            switch (PortHelpers.GetPortDirection(port, node.RotationAngle))
            {
                case "L":
                    return 180;                    
                case "R":
                    return 0;
                case "T":
                    return 270;
                case "B":
                    return 90;
                default:
                    return base.GetLinkDirection(node, port, linkpoint, spot, from, ortho, othernode, otherport);
            }
        }

This change has fixed the routing with the temp link, but not for the final link… not sure what the difference is. Both are using the base Route.ComputePoints.

So why is your overridden GetLinkDirection method returning the wrong value?

You might want to stop using your custom Route class in the temporary link, just to simplify debugging by reducing the calls to GetLinkDirection.

I’ve done that and I think I’ve found the problem… It seems that the port being passed to GetLinkDirection is not the right one.

I checked the Node.Ports for “node”, and they all refer to the correct Rectangle FrameworkElements in the ItemsControl.


I checked this.Link.FromPort, and it refers to the correct FrameworkElement with the right PortID.


The problem is that the “port” being passed to GetLinkDirection (which should be one of the elements I found in the previous steps) is a Viewbox. I updated the object template with names for each of the Viewboxes to determine which one was being used, and the element being passed is “V1”.

Here’s the template of the object:


        <Grid
            go:Part.SelectionAdorned="False"
            go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}"   
            go:Part.Resizable="{Binding Path=Data.Resizable, Mode=OneTime}"
            go:Part.ResizeElementName="Container"
            go:Part.ResizeAdornmentTemplate="{DynamicResource NodeResizeAdornmentTemplate}"
            go:Part.Rotatable="{Binding Path=Data.Rotatable, Mode=OneWay}"
            go:Node.RotationAngle="{Binding Path=Data.Angle, Mode=TwoWay}" 
            go:Part.RotateAdornmentTemplate="{DynamicResource NodeRotationAdornmentTemplate}"
            go:Part.Reshapable="False">

            <ContextMenuService.ContextMenu>
                <ContextMenu ItemsSource="{DynamicResource NodeContextMenuTemplate}" />
            </ContextMenuService.ContextMenu>

            <Grid Name="Container"
                  Height="{Binding Path=Data.HeightWithMargin, Mode=TwoWay}"
                  Width="{Binding Path=Data.WidthWithMargin, Mode=TwoWay}"
                  RenderTransformOrigin ="0.5 0.5">
                <Grid.RenderTransform>
                    <ScaleTransform ScaleX="{Binding Path=Data.ScaleX, Mode=OneWay}" />
                </Grid.RenderTransform>
                <Grid>

<span style="font-size: 12px; line-height: 1.4;">                   </span><span style="font-size: 12px; line-height: 1.4;"> </span><b><Viewbox Stretch="UniformToFill" Name="V1"></b>
                        <Grid>
                            <Viewbox Stretch="UniformToFill" Name="V2">
                                <Viewbox Stretch="Fill" Height="{Binding Path=Data.Height, Mode=OneTime}" Width="{Binding Path=Data.Width, Mode=OneTime}" Margin="{Binding Path=Data.MarginWidth, Mode=OneTime}" Name="V3">
                                    <Grid UseLayoutRounding="True" Name="PathContainer">
<span ="Apple-tab-span" style="white-space:pre">					</span>...
                                    </Grid>
                                </Viewbox>
                            </Viewbox>

                            <Viewbox Stretch="Uniform" Name="V4">
                                <ItemsControl Height="{Binding Path=Data.HeightWithMargin, Mode=OneTime}"
                                          Width="{Binding Path=Data.WidthWithMargin, Mode=OneTime}"
                                          Name="DynamicPorts" ItemsSource="{Binding Path=Data.DynamicAnchors}"
                                          Style="{DynamicResource DynamicPortListDiagramStyle}"/>
                            </Viewbox>
                        </Grid>
                    </Viewbox>

                </Grid>
            </Grid>
        </Grid>