【问题标题】:Why can't a DataTemplate bind to an interface when that DataTemplate was explicitly returned from a DataTemplateSelector?当 DataTemplate 从 DataTemplateSelector 显式返回时,为什么 DataTemplate 不能绑定到接口?
【发布时间】:2017-01-18 08:52:42
【问题描述】:

我创建了一个 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,正如预期的那样,通过代码和因为 TreeView 正在根据 HierarchicalDataTemplate 上的 ItemSource 绑定正确加载子类别。所有这些都按预期工作。

不起作用的是模板本身的内容。如您所见,模板只是应该显示类别的名称,但它只是呈现对象原始,就好像它直接放置在没有任何模板的 ContentPresenter 中一样。您在 UI 中看到的只是 ToString 的结果。模板的内容被完全忽略。

我唯一能想到的是它不起作用,因为我正在使用 DataType 的接口,但同样,儿童 ItemsSource 的绑定确实有效,所以我在这里有点难过。

注意:作为测试,我根据具体类型(即 Category 而不仅仅是 ICategory)创建了第二个 DataTemplate,当我这样做时,它按预期工作。问题是具体类型位于 UI 不应引用的程序集中。这就是我们首先使用接口的全部原因。

*注意:我还尝试通过使用 Key 而不是设置 DataType 属性来更改查找模板的方式。在那种情况下,和以前一样,选择器仍然会找到相同的资源,但它仍然不起作用!

然而,具有讽刺意味的是,如果我使用相同的键直接通过 StaticResource 绑定设置 TreeView 的 ItemTemplate,那么它确实 工作,这意味着它只有在我返回模板时不起作用选择器和 not 是否出现与 DataType 是否设置有关。*

【问题讨论】:

  • 简短回答:因为它是这样设计的。可能是因为您可以为单个课程获得多个匹配项。 Official answer on MSDN
  • 如果你查看你的 MSDN 链接,你会看到他们特别说你可以使用 DataTemplateSelector,我在上面做的。你的论点,以及他们说他们决定反对的是他们试图自己匹配接口,这不是我在这里想要实现的。
  • 您最初的问题是“为什么 DataTemplate 不能绑定到接口?”。我以为你对它为什么会这样感兴趣。

标签: c# wpf datatemplate hierarchicaldatatemplate datatemplateselector


【解决方案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 绑定在任何一种情况下都有效,这让我更加困惑。
【解决方案2】:
  1. DataTemplates 不能针对接口 - 所以你需要一个选择器或解决方法 -> 完成。
  2. 您可以只使用 {x:Type} 而不是实现 BaseTypeMapping
  3. 使用BaseType.IsAssignableFrom(item.GetType()) 检查是否有匹配项
  4. 您必须从模板定义中删除该类型,如果您的类型错误,则无法分配它 - 因此您添加了一个键。
  5. 在 #4 之后,您的模板有一个键,并且仅通过分配类型就不能隐式工作,因此您必须在分配类型后将其删除 -> dataTemplate.Key = null
  6. 我这里没有 IDE,但是 find ressources 给了你一个实例,所以你通过引用来改变。这是你想要的吗?

【讨论】:

  • 我实际上使用 BaseTypeMapping 因为它还有一个 DataTemplate 和 DataTemplateKey 属性(为了简洁起见,我从这里省略了。)但是......使用它代替,意味着模板没有分配 DataType,它在直接设置为 ItemTemplate 时有效,但在通过 ItemTemplateSelector 返回时无效,这就是我在这里完全被难住的原因,特别是因为正如我所说,ItemsSource 绑定确实有效。
猜你喜欢
  • 2011-01-06
  • 1970-01-01
  • 2013-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-01
  • 2023-04-05
  • 2016-09-08
相关资源
最近更新 更多