当一个DataTemplate被显式地从DataTemplateSelector返回时,为什么不能将其绑定到一个接口?

9
我已经创建了一个DataTemplateSelector,并用一组已知接口进行初始化。如果传递到选择器中的项实现了这些接口中的一个,则会返回相应的数据模板。
首先,这是相关的ICategory接口...
public interface ICategory
{
    ICategory ParentCategory { get; set; }
    string    Name           { get; set; }

    ICategoryCollection Subcategories { get; }
}

这是一个DataTemplateSelector,它基于基类或接口进行匹配,而不仅仅是特定的具体类...

[ContentProperty("BaseTypeMappings")]
public class SubclassedTypeTemplateSelector : DataTemplateSelector
{
    private delegate object TryFindResourceDelegate(object key);

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        var frameworkElement = container as FrameworkElement;

        foreach(var baseTypeMapping in BaseTypeMappings)
        {
            // Check if the item is an instance of, a subclass of,
            // or implements the interface specified in BaseType
            if(baseTypeMapping.BaseType.IsInstanceOfType(item))
            {
                // Create a key based on the BaseType, (not item.DataType as usual)
                var resourceKey = new DataTemplateKey(baseTypeMapping.BaseType);

                // Get TryFindResource method from either the FrameworkElement,
                // or from the application
                var tryFindResource = (frameworkElement != null)
                    ? (TryFindResourceDelegate)frameworkElement.TryFindResource
                    : Application.Current.TryFindResource;

                // Use the TryFindResource delegate from above to try finding
                // the resource based on the resource key
                var dataTemplate = (DataTemplate)tryFindResource(resourceKey);
                dataTemplate.DataType = item.GetType();
                if(dataTemplate != null)
                    return dataTemplate;
            }
        }

        var defaultTemplate = DefaultDataTemplate ?? base.SelectTemplate(item, container);
        return defaultTemplate;
    }

    public DataTemplate DefaultDataTemplate { get; set; }

    public Collection<BaseTypeMapping> BaseTypeMappings { get; } = new Collection<BaseTypeMapping>();
}

public class BaseTypeMapping
{
    public Type BaseType { get; set; }
}

这是如何在资源中设置的,以及与 DataType = ICategory 相应的 HierarchicalDataTemplate...
    <HierarchicalDataTemplate DataType="{x:Type model:ICategory}"
        ItemsSource="{Binding Subcategories}">

        <TextBlock Text="{Binding Name}" />

    </HierarchicalDataTemplate>

    <is:SubclassedTypeTemplateSelector x:Key="SubclassedTypeTemplateSelector">
        <!--<is:BaseTypeMapping BaseType="{x:Type model:ICategory}" />-->
    </is:SubclassedTypeTemplateSelector>

最后,这里有一个使用它的TreeView...
<TreeView x:Name="MainTreeView"
    ItemsSource="{Binding Categories}"
    ItemTemplateSelector="{StaticResource SubclassedTypeTemplateSelector}" />

我已经调试过了,并且可以确认正确的数据模板按预期返回到TreeView中,这是通过代码步进和HierarchicalDataTemplate上的ItemSource绑定加载子类别时实现的。所有这些都正常工作。
不起作用的是模板本身的内容。如您所见,该模板只需显示类别的名称,但它只是将对象原始呈现出来,就好像直接放置在ContentPresenter中一样,没有任何模板。在UI中你只能看到ToString的结果。模板的内容被完全忽略了。
我唯一能想到的是因为我正在使用DataType的接口而导致无法工作,但是,孩子们的ItemsSource绑定确实起作用,所以我有点困惑。
值得注意的是:作为测试,我创建了一个基于具体类型(即Category而不仅仅是ICategory)的第二个DataTemplate,当我这样做时,它按预期工作。问题在于具体类型位于不应被UI引用的程序集中。这就是我们首先使用接口的全部原因。
注:我还尝试过改变查找模板的方式,使用Key而不是设置DataType属性。在那种情况下,选择器仍然找到相同的资源,但仍然不起作用!
具有讽刺意味的是,如果我使用相同的键通过StaticResource绑定直接设置TreeView的ItemTemplate,那么它会起作用,这意味着仅当我从选择器返回模板时才不起作用,并且与是否设置DataType无关。

简短回答:因为它是这样设计的。可能是因为一个类可以有多个匹配项。MSDN官方答案 - Manfred Radlwimmer
如果您查看MSDN链接,您会发现他们明确表示您可以使用DataTemplateSelector,这就是我在上面所做的。您的论点以及他们说他们决定反对的是他们试图自己匹配接口,而这不是我在这里要实现的。 - Mark A. Donohoe
你最初的问题是“为什么DataTemplate不能绑定到接口?”。我认为你对它为什么会这样很感兴趣。 - Manfred Radlwimmer
你找到解决办法了吗? - Mike Nakis
你的“SubclassedTypeTemplateSelector”的修改版在使用视图模型作为接口的非层级场景中对我起作用。你调试过并验证了它返回正确的模板,这意味着我所做的修改是无关紧要的。所以,你的问题很可能与你(在2017年)工作在一个层级场景相关。 - Mike Nakis
2个回答

1
“不起作用的是模板本身的内容。”
这是因为您在XAML标记中定义的模板没有被应用,因为DataType属性设置为接口类型。正如@Manfred Radlwimmer所建议的那样,这是按设计来的: https://social.msdn.microsoft.com/Forums/vstudio/en-US/1e774a24-0deb-4acd-a719-32abd847041d/data-templates-and-interfaces?forum=wpf。从DataTemplateSelector返回这样的模板并不能使其工作,正如您已经发现的那样。
但是,如果使用DataTemplateSelector来选择适当的数据模板,则可以从数据模板中删除DataType属性,并为每个模板分配一个唯一的x:Key:
<HierarchicalDataTemplate x:Key="ICategory" ItemsSource="{Binding Subcategories}">
    <TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>

您应该能够使用此键解析资源,例如:
var baseTypeName = "ICategory";
var dataTemplate = (DataTemplate)tryFindResource("baseTypeName");

实际上,我尝试过了,但那也不起作用。具体来说,我删除了 DataType 属性,添加了一个键,然后返回了通过该键找到的模板(这基本上就是您建议的方法),但仍然不起作用。然而,更奇怪的是,当我直接将该模板设置为 TreeView 的 ItemTemplate ({StaticResource ICategory}) 时,它确实可以工作。而且,无论哪种情况下,ItemsSource 绑定都可以工作,这让我更加困惑。 - Mark A. Donohoe

0
  1. DataTemplates 不能针对接口 - 所以你需要一个选择器或解决方法 -> 完成。
  2. 你可以使用 {x:Type} 而不是实现 BaseTypeMapping
  3. 使用 BaseType.IsAssignableFrom(item.GetType()) 检查是否匹配
  4. 你必须从模板定义中删除类型,如果你有错误的类型,它不能被分配 - 所以你添加一个键。
  5. #4 后,你的模板有一个键,并且不能通过仅分配类型来隐式工作,所以你必须将其删除 -> 在分配类型后,dataTemplate.Key = null
  6. 我这里没有 IDE,但是找到资源会给你一个实例,所以你通过引用进行更改。这是你想要的吗?

我实际上使用BaseTypeMapping,因为它还具有DataTemplate和DataTemplateKey属性(由于篇幅原因,在此处省略)。但是...如果使用它,这意味着模板没有分配DataType时,它可以在直接设置到ItemTemplate时工作,但是当通过ItemTemplateSelector返回时却无法工作,这就是为什么我完全被难住了,特别是因为如我所说,ItemsSource绑定确实起作用。 - Mark A. Donohoe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接