This is ancient code that I haven’t tried in years, but you can try it:
<!-- Copyright © Northwoods Software Corporation, 2008-2023. All Rights Reserved. -->
<Window x:Class="Guidelines.Guidelines"
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"
Title="Guidelines" Height="600" Width="600">
<Window.Resources>
<DataTemplate x:Key="NodeTemplate">
<go:NodePanel Width="{Binding Path=Data.Width, Mode=TwoWay}"
Height="{Binding Path=Data.Height, Mode=TwoWay}"
Background="{Binding Path=Data.Color}"
go:Part.SelectionAdorned="True"
go:Part.Resizable="True"
go:Node.LocationSpot="Center"
go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}">
<TextBlock Text="{Binding Path=Data.Key}" />
</go:NodePanel>
</DataTemplate>
<DataTemplate x:Key="LinkTemplate">
<go:LinkPanel>
<go:Link.Route>
<go:Route Routing="AvoidsNodes" Corner="5" />
</go:Link.Route>
<go:LinkShape Stroke="Blue" StrokeThickness="2" />
<Path Fill="Blue" go:LinkPanel.ToArrow="Standard" />
</go:LinkPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<go:Diagram x:Name="myDiagram"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
NodeTemplate="{StaticResource NodeTemplate}"
LinkTemplate="{StaticResource LinkTemplate}">
</go:Diagram>
</Grid>
</Window>
/* Copyright © Northwoods Software Corporation, 2008-2023. All Rights Reserved. */
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
using Northwoods.GoXam;
using Northwoods.GoXam.Model;
using Northwoods.GoXam.Tool;
namespace Guidelines {
public partial class Guidelines : Window {
public Guidelines() {
InitializeComponent();
var model = new GraphLinksModel<NodeInfo, String, String, UniversalLinkData>();
model.NodesSource = new ObservableCollection<NodeInfo>() {
new NodeInfo() { Key="A", Color="LightBlue" },
new NodeInfo() { Key="B", Color="LightYellow" },
new NodeInfo() { Key="C", Color="LightGreen" },
new NodeInfo() { Key="D", Color="Pink" },
new NodeInfo() { Key="E" },
};
model.LinksSource = new ObservableCollection<UniversalLinkData>() {
new UniversalLinkData() { From="A", To="B" },
new UniversalLinkData() { From="C", To="D" },
new UniversalLinkData() { From="A", To="C" }
};
model.Modifiable = true;
model.HasUndoManager = true;
myDiagram.Model = model;
myDiagram.DraggingTool = new GuidedDraggingTool();
}
}
public class NodeInfo : GraphLinksModelNodeData<String> {
public String Color {
get { return _Color; }
set { if (_Color != value) { String old = _Color; _Color = value; RaisePropertyChanged("Color", old, value); } }
}
private String _Color = "Fuchsia";
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 class GuidedDraggingTool : DraggingTool {
public GuidedDraggingTool() {
this.GuidelineSnapDistance = 6;
this.GuidelineVicinity = 1000;
}
public override void DoActivate() {
if (this.HorizontalGuideline == null) CreateGuidelines();
base.DoActivate();
}
public override void DoDeactivate() {
this.Diagram.PartsModel.RemoveNode(this.HorizontalGuideline);
this.Diagram.PartsModel.RemoveNode(this.VerticalGuideline);
this.HorizontallyAlignedNode = null;
this.VerticallyAlignedNode = null;
base.DoDeactivate();
}
public double GuidelineSnapDistance { get; set; }
public double GuidelineVicinity { get; set; }
private Node HorizontalGuideline { get; set; }
private Node VerticalGuideline { get; set; }
private Node HorizontallyAlignedNode { get; set; }
private Node VerticallyAlignedNode { get; set; }
private void CreateGuidelines() {
Node n = new Node();
var line = new Line();
line.Stroke = new SolidColorBrush(Colors.Red);
line.StrokeThickness = 1;
line.X1 = 0;
line.Y1 = 0;
line.X2 = this.GuidelineVicinity;
line.Y2 = 0;
n.Content = line;
n.Id = "HorizontalGuideline";
n.LayerName = "Tool";
n.InDiagramBounds = false;
this.HorizontalGuideline = n;
n = new Node();
line = new Line();
line.Stroke = new SolidColorBrush(Colors.Red);
line.StrokeThickness = 1;
line.X1 = 0;
line.Y1 = 0;
line.X2 = 0;
line.Y2 = this.GuidelineVicinity;
n.Content = line;
n.Id = "VerticalGuideline";
n.LayerName = "Tool";
n.InDiagramBounds = false;
this.VerticalGuideline = n;
}
protected override void DragOver(Point pt, bool moving, bool copying) {
// Just guide the first dragged Node
Node node = null;
if (moving && this.DraggedParts != null) node = this.DraggedParts.Keys.First(p => p is Node) as Node;
if (copying && this.CopiedParts != null) node = this.CopiedParts.Keys.First(p => p is Node) as Node;
ShowGuidelines(node);
base.DragOver(pt, moving, copying);
}
protected override void DropOnto(Point pt) {
base.DropOnto(pt);
// Just shift the first dragged Node
Node node = this.Diagram.SelectedParts.OfType<Node>().FirstOrDefault();
if (node != null) {
double dx = 0;
double dy = 0;
if (this.VerticallyAlignedNode != null) {
dx = this.VerticallyAlignedNode.Bounds.X + this.VerticallyAlignedNode.Bounds.Width/2 - (node.Bounds.X + node.Bounds.Width/2);
}
if (this.HorizontallyAlignedNode != null) {
dy = this.HorizontallyAlignedNode.Bounds.Y + this.HorizontallyAlignedNode.Bounds.Height/2 - (node.Bounds.Y + node.Bounds.Height/2);
}
var pos = node.Position;
pos.Offset(dx, dy);
node.Move(pos, true);
}
}
private void ShowGuidelines(Node node) {
if (node == null) return;
// just align centers, for now
double cx = node.Bounds.X + node.Bounds.Width/2;
double cy = node.Bounds.Y + node.Bounds.Height/2;
this.HorizontallyAlignedNode = null;
this.VerticallyAlignedNode = null;
double bestdx = this.GuidelineSnapDistance + 0.5;
double bestdy = this.GuidelineSnapDistance + 0.5;
foreach (Node n in this.Diagram.Nodes) { // this could be more efficient by only looking at nodes within the viewport?
// ignore invisible and dragged Nodes
if (!n.Visible || !n.IsBoundToData) continue;
if (this.DraggedParts != null && this.DraggedParts.ContainsKey(n)) continue;
if (this.CopiedParts != null && this.CopiedParts.ContainsKey(n)) continue;
// now see if it's the best aligned within GuidelineSnapDistance
Rect b = n.Bounds;
double nx = b.X + b.Width/2;
double ny = b.Y + b.Height/2;
double dx = Math.Abs(nx - cx);
double dy = Math.Abs(ny - cy);
if (dx < bestdx && dy < this.GuidelineVicinity) {
this.VerticallyAlignedNode = n;
bestdx = dx;
}
if (dy < bestdy && dx < this.GuidelineVicinity) {
this.HorizontallyAlignedNode = n;
bestdy = dy;
}
}
if (this.HorizontallyAlignedNode != null) {
double minx = Math.Min(this.HorizontallyAlignedNode.Bounds.X, node.Bounds.X) - 10;
double maxx = Math.Max(this.HorizontallyAlignedNode.Bounds.Right, node.Bounds.Right) + 10;
this.HorizontalGuideline.Position = new Point(minx, this.HorizontallyAlignedNode.Bounds.Y + this.HorizontallyAlignedNode.Bounds.Height/2);
((Line)this.HorizontalGuideline.Content).X2 = maxx-minx;
this.Diagram.PartsModel.AddNode(this.HorizontalGuideline);
} else {
this.Diagram.PartsModel.RemoveNode(this.HorizontalGuideline);
}
if (this.VerticallyAlignedNode != null) {
double miny = Math.Min(this.VerticallyAlignedNode.Bounds.Y, node.Bounds.Y) - 10;
double maxy = Math.Max(this.VerticallyAlignedNode.Bounds.Bottom, node.Bounds.Bottom) + 10;
this.VerticalGuideline.Position = new Point(this.VerticallyAlignedNode.Bounds.X + this.VerticallyAlignedNode.Bounds.Width/2, miny);
((Line)this.VerticalGuideline.Content).Y2 = maxy-miny;
this.Diagram.PartsModel.AddNode(this.VerticalGuideline);
} else {
this.Diagram.PartsModel.RemoveNode(this.VerticalGuideline);
}
}
}
}