WPF MVVM实现层级TreeView:根据ValueType动态渲染控件
WPF TreeView 根据ValueType动态渲染控件解决方案
问题背景
现有ViewModel结构如下:
// 顶层策略ViewModel class MainWindowVM { // 对应每个应用角色的ViewModel集合 public List<RolePolicyVM> ComponentPolicyVMs { get; set; } = new(); } // 对应特定应用角色的组件策略 class RolePolicyVM { // 角色名称 public String Name { get; set; } // 应用于该角色的组件策略集合 public List<ComponentPolicyVM> ComponentPolicies { get; set; } = new(); } // 对应特定代理插件或工作项的组件策略 class ComponentPolicyVM { // 插件或工作项ID(不渲染) public Guid Id { get; set; } // 在AgentConfig工具中显示的设置描述 public String Description { get; set; } // 鼠标悬停在描述文本块时显示的提示文本 public String ToolTipText { get; set; } // 绑定到控件的值 public Object Value { get; set; } // "Value"的数据类型,决定渲染的控件类型 public Type ValueType { get; set; } }
需要构建基于MainWindowVM.ComponentPolicyVMs的层级TreeView:
- 顶层层级绑定
RolePolicyVM.Name - 二级层级包含两列:
- 第一列绑定
ComponentPolicyVM.Description - 第二列绑定
ComponentPolicyVM.Value,并根据ValueType动态渲染控件:typeof(bool)显示CheckBox,typeof(string)显示TextBox,typeof(int)显示数值输入控件
- 第一列绑定
目前已实现TreeView但仅能渲染CheckBox,现有XAML代码如下:
<Window x:Class="AppPolicyConfig.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="clr-namespace:AppPolicyConfig.ViewModels" xmlns:local="clr-namespace:AppPolicyConfig" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Window.DataContext> <vm:MainWindowVM/> </Window.DataContext> <TreeView x:Name="MainTreeView" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10" ItemsSource="{Binding ComponentPolicyVMs}"> <TreeView.ItemContainerStyle> <Style TargetType="{x:Type TreeViewItem}"> <Setter Property="IsExpanded" Value="True" /> </Style> </TreeView.ItemContainerStyle> <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding ComponentPolicies}" DataType="{x:Type vm:RolePolicyVM}"> <TextBlock Text="{Binding Name}"/> <HierarchicalDataTemplate.ItemTemplate> <DataTemplate DataType="{x:Type vm:ComponentPolicyVM}"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="300" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="{Binding Description}"/> <CheckBox Grid.Column="1" IsChecked="{Binding Value}"/> </Grid> </DataTemplate> </HierarchicalDataTemplate.ItemTemplate> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </Window>
解决方案
使用DataTemplateSelector实现动态控件渲染,这是适配现有ViewModel结构最灵活的方式。
步骤1:创建自定义模板选择器
添加继承自DataTemplateSelector的类,根据ComponentPolicyVM.ValueType返回对应控件模板:
using System; using System.Windows; using System.Windows.Controls; using AppPolicyConfig.ViewModels; namespace AppPolicyConfig { public class PolicyControlTemplateSelector : DataTemplateSelector { public DataTemplate BoolTemplate { get; set; } public DataTemplate StringTemplate { get; set; } public DataTemplate IntTemplate { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { if (item is ComponentPolicyVM policyVM) { if (policyVM.ValueType == typeof(bool)) return BoolTemplate; else if (policyVM.ValueType == typeof(string)) return StringTemplate; else if (policyVM.ValueType == typeof(int)) return IntTemplate; } return base.SelectTemplate(item, container); } } }
步骤2:在XAML中定义模板与选择器
修改原XAML,在Window.Resources中定义三种类型的控件模板,再实例化模板选择器并关联这些模板:
<Window x:Class="AppPolicyConfig.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="clr-namespace:AppPolicyConfig.ViewModels" xmlns:local="clr-namespace:AppPolicyConfig" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Window.Resources> <!-- Bool类型控件模板 --> <DataTemplate x:Key="BoolTemplate"> <CheckBox IsChecked="{Binding Value, Mode=TwoWay}" /> </DataTemplate> <!-- String类型控件模板 --> <DataTemplate x:Key="StringTemplate"> <TextBox Text="{Binding Value, Mode=TwoWay}" /> </DataTemplate> <!-- Int类型控件模板(带验证) --> <DataTemplate x:Key="IntTemplate"> <TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> <TextBox.Style> <Style TargetType="TextBox"> <Setter Property="Validation.ErrorTemplate"> <Setter.Value> <ControlTemplate> <DockPanel> <TextBlock Foreground="Red" FontSize="12" Text="请输入有效整数"/> <AdornedElementPlaceholder/> </DockPanel> </ControlTemplate> </Setter.Value> </Setter> <Style.Triggers> <Trigger Property="Validation.HasError" Value="True"> <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/> </Trigger> </Style.Triggers> </Style> </TextBox.Style> <TextBox.Text> <Binding Path="Value" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <local:IntValidationRule/> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> </DataTemplate> <!-- 自定义模板选择器 --> <local:PolicyControlTemplateSelector x:Key="PolicyTemplateSelector" BoolTemplate="{StaticResource BoolTemplate}" StringTemplate="{StaticResource StringTemplate}" IntTemplate="{StaticResource IntTemplate}"/> </Window.Resources> <Window.DataContext> <vm:MainWindowVM/> </Window.DataContext> <TreeView x:Name="MainTreeView" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10" ItemsSource="{Binding ComponentPolicyVMs}"> <TreeView.ItemContainerStyle> <Style TargetType="{x:Type TreeViewItem}"> <Setter Property="IsExpanded" Value="True" /> </Style> </TreeView.ItemContainerStyle> <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding ComponentPolicies}" DataType="{x:Type vm:RolePolicyVM}"> <TextBlock Text="{Binding Name}"/> <HierarchicalDataTemplate.ItemTemplate> <DataTemplate DataType="{x:Type vm:ComponentPolicyVM}"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="300" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="{Binding Description}" ToolTip="{Binding ToolTipText}"/> <!-- 使用模板选择器动态加载控件 --> <ContentControl Grid.Column="1" Content="{Binding}" ContentTemplateSelector="{StaticResource PolicyTemplateSelector}"/> </Grid> </DataTemplate> </HierarchicalDataTemplate.ItemTemplate> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </Window>
步骤3:添加整数验证规则(可选)
为避免非整数输入导致绑定错误,添加验证规则类:
using System.Globalization; using System.Windows.Controls; namespace AppPolicyConfig { public class IntValidationRule : ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { if (int.TryParse(value.ToString(), out _)) return ValidationResult.ValidResult; else return new ValidationResult(false, "输入不是有效整数"); } } }
核心说明
- 模板选择器会自动匹配
ComponentPolicyVM.ValueType对应的控件模板 - 所有控件使用双向绑定,确保ViewModel的
Value属性与控件值同步 - 整数输入框添加了验证逻辑,提升输入体验
内容的提问来源于stack exchange,提问作者Mike Bruno
相关产品推荐
相关产品推荐

