Search code examples
wpftreeviewvisual-studio-2022

Cannot add templates to TreeView resources without setting Keys


I'm trying to create a TreeView, where ItemsSource is a collection of different types of objects. Previously (VS2019) all I needed to do was to specify templates in TreeView.Resources.

<TreeView ItemsSource="{Binding collectionA}">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type ns:type1}" ItemsSource="{Binding collectionB}">
            <TextBlock Text="{Binding name}"/>
        </HierarchicalDataTemplate>
        <DataTemplate DataType="{x:Type ns:type2}">
            <CheckBox IsChecked="{Binding Checked}" Content="{Binding element.name}"/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type ns:type3}">
            <CheckBox IsChecked="{Binding Checked}" Content="{Binding name}"/>
        </DataTemplate>
    </TreeView.Resources>
</TreeView>

Now for some reason when I try to build my project, I get an error:

All objects added to an IDictionary must have a Key attribute or some other type of key associated with them.

My understanding is that DataType should automatically generate a key ( x:Key="{x:Type ns:typeX}" ) and set a correct template. I tried defining the keys manually, but templates still don't get assigned. I managed to make it work when I added templates without keys to resources during runtime, but this is not a viable workaround.

Is this a bug in VS2022 or am I doing something wrong?

How should I make this code work? Is there a manual way of selecting templates?


Solution

  • In the end I used a custom TemplateSelector to manually assign templates to objects from my collection.

    public class TreeViewTemplateSelector : DataTemplateSelector
        {
            public override DataTemplate SelectTemplate(object item, DependencyObject container)
            {
                if (item is type1)
                {
                    var template = new HierarchicalDataTemplate();
                    Binding itemsSource = new Binding();
                    itemsSource.Path = new PropertyPath("collectionB");
                    template.ItemsSource = itemsSource;
                    FrameworkElementFactory fef = new FrameworkElementFactory(typeof(TextBlock));
                    Binding name = new Binding();
                    name.Path = new PropertyPath("name");
                    fef.SetBinding(TextBlock.TextProperty, name);
                    template.VisualTree = fef;
                    return template;
                }
    
                if (item is type2)
                {
                    var template = new DataTemplate();
                    FrameworkElementFactory fef = new FrameworkElementFactory(typeof(CheckBox));
                    Binding name = new Binding();
                    name.Path = new PropertyPath("element.name");
                    Binding isChecked = new Binding();
                    isChecked.Path = new PropertyPath("Checked");
                    fef.SetBinding(CheckBox.ContentProperty, name);
                    fef.SetBinding(CheckBox.IsCheckedProperty, isChecked);
                    template.VisualTree = fef;
                    return template;
                }
    
                if (item is type3)
                {
                    var template = new DataTemplate();
                    FrameworkElementFactory fef = new FrameworkElementFactory(typeof(CheckBox));
                    Binding name = new Binding();
                    name.Path = new PropertyPath("name");
                    Binding isChecked = new Binding();
                    isChecked.Path = new PropertyPath("Checked");
                    fef.SetBinding(CheckBox.ContentProperty, name);
                    fef.SetBinding(CheckBox.IsCheckedProperty, isChecked);
                    template.VisualTree = fef;
                    return template;
                }
    
                return null;
            }
        }
    
    

    And in xaml:

    <UserControl.Resources>
            <ns:TreeViewTemplateSelector x:Key="templateSelector"/>
    </UserControl.Resources>
    
    (...)
    
    <TreeView ItemsSource="{Binding collectionA}" ItemTemplateSelector="{StaticResource templateSelector}">