Rotating Ports Outside of SelectionElement

I am running into a problem with some of my nodes where the link ports aren’t rotating with the node. I’ve centered my ports on the edge of the node path, so they extend past the bounds of the object, and set the SelectionElement to a container which does not contain the ports in order to keep the selection box around the box. This seems to be the cause of ports not rotating, but if I change the SelectionElement to contain the ports it doesn’t seem to adjust properly (the left side of the selection box is at the edge of the node and cuts off the port, and the right side has white space to the right of the object that doesn’t contain anything. It seems that the selection box may be the right size, but it is shifted to the right.

This is the template I am working with:

<go:SpotPanel   
            go:Part.SelectionAdorned="True"
            go:Part.SelectionElementName="Container"
            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.Orientation.Value, 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.Height, Mode=TwoWay}"
                  Width="{Binding Path=Data.Width, Mode=TwoWay}">

                <Grid Background="{Binding Path=Data.isInPalette, Converter={StaticResource backgroundChooser}}">
                    <FrameworkElement.ToolTip>
                        <TextBlock Text="{Binding Path=Data.Text}" />
                    </FrameworkElement.ToolTip>
                    
                    <Viewbox Stretch="Fill">
                        <Grid UseLayoutRounding="True">
                            <Path StrokeThickness="2.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Fill="#ffffffff" Data="F1 M 84.696,22.195 C 84.696,10.489 65.959,1.000 42.848,1.000 C 19.736,1.000 1.000,10.489 1.000,22.195 C 1.000,33.901 19.736,43.391 42.848,43.391 C 65.959,43.391 84.696,33.901 84.696,22.195 Z"/>
                            <Path StrokeThickness="2.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Fill="#ffffffff" Data="F1 M 84.696,202.993 C 84.696,191.286 65.959,181.797 42.848,181.797 C 19.736,181.797 1.000,191.286 1.000,202.993 C 1.000,214.698 19.736,224.188 42.848,224.188 C 65.959,224.188 84.696,214.698 84.696,202.993 Z"/>
                            <Path StrokeThickness="2.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Fill="#ffffffff" Data="F1 M 84.696,204.260 L 1.000,204.260 L 1.000,22.014 L 84.696,22.014 L 84.696,204.260 Z"/>
                        </Grid>
                    </Viewbox>

                    <Grid UseLayoutRounding="True">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="20*"/>
                            <RowDefinition Height="60*"/>
                            <RowDefinition Height="20*"/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="37*"/>
                            <ColumnDefinition Width="26*"/>
                            <ColumnDefinition Width="37*"/>
                        </Grid.ColumnDefinitions>

                        <Viewbox Stretch="Uniform" Grid.Row="1" Grid.Column="1">
                            <Path HorizontalAlignment="Center" VerticalAlignment="Center" 
                                      Fill="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" 
                                      Data="{Binding Path=Data.Tag.Points, Mode=OneWay}"/>
                        </Viewbox>
                    </Grid>
                </Grid>                

                <Rectangle Fill="Transparent" Margin="13" Cursor="SizeAll" />
            </Grid>

            <Rectangle Style="{DynamicResource PortStyle}" Name="L1"
                       
                       Visibility="{Binding Path=Data.isInDiagram, Converter={StaticResource AnchorVisibilityChooser}}"
                       StrokeThickness="{Binding Path=Node.IsMouseOver, Converter={StaticResource AnchorThicknessChooser}}"
                       Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource AnchorStrokeChooser}}"
                       
                       go:Node.FromSpot="MiddleLeft" go:Node.ToSpot="MiddleLeft"
<span ="apple-tab-span"="" style="white-space:pre">					</span>   go:SpotPanel.Spot="0 0.1667 0 0" go:Node.PortId="L1" />

            <Rectangle Style="{DynamicResource PortStyle}" Name="L2"
                       
                       Visibility="{Binding Path=Data.isInDiagram, Converter={StaticResource AnchorVisibilityChooser}}"
                       StrokeThickness="{Binding Path=Node.IsMouseOver, Converter={StaticResource AnchorThicknessChooser}}"
                       Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource AnchorStrokeChooser}}"
                       
                       go:Node.FromSpot="MiddleLeft" go:Node.ToSpot="MiddleLeft"
<span ="apple-tab-span"="" style="white-space:pre">					</span>   go:SpotPanel.Spot="0 0.5 0 0" go:Node.PortId="L2" />

            <Rectangle Style="{DynamicResource PortStyle}" Name="L3"
                       
                       Visibility="{Binding Path=Data.isInDiagram, Converter={StaticResource AnchorVisibilityChooser}}"
                       StrokeThickness="{Binding Path=Node.IsMouseOver, Converter={StaticResource AnchorThicknessChooser}}"
                       Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource AnchorStrokeChooser}}"
                       
                       go:Node.FromSpot="MiddleLeft" go:Node.ToSpot="MiddleLeft"
<span ="apple-tab-span"="" style="white-space:pre">					</span>   go:SpotPanel.Spot="0 0.8333 0 0" go:Node.PortId="L3" />

            <Rectangle Style="{DynamicResource PortStyle}" Name="T"
                       
                       Visibility="{Binding Path=Data.isInDiagram, Converter={StaticResource AnchorVisibilityChooser}}"
                       StrokeThickness="{Binding Path=Node.IsMouseOver, Converter={StaticResource AnchorThicknessChooser}}"
                       Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource AnchorStrokeChooser}}"
                       
                       go:Node.FromSpot="MiddleTop" go:Node.ToSpot="MiddleTop"
<span ="apple-tab-span"="" style="white-space:pre">					</span>   go:SpotPanel.Spot="0.5 0 0 0" go:Node.PortId="T" />

            <Rectangle Style="{DynamicResource PortStyle}" Name="R1"
                       
                       Visibility="{Binding Path=Data.isInDiagram, Converter={StaticResource AnchorVisibilityChooser}}"
                       StrokeThickness="{Binding Path=Node.IsMouseOver, Converter={StaticResource AnchorThicknessChooser}}"
                       Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource AnchorStrokeChooser}}"
                       
                       go:Node.FromSpot="MiddleRight" go:Node.ToSpot="MiddleRight" 
                       go:SpotPanel.Spot="1 0.1667 0 0" go:Node.PortId="R1" />

            <Rectangle Style="{DynamicResource PortStyle}" Name="R2"
                       
                       Visibility="{Binding Path=Data.isInDiagram, Converter={StaticResource AnchorVisibilityChooser}}"
                       StrokeThickness="{Binding Path=Node.IsMouseOver, Converter={StaticResource AnchorThicknessChooser}}"
                       Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource AnchorStrokeChooser}}"
                       
                       go:Node.FromSpot="MiddleRight" go:Node.ToSpot="MiddleRight"
<span ="apple-tab-span"="" style="white-space:pre">					</span>   go:SpotPanel.Spot="1 0.5 0 0" go:Node.PortId="R2" />

            <Rectangle Style="{DynamicResource PortStyle}" Name="R3"
                       
                       Visibility="{Binding Path=Data.isInDiagram, Converter={StaticResource AnchorVisibilityChooser}}"
                       StrokeThickness="{Binding Path=Node.IsMouseOver, Converter={StaticResource AnchorThicknessChooser}}"
                       Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource AnchorStrokeChooser}}"
                       
                       go:Node.FromSpot="MiddleRight" go:Node.ToSpot="MiddleRight"
<span ="apple-tab-span"="" style="white-space:pre">					</span>   go:SpotPanel.Spot="1 0.8333 0 0" go:Node.PortId="R3" />

            <Rectangle Style="{DynamicResource PortStyle}" Name="B"
                       
                       Visibility="{Binding Path=Data.isInDiagram, Converter={StaticResource AnchorVisibilityChooser}}"
                       StrokeThickness="{Binding Path=Node.IsMouseOver, Converter={StaticResource AnchorThicknessChooser}}"
                       Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource AnchorStrokeChooser}}"
                       
                       go:Node.FromSpot="MiddleBottom" go:Node.ToSpot="MiddleBottom"
                       go:SpotPanel.Spot="0.5 1 0 0" go:Node.PortId="B" />            
        </go:SpotPanel>

<Style TargetType="Rectangle" x:Key="PortStyle">
            <Setter Property="Width" Value="7" />
            <Setter Property="Height" Value="7" />
            <Setter Property="Cursor" Value="Hand" />
            <Setter Property="Fill" Value="Transparent" />
            <Setter Property="go:Node.LinkableFrom" Value="True" />
            <Setter Property="go:Node.LinkableTo" Value="True" />
            <Setter Property="go:SpotPanel.Alignment" Value="Center" />            
        </Style>

I’ll take a look when I get a chance.

You do need to include the port elements in the rotated element if you want the ports to be rotated.

If you remove the ResizeAdornmentTemplate and the SelectionElementName, does the selection look OK and does rotating work OK? Of course resizing might not do what you want, but I’m curious if the problem is with the ResizeAdornmentTemplate.

Just to clarify, here is what I am seeing…

With SelectionElementName set to element not containing ports:

Rotated:

Without SelectionElementName set:

Rotated:

So I’m looking to have the selection box look like in the first set of images, but have the ports rotate like in the second set.

The selection handle is supposed to go around the selection element of the selected part, which is why it is bigger than you would like. The reason it is offset unexpectedly is due to how SpotPanels are arranged.

I think you can get the effect that you want by replacing the standard selection adornment with a custom one and if you also customize the code that creates and positions and sizes that adornment. That code is normally Part.UpdateSelectionAdornment. You could override it, but if you already have a custom rotating tool, you might as well put the code in there and disable the standard selection handle.

<DataTemplate x:Key="NodeSelectionAdornmentTemplate"> <Rectangle go:Part.Selectable="False" Stroke="DeepSkyBlue" StrokeThickness="1" StrokeDashArray="2 2" /> </DataTemplate>
The node’s template of course makes use of the custom Selection Adornment template, but it also disables the standard selection adornment mechanism. The selection element is the whole node, as it is by default. This will cause the rotating tool to rotate the whole node, including ports.

go:Part.SelectionAdorned="False" go:Part.SelectionAdornmentTemplate="{StaticResource NodeSelectionAdornmentTemplate}"
Here’s the custom RotatingTool. It creates both the standard rotating tool handle as well as the selection handle, the Rectangle.

This code was taken from the DraggableLink sample, which already has a custom RotatingTool.

public class CustomRotatingTool : RotatingTool { private const String ToolCategory = "Rotate"; public override void UpdateAdornments(Part part) { if (part == null || part is Link) return; // this tool never applies to Links Adornment adornment = null; if (part.IsSelected) { FrameworkElement selelt = part.SelectionElement; if (selelt != null && part.CanRotate() && Part.IsVisibleElement(selelt)) { adornment = part.GetAdornment(ToolCategory); if (adornment == null) { DataTemplate template = part.RotateAdornmentTemplate; if (template == null) template = Diagram.FindDefault<DataTemplate>("DefaultRotateAdornmentTemplate"); adornment = part.MakeAdornment(selelt, template); if (adornment != null) { adornment.Category = ToolCategory; adornment.LocationSpot = Spot.Center; } } if (adornment != null) { adornment.Location = part.GetElementPoint(selelt, new Spot(0.5, 0, 0, -30)); // above middle top } Adornment selad = part.GetAdornment("Selection"); if (selad == null) { DataTemplate template = part.SelectionAdornmentTemplate; if (template == null) template = Diagram.FindDefault<DataTemplate>("DefaultSelectionAdornmentTemplate"); selad = part.MakeAdornment(selelt, template); if (selad != null) { selad.Category = "Selection"; selad.LocationSpot = Spot.TopLeft; } } if (selad != null) { Point pos = part.GetElementPoint(selelt, Spot.TopLeft); double angle = part.GetAngle(selelt); selad.Location = pos; selad.Width = selelt.ActualWidth-7; selad.Height = selelt.ActualHeight-7; selad.RotationAngle = angle; } part.SetAdornment("Selection", selad); } } part.SetAdornment(ToolCategory, adornment); }
Install this tool as the Diagram.RotatingTool, either in XAML or by code.

Note that the size of the selection handle is explicitly shrunk by the how far the ports (or any other elements) stick out from the SpotPanel’s main element. Here it is hard-coded to be 7 width and 7 height, which I believe is how far your ports stick out.