As mm8's answer says, interfaces are not supported. The explanation given by the "data binding team" is kind of funny: they did not know what to do when many interfaces match, so they did not implement the feature.
Here is my InheritanceDataTemplateSelector
which solves the problem in the following pragmatic way:
If your viewmodels implement multiple interfaces, and if you happen to have a DataTemplate defined for more than one such interfaces, this selector will just return the first matching DataTemplate.
It just does a breadth-first search, which will most likely be just fine for the vast majority of use cases, and if it is not fine for you, it should be fairly trivial to customize the search strategy to suit your needs.
namespace MyWpf;
using Sys = System;
using Wpf = System.Windows;
using WpfControls = System.Windows.Controls;
//PEARL: DataTemplate in WPF does not work with interfaces!
// The declaration <DataTemplate DataType="{x:Type SomeInterface}"> silently fails.
// We solve this problem by introducing a DataTemplateSelector
// that takes interfaces into consideration.
//Original inspiration from https://stackoverflow.com/q/41714918/773113
public class InterfaceDataTemplateSelector : WpfControls.DataTemplateSelector
{
delegate object? ResourceFinder( object key );
public override Wpf.DataTemplate? SelectTemplate( object item, Wpf.DependencyObject container )
{
ResourceFinder resourceFinder = getResourceFinder( container );
return tryGetDataTemplateRecursively( item.GetType(), resourceFinder );
}
static ResourceFinder getResourceFinder( Wpf.DependencyObject container ) //
=> (container is Wpf.FrameworkElement containerAsFrameworkElement) //
? containerAsFrameworkElement.TryFindResource //
: Wpf.Application.Current.TryFindResource;
static Wpf.DataTemplate? tryGetDataTemplateRecursively( Sys.Type type, ResourceFinder resourceFinder )
{
return tryGetDataTemplateFromType( type, resourceFinder ) //
?? tryGetDataTemplateFromInterfacesRecursively( type, resourceFinder ) //
?? tryGetDataTemplateFromSuperTypeRecursively( type, resourceFinder );
}
static Wpf.DataTemplate? tryGetDataTemplateFromType( Sys.Type type, ResourceFinder tryFindResource )
{
Wpf.DataTemplateKey resourceKey = new Wpf.DataTemplateKey( type );
object? resource = tryFindResource( resourceKey );
if( resource is Wpf.DataTemplate dataTemplate )
{
if( !dataTemplate.IsSealed )
dataTemplate.DataType = type;
return dataTemplate;
}
return null;
}
static Wpf.DataTemplate? tryGetDataTemplateFromInterfacesRecursively( Sys.Type type, ResourceFinder resourceFinder )
{
foreach( var interfaceType in type.GetInterfaces() )
{
Wpf.DataTemplate? dataTemplate = tryGetDataTemplateRecursively( interfaceType, resourceFinder );
if( dataTemplate != null )
return dataTemplate;
}
return null;
}
static Wpf.DataTemplate? tryGetDataTemplateFromSuperTypeRecursively( Sys.Type type, ResourceFinder resourceFinder )
{
return type.BaseType == null ? null : tryGetDataTemplateRecursively( type.BaseType, resourceFinder );
}
}
How to use:
In your Resources
section, define each DataTemplate
as usual, where now each DataType
is an interface instead of a concrete type:
<DataTemplate DataType="{x:Type viewModels:MyViewModelInterface}">
<local:MyView />
</DataTemplate>
Then, add one more resource for the InheritanceDataTemplateSelector
:
<myWpf:InterfaceDataTemplateSelector x:Key="InterfaceDataTemplateSelector" />
Then, at the right place which needs to make use of a DataTemplate
, specify that this selector should be used. For example, in an ItemsControl
:
<ItemsControl ItemsSource="{Binding SomeViewModelCollection}"
ItemTemplateSelector="{StaticResource InterfaceDataTemplateSelector}">
Note: the ViewModel interfaces do not have to extend INotifyPropertyChanged
. The concrete implementation of a ViewModel may implement it, if needed.