wpf 如何将DataTemplate数据类型绑定到接口?

mcvgt66p  于 2023-06-24  发布在  其他
关注(0)|答案(5)|浏览(179)

我正在编写一个复合的松散耦合MVVM WPF应用程序,父VM中的子VM是接口而不是类示例,例如

public IChildViewModel { get; set; }

现在我如何使用DataTemplate呈现这个属性呢?例如:

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

我理解由于接口的性质(多重继承等),WPF不允许这种直接绑定。但是,由于接口应该在松散耦合的应用程序中广泛使用,是否有将DataTemplate绑定到接口的变通方法?谢谢

1yjd4xko

1yjd4xko1#

你可以通过显式地告诉wpf你正在绑定到一个接口字段来绑定到接口:
(请注意,ViewModelBase只是实现INotifyPropertyChanged接口的基类)

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; }
}

然后在ViewModel上:

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

最后,Xaml使之成为可能:

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

正如您所看到的,绑定显式引用了'IInterface'定义。

qc6wkl3g

qc6wkl3g2#

似乎使用DataTemplateSelector是在这种情况下要走的路。

fwzugrvs

fwzugrvs3#

您可以将接口转换为等效的抽象类。它是这样工作的。

xvw2m8pv

xvw2m8pv4#

我在uwp中的数据模板中使用了绑定接口类型。我没有在Binding路径上显式指定接口类型。它在接口没有显式实现时工作。当接口被显式实现时,它会默默地失败。我相信,如果接口是显式实现的,那么就需要在Binding路径中显式引用接口类型,这样Binding就可以正确地查找属性路径。

nnt7mjpx

nnt7mjpx5#

下面是我的InheritanceDataTemplateSelector,它只适用于接口:

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 );
    }
}

使用方法:
Resources部分,像往常一样定义每个DataTemplate,现在每个DataType都是一个接口,而不是具体类型:

<DataTemplate DataType="{x:Type viewModels:MyViewModelInterface}">
    <local:MyView />
</DataTemplate>

然后,为InheritanceDataTemplateSelector再添加一个资源:

<myWpf:InterfaceDataTemplateSelector x:Key="InterfaceDataTemplateSelector" />

然后,在需要使用DataTemplate的正确位置,指定应该使用此选择器。例如,在ItemsControl中:

<ItemsControl ItemsSource="{Binding SomeViewModelCollection}"
    ItemTemplateSelector="{StaticResource InterfaceDataTemplateSelector}">

注意:ViewModel接口不必扩展INotifyPropertyChanged。ViewModel的具体实现可以*实现它,如果需要。
另外注意:与其他答案相反,在绑定到接口视图模型的成员时,不需要使用任何特殊的符号。(至少在WPF的任何最新版本中没有)。

相关问题