Search code examples
c#wpftreeview

TreeView HierarchicalDataTemplate with multiple nested node-types and potentially "infinite" levels


I have three classes viz.

public class ConfiguratorObjectViewModel
{
    public string Name { get; set; }
    
    public string Id { get; set; }
        
        public string ParentId { get; set; }
    
    public ObservableCollection<ConfiguratorAttributeViewModel> Attributes { get; set; } 
        
    public ObservableCollection<ConfiguratorObjectFamilyViewModel> Children { get; set; }   
}
public class ConfiguratorObjectFamilyViewModel
{
    public string Name { get; set; }
    
    public string Id { get; set; }
        
        public string ParentId { get; set; }
        
    public ObservableCollection<ConfiguratorObjectViewModel> ConfiguratorObjects { get; set; }  
}
public class ConfiguratorAttributeViewModel
{
    public string Name { get; set; }
    
    public string Id { get; set; }
        
    public string ParentId { get; set; }    
}

I wish to get a TreeView HierarchicalDataTemplate that recursively builds a tree of potentially infinite depth, depending on the data available, for example

-Root(ConfiguratorObject)
    |-Family1(ConfiguratorObjectFamily)
        |-Object1(ConfiguratorObject)
        |-Object2(ConfiguratorObject)
            |-AnotherFamily(ConfiguratorObjectFamily)
                |-AnotherObject1(ConfiguratorObject)
                |-AnotherObject2(ConfiguratorObject)
    |-Family2(ConfiguratorObjectFamily)
    |-Famil3(ConfiguratorObjectFamily)

I tried this, which works, but is limited because some values(levels) will not show ...

<TreeView ItemsSource="{Binding RootObject}" >
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
             <!--Root--> 
            <local:ConfiguratorObjectTreeItem  />
            
            <HierarchicalDataTemplate.ItemTemplate>
                <HierarchicalDataTemplate  ItemsSource="{Binding ConfiguratorObjects}">
                     <!--Children--> 
                    <local:ConfiguratorObjectTreeItem  />
                    
                    <HierarchicalDataTemplate.ItemTemplate>
                        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                             <!--Variants--> 
                            <local:ConfiguratorObjectTreeItem  />                                        

                        </HierarchicalDataTemplate>
                    </HierarchicalDataTemplate.ItemTemplate>
                </HierarchicalDataTemplate>
            </HierarchicalDataTemplate.ItemTemplate>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>

</TreeView>

I tried the solution provided above. The solution "works" but is static - is limited on the number of levels that are shown on the tree. I need a solution that is dynamic. Any help will be appreciated!

Edit

I have tried @BionicOCode suggestion but get only the root node

<TreeView ItemsSource="{Binding RootObject}" >
    <TreeView.Resources>
    
    <HierarchicalDataTemplate DataType="{x:Type local:ConfiguratorObjectFamilyViewModel}" ItemsSource="{Binding Children}">
            <local:ConfiguratorObjectTreeItem  />
        </HierarchicalDataTemplate>
    
     <HierarchicalDataTemplate DataType="{x:Type local:ConfiguratorObjectViewModel}" ItemsSource="{Binding ConfiguratorObjects}" >
            <local:ConfiguratorObjectTreeItem  />
        </HierarchicalDataTemplate>   

    </TreeView.Resources>

</TreeView>

Solution

  • You must define all templates as implicit HierarchicalDataTemplate. This means you must set the HierarchicalDataTemplate.DataType property and add the template without an explicit key to a ResourceDictionary within the scope of the TreeView (for example TreeView.Resources or App.xaml).
    Now, when you define an implicit HierarchicalDataTemplate (or DataTemplate if the node can't have or is not allowed to have children) for each data model type, WPF will automatically load the matching template for the current data model type in the tree.

    Alternatively define a DataTemplateSelector to explicitly select the template to load for the current data model.

    The following example shows how to define the data tree visual appearance implicitly to allow fully dynamic tree structure with dynamic tree levels (depth and width/breadth) and tree compositions. The tree randomly contains orange, yellow and blue nodes that are rendered as a circle, rectangle and line.

    MainWindow.xaml.cs

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
      var orange1 = new OrangeDataModel("Orange 1 (Root)");
    
      var yellow1 = new YellowDataModel("Yellow 1");
      orange1.Children.Add(yellow1);
    
      var blue1 = new BlueDataModel("Blue 1");
      orange1.Children.Add(blue1);
    
      var orange2 = new OrangeDataModel("Orange 2");
      var orange3 = new OrangeDataModel("Orange 3");
      blue1.Children.Add(orange2);
      blue1.Children.Add(orange3);
    
      var yellow2 = new YellowDataModel("Yellow 2");
      blue1.Children.Add(yellow2);
    
      var blue2 = new BlueDataModel("Blue 2");
      var blue3 = new BlueDataModel("Blue 3");
      yellow2.Children.Add(blue1);
      yellow2.Children.Add(blue3);
          
      var orange4 = new OrangeDataModel("Orange 4");
      yellow1.Children.Add(orange4);
    
      var root = new ObservableCollection<object> { orange1 };
      this.TreeView.ItemsSource = root;
    }
    

    MainWindow.xaml

    <TreeView x:Name="TreeView">
      <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type local:OrangeDataModel}"
                                  ItemsSource="{Binding Yellows}">
          <Grid>
            <Ellipse Height="50"
                      Width="50"
                      Fill="DarkOrange" />
            <TextBlock Text="{Binding TextValue}" />
          </Grid>
        </HierarchicalDataTemplate>
        <HierarchicalDataTemplate DataType="{x:Type local:YellowDataModel}"
                                  ItemsSource="{Binding Blues}">
          <Grid>
            <Rectangle Height="50"
                        Width="50"
                        Fill="Yellow" />
            <TextBlock Text="{Binding TextValue}" />
          </Grid>
        </HierarchicalDataTemplate>
        <HierarchicalDataTemplate DataType="{x:Type local:BlueDataModel}"
                                  ItemsSource="{Binding Oranges}">
          <Grid>
            <Line X2="50"
                  Stroke="Blue" />
            <TextBlock Text="{Binding TextValue}" />
          </Grid>
        </HierarchicalDataTemplate>
      </TreeView.Resources>
    </TreeView>
    

    OrangeDataModel.cs

    public class OrangeDataModel : INotifyPropertyChanged
    {
      public OrangeDataModel(string textValue)
      {
        this.TextValue = textValue;
        this.Yellows = new ObservableCollection<object>();
      }
    
      public string TextValue { get; set; }
      public ObservableCollection<object> Children { get; }
      public event PropertyChangedEventHandler? PropertyChanged;
    }
    

    YellowDataModel.cs

    public class YellowDataModel : INotifyPropertyChanged
    {
      public YellowDataModel(string textValue)
      {
        this.TextValue = textValue;
        this.Blues = new ObservableCollection<object>();
      }
    
      public string TextValue { get; set; }
      public ObservableCollection<object> Children{ get; }
      public event PropertyChangedEventHandler? PropertyChanged;
    }
    

    BlueDataModel.cs

    public class BlueDataModel : INotifyPropertyChanged
    {
      public BlueDataModel(string textValue)
      {
        this.TextValue = textValue;
        this.Oranges = new ObservableCollection<object>();
      }
    
      public string TextValue { get; set; }
      public ObservableCollection<object> Children{ get; }
      public event PropertyChangedEventHandler? PropertyChanged;
    }