Search code examples
c#wpfxamlcanvashierarchical

Drawing hierarchically Rectangle on WPF Canvas


I'm trying to place rectangles like in the next picture

enter image description here

The blue arrows shows the parent-element from the child.

I got a class named Box with a parent propertie to a Box. All the created Boxes I push into an ObservableCollection for my bindings in the XAML code.

Here is the Box class:

public class Box
{
    public string Content { get; set; } //Content in the Box
    public double X { get; set; }       //For Canvas.Left propertie
    public double Y { get; set; }       //For Canvas.Right propertie
    public double Width { get; set; }
    public double Height { get; set; }
    public Box Parent { get; set; }
}

But now I dont get a correct way to draw the rectangles on a canvas like in the picture. I got the idea to create a grid with different column amount but I'm not shure if this is possible.

Best regards.


Solution

  • If you want to draw Rectangles in a Canvas just for layout purpose, it will be much easier by taking advantage of WPF's Panels. To use complete binding with ObservableCollection, a lot of coding is required. So the following is a simple example.

    Box Class

    public class Box
    {
      public int Id { get; private set; }
      public int ParentId { get; private set; }
      public string Content { get; private set; }
    
      public Box(string content, int id, int parentId)
      {
        this.Id = id;
        this.ParentId = parentId;
        this.Content = content;
      }
    }
    

    BoxPanel Class inherited from StackPanel

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    
    public class BoxPanel : StackPanel
    {
      public int Id { get; private set; }
    
      private readonly Border topPanel;
      private readonly StackPanel bottomPanel;
    
      public BoxPanel()
      {
        topPanel = new Border();
        bottomPanel = new StackPanel { Orientation = Orientation.Horizontal };
    
        this.Children.Add(topPanel);
        this.Children.Add(bottomPanel);
      }
    
      public BoxPanel(Box box)
        : this()
      {
        Id = box.Id;
    
        topPanel.Child = new TextBlock
        {
          Text = box.Content,
          HorizontalAlignment = HorizontalAlignment.Center,
          VerticalAlignment = VerticalAlignment.Center,
          Padding = new Thickness(14),
          Foreground = Brushes.White,
        };
        topPanel.Background = (Id % 2 == 0) ? Brushes.Gray : Brushes.DarkGray;
        topPanel.BorderBrush = Brushes.Black;
        topPanel.BorderThickness = new Thickness(1);
      }
    
      protected override void OnInitialized(EventArgs e)
      {
        base.OnInitialized(e);
    
        this.Loaded += (_, __) => AdjustBottomPanel();
        this.LayoutUpdated += (_, __) => AdjustBottomPanel();
      }
    
      public IReadOnlyCollection<BoxPanel> ChildrenPanel
      {
        get { return bottomPanel.Children.Cast<BoxPanel>().ToArray(); }
      }
    
      public void AddChildPanel(BoxPanel child)
      {
        bottomPanel.Children.Add(child);
        AdjustBottomPanel();
      }
    
      private void AdjustBottomPanel()
      {
        if (!ChildrenPanel.Any())
          return;
    
        var childWidth = Math.Max((Double.IsNaN(this.Width) ? 0 : this.Width), this.ActualWidth)
          / ChildrenPanel.Count;
    
        foreach (var child in ChildrenPanel)
          child.Width = childWidth;
      }
    }
    

    XAML of MainWindow

    <Window x:Class="WpfBoxPanel.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Width="500" Height="240">
      <Grid x:Name="LayoutRoot"/>
    </Window>
    

    And code behind of MainWindow

    using System.Collections.Generic;
    using System.Linq;
    using System.Windows;
    
    public partial class MainWindow : Window
    {
      public MainWindow()
      {
        InitializeComponent();
    
        boxPanelRoot = new BoxPanel();
        LayoutRoot.Children.Add(boxPanelRoot);
    
        this.Loaded += (_, __) => PopulateBoxPanel();
      }
    
      private readonly IList<Box> Boxes = new List<Box>
      {
        new Box("1st", 1, 0),
        new Box("2nd 1", 2, 1),
        new Box("2nd 2", 3, 1),
        new Box("3rd 1", 4, 2),
        new Box("3rd 2", 5, 2),
        new Box("3rd 3", 6, 3),
        new Box("4th 1", 7, 4),
        new Box("4th 2", 8, 5),
        new Box("4th 3", 9, 5),
        new Box("4th 4", 10, 6),
        new Box("4th 5", 11, 6),
      };
    
      private readonly BoxPanel boxPanelRoot;
    
      private void PopulateBoxPanel()
      {
        foreach (var box in Boxes)
        {
          var existingPanels = boxPanelRoot.GetDescendants() // See VisualTreeHelperExtensions.GetDescendants method of WinRT Xaml Toolkit
            .OfType<BoxPanel>()
            .ToArray();
    
          if (existingPanels.Any(x => x.Id == box.Id))
            continue;
    
          var parent = existingPanels.FirstOrDefault(x => x.Id == box.ParentId);
          if (parent == null)
            parent = boxPanelRoot;
    
          parent.AddChildPanel(new BoxPanel(box));
        }
      }
    }
    

    WpfBoxPanel screenshot