I’m trying to build a multi-level tree diagram with connectors between, where you can have an indeterminate number of nodes branching off.
I’ve based my solution so far on the ‘drawing the line’ section of this answer: /a/2824595/6691231, but I’m having trouble getting connectors to show at every level. This is the output I get for the code I’ve got:
which as you can see, only shows connectors in the first child level, not the second. I’ve tried adding all connectors to a single canvas, but got none displaying at all then. Can anyone assist?
Code to draw the chain UI:
<code>```private void DrawChain(List<ChainItem> itemList)
items = new ObservableCollection<ChainItem>();
Canvas connectorCanvas = new Canvas() { HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch };
mainGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
mainGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
mainGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
//connectorCanvas.Children.Add(mainGrid);
Dictionary<string, List<ChainItem>> childDict = itemList.GroupBy(i => i.ParentCode).ToDictionary(i => i.Key, i => i.ToList());
Dictionary<string, List<ChainItem>> parentDict = itemList.GroupBy(i => i.ChildCode).ToDictionary(i => i.Key, i => i.ToList());
ChainLink ourLink = new ChainLink(currentLinkObj, connectorCanvas);
mainGrid.Children.Add(ourLink);
StackPanel childrenVerticalPanel = new StackPanel() { Orientation = Orientation.Vertical };
mainGrid.Children.Add(childrenVerticalPanel);
Grid.SetRow(childrenVerticalPanel, 2);
DrawChildren(ourLink, childrenVerticalPanel, connectorCanvas, childDict, false);
private void DrawChildren(ChainLink parentLinkItem, StackPanel parentPanel, Canvas connectorCanvas, Dictionary<string, List<ChainItem>> itemDict, bool reverseConnector)
StackPanel chainLinkPanel = new StackPanel() { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center };
parentPanel.Children.Add(connectorCanvas);
parentPanel.Children.Add(chainLinkPanel);
if (itemDict.TryGetValue(parentLinkItem.Offer.Code, out List<ChainItem> children))
foreach (ChainItem child in children)
object linkObj = chain.FirstOrDefault(o => o.Code == child.ChildCode);
ChainLink childLink = new ChainLink(linkObj, connectorCanvas) { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top };
ChainConnector connector = new ChainConnector(reverseConnector) { Height = 35, HorizontalAlignment = HorizontalAlignment.Left };
connector.SetBinding(ChainConnector.SourceProperty, new Binding("AnchorPoint") { Source = parentLinkItem });
connector.SetBinding(ChainConnector.DestinationProperty, new Binding("AnchorPoint") { Source = childLink });
connectorCanvas.Children.Add(connector);
if (itemDict.TryGetValue(linkObj.Code, out _))
StackPanel verticalPanel = new StackPanel() { Orientation = Orientation.Vertical };
chainLinkPanel.Children.Add(verticalPanel);
verticalPanel.Children.Add(childLink);
Canvas childCanvas = new Canvas() { HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top };
DrawChildren(childLink, verticalPanel, childCanvas, itemDict, reverseConnector);
chainLinkPanel.Children.Add(childLink);
<code>```private void DrawChain(List<ChainItem> itemList)
{
items = new ObservableCollection<ChainItem>();
Canvas connectorCanvas = new Canvas() { HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch };
mainGrid = new Grid();
mainGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
mainGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
mainGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
//connectorCanvas.Children.Add(mainGrid);
// Set buyer links
Dictionary<string, List<ChainItem>> childDict = itemList.GroupBy(i => i.ParentCode).ToDictionary(i => i.Key, i => i.ToList());
Dictionary<string, List<ChainItem>> parentDict = itemList.GroupBy(i => i.ChildCode).ToDictionary(i => i.Key, i => i.ToList());
ChainLink ourLink = new ChainLink(currentLinkObj, connectorCanvas);
mainGrid.Children.Add(ourLink);
Grid.SetRow(ourLink, 1);
StackPanel childrenVerticalPanel = new StackPanel() { Orientation = Orientation.Vertical };
mainGrid.Children.Add(childrenVerticalPanel);
Grid.SetRow(childrenVerticalPanel, 2);
DrawChildren(ourLink, childrenVerticalPanel, connectorCanvas, childDict, false);
}
private void DrawChildren(ChainLink parentLinkItem, StackPanel parentPanel, Canvas connectorCanvas, Dictionary<string, List<ChainItem>> itemDict, bool reverseConnector)
{
StackPanel chainLinkPanel = new StackPanel() { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center };
parentPanel.Children.Add(connectorCanvas);
parentPanel.Children.Add(chainLinkPanel);
if (itemDict.TryGetValue(parentLinkItem.Offer.Code, out List<ChainItem> children))
{
foreach (ChainItem child in children)
{
object linkObj = chain.FirstOrDefault(o => o.Code == child.ChildCode);
if (linkObj != null)
{
ChainLink childLink = new ChainLink(linkObj, connectorCanvas) { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top };
ChainConnector connector = new ChainConnector(reverseConnector) { Height = 35, HorizontalAlignment = HorizontalAlignment.Left };
connector.SetBinding(ChainConnector.SourceProperty, new Binding("AnchorPoint") { Source = parentLinkItem });
connector.SetBinding(ChainConnector.DestinationProperty, new Binding("AnchorPoint") { Source = childLink });
connectorCanvas.Children.Add(connector);
if (itemDict.TryGetValue(linkObj.Code, out _))
{
StackPanel verticalPanel = new StackPanel() { Orientation = Orientation.Vertical };
chainLinkPanel.Children.Add(verticalPanel);
verticalPanel.Children.Add(childLink);
Canvas childCanvas = new Canvas() { HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top };
DrawChildren(childLink, verticalPanel, childCanvas, itemDict, reverseConnector);
}
else
{
chainLinkPanel.Children.Add(childLink);
}
}
}
}
}```
</code>
```private void DrawChain(List<ChainItem> itemList)
{
items = new ObservableCollection<ChainItem>();
Canvas connectorCanvas = new Canvas() { HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch };
mainGrid = new Grid();
mainGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
mainGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
mainGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
//connectorCanvas.Children.Add(mainGrid);
// Set buyer links
Dictionary<string, List<ChainItem>> childDict = itemList.GroupBy(i => i.ParentCode).ToDictionary(i => i.Key, i => i.ToList());
Dictionary<string, List<ChainItem>> parentDict = itemList.GroupBy(i => i.ChildCode).ToDictionary(i => i.Key, i => i.ToList());
ChainLink ourLink = new ChainLink(currentLinkObj, connectorCanvas);
mainGrid.Children.Add(ourLink);
Grid.SetRow(ourLink, 1);
StackPanel childrenVerticalPanel = new StackPanel() { Orientation = Orientation.Vertical };
mainGrid.Children.Add(childrenVerticalPanel);
Grid.SetRow(childrenVerticalPanel, 2);
DrawChildren(ourLink, childrenVerticalPanel, connectorCanvas, childDict, false);
}
private void DrawChildren(ChainLink parentLinkItem, StackPanel parentPanel, Canvas connectorCanvas, Dictionary<string, List<ChainItem>> itemDict, bool reverseConnector)
{
StackPanel chainLinkPanel = new StackPanel() { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center };
parentPanel.Children.Add(connectorCanvas);
parentPanel.Children.Add(chainLinkPanel);
if (itemDict.TryGetValue(parentLinkItem.Offer.Code, out List<ChainItem> children))
{
foreach (ChainItem child in children)
{
object linkObj = chain.FirstOrDefault(o => o.Code == child.ChildCode);
if (linkObj != null)
{
ChainLink childLink = new ChainLink(linkObj, connectorCanvas) { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Top };
ChainConnector connector = new ChainConnector(reverseConnector) { Height = 35, HorizontalAlignment = HorizontalAlignment.Left };
connector.SetBinding(ChainConnector.SourceProperty, new Binding("AnchorPoint") { Source = parentLinkItem });
connector.SetBinding(ChainConnector.DestinationProperty, new Binding("AnchorPoint") { Source = childLink });
connectorCanvas.Children.Add(connector);
if (itemDict.TryGetValue(linkObj.Code, out _))
{
StackPanel verticalPanel = new StackPanel() { Orientation = Orientation.Vertical };
chainLinkPanel.Children.Add(verticalPanel);
verticalPanel.Children.Add(childLink);
Canvas childCanvas = new Canvas() { HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top };
DrawChildren(childLink, verticalPanel, childCanvas, itemDict, reverseConnector);
}
else
{
chainLinkPanel.Children.Add(childLink);
}
}
}
}
}```
Chain item with LayoutUpdated method:
<code>```public ChainLink(object linkObj, Canvas canvas)
LayoutUpdated += OnLayoutUpdated;
private void OnLayoutUpdated(object sender, EventArgs e)
Point ofs = new Point(size.Width / 2, size.Height);
AnchorPoint = TransformToVisual(canvas).Transform(ofs);
<code>```public ChainLink(object linkObj, Canvas canvas)
{
this.linkObj= linkObj;
this.canvas = canvas;
LayoutUpdated += OnLayoutUpdated;
InitializeComponent();
}
private void OnLayoutUpdated(object sender, EventArgs e)
{
if (IsLoaded)
{
try
{
Size size = RenderSize;
Point ofs = new Point(size.Width / 2, size.Height);
AnchorPoint = TransformToVisual(canvas).Transform(ofs);
}
catch (Exception ex)
{
// Do nothing yet
}
}
}```
</code>
```public ChainLink(object linkObj, Canvas canvas)
{
this.linkObj= linkObj;
this.canvas = canvas;
LayoutUpdated += OnLayoutUpdated;
InitializeComponent();
}
private void OnLayoutUpdated(object sender, EventArgs e)
{
if (IsLoaded)
{
try
{
Size size = RenderSize;
Point ofs = new Point(size.Width / 2, size.Height);
AnchorPoint = TransformToVisual(canvas).Transform(ofs);
}
catch (Exception ex)
{
// Do nothing yet
}
}
}```
Connector code:
<code>```public partial class ChainConnector : UserControl
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(Point), typeof(ChainConnector), new FrameworkPropertyMetadata(default(Point)));
public static readonly DependencyProperty DestinationProperty = DependencyProperty.Register("Destination", typeof(Point), typeof(ChainConnector), new FrameworkPropertyMetadata(default(Point)));
private bool reverseDirection;
get { return (Point)this.GetValue(SourceProperty); }
set { this.SetValue(SourceProperty, value); }
get { return (Point)this.GetValue(DestinationProperty); }
set { this.SetValue(DestinationProperty, value); }
public ChainConnector(bool reverseDirection)
LineSegment segment = new LineSegment(default(Point), true);
PathFigure figure = new PathFigure(default(Point), new[] { segment }, false);
PathGeometry geometry = new PathGeometry(new[] { figure });
BindingBase sourceBinding = new Binding { Source = this, Path = new PropertyPath(SourceProperty) };
BindingBase destinationBinding = new Binding { Source = this, Path = new PropertyPath(DestinationProperty) };
BindingOperations.SetBinding(figure, PathFigure.StartPointProperty, sourceBinding);
BindingOperations.SetBinding(segment, LineSegment.PointProperty, destinationBinding);
BindingOperations.SetBinding(figure, PathFigure.StartPointProperty, destinationBinding);
BindingOperations.SetBinding(segment, LineSegment.PointProperty, sourceBinding);
<code>```public partial class ChainConnector : UserControl
{
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(Point), typeof(ChainConnector), new FrameworkPropertyMetadata(default(Point)));
public static readonly DependencyProperty DestinationProperty = DependencyProperty.Register("Destination", typeof(Point), typeof(ChainConnector), new FrameworkPropertyMetadata(default(Point)));
private bool reverseDirection;
public Point Source
{
get { return (Point)this.GetValue(SourceProperty); }
set { this.SetValue(SourceProperty, value); }
}
public Point Destination
{
get { return (Point)this.GetValue(DestinationProperty); }
set { this.SetValue(DestinationProperty, value); }
}
public ChainConnector(bool reverseDirection)
{
LineSegment segment = new LineSegment(default(Point), true);
PathFigure figure = new PathFigure(default(Point), new[] { segment }, false);
PathGeometry geometry = new PathGeometry(new[] { figure });
BindingBase sourceBinding = new Binding { Source = this, Path = new PropertyPath(SourceProperty) };
BindingBase destinationBinding = new Binding { Source = this, Path = new PropertyPath(DestinationProperty) };
if (!reverseDirection)
{
BindingOperations.SetBinding(figure, PathFigure.StartPointProperty, sourceBinding);
BindingOperations.SetBinding(segment, LineSegment.PointProperty, destinationBinding);
}
else
{
BindingOperations.SetBinding(figure, PathFigure.StartPointProperty, destinationBinding);
BindingOperations.SetBinding(segment, LineSegment.PointProperty, sourceBinding);
}
Content = new Path
{
Data = geometry,
StrokeThickness = 1,
Stroke = Brushes.Black,
MinWidth = 1,
MinHeight = 1
};
}
}```
</code>
```public partial class ChainConnector : UserControl
{
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(Point), typeof(ChainConnector), new FrameworkPropertyMetadata(default(Point)));
public static readonly DependencyProperty DestinationProperty = DependencyProperty.Register("Destination", typeof(Point), typeof(ChainConnector), new FrameworkPropertyMetadata(default(Point)));
private bool reverseDirection;
public Point Source
{
get { return (Point)this.GetValue(SourceProperty); }
set { this.SetValue(SourceProperty, value); }
}
public Point Destination
{
get { return (Point)this.GetValue(DestinationProperty); }
set { this.SetValue(DestinationProperty, value); }
}
public ChainConnector(bool reverseDirection)
{
LineSegment segment = new LineSegment(default(Point), true);
PathFigure figure = new PathFigure(default(Point), new[] { segment }, false);
PathGeometry geometry = new PathGeometry(new[] { figure });
BindingBase sourceBinding = new Binding { Source = this, Path = new PropertyPath(SourceProperty) };
BindingBase destinationBinding = new Binding { Source = this, Path = new PropertyPath(DestinationProperty) };
if (!reverseDirection)
{
BindingOperations.SetBinding(figure, PathFigure.StartPointProperty, sourceBinding);
BindingOperations.SetBinding(segment, LineSegment.PointProperty, destinationBinding);
}
else
{
BindingOperations.SetBinding(figure, PathFigure.StartPointProperty, destinationBinding);
BindingOperations.SetBinding(segment, LineSegment.PointProperty, sourceBinding);
}
Content = new Path
{
Data = geometry,
StrokeThickness = 1,
Stroke = Brushes.Black,
MinWidth = 1,
MinHeight = 1
};
}
}```