I’ve tried copying over the code from the Draggable Link demo into a new .Net v4 WPF project and then putting in my link template and it seems to be doing the same thing (the thickness is different depending on if the link is selected or not).
Here’s the 2 files I am using…
MainWindow.xaml
<Window x:Class="MouseOverTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:go="http://schemas.nwoods.com/GoXam"
xmlns:local="clr-namespace:MouseOverTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="NodeSelectionAdornmentTemplate">
<go:SelectionHandle go:Part.Selectable="False"
Stroke="DeepSkyBlue" StrokeThickness="1" StrokeDashArray="2 2" />
</DataTemplate>
<DataTemplate x:Key="NodeResizeAdornmentTemplate">
<go:SpotPanel>
<go:ToolHandle go:SpotPanel.Spot="0.0 0.0" go:NodePanel.Figure="Rectangle" Width="6" Height="6" Fill="LightBlue" Stroke="DeepSkyBlue" StrokeThickness="1" />
<go:ToolHandle go:SpotPanel.Spot="0.5 0.0" go:NodePanel.Figure="Rectangle" Width="6" Height="6" Fill="LightBlue" Stroke="DeepSkyBlue" StrokeThickness="1" />
<go:ToolHandle go:SpotPanel.Spot="1.0 0.0" go:NodePanel.Figure="Rectangle" Width="6" Height="6" Fill="LightBlue" Stroke="DeepSkyBlue" StrokeThickness="1" />
<go:ToolHandle go:SpotPanel.Spot="0.0 0.5" go:NodePanel.Figure="Rectangle" Width="6" Height="6" Fill="LightBlue" Stroke="DeepSkyBlue" StrokeThickness="1" />
<go:ToolHandle go:SpotPanel.Spot="1.0 0.5" go:NodePanel.Figure="Rectangle" Width="6" Height="6" Fill="LightBlue" Stroke="DeepSkyBlue" StrokeThickness="1" />
<go:ToolHandle go:SpotPanel.Spot="0.0 1.0" go:NodePanel.Figure="Rectangle" Width="6" Height="6" Fill="LightBlue" Stroke="DeepSkyBlue" StrokeThickness="1" />
<go:ToolHandle go:SpotPanel.Spot="0.5 1.0" go:NodePanel.Figure="Rectangle" Width="6" Height="6" Fill="LightBlue" Stroke="DeepSkyBlue" StrokeThickness="1" />
<go:ToolHandle go:SpotPanel.Spot="1.0 1.0" go:NodePanel.Figure="Rectangle" Width="6" Height="6" Fill="LightBlue" Stroke="DeepSkyBlue" StrokeThickness="1" />
</go:SpotPanel>
</DataTemplate>
<DataTemplate x:Key="NodeRotateAdornmentTemplate">
<go:ToolHandle go:NodePanel.Figure="Ellipse" Width="6" Height="6" Fill="LightBlue" Stroke="DeepSkyBlue" StrokeThickness="1" Cursor="Hand" />
</DataTemplate>
<DataTemplate x:Key="NodeTemplate">
<go:SpotPanel
go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}"
go:Node.LocationSpot="Center"
go:Part.SelectionAdorned="True"
go:Part.SelectionAdornmentTemplate="{StaticResource NodeSelectionAdornmentTemplate}"
go:Part.Resizable="True"
go:Part.ResizeElementName="Icon"
go:Part.ResizeAdornmentTemplate="{StaticResource NodeResizeAdornmentTemplate}"
go:Node.RotationAngle="{Binding Path=Data.Angle, Mode=TwoWay}"
go:Part.Rotatable="True"
go:Part.RotateAdornmentTemplate="{StaticResource NodeRotateAdornmentTemplate}">
<go:NodePanel>
<go:NodeShape x:Name="Icon"
go:NodePanel.Figure="{Binding Path=Data.Figure}"
Stroke="Black" StrokeThickness="1"
Fill="{Binding Path=Data.Color}"
Width="{Binding Path=Data.Width, Mode=TwoWay}"
Height="{Binding Path=Data.Height, Mode=TwoWay}"
go:NodePanel.Spot1="0 0" go:NodePanel.Spot2="1 1"
go:Node.PortId="" go:Node.LinkableFrom="True" go:Node.LinkableTo="True" Cursor="Hand"
go:Node.LinkableDuplicates="True" go:Node.LinkableSelfNode="True" />
<Rectangle Fill="Transparent" Margin="12"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<TextBlock Text="{Binding Path=Data.Text, Mode=TwoWay}" TextWrapping="Wrap"
HorizontalAlignment="Center" VerticalAlignment="Center"
go:Part.TextEditable="True" />
</go:NodePanel>
<Rectangle Fill="Transparent" Width="6" Height="6"
go:SpotPanel.Spot="MiddleLeft" go:SpotPanel.Alignment="MiddleLeft"
go:Node.PortId="L" go:Node.LinkableFrom="True" go:Node.LinkableTo="True" Cursor="Hand"
go:Node.FromSpot="MiddleLeft" go:Node.ToSpot="MiddleLeft" />
<Rectangle Fill="Transparent" Width="6" Height="6"
go:SpotPanel.Spot="MiddleTop" go:SpotPanel.Alignment="MiddleTop"
go:Node.PortId="T" go:Node.LinkableFrom="True" go:Node.LinkableTo="True" Cursor="Hand"
go:Node.FromSpot="MiddleTop" go:Node.ToSpot="MiddleTop" />
<Rectangle Fill="Transparent" Width="6" Height="6"
go:SpotPanel.Spot="MiddleRight" go:SpotPanel.Alignment="MiddleRight"
go:Node.PortId="R" go:Node.LinkableFrom="True" go:Node.LinkableTo="True" Cursor="Hand"
go:Node.FromSpot="MiddleRight" go:Node.ToSpot="MiddleRight" />
<Rectangle Fill="Transparent" Width="6" Height="6"
go:SpotPanel.Spot="MiddleBottom" go:SpotPanel.Alignment="MiddleBottom"
go:Node.PortId="B" go:Node.LinkableFrom="True" go:Node.LinkableTo="True" Cursor="Hand"
go:Node.FromSpot="MiddleBottom" go:Node.ToSpot="MiddleBottom" />
</go:SpotPanel>
</DataTemplate>
<DataTemplate x:Key="PaletteNodeTemplate">
<go:NodePanel go:Part.SelectionAdorned="True">
<go:NodeShape go:NodePanel.Figure="{Binding Path=Data.Figure}"
Stroke="Black" StrokeThickness="1"
Fill="{Binding Path=Data.Color}"
Width="{Binding Path=Data.Width, Mode=TwoWay}"
Height="{Binding Path=Data.Height, Mode=TwoWay}"
go:NodePanel.Spot1="0 0" go:NodePanel.Spot2="1 1" />
<TextBlock Text="{Binding Path=Data.Text, Mode=TwoWay}" TextWrapping="Wrap"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</go:NodePanel>
</DataTemplate>
<DataTemplate x:Key="LinkSelectionAdornmentTemplate">
<go:SelectionHandle go:NodePanel.Figure="None" Stroke="DeepSkyBlue" StrokeThickness="2" go:Part.Selectable="False" />
</DataTemplate>
<DataTemplate x:Key="LinkReshapeHandleTemplate">
<go:ToolHandle go:NodePanel.Figure="Diamond" Width="7" Height="7" Fill="LightBlue" Stroke="DeepSkyBlue" StrokeThickness="1" />
</DataTemplate>
<local:WidthConverter x:Key="theWidthConverter" />
<go:BooleanBrushConverter x:Key="theStrokeChooser" FalseBrush="Transparent" TrueBrush="Magenta" />
<DataTemplate x:Key="LinkTemplate">
<go:LinkPanel go:Part.Reshapable="True"
go:Part.SelectionAdorned="True"
go:Part.SelectionElementName="Pipe"
go:Part.DropOntoBehavior="SplicesIntoLink">
<go:LinkShape Name="Pipe" Stroke="Black" StrokeThickness="{Binding Path=Link.IsMouseOver, Converter={StaticResource theWidthConverter}}" />
<go:LinkShape Name="LinkHighlight" Stroke="{Binding Path=Link.IsDropOntoAccepted, Converter={StaticResource theStrokeChooser}}" StrokeThickness="5" />
<Path Name="arrowhead" Stroke="Black" Fill="Black" StrokeThickness="2"
go:LinkPanel.ToArrow="Triangle"
go:LinkPanel.Alignment="MiddleRight"
go:LinkPanel.Index="-1"
go:LinkPanel.Orientation="Along"/>
</go:LinkPanel>
</DataTemplate>
<DataTemplate x:Key="PaletteLinkTemplate">
<go:LinkPanel Background="Transparent"
go:Part.SelectionElementName="Path" go:Part.SelectionAdorned="True"
go:Part.SelectionAdornmentTemplate="{StaticResource LinkSelectionAdornmentTemplate}">
<go:LinkShape Stroke="Transparent" StrokeThickness="5" />
<go:LinkShape x:Name="Path" Stroke="Black" StrokeThickness="1" />
<Path Fill="Black" go:LinkPanel.ToArrow="Triangle" />
</go:LinkPanel>
</DataTemplate>
<DataTemplate x:Key="TemporaryLinkTemplate">
<go:LinkPanel go:Part.Selectable="False" go:Link.LayerName="Tool">
<go:Link.Route>
<go:Route Routing="Orthogonal" />
</go:Link.Route>
<go:LinkShape x:Name="Path" Stroke="Blue" StrokeThickness="1" />
<Path Fill="Black" go:LinkPanel.ToArrow="Triangle" />
</go:LinkPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<go:Palette Grid.Row="0"
x:Name="myPalette"
BorderBrush="Black" BorderThickness="1" Padding="5"
HorizontalContentAlignment="Left" VerticalContentAlignment="Top"
NodeTemplate="{StaticResource PaletteNodeTemplate}"
LinkTemplate="{StaticResource PaletteLinkTemplate}">
<go:Diagram.PartManager>
<local:CustomPartManager />
</go:Diagram.PartManager>
<go:Diagram.Layout>
<go:GridLayout CellSize="10 10" />
</go:Diagram.Layout>
</go:Palette>
<StackPanel Grid.Row="1">
</StackPanel>
</Grid>
<go:Diagram Grid.Column="1"
x:Name="myDiagram" AllowDrop="True"
Padding="10"
HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"
NodeTemplate="{StaticResource NodeTemplate}"
LinkTemplate="{StaticResource LinkTemplate}"
GridVisible="True" GridSnapEnabled="True">
<go:Diagram.PartManager>
<local:CustomPartManager />
</go:Diagram.PartManager>
<go:Diagram.LinkingTool>
<go:LinkingTool PortGravity="20"
TemporaryLinkTemplate="{StaticResource TemporaryLinkTemplate}" />
</go:Diagram.LinkingTool>
<go:Diagram.RelinkingTool>
<go:RelinkingTool PortGravity="20"
TemporaryLinkTemplate="{StaticResource TemporaryLinkTemplate}" />
</go:Diagram.RelinkingTool>
<go:Diagram.DraggingTool>
<go:DraggingTool DraggableLinks="True" />
</go:Diagram.DraggingTool>
<go:Diagram.RotatingTool>
<local:CustomRotatingTool SnapAngleMultiple="15" SnapAngleEpsilon="15" />
</go:Diagram.RotatingTool>
<go:Diagram.GridPattern>
<go:GridPattern CellSize="10 10">
<Path Stroke="LightGray" StrokeThickness="0.2" go:GridPattern.Figure="HorizontalLine" />
<Path Stroke="LightGray" StrokeThickness="0.2" go:GridPattern.Figure="VerticalLine" />
<Path Stroke="LightGray" StrokeThickness="0.3" go:GridPattern.Figure="HorizontalLine" go:GridPattern.Interval="2" />
<Path Stroke="LightGray" StrokeThickness="0.3" go:GridPattern.Figure="VerticalLine" go:GridPattern.Interval="2" />
<Path Stroke="Gray" StrokeThickness="0.3" go:GridPattern.Figure="HorizontalLine" go:GridPattern.Interval="4" />
<Path Stroke="Gray" StrokeThickness="0.3" go:GridPattern.Figure="VerticalLine" go:GridPattern.Interval="4" />
<Path Stroke="Black" StrokeThickness="0.3" go:GridPattern.Figure="HorizontalLine" go:GridPattern.Interval="8" />
<Path Stroke="Black" StrokeThickness="0.3" go:GridPattern.Figure="VerticalLine" go:GridPattern.Interval="8" />
</go:GridPattern>
</go:Diagram.GridPattern>
</go:Diagram>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Xml.Linq;
using Northwoods.GoXam;
using Northwoods.GoXam.Model;
using Northwoods.GoXam.Tool;
namespace MouseOverTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// palette
var pmodel = new MyModel();
pmodel.ValidUnconnectedLinks = ValidUnconnectedLinks.Allowed;
pmodel.NodesSource = new ObservableCollection<MyData>() {
new MyData() { Text="Alpha", Color="Pink" },
new MyData() { Text="Beta", Color="PapayaWhip", Figure=NodeFigure.Diamond },
new MyData() { Text="Gamma", Color="PeachPuff", Figure=NodeFigure.Database }
};
pmodel.LinksSource = new ObservableCollection<MyLinkData>() {
new MyLinkData() { Points=new List<Point>() { new Point(0, 0), new Point(25, 0), new Point(25, 25), new Point(50, 25) } },
new MyLinkData() { Points=new List<Point>() { new Point(50, 50), new Point(25, 50), new Point(25, 75), new Point(0, 75) } },
};
myPalette.LayoutCompleted += LoadLinkRoutes;
myPalette.Model = pmodel;
// initial diagram
var model = new MyModel();
model.Modifiable = true;
model.ValidUnconnectedLinks = ValidUnconnectedLinks.Allowed;
model.NodesSource = new ObservableCollection<MyData>() {
new MyData() { Key="A", Text="A", Color="Yellow", Figure=NodeFigure.ACvoltageSource, Location = new Point(50, 200) },
new MyData() { Key="B", Text="B", Color="Aquamarine", Figure=NodeFigure.Alternative, Location = new Point(200, 50) },
new MyData() { Key="C", Text="C", Color="Wheat", Figure=NodeFigure.AndGate, Location = new Point(200, 200), Width=100, Height=50 },
};
model.LinksSource = new ObservableCollection<MyLinkData>() {
new MyLinkData() { From="A", Text="only connected to A", Points=new List<Point>() { new Point(50, 230), new Point(50, 240), new Point(50, 244), new Point(103, 244), new Point(103, 283), new Point(103, 293) } }
};
myDiagram.LayoutCompleted += LoadLinkRoutes;
myDiagram.Model = model;
model.HasUndoManager = true;
myDiagram.AllowDrop = true; // must be in code for Silverlight3
}
private void LoadLinkRoutes(Object s, EventArgs e)
{
// just set the Route points once per Load
myDiagram.LayoutCompleted -= LoadLinkRoutes;
foreach (Link link in myDiagram.Links)
{
var d = link.Data as MyLinkData;
if (d == null || d.Points == null) continue;
link.Route.Points = (IList<Point>)d.Points;
}
myDiagram.PartManager.UpdatesRouteDataPoints = true; // OK for CustomPartManager to update Transition.Points automatically
}
}
public class WidthConverter : Converter
{
public override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is Boolean)
{
if ((Boolean)value == true) return 5.0;
if ((Boolean)value == false) return 2.0;
}
return base.Convert(value, targetType, parameter, culture);
}
}
public class MyModel : GraphLinksModel<MyData, String, String, MyLinkData> {
// nothing to add or override, for now
}
// data representing important state about each node
#if !SILVERLIGHT
[Serializable]
#endif
public class MyData : GraphLinksModelNodeData<String> {
public NodeFigure Figure {
get { return _Figure; }
set { if (_Figure != value) { NodeFigure old = _Figure; _Figure = value; RaisePropertyChanged("Figure", old, value); } }
}
private NodeFigure _Figure = NodeFigure.Rectangle;
public String Color {
get { return _Color; }
set { if (_Color != value) { String old = _Color; _Color = value; RaisePropertyChanged("Color", old, value); } }
}
private String _Color = "White";
public double Width {
get { return _Width; }
set { if (_Width != value) { double old = _Width; _Width = value; RaisePropertyChanged("Width", old, value); } }
}
private double _Width = 60;
public double Height {
get { return _Height; }
set { if (_Height != value) { double old = _Height; _Height = value; RaisePropertyChanged("Height", old, value); } }
}
private double _Height = 60;
public double Angle {
get { return _Angle; }
set { if (_Angle != value) { double old = _Angle; _Angle = value; RaisePropertyChanged("Angle", old, value); } }
}
private double _Angle;
// Because we added properties that we want to persist,
// we should override these two methods to write/read those properties.
public override XElement MakeXElement(XName n) {
XElement e = base.MakeXElement(n);
e.Add(XHelper.AttributeEnum<NodeFigure>("Figure", this.Figure, NodeFigure.Rectangle));
e.Add(XHelper.Attribute("Color", this.Color, "White"));
e.Add(XHelper.Attribute("Width", this.Width, 50.0));
e.Add(XHelper.Attribute("Height", this.Height, 50.0));
e.Add(XHelper.Attribute("Angle", this.Angle, 0.0));
return e;
}
public override void LoadFromXElement(XElement e) {
base.LoadFromXElement(e);
this.Figure = XHelper.ReadEnum<NodeFigure>("Figure", e, NodeFigure.Rectangle);
this.Color = XHelper.Read("Color", e, "White");
this.Width = XHelper.Read("Width", e, 50.0);
this.Height = XHelper.Read("Height", e, 50.0);
this.Angle = XHelper.Read("Angle", e, 0.0);
}
}
// data representing important state about each link
#if !SILVERLIGHT
[Serializable]
#endif
public class MyLinkData : GraphLinksModelLinkData<String, String> {
// nothing to add or override, for now
}
// no data-binding of Route.Points means we have to copy the Points data explicitly
public class CustomPartManager : PartManager {
public CustomPartManager() {
this.UpdatesRouteDataPoints = true; // call UpdateRouteDataPoints when Link.Route.Points has changed
}
// copy Route.Points to MyLinkData
public override ICopyDictionary CopyParts(IEnumerable<Part> coll, IDiagramModel destmodel) {
ICopyDictionary dict = base.CopyParts(coll, destmodel);
foreach (object data in dict.SourceCollection.Links) {
MyLinkData origdata = data as MyLinkData;
Link origlink = FindLinkForData(origdata, this.Diagram.Model);
if (origlink != null && origlink.Route.PointsCount > 1) {
// copy the MyLinkData.Points
MyLinkData copieddata = dict.FindCopiedLink(origdata) as MyLinkData;
if (copieddata != null) {
copieddata.Points = new List<Point>(origlink.Route.Points);
// now transfer to the Link.Route.Points
Link copiedlink = FindLinkForData(copieddata, this.Diagram.Model);
if (copiedlink != null) {
copiedlink.Route.Points = (IList<Point>)copieddata.Points;
}
}
}
}
return dict;
}
// use MyLinkData.Points, if any, when creating a Link
protected override void OnLinkAdded(Link link) {
base.OnLinkAdded(link);
MyLinkData data = link.Data as MyLinkData;
if (data != null && data.Points != null) {
link.Route.Points = (IList<Point>)data.Points;
}
}
// this supports undo/redo of link route reshaping
protected override void UpdateRouteDataPoints(Link link) {
if (!this.UpdatesRouteDataPoints) return; // in coordination with Load_Click and LoadLinkRoutes, above
MyLinkData data = link.Data as MyLinkData;
if (data != null) {
data.Points = new List<Point>(link.Route.Points);
}
}
}
// put rotation handle above node instead of on the right side
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
}
}
}
part.SetAdornment(ToolCategory, adornment);
}
protected override void DoRotate(double newangle) {
base.DoRotate(newangle + 90);
}
public override void DoCancel() {
base.DoRotate(this.OriginalAngle);
StopTool();
}
}
}