Here is my InheritanceDataTemplateSelector
which just works with interfaces:
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://mcmap.net/q/452132/-why-can-39-t-a-datatemplate-bind-to-an-interface-when-that-datatemplate-was-explicitly-returned-from-a-datatemplateselector/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.
Also note: contrary to what other answers suggest, there is no need to use any special notation when binding to members of an interface viewmodel. (At least not in any recent version of WPF.)
ContentControl
that sets it'sContentTemplate
based on aDataTrigger
that passes the DataContext and Interface to anIValueConverter
? You could then test if the Value is of the type passed in with the Parameter, and if True uses the appropriate DataTemplate – Wireman