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; }
}
}
附加信息:
- 我不能简单地使用
ItemTemplate
,因为在我的实际使用场景中,使用CompositeCollection
会混合使用非IHardware
项,所以我需要多个模板。
- 我无法将集合的类型从
IHardware
更改为具体的内容,因为我正在显示来自我无法控制的代码的数据。
- 这只是示例代码,并不代表实际使用的任何设计模式。
- 如果类型从
IHardware
更改为 Hardware
,那么在 TreeView.Resources
中定义模板就可以正常工作。
事实证明,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,从而规避了这个问题。
我在 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; }
}
}
附加信息:
- 我不能简单地使用
ItemTemplate
,因为在我的实际使用场景中,使用CompositeCollection
会混合使用非IHardware
项,所以我需要多个模板。 - 我无法将集合的类型从
IHardware
更改为具体的内容,因为我正在显示来自我无法控制的代码的数据。 - 这只是示例代码,并不代表实际使用的任何设计模式。
- 如果类型从
IHardware
更改为Hardware
,那么在TreeView.Resources
中定义模板就可以正常工作。
事实证明,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,从而规避了这个问题。