【问题标题】:WPF Specifying HierarchicalDataTemplate for InterfaceWPF 为接口指定 HierarchicalDataTemplate
【发布时间】:2015-02-26 07:21:50
【问题描述】:

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

具体例子:

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

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

附加信息:

  • 我不能简单地使用ItemTemplate,因为在我的实际使用场景中,使用CompositeCollection 时会混入非IHardware 的项目,所以我需要多个模板。
  • 我无法将集合的类型从 IHardware 更改为具体的类型,因为我显示的数据来自我无法控制的代码。
  • 这只是示例代码,不代表实际使用的任何设计模式。
  • 如果类型从IHardware 更改为Hardware,在TreeView.Resources 中定义模板就可以正常工作。

【问题讨论】:

    标签: wpf data-binding interface datatemplate


    【解决方案1】:

    事实证明,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,从而规避了这个问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-16
      • 2020-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多