WPF Specifying HierarchicalDataTemplate for Interface
data-binding, datatemplate, interface, wpf
Solution
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.
Problem
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: - I can't simply use `ItemTemplate` because in my actual usage scenario there will be non-`IHardware` items mixed in using a `CompositeCollection` so I need multiple templates. - I can't change the types of the collections from `IHardware` to something concrete because I'm displaying data from code I don't control. - This is just example code, not representative of any design patterns actually in use. - Defining the template inside `TreeView.Resources` works just fine if the type is changed from `IHardware` to `Hardware`.