datatemplatecontentcontrolcontenttemplateselector

How to achieve recursive WPF ContentTemplateSelector for nested data?


I'm working on a WPF project and trying to use a recursive ContentTemplateSelector to display nested hierarchical data.

Here’s a simplified version of my model:

/***MyItem is recursive***/
public class MyItem
{
    public object Value { get; set; } //primitive values
    public List<object> Values {get; set; } // list of primitive values
    public List<Dictionary<string, MyItem>> ListValues { get; set; } // ><"
}

public ObservableCollection<MyItem> MyItems{get;set;}

And here’s how I start rendering it in XAML:

<ListBox ItemsSource="{Binding MyItems}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <ContentControl Content="{Binding}" 
                            ContentTemplateSelector="{StaticResource MySelector}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

The issue is that I want to render children recursively — and each child uses the same MySelector. Here's a simplified version of my ResourceDictionary:

<!-- Defining the selector at top of xaml causes it to not see the templates -->
<local:MyTemplateSelector x:Key="MySelector"
                          Primitive = PrimitiveTemplate
                          Composite = CompositeTemplate/>

<DataTemplate x:Key="PrimitiveTemplate">
    <TextBox Text="{Binding Value}"/>
</DataTemplate>

<DataTemplate x:Key="CompositeTemplate">
    <!-- Same recursive usage -->
    <ContentControl Content="{Binding Child}" 
                    ContentTemplateSelector="{StaticResource MySelector}" />
</DataTemplate>

Problem: If I define MySelector before the templates it depends on, WPF throws an error or fails to apply the templates. But I need to use MySelector recursively, so putting it at the bottom of the file feels counterintuitive and fragile.

Questions: Is there a cleaner way to handle recursive TemplateSelectors like this?

Can I avoid the issue of having to define MySelector after all the templates?

Is there a better WPF pattern for this type of deeply nested structure?

Any advice or better architectural patterns would be greatly appreciated!

YOU CAN STOP READ HERE, I describe ugly workarounds which I don't want to use...

I managed to ugly workaround by defining the same TemplateSelector after PrimitiveTemplate and before CompositeTemplate and giving it a different key, assuming I will have only 1 level of composite recursion (which is a fact, but nope.).

I also managed to find an even uglier workaround by defining a new class OtherTemplateSelector, and also calling it after PrimitiveTemplate but before CompositeTemplate

I also tries to define ContentTemplateSelector as a DynamicResource, but then compiler throws error about circular referance.

Here's a simplified version of the ugliest workaround:

<!-- Defining the selector at top of xaml causes it to not see the templates -->
<local:MyTemplateSelector x:Key="MySelector"
                          Primitive = PrimitiveTemplate
                          Composite = CompositeTemplate/>

<DataTemplate x:Key="PrimitiveTemplate">
    <TextBox Text="{Binding Value}"/>
</DataTemplate>

<local:OtherTemplateSelector x:Key="OtherSelector"
                          Primitive = PrimitiveTemplate/>

<DataTemplate x:Key="CompositeTemplate">
    <!-- Same recursive usage -->
    <ContentControl Content="{Binding Child}" 
                    ContentTemplateSelector="{StaticResource OtherSelector}" />
</DataTemplate>

I'll put here again the Problem and Questions just in case..

Problem: If I define MySelector before the templates it depends on, WPF throws an error or fails to apply the templates. But I need to use MySelector recursively, so putting it at the bottom of the file feels counterintuitive and fragile.

Questions: Is there a cleaner way to handle recursive TemplateSelectors like this?

Can I avoid the issue of having to define MySelector after all the templates?

Is there a better WPF pattern for this type of deeply nested structure?

Heres what I've tries:

searching the web for recursive template selector and nested template selector

discussing with AI about the problem (it keeps telling me to switch the position from top to bottm and vice versa, then it gave me a whole solution for the class of TemplateSelector but it didn't work.


Solution

  • Due disclosure: MyItem is not a class I wrote. I decided to:

    1. Transform each of MyItem value properties (Value, Values and CompositeValues) to class of their own by using Interface, abstract and Factory.
    2. Replace TemplateSelector with DataType="{x:Type local:MyItemValueSpecifcClass}", Pluse is I get to keep all the DataTemplates which handle visualization with no modifications. WPF knows to handle recursive calls to DataType :)

    I already wrote a MyItemWrapper that had lots of logic regarding MyItem properties. Here is a simplified example of the solution:

    public abstract class MyItemWrapper<IValue> : ViewModelBase
    {
    /***move getters and setters of Value and some extra logic to Concrete classes
    keep here logic which is same for all Concrete classes***/
    public abstract object Value{get;set;}
    public string Name{get;set;}
    }
    
    public class PrimitiveConcrete : MyItemWrapper<IValue>
    {
       private object _value;
       public override object Value{//getters and setters logic}
    ...
    }
    
    public class ListConcrete : MyItemWrapper<IValue>
    {
       private List<object> _value;
       public override object Value{//getters and setter logic initializing _value}
       ...
    }
    
    public class CompositeConcrete: MyItemWrapper<IValue>
    {
       private ObservableCollection<KeyedCollection<string, MyItemWrapper<IValue>>> _value;
       public override object Value{
       //logic regarding transforming List<Dictionary<...> to Obs<KeyedCollection and more (I need Two-Way modification)}
    }
    

    and lastly, I've added a factory class which is called from where calling MyItemWrapper() constructor class was.

    public static class MyItemFactory
    {
       public MyItemWrapper<IValue> Create(string name, MyItem myItem)
       {
          if (myItem is null)
             return new EmptyConcrete(name);
          if (myItem is List<Dictionary<string, MyItemWrapper>>)
             return new CompositeConcrete(name, myItem); 
          ... etc
       }
    }
    

    heres simplified xaml

    <ListBox ItemsSource="{Binding MyItems}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <ContentControl Content="{Binding}"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    
    <DataTemplate DataType="{x:Type local:PrimitiveConcrete}">
        <TextBox Text="{Binding Value}"/>
    </DataTemplate>
    but what really interesting is the nested templates:
    <DataTemplate DataType="{x:Type local:CompositeConcrete}">
        <ListBox ItemsSource="{Binding}">
           <ListBox.ItemTemplate>
             <DataTemplate>
                <WrapPanel>
    Thats it, here is the recursive DataType selection. 
    WPF knows by ContentControl to find the DataTemplate that suits the structure in Binding.
                      <ContentControl Content="{Binding}"/>
                </WrapPanel>
             </DataTemplate>
            </ListBox.ItemTemplate>
          </ListBox>
    </DataTemplate>
    

    I really hopes this helps for someone, let me know if there is a better approach please :>