Adam
Adam

Reputation: 1221

WPF Specifying HierarchicalDataTemplate for Interface

I've found a really strange quirk in WPF. If I specify a DataTemplate for an interface, it will work if defined inside an ItemsControl.ItemTemplate, but will not work if defined inside ItemsControl.Resrouces.

Concrete example:

I have a tree structure I want to represent. All items in the tree implement IHardware, but they do not necessarily have a common base type. If I define a HierarchicalDataTemplate for IHardware inside TreeView.ItemTemplate, everything works swimmingly. If I define the template inside TreeView.Resources, it never gets used/applied. The following shows the same data in 2 columns, the first column works as expected, the second column does not.

<Window x:Class="WPFInterfaceBinding.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:self ="clr-namespace:WPFInterfaceBinding"
    DataContext="{Binding RelativeSource={RelativeSource Self}}"
    Title="MainWindow" Height="350" Width="525">

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <!-- Works -->
        <Border
            Grid.Column="0"
            Background="Gray">
            <TreeView
                ItemsSource="{Binding Hardware}">

                <TreeView.ItemTemplate>
                    <HierarchicalDataTemplate
                        DataType="{x:Type self:IHardware}"
                        ItemsSource="{Binding SubHardware}">
                        <TextBlock Text="{Binding Path=Name}" />
                    </HierarchicalDataTemplate>
                </TreeView.ItemTemplate>
            </TreeView>
        </Border>

        <!-- Doesn't work -->
        <Border
            Grid.Column="1"
            Background="Gray">
            <TreeView
                ItemsSource="{Binding Hardware}">

                <TreeView.Resources>
                    <HierarchicalDataTemplate
                        DataType="{x:Type self:IHardware}"
                        ItemsSource="{Binding SubHardware}">
                        <TextBlock Text="{Binding Path=Name}" />
                    </HierarchicalDataTemplate>
                </TreeView.Resources>
            </TreeView>
        </Border>

    </Grid>
</Window>

Note that in the second column, nothing has changed except TreeView.ItemTemplate -> TreeView.Resources Why is this the case? How can I get the template to work when inside Resources? I imagine I can work around this using a DataTemplateSelector, but first I'm curious if there's a way to actually get it working as expected.

Code behind, for completeness

using System.Windows;

namespace WPFInterfaceBinding
{
    public partial class MainWindow : Window
    {
        public IHardware[] Hardware { get; private set; }

        public MainWindow ()
        {
            Hardware = InitializeHardware();

            InitializeComponent();
        }

        private IHardware[] InitializeHardware ()
        {
            return  new Hardware[] {
                new Hardware("Component 1", new Hardware[] {
                    new Hardware("Sub Component 1"),
                    new Hardware("Sub Component 2")
                }),
                new Hardware("Component 2", new Hardware[] {
                    new Hardware("Sub Component 3"),
                    new Hardware("Sub Component 4")
                })
            };
        }
    }

    public class Hardware : IHardware
    {
        public string      Name        { get; set; }
        public IHardware[] SubHardware { get; set; }

        public Hardware ( string name, Hardware[] subHardware = null )
        {
            Name = name;
            SubHardware = subHardware ?? new Hardware[0];
        }
    }

    public interface IHardware
    {
        string      Name        { get; set; }
        IHardware[] SubHardware { get; set; }
    }
}

Additional information:

Upvotes: 4

Views: 940

Answers (1)

Adam
Adam

Reputation: 1221

Turns out, WPF just doesn't like binding to interfaces. The only work around I could figure out was to use a DataTemplateSelector.

public class OHMTreeTemplateSelector : DataTemplateSelector
{
    public HierarchicalDataTemplate HardwareTemplate { get; set; }
    public             DataTemplate   SensorTemplate { get; set; }

    public override DataTemplate SelectTemplate ( object item, DependencyObject container )
    {
             if ( item is IHardware ) return HardwareTemplate;
        else if ( item is ISensor   ) return   SensorTemplate;

        return base.SelectTemplate(item, container);
    }
}

Though, for other reasons I ended up creating a separate ViewModel for the data that exposes it through concrete types, circumventing this issue.

Upvotes: 4

Related Questions