WPF递归ContentTemplateSelector嵌套数据展示问题及优化咨询
解决方案与建议
1. 更简洁的递归TemplateSelector实现
把模板选择逻辑改为运行时动态查找资源,而非在Selector初始化时直接绑定模板实例。修改Selector类,通过模板Key在SelectTemplate方法中获取对应模板:
public class MyTemplateSelector : DataTemplateSelector { // 用模板Key代替直接绑定模板实例 public string PrimitiveTemplateKey { get; set; } = "PrimitiveTemplate"; public string CompositeTemplateKey { get; set; } = "CompositeTemplate"; public override DataTemplate SelectTemplate(object item, DependencyObject container) { if (item is not MyItem myItem) return base.SelectTemplate(item, container); var element = container as FrameworkElement; if (element == null) return null; // 根据业务逻辑判断是Primitive还是Composite类型 bool isPrimitive = myItem.Value != null && (myItem.Values == null || !myItem.Values.Any()) && (myItem.ListValues == null || !myItem.ListValues.Any()); return isPrimitive ? element.FindResource(PrimitiveTemplateKey) as DataTemplate : element.FindResource(CompositeTemplateKey) as DataTemplate; } }
XAML中可以随意放置Selector定义位置,完全不受模板顺序影响:
<local:MyTemplateSelector x:Key="MySelector" PrimitiveTemplateKey="PrimitiveTemplate" CompositeTemplateKey="CompositeTemplate"/> <DataTemplate x:Key="PrimitiveTemplate"> <TextBox Text="{Binding Value}"/> </DataTemplate> <DataTemplate x:Key="CompositeTemplate"> <!-- 递归使用Selector,运行时自动查找资源 --> <StackPanel> <!-- 渲染Values列表 --> <ItemsControl ItemsSource="{Binding Values}" Visibility="{Binding Values, Converter={StaticResource EmptyToCollapsed}}"> <ItemsControl.ItemTemplate> <DataTemplate> <TextBox Text="{Binding}"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> <!-- 渲染ListValues嵌套项 --> <ItemsControl ItemsSource="{Binding ListValues}"> <ItemsControl.ItemTemplate> <DataTemplate> <ContentControl Content="{Binding Value}" ContentTemplateSelector="{StaticResource MySelector}" /> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </StackPanel> </DataTemplate>
2. 彻底摆脱Selector定义顺序限制
上面的方案已经完全解决了顺序问题——Selector初始化时仅存储模板Key字符串,不会立即引用模板实例,直到运行时渲染才通过FindResource查找资源,因此可以随意放置Selector的定义位置,无需放在所有模板之后。
如果不想修改Selector代码,也可以把模板和Selector拆分到不同的ResourceDictionary中,在App.xaml里先合并模板字典,再合并Selector字典,但这种方式不如代码修改直接。
3. 深度嵌套结构的更优WPF模式
方案A:拆分视图模型,用DataType自动匹配模板
把MyItem拆分为不同子类,让WPF自动根据类型选择模板,彻底抛弃TemplateSelector:
public abstract class MyItemBase { } // 原始值类型 public class PrimitiveItem : MyItemBase { public object Value { get; set; } } // 复合类型(包含列表和嵌套项) public class CompositeItem : MyItemBase { public List<object> Values { get; set; } public List<MyItemBase> NestedItems { get; set; } } // 视图模型中的集合改为子类集合 public ObservableCollection<MyItemBase> MyItems { get; set; }
XAML中直接定义对应类型的DataTemplate,WPF会自动递归渲染:
<DataTemplate DataType="{x:Type local:PrimitiveItem}"> <TextBox Text="{Binding Value}"/> </DataTemplate> <DataTemplate DataType="{x:Type local:CompositeItem}"> <StackPanel Margin="0 0 0 8"> <!-- 渲染Values列表 --> <ItemsControl ItemsSource="{Binding Values}" Visibility="{Binding Values, Converter={StaticResource EmptyToCollapsed}}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <WrapPanel/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <TextBox Width="100" Margin="4" Text="{Binding}"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> <!-- 递归渲染嵌套项 --> <ItemsControl ItemsSource="{Binding NestedItems}" Margin="8 0 0 0"/> </StackPanel> </DataTemplate>
这种方式最贴合WPF设计理念,代码简洁易维护。
方案B:使用HierarchicalDataTemplate(树形结构场景)
如果嵌套结构是标准树形层级(每个节点仅有单一父节点),直接用HierarchicalDataTemplate零代码实现递归渲染:
<HierarchicalDataTemplate DataType="{x:Type local:MyItem}" ItemsSource="{Binding ListValues}"> <StackPanel> <TextBox Text="{Binding Value}" Visibility="{Binding Value, Converter={StaticResource NullToCollapsed}}"/> <ItemsControl ItemsSource="{Binding Values}" Visibility="{Binding Values, Converter={StaticResource EmptyToCollapsed}}"> <ItemsControl.ItemTemplate> <DataTemplate> <TextBox Text="{Binding}"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </StackPanel> </HierarchicalDataTemplate>
用TreeView或支持该模板的控件展示即可:
<TreeView ItemsSource="{Binding MyItems}"/>
内容的提问来源于stack exchange,提问作者Dorin
相关产品推荐
相关产品推荐

