I've created a DataTemplateSelector which is initialized with a collection of known interfaces. If an item passed into the selector implements one of those interfaces, the associated data template is returned.
First, here's the ICategory interface in question...
public interface ICategory
{
ICategory ParentCategory { get; set; }
string Name { get; set; }
ICategoryCollection Subcategories { get; }
}
Here's the DataTemplateSelector which matches based on a base class or interface rather than just a specific concrete class...
[ContentProperty("BaseTypeMappings")]
public class SubclassedTypeTemplateSelector : DataTemplateSelector
{
private delegate object TryFindResourceDelegate(object key);
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var frameworkElement = container as FrameworkElement;
foreach(var baseTypeMapping in BaseTypeMappings)
{
// Check if the item is an instance of, a subclass of,
// or implements the interface specified in BaseType
if(baseTypeMapping.BaseType.IsInstanceOfType(item))
{
// Create a key based on the BaseType, (not item.DataType as usual)
var resourceKey = new DataTemplateKey(baseTypeMapping.BaseType);
// Get TryFindResource method from either the FrameworkElement,
// or from the application
var tryFindResource = (frameworkElement != null)
? (TryFindResourceDelegate)frameworkElement.TryFindResource
: Application.Current.TryFindResource;
// Use the TryFindResource delegate from above to try finding
// the resource based on the resource key
var dataTemplate = (DataTemplate)tryFindResource(resourceKey);
dataTemplate.DataType = item.GetType();
if(dataTemplate != null)
return dataTemplate;
}
}
var defaultTemplate = DefaultDataTemplate ?? base.SelectTemplate(item, container);
return defaultTemplate;
}
public DataTemplate DefaultDataTemplate { get; set; }
public Collection<BaseTypeMapping> BaseTypeMappings { get; } = new Collection<BaseTypeMapping>();
}
public class BaseTypeMapping
{
public Type BaseType { get; set; }
}
Here's how it's set up in the resources along with the respective HierarchicalDataTemplate with DataType = ICategory...
<HierarchicalDataTemplate DataType="{x:Type model:ICategory}"
ItemsSource="{Binding Subcategories}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<is:SubclassedTypeTemplateSelector x:Key="SubclassedTypeTemplateSelector">
<!--<is:BaseTypeMapping BaseType="{x:Type model:ICategory}" />-->
</is:SubclassedTypeTemplateSelector>
And finally, here's a TreeView which uses it...
<TreeView x:Name="MainTreeView"
ItemsSource="{Binding Categories}"
ItemTemplateSelector="{StaticResource SubclassedTypeTemplateSelector}" />
I've debugged it and can confirm the correct data template is being returned to the TreeView as expected both stepping through the code and because the TreeView is properly loading the subcategories as per the ItemSource binding on the HierarchicalDataTemplate. All of this works as expected.
What doesn't work is the contents of the template itself. As you can see, the template is simply supposed to show the name of the category but it's just presenting the object raw as if it were placed directly in a ContentPresenter without any template. All you see in the UI is the result of ToString. The template's contents are completely ignored.
The only thing I can think of is its not working because I'm using an interface for the DataType, but again, the binding for the children's ItemsSource does work, so I'm kind of stumped here.
Of note: As a test, I created a second DataTemplate based on the concrete type (i.e. Category and not just ICategory) and when I did, it worked as expected. The problem is the concrete type is in an assembly that's not supposed to be referenced by the UI. That's the entire reason we're using interfaces in the first place.
*NOTE: I have also tried changing the way I look up the template by using a Key instead of setting the DataType property. In that case, just as before, the selector still finds the same resource, but it still doesn't work!
Ironically however, if I use that same key to set the ItemTemplate of the TreeView directly via a StaticResource binding, then it does work, meaning it only doesn't work when I return the template from the selector and does not appear related to whether DataType is set or not.*
SubclassedTypeTemplateSelector
worked for me with viewmodels-as-interfaces in a non-hierarchical scenario. The fact that you debugged yours and you verified that it returns the right template means that the modifications I made are irrelevant. So, your problem is most likely related to the fact that you are (were, in 2017,) working in a hierarchical scenario. – Kor