Just Code‎ > ‎

WPF - Recursive DataTemplates to display graphs/trees or Recursive objects

posted Feb 5, 2013, 1:58 AM by Peter Henell   [ updated Feb 5, 2013, 2:37 AM ]
There are some occasions when your data is recursive and you want to display it all.
Here are a few examples of how to recursively, including a few that does not recursively list children just to compare.

The name of the datatemplate is written above each example.

 


MainViewWindow.xaml
<Window x:Class="DrawingBoxesAndLines.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:DrawingBoxesAndLines.ViewModels"
        Title="MainWindow" Height="600" Width="1200">
    <Window.Resources>
        <my:MainWindowViewModel x:Key="mainVM" />

        <DataTemplate DataType="{x:Type my:Node}" x:Key="twoLevels_NodeTemplate">
            <Border BorderThickness="1" BorderBrush="Black" Margin="3" MinHeight="50" MinWidth="100" Background="AliceBlue">
                <WrapPanel Orientation="Vertical">
                    <TextBlock Text="Children: " Margin="3" />
                    <ListBox ItemsSource="{Binding Children}" >
                        <ListBox.ItemTemplate>
                            <DataTemplate>
                                <Border BorderThickness="1" BorderBrush="Black" Margin="3">
                                    <Rectangle MinHeight="50" MinWidth="100" Fill="AliceBlue"/>
                                </Border>
                            </DataTemplate>
                        </ListBox.ItemTemplate>
                    </ListBox>
                </WrapPanel>
            </Border>
        </DataTemplate>
        
        <HierarchicalDataTemplate DataType="{x:Type my:Node}" ItemsSource="{Binding Path=Children}" x:Key="hierarchical_NodeTemplate" >
                <Border BorderThickness="1" BorderBrush="Black" Margin="3">
                    <Rectangle MinHeight="50" MinWidth="100" Fill="AliceBlue"/>
                </Border>
        </HierarchicalDataTemplate>

      
        <DataTemplate x:Key="listViewAttr">
            <StackPanel>
                <TextBlock Text="Children: " />
                <Border BorderThickness="1" BorderBrush="Black" Margin="3" MinHeight="50" MinWidth="100">
                    <ListView ItemsSource="{Binding Children}" ItemTemplate="{DynamicResource listViewData}" />
                </Border>
            </StackPanel>
        </DataTemplate>
        <DataTemplate x:Key="listViewData">
            <StackPanel>
                    <Border BorderThickness="1" BorderBrush="Black" Margin="3" MinHeight="50" MinWidth="100" >
                            <ListView ItemsSource="{Binding Children}" ItemTemplate="{StaticResource listViewAttr}" />
                    </Border>
            </StackPanel>
        </DataTemplate>

        <DataTemplate x:Key="recursiveListViewData">
            <StackPanel>
                <TextBlock Text="Children: " Margin="3"/>
                    <Border BorderThickness="1" BorderBrush="Black" Margin="3" MinHeight="50" MinWidth="100" >
                        <ListView ItemsSource="{Binding Children}" ItemTemplate="{DynamicResource recursiveListViewData}" />
                    </Border>
            </StackPanel>
        </DataTemplate>

    </Window.Resources>
    
    <Grid DataContext="{StaticResource ResourceKey=mainVM}">
        <Grid.Resources>
            <Style TargetType="TextBlock">
                <Setter Value="5" Property="Margin" />
            </Style>
            
        </Grid.Resources>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>


        <TextBlock Grid.Column="0" Text="hierarchical_NodeTemplate" />
        <ListBox ItemsSource="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}" ItemTemplate="{StaticResource ResourceKey=hierarchical_NodeTemplate}" Grid.Row="1">
        </ListBox>

        <TextBlock Grid.Column="1" Text="hierarchical_NodeTemplate tree" />
        <TreeView Grid.Column="1" ItemsSource="{Binding Nodes}" Grid.Row="1">
            <TreeView.Resources>
                <HierarchicalDataTemplate DataType="{x:Type my:Node}" ItemsSource="{Binding Path=Children}">
                    <Border BorderThickness="1" BorderBrush="Black" Margin="3">
                        <Rectangle MinHeight="50" MinWidth="100" Fill="AliceBlue"/>
                    </Border>
                </HierarchicalDataTemplate>
            </TreeView.Resources>
        </TreeView>

        <TextBlock Grid.Column="2" Text="inline HierarchicalDataTemplate" />
        <ListBox ItemsSource="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}" Grid.Column="2" Grid.Row="1">
            <ListBox.ItemTemplate>
                <HierarchicalDataTemplate DataType="{x:Type my:Node}" ItemsSource="{Binding Path=Children}">
                    <Border BorderThickness="1" BorderBrush="Black" Margin="3">
                        <Rectangle MinHeight="50" MinWidth="100" Fill="AliceBlue"/>
                    </Border>
                </HierarchicalDataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <TextBlock Grid.Column="3" Text="listViewData" />
        <ListBox Grid.Column="3" ItemTemplate="{DynamicResource listViewData}" ItemsSource="{Binding Nodes}" Grid.Row="1">
        </ListBox>


        <TextBlock Grid.Column="4" Text="recursiveListViewData" />
        <ListBox Grid.Column="4" ItemTemplate="{DynamicResource recursiveListViewData}" ItemsSource="{Binding Nodes}" Grid.Row="1">
        </ListBox>

        <TextBlock Grid.Column="5" Text="twoLevels_NodeTemplate" />
        <ListBox ItemsSource="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}" ItemTemplate="{StaticResource ResourceKey=twoLevels_NodeTemplate}" Grid.Column="5" Grid.Row="1">
        </ListBox>

    </Grid>
</Window>


MainViewModel.cs
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace DrawingBoxesAndLines.ViewModels 
{
    class Node : INotifyPropertyChanged
    {      
        Node _parent;
        public Node Parent
        {
            get
            {
                return _parent;
            }
            set
            {
                if (_parent != value)
                {
                    _parent = value;
                    OnPropertyChanged("Parent");
                }
            }
        }

        ObservableCollection<Node> _children;
        public ObservableCollection<Node> Children
        {
            get
            {
                return _children;
            }
            set
            {
                if (_children != value)
                {
                    _children = value;
                    OnPropertyChanged("Children");
                }
            }
        }


        int _nodeId;
        public int NodeId
        {
            get
            {
                return _nodeId;
            }
            set
            {
                if (_nodeId != value)
                {
                    _nodeId = value;
                    OnPropertyChanged("NodeId");
                }
            }
        }


        int _level;
        public int Level
        {
            get
            {
                return _level;
            }
            set
            {
                if (_level != value)
                {
                    _level = value;
                    OnPropertyChanged("Level");
                }
            }
        }


        bool _hasChildren;
        public bool HasChildren
        {
            get
            {
                return _hasChildren;
            }
            set
            {
                if (_hasChildren != value)
                {
                    _hasChildren = value;
                    OnPropertyChanged("HasChildren");
                }
            }
        }

        public List<ExecutionItem> ExecutionItems { get; set; }

        private static int nodeCounter = 0;

        public Node AddChild()
        {
            var n = new Node();
            n.Parent = this;
            n.Level = this.Level + 1;
            this.HasChildren = true;
            Children.Add(n);
            return n;
        }


        public static Node CreateLevelOneNode()
        {
            return new Node();
        }

        private Node()
        {
            NodeId = nodeCounter++;
            Children = new ObservableCollection<Node>();
            Level = 1;
            HasChildren = false;
        }

        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged == null)
                return;
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }

    class MainWindowViewModel : INotifyPropertyChanged
    {
        public ObservableCollection<Node> Nodes { get; set; }

        Node _selectedNode;
        public Node SelectedNode
        {
            get
            {
                return _selectedNode;
            }
            set
            {
                if (_selectedNode != value)
                {
                    _selectedNode = value;
                    OnPropertyChanged("SelectedNode");
                }
            }
        }

        public MainWindowViewModel()
        {
            Nodes = new ObservableCollection<Node>();
            
            var n = Node.CreateLevelOneNode();
            n.AddChild();
            
            var n2 = n.AddChild();
            n2.AddChild();
            
            n.AddChild();
            n.AddChild();

            Nodes.Add(n);
            Nodes.Add(Node.CreateLevelOneNode());
        }

        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
        }
    }
}
Comments