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

Post date: Feb 5, 2013 9:58: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)); } }}