How to bind DataTemplate datatype to interface?
Asked Answered
A

5

34

I am writing a composite loosely coupled MVVM WPF application and child VMs in a parent VM are interfaces rather than class instances, e.g.

public IChildViewModel { get; set; }

Now how do I render this property using a DataTemplate? like:

<DataTemplate DataType="{x:Type contracts:IChildViewModel}">

I understand due to the nature of interfaces (multiple inheritance etc.) WPF does not allow this direct binding. But as interfaces should be used widely in loosely coupled applications, is there any workaround to bind DataTemplate to interfaces? Thanks.

Alchemize answered 22/2, 2013 at 11:43 Comment(1)
What about using a ContentControl that sets it's ContentTemplate based on a DataTrigger that passes the DataContext and Interface to an IValueConverter? You could then test if the Value is of the type passed in with the Parameter, and if True uses the appropriate DataTemplateWireman
B
4

You can bind to interfaces by telling wpf explicitly that you are binding to an interface field:

(Please note that ViewModelBase is simply a base-class that implements the INotifyPropertyChanged interface)

public class Implementation : ViewModelBase, IInterface
{
    private string textField;

    public string TextField
    {
        get
        {
            return textField;
        }
        set
        {
            if (value == textField) return;
            textField = value;
            OnPropertyChanged();
        }
    }
}

public interface IInterface
{
    string TextField { get; set; }
}

Then on the ViewModel:

private IInterface interfaceContent;
public IInterface InterfaceContent
{
    get { return interfaceContent; }
}

And finally the Xaml that makes it possible:

<ContentControl Grid.Row="1" Grid.Column="0" Content="{Binding InterfaceContent}">
    <ContentControl.ContentTemplate>
        <DataTemplate DataType="{x:Type viewModels:IInterface}">
            <TextBox Text="{Binding Path=(viewModels:IInterface.TextField)}"/>
        </DataTemplate>
    </ContentControl.ContentTemplate>
</ContentControl>

As you can see, the binding refers explicitly to the 'IInterface' definiton.

Basutoland answered 3/9, 2014 at 11:19 Comment(3)
Does this actually work? I believe the XAML type system doesn't consider interfaces, as discussed here: badecho.com/2012/07/adding-interface-support-to-datatemplates Although this might compile. I doubt it works as expected.Indelicacy
Indeed, I just tried: It compiles but the DataTemplate is not applied.Acquisition
Just tried this too. It works but only if you explicitly write Path=. I.e. {Binding (viewModels:IInterface.TextField)} won't work.Portsmouth
A
4

It seems that using a DataTemplateSelectoris the way to go in such situations.

Aemia answered 12/11, 2019 at 15:46 Comment(0)
H
1

You can convert your interface to an equivalent abstract class. It works in this way.

Helvetic answered 23/8, 2019 at 12:9 Comment(0)
E
1

I have used Binding with interface types in a data template, in uwp. I did not specify the interface type explicitly on the Binding path. It worked when the interface was not implemented explicitly. When the interface was implemented explicitly it failed silently. I believe that if the interface is implemented explicitly then the explicit reference to the interface type in the Binding path is needed, so that the Binding can correctly look up the property path.

Exocrine answered 28/1, 2020 at 13:2 Comment(0)
T
1

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.)

Trunks answered 11/6, 2023 at 17:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.