当所有项目都在包装器中时,如何为 TreeView 编写数据模板?
How do I write the DataTemplates for a TreeView when all the items are in a wrapper?
我正在处理一个 WPF 项目。我最近做了一个重构,将我的一些模型 object 放入包装器中。我删减了 classes 以便他们阅读得很好:
public class Wrapper
{
public AbstractModel Model;
}
public abstract class AbstractModel
{
public string DisplayName;
}
public class Parent : AbstractModel
{
public ObservableCollection<Wrapper> MyChildren;
}
public class Child : AbstractModel
{
//Nothing relevant to the issue (I think).
}
//a .xaml.cs file
public partial class MyPropertiesControl
{
//This is actually a dependency property that I bind to in other views, but I was too lazy to write the whole thing out.
public ObservableCollection<Wrapper> ItemsToDisplay { get; }
}
我想显示一个 TreeView,其中显示了 Wrappers 的集合及其(潜在的)children。以前,没有 Wrapper class,只有 AbstractModel、Parent 和 Child。在这种情况下创建树视图非常简单:
<TreeView ItemsSource="{Binding ElementName=Root, Path=ItemsToDisplay}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type Parent}" ItemsSource="{Binding MyChildren}">
<Label Content="{Binding DisplayName}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type Child}">
<Label Content="{Binding DisplayName}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
既然 object 都在包装器中,我不知道如何编写模板。我尝试了很多不同的东西。这是我尝试过的两个想法:
<TreeView ItemsSource="{Binding ElementName=Root, Path=ItemsToDisplay}">
<TreeView.Resources>
<DataTemplate DataType="{x:Type Wrapper}">
<ContentControl Content="{Binding Model}">
<ContentControl.Resources>
<HierarchicalDataTemplate DataType="{x:Type Parent}" ItemsSource="{Binding MyChildren}">
<Label Content="{Binding DisplayName}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type Child}">
<Label Content="{Binding DisplayName}"/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TreeView.Resources>
</TreeView>
<TreeView ItemsSource="{Binding ElementName=Root, Path=ItemsToDisplay}">
<TreeView.Resources>
<DataTemplate DataType="{x:Type Wrapper}">
<ContentControl Content="{Binding Model}">
<ContentControl.Resources>
<HierarchicalDataTemplate DataType="{x:Type Parent}" ItemsSource="{Binding MyChildren}">
<HierarchicalDataTemplate.Resources>
<DataTemplate DataType="{x:Type Wrapper}">
<ContentControl Content="{Binding Model}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type Child}">
<Label Content="{Binding DisplayName}"/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</HierarchicalDataTemplate.Resources>
<Label Content="{Binding DisplayName}"/>
</HierarchicalDataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TreeView.Resources>
</TreeView>
在这两种情况下,我只看到 parent 的第一层,没有看到任何 children。甚至没有 WPF 在没有为该类型定义的模板时使用的默认 class 名称,但 parent 节点实际上根本不显示 children。
我为鼠标在树视图上拖动添加了一个事件处理程序,并在事件处理程序上放置了一个断点,这样我就可以确保所有集合都已填充(它们是)。
这里还有其他人将 wrapped objects 绑定到树视图吗?您是如何让所有数据正确显示的?
我想另一个潜在的问题是通过 ContentControls 进入包装的 object 可能不是最好的主意。我没有从中得到 code-smell 的感觉,但我无法以更简洁的方式访问包装的 object 有点烦人。如果有人有任何补救的想法,它可能有助于解决 TreeView 问题。
我不确定是否有任何 XAML 唯一的方法可以做到这一点。但是你应该可以使用 DataTemplateSelector.
Provides a way to choose a DataTemplate based on the data object and the data-bound element.
他们在该文档中给出的示例涉及根据所显示项目的 属性 在两个 DataTemplate
之间进行选择。这正是您想要的:根据 Model
.
的类型选择两个 HierarchicalDataTemplate
之一
包装器的一个 DataTemplate 就可以了。在它里面绑定到模型属性:
<TreeView ItemsSource="{Binding ElementName=Root, Path=ItemsToDisplay}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Wrapper}"
ItemsSource="{Binding Path=Model.MyChildren}">
<Label Content="{Binding Model.DisplayName}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Child
类型没有 MyChildren 属性,所以它会产生一个警告:
System.Windows.Data Error: 40 : BindingExpression path error: 'MyChildren' property not found on 'object' ''Child' (HashCode=46092238)'. BindingExpression:Path=Model.MyChildren; DataItem='Wrapper' (HashCode=16310625); target element is 'TreeViewItem' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
可以安全地忽略它。或者您可以将 MyChildren 属性 移动到 AbstractModel 并在 Child 对象中将其留空。
我创建了一个测试 TreeView (tv):
public class Wrapper
{
public AbstractModel Model { get; set;}
public static implicit operator Wrapper(Parent p)
{
return new Wrapper { Model = p };
}
public static implicit operator Wrapper(Child c)
{
return new Wrapper { Model = c };
}
}
使用此数据(隐式类型转换和集合初始化器用于缩短集合初始化)
var L = new List<Wrapper>()
{
new Parent
{
DisplayName = "A",
MyChildren =
{
new Child { DisplayName = "A1"},
new Parent
{
DisplayName = "X",
MyChildren =
{
new Parent { DisplayName = "X1" }
}
}
}
},
new Child { DisplayName = "B"},
new Parent { DisplayName = "C", MyChildren = { new Child { DisplayName = "C1"} } },
};
tv.ItemsSource = L;
我正在处理一个 WPF 项目。我最近做了一个重构,将我的一些模型 object 放入包装器中。我删减了 classes 以便他们阅读得很好:
public class Wrapper
{
public AbstractModel Model;
}
public abstract class AbstractModel
{
public string DisplayName;
}
public class Parent : AbstractModel
{
public ObservableCollection<Wrapper> MyChildren;
}
public class Child : AbstractModel
{
//Nothing relevant to the issue (I think).
}
//a .xaml.cs file
public partial class MyPropertiesControl
{
//This is actually a dependency property that I bind to in other views, but I was too lazy to write the whole thing out.
public ObservableCollection<Wrapper> ItemsToDisplay { get; }
}
我想显示一个 TreeView,其中显示了 Wrappers 的集合及其(潜在的)children。以前,没有 Wrapper class,只有 AbstractModel、Parent 和 Child。在这种情况下创建树视图非常简单:
<TreeView ItemsSource="{Binding ElementName=Root, Path=ItemsToDisplay}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type Parent}" ItemsSource="{Binding MyChildren}">
<Label Content="{Binding DisplayName}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type Child}">
<Label Content="{Binding DisplayName}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
既然 object 都在包装器中,我不知道如何编写模板。我尝试了很多不同的东西。这是我尝试过的两个想法:
<TreeView ItemsSource="{Binding ElementName=Root, Path=ItemsToDisplay}">
<TreeView.Resources>
<DataTemplate DataType="{x:Type Wrapper}">
<ContentControl Content="{Binding Model}">
<ContentControl.Resources>
<HierarchicalDataTemplate DataType="{x:Type Parent}" ItemsSource="{Binding MyChildren}">
<Label Content="{Binding DisplayName}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type Child}">
<Label Content="{Binding DisplayName}"/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TreeView.Resources>
</TreeView>
<TreeView ItemsSource="{Binding ElementName=Root, Path=ItemsToDisplay}">
<TreeView.Resources>
<DataTemplate DataType="{x:Type Wrapper}">
<ContentControl Content="{Binding Model}">
<ContentControl.Resources>
<HierarchicalDataTemplate DataType="{x:Type Parent}" ItemsSource="{Binding MyChildren}">
<HierarchicalDataTemplate.Resources>
<DataTemplate DataType="{x:Type Wrapper}">
<ContentControl Content="{Binding Model}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type Child}">
<Label Content="{Binding DisplayName}"/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</HierarchicalDataTemplate.Resources>
<Label Content="{Binding DisplayName}"/>
</HierarchicalDataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TreeView.Resources>
</TreeView>
在这两种情况下,我只看到 parent 的第一层,没有看到任何 children。甚至没有 WPF 在没有为该类型定义的模板时使用的默认 class 名称,但 parent 节点实际上根本不显示 children。
我为鼠标在树视图上拖动添加了一个事件处理程序,并在事件处理程序上放置了一个断点,这样我就可以确保所有集合都已填充(它们是)。
这里还有其他人将 wrapped objects 绑定到树视图吗?您是如何让所有数据正确显示的?
我想另一个潜在的问题是通过 ContentControls 进入包装的 object 可能不是最好的主意。我没有从中得到 code-smell 的感觉,但我无法以更简洁的方式访问包装的 object 有点烦人。如果有人有任何补救的想法,它可能有助于解决 TreeView 问题。
我不确定是否有任何 XAML 唯一的方法可以做到这一点。但是你应该可以使用 DataTemplateSelector.
Provides a way to choose a DataTemplate based on the data object and the data-bound element.
他们在该文档中给出的示例涉及根据所显示项目的 属性 在两个 DataTemplate
之间进行选择。这正是您想要的:根据 Model
.
HierarchicalDataTemplate
之一
包装器的一个 DataTemplate 就可以了。在它里面绑定到模型属性:
<TreeView ItemsSource="{Binding ElementName=Root, Path=ItemsToDisplay}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Wrapper}"
ItemsSource="{Binding Path=Model.MyChildren}">
<Label Content="{Binding Model.DisplayName}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Child
类型没有 MyChildren 属性,所以它会产生一个警告:
System.Windows.Data Error: 40 : BindingExpression path error: 'MyChildren' property not found on 'object' ''Child' (HashCode=46092238)'. BindingExpression:Path=Model.MyChildren; DataItem='Wrapper' (HashCode=16310625); target element is 'TreeViewItem' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
可以安全地忽略它。或者您可以将 MyChildren 属性 移动到 AbstractModel 并在 Child 对象中将其留空。
我创建了一个测试 TreeView (tv):
public class Wrapper
{
public AbstractModel Model { get; set;}
public static implicit operator Wrapper(Parent p)
{
return new Wrapper { Model = p };
}
public static implicit operator Wrapper(Child c)
{
return new Wrapper { Model = c };
}
}
使用此数据(隐式类型转换和集合初始化器用于缩短集合初始化)
var L = new List<Wrapper>()
{
new Parent
{
DisplayName = "A",
MyChildren =
{
new Child { DisplayName = "A1"},
new Parent
{
DisplayName = "X",
MyChildren =
{
new Parent { DisplayName = "X1" }
}
}
}
},
new Child { DisplayName = "B"},
new Parent { DisplayName = "C", MyChildren = { new Child { DisplayName = "C1"} } },
};
tv.ItemsSource = L;