XAML WPF中的自动模板选择不适用于接口

ar7v8xwq  于 2023-06-19  发布在  其他
关注(0)|答案(2)|浏览(104)

我有一个TreeView绑定到一个Tileset列表。Tileset包含TileGroupTileGroup包含TileTileRun示例。TileTileRun都实现了ITile,但最终会有更多类型实现ITile
我有以下XAML:

<TreeView
    Grid.Row="0"
    Grid.Column="0"
    BorderThickness="0"
    ItemsSource="{Binding Path=Tilesets}">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type local:Tileset}" ItemsSource="{Binding Path=TileGroups}">
            <TextBlock Text="{Binding Path=Name}" />
        </HierarchicalDataTemplate>
        <HierarchicalDataTemplate DataType="{x:Type local:TileGroup}" ItemsSource="{Binding Path=Tiles}">
            <TextBlock Text="{Binding Path=Name}" />
        </HierarchicalDataTemplate>
        <DataTemplate DataType="{x:Type tiles:ITile}">
            <TextBlock Text="{Binding Path=Name}" />
        </DataTemplate>
    </TreeView.Resources>
</TreeView>

TilesetTileGroup选择了正确的DataTemplate,但ITile没有选择,没有选择模板,树只显示数据类型。
但是,如果我为TileTileRun显式添加一个DataTemplate,一切都很好。
我知道我可以使用DataTemplateSelector来处理这个问题,但如果可能的话,我希望使用纯XAML解决方案。
是我做错了什么,还是WPF不支持这种基于接口的自动模板选择?

t40tm48m

t40tm48m1#

是我做错了什么,还是WPF不支持这种基于接口的自动模板选择?
你没有做错什么。接口的这种数据绑定支持根本不受支持。请参阅Beatriz Costa(MSFT)在MSDN论坛上的以下帖子中的回答,以了解有关原因的更多信息。

数据模板及接口:https://social.msdn.microsoft.com/Forums/vstudio/en-US/1e774a24-0deb-4acd-a719-32abd847041d/data-templates-and-interfaces?forum=wpf

  • “数据绑定团队在不久前讨论过添加对接口的支持,但最终没有实现它,因为我们无法提出一个好的设计。问题是接口没有像对象类型那样的层次结构。考虑这样一种场景:您的数据源同时实现IMyInterface1和IMyInterface2,并且资源中有这两个接口的DataTemplates:您认为我们应该选择哪个数据模板?*
  • 当为对象类型做隐式数据模板时,我们首先尝试为确切的类型找到一个DataTemplate,然后为它的父类、祖类等等。有非常明确的类型顺序供我们应用。当我们谈到添加对接口的支持时,我们考虑使用反射来找出所有接口并将它们添加到类型列表的末尾。我们遇到的问题是当类型实现多个接口时定义接口的顺序。

因此,您必须为Tile和TileRun显式定义DataTemplate,或者使用DataTemplateSelector。

q0qdq0h2

q0qdq0h22#

下面是我的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的具体实现可以*实现它,如果需要。
还要注意:如果你的视图模型实现了多个接口,并且你碰巧为多个这样的接口定义了一个DataTemplate,这个选择器将只返回第一个匹配的DataTemplate。它只是进行广度优先的搜索,自定义其搜索策略应该是相当简单的。

相关问题