WPF 为接口指定 HierarchicalDataTemplate

WPF Specifying HierarchicalDataTemplate for Interface

我在 WPF 中发现了一个非常奇怪的怪癖。如果我为接口指定一个 DataTemplate,如果在 ItemsControl.ItemTemplate 中定义它会工作,但如果在 ItemsControl.Resrouces.

中定义则不会工作

具体例子:

我有一个要表示的树结构。树中的所有项都实现 IHardware,但它们不一定具有共同的基类型。如果我在 TreeView.ItemTemplate 内为 IHardware 定义一个 HierarchicalDataTemplate,一切都会顺利进行。如果我在 TreeView.Resources 内定义模板,它永远不会得到 used/applied。下面显示了两列相同的数据,第一列按预期工作,第二列没有。

<Window x:Class="WPFInterfaceBinding.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:self ="clr-namespace:WPFInterfaceBinding"
    DataContext="{Binding RelativeSource={RelativeSource Self}}"
    Title="MainWindow" Height="350" Width="525">

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <!-- Works -->
        <Border
            Grid.Column="0"
            Background="Gray">
            <TreeView
                ItemsSource="{Binding Hardware}">

                <TreeView.ItemTemplate>
                    <HierarchicalDataTemplate
                        DataType="{x:Type self:IHardware}"
                        ItemsSource="{Binding SubHardware}">
                        <TextBlock Text="{Binding Path=Name}" />
                    </HierarchicalDataTemplate>
                </TreeView.ItemTemplate>
            </TreeView>
        </Border>

        <!-- Doesn't work -->
        <Border
            Grid.Column="1"
            Background="Gray">
            <TreeView
                ItemsSource="{Binding Hardware}">

                <TreeView.Resources>
                    <HierarchicalDataTemplate
                        DataType="{x:Type self:IHardware}"
                        ItemsSource="{Binding SubHardware}">
                        <TextBlock Text="{Binding Path=Name}" />
                    </HierarchicalDataTemplate>
                </TreeView.Resources>
            </TreeView>
        </Border>

    </Grid>
</Window>

请注意,在第二列中,除了 TreeView.ItemTemplate -> TreeView.Resources 之外没有任何变化 为什么会这样?如何让模板在 Resources 内工作?我想我可以使用 DataTemplateSelector 来解决这个问题,但首先我很好奇是否有办法让它按预期实际工作。

代码隐藏,为了完整性

using System.Windows;

namespace WPFInterfaceBinding
{
    public partial class MainWindow : Window
    {
        public IHardware[] Hardware { get; private set; }

        public MainWindow ()
        {
            Hardware = InitializeHardware();

            InitializeComponent();
        }

        private IHardware[] InitializeHardware ()
        {
            return  new Hardware[] {
                new Hardware("Component 1", new Hardware[] {
                    new Hardware("Sub Component 1"),
                    new Hardware("Sub Component 2")
                }),
                new Hardware("Component 2", new Hardware[] {
                    new Hardware("Sub Component 3"),
                    new Hardware("Sub Component 4")
                })
            };
        }
    }

    public class Hardware : IHardware
    {
        public string      Name        { get; set; }
        public IHardware[] SubHardware { get; set; }

        public Hardware ( string name, Hardware[] subHardware = null )
        {
            Name = name;
            SubHardware = subHardware ?? new Hardware[0];
        }
    }

    public interface IHardware
    {
        string      Name        { get; set; }
        IHardware[] SubHardware { get; set; }
    }
}

附加信息:

事实证明,WPF 只是不喜欢绑定到接口。我唯一能想到的解决方法是使用 DataTemplateSelector.

public class OHMTreeTemplateSelector : DataTemplateSelector
{
    public HierarchicalDataTemplate HardwareTemplate { get; set; }
    public             DataTemplate   SensorTemplate { get; set; }

    public override DataTemplate SelectTemplate ( object item, DependencyObject container )
    {
             if ( item is IHardware ) return HardwareTemplate;
        else if ( item is ISensor   ) return   SensorTemplate;

        return base.SelectTemplate(item, container);
    }
}

不过,出于其他原因,我最终为通过具体类型公开数据的数据创建了一个单独的 ViewModel,从而规避了这个问题。