Look, this is a simple example that I did in 20 minutes:
XAML:
<Window x:Class="NodesEditor.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:NodesEditor" Title="Window1" Height="800" Width="800" x:Name="view"> <Grid Margin="10"> <Grid.Resources> <CompositeCollection x:Key="Col"> <CollectionContainer Collection="{Binding DataContext.Connectors,Source={x:Reference view}}"/> <CollectionContainer Collection="{Binding DataContext.Nodes,Source={x:Reference view}}"/> </CompositeCollection> <DataTemplate DataType="{x:Type local:Node}"> <Thumb DragDelta="Thumb_Drag"> <Thumb.Template> <ControlTemplate TargetType="Thumb"> <Ellipse Height="10" Width="10" Stroke="Black" StrokeThickness="1" Fill="Blue" Margin="-5,-5,5,5" x:Name="Ellipse"/> <ControlTemplate.Triggers> <Trigger Property="IsDragging" Value="True"> <Setter TargetName="Ellipse" Property="Fill" Value="Yellow"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Thumb.Template> </Thumb> </DataTemplate> <DataTemplate DataType="{x:Type local:Connector}"> <Line Stroke="Black" StrokeThickness="1" X1="{Binding Start.X}" Y1="{Binding Start.Y}" X2="{Binding End.X}" Y2="{Binding End.Y}"/> </DataTemplate> </Grid.Resources> <Border> <Border.Background> <VisualBrush TileMode="Tile" Viewport="0,0,50,50" ViewportUnits="Absolute" Viewbox="0,0,50,50" ViewboxUnits="Absolute"> <VisualBrush.Visual> <Rectangle Stroke="Darkgray" StrokeThickness="1" Height="50" Width="50" StrokeDashArray="5 3"/> </VisualBrush.Visual> </VisualBrush> </Border.Background> </Border> <ItemsControl> <ItemsControl.ItemsSource> <StaticResource ResourceKey="Col"/> </ItemsControl.ItemsSource> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas IsItemsHost="True"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemContainerStyle> <Style TargetType="ContentPresenter"> <Setter Property="Canvas.Left" Value="{Binding X}"/> <Setter Property="Canvas.Top" Value="{Binding Y}"/> </Style> </ItemsControl.ItemContainerStyle> </ItemsControl> </Grid> </Window>
Code for:
using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls.Primitives; namespace NodesEditor { public partial class MainWindow : Window { public List<Node> Nodes { get; set; } public List<Connector> Connectors { get; set; } public MainWindow() { InitializeComponent(); Nodes = NodesDataSource.GetRandomNodes().ToList(); Connectors = NodesDataSource.GetRandomConnectors(Nodes).ToList(); DataContext = this; } private void Thumb_Drag(object sender, DragDeltaEventArgs e) { var thumb = sender as Thumb; if (thumb == null) return; var data = thumb.DataContext as Node; if (data == null) return; data.X += e.HorizontalChange; data.Y += e.VerticalChange; } } }
Data Model:
public class Node: INotifyPropertyChanged { private double _x; public double X { get { return _x; } set {
Random data source (to fill the example with something)
using System; using System.Collections.Generic; using System.Linq; namespace NodesEditor { public static class NodesDataSource { public static Random random = new Random(); public static Node GetRandomNode() { return new Node { X = random.Next(0,500), Y = random.Next(0,500) }; } public static IEnumerable<Node> GetRandomNodes() { return Enumerable.Range(5, random.Next(6, 10)).Select(x => GetRandomNode()); } public static Connector GetRandomConnector(IEnumerable<Node> nodes) { return new Connector { Start = nodes.FirstOrDefault(), End = nodes.Skip(1).FirstOrDefault() }; } public static IEnumerable<Connector> GetRandomConnectors(List<Node> nodes) { var result = new List<Connector>(); for (int i = 0; i < nodes.Count() - 1; i++) { result.Add(new Connector() {Start = nodes[i], End = nodes[i + 1]}); } return result; } } }
This is what looks like on my computer:

source share