WPF MVVM实现TreeView根据子项勾选状态联动父项复选框
WPF MVVM TreeView 子项联动父项复选框实现方案
我是WPF新手,参考示例搭建了一个C# WPF MVVM模式的TreeView实例,已实现父项复选框勾选/取消时联动子项状态。但不清楚如何通过ViewModel实现根据子项状态联动父项:当所有子项都勾选时,父项自动勾选;只要有一个子项未勾选,父项就取消勾选。恳请提供实现建议。
当前代码实现
Xaml View
<Window x:Class="WpfApp1.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:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Window.DataContext> <local:MainViewModel></local:MainViewModel> </Window.DataContext> <Window.Resources> <HierarchicalDataTemplate DataType="{x:Type local:CheckableItem}" ItemsSource="{Binding Children, Mode=TwoWay}"> <StackPanel Orientation="Horizontal"> <CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}" Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type TreeView}}, Path=DataContext.CheckBoxCommand}" CommandParameter="{Binding}"/> <TextBlock Text="{Binding Name}"/> </StackPanel> </HierarchicalDataTemplate> </Window.Resources> <StackPanel> <Label Content="Miscellaneous Imports" HorizontalAlignment="Center" /> <ScrollViewer> <TreeView ItemsSource="{Binding MiscellaneousImports, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" FontSize="10" Height="450"/> </ScrollViewer> </StackPanel> </Window>
ViewModel
public class MainViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler? PropertyChanged; public ICommand CheckBoxCommand { get; set; } public MainViewModel() { LoadCheckableItems(); CheckBoxCommand = new DelegateCommand<CheckableItem>(OnCheckBoxChecked); } private void OnCheckBoxChecked(CheckableItem item) { throw new NotImplementedException(); } private ObservableCollection<CheckableItem> miscellaneousImports; public ObservableCollection<CheckableItem> MiscellaneousImports { get { return miscellaneousImports; } set { miscellaneousImports = value; OnPropertyChanged("MiscellaneousImports"); } } private void LoadCheckableItems() { List<CheckableItem> lstItems = new List<CheckableItem>(); CheckableItem item = new CheckableItem { Name = "Coffee", Children = new ObservableCollection<CheckableItem>() { new CheckableItem { Name="Medium" }, new CheckableItem {Name="Dark" } } }; lstItems.Add(item); MiscellaneousImports = new ObservableCollection<CheckableItem> ( lstItems ); } protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
Checkbox集合类
public class CheckableItem:INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string _name; public string Name { get { return _name; } set { _name = value; OnPropertyChanged("Name"); } } public ObservableCollection<CheckableItem> Children { get; set; } private bool _isChecked; public bool IsChecked { get { return _isChecked; } set { _isChecked = value; OnPropertyChanged("IsChecked"); if (Children != null) { foreach (CheckableItem child in Children) { child.IsChecked = IsChecked; } OnPropertyChanged(nameof(Children)); } } } protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
实现子项联动父项的核心修改
要实现子项状态联动父项,需要在CheckableItem中添加父项引用,监听子项的PropertyChanged事件,同时在子项状态变化时递归更新父项的勾选状态。
步骤1:修改CheckableItem类
添加父项属性,初始化时绑定子项的事件监听,同时添加标记避免循环触发状态更新:
public class CheckableItem : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string _name; public string Name { get => _name; set { _name = value; OnPropertyChanged(nameof(Name)); } } // 添加父项引用 public CheckableItem Parent { get; set; } private ObservableCollection<CheckableItem> _children; public ObservableCollection<CheckableItem> Children { get => _children; set { if (_children != null) { // 移除旧子项的事件监听 foreach (var child in _children) { child.PropertyChanged -= Child_PropertyChanged; child.Parent = null; } } _children = value; if (_children != null) { // 为新子项添加事件监听并设置父项 foreach (var child in _children) { child.PropertyChanged += Child_PropertyChanged; child.Parent = this; } } OnPropertyChanged(nameof(Children)); } } private bool _isChecked; // 添加标记避免循环触发状态更新 private bool _isUpdating; public bool IsChecked { get => _isChecked; set { if (_isUpdating) return; _isChecked = value; _isUpdating = true; // 联动子项状态 if (Children != null) { foreach (var child in Children) { child.IsChecked = value; } } // 触发父项状态更新 Parent?.UpdateParentCheckState(); _isUpdating = false; OnPropertyChanged(nameof(IsChecked)); } } // 监听子项状态变化 private void Child_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(IsChecked)) { UpdateParentCheckState(); } } // 更新父项的勾选状态 public void UpdateParentCheckState() { if (Parent == null || Parent._isUpdating) return; Parent._isUpdating = true; // 判断所有子项是否都已勾选 Parent.IsChecked = Parent.Children.All(c => c.IsChecked); Parent._isUpdating = false; } protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
步骤2:简化XAML中的CheckBox绑定
现在逻辑都在CheckableItem内部处理,无需保留Command绑定,修改为:
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}"/>
步骤3:移除ViewModel中未实现的Command逻辑
删除CheckBoxCommand相关代码,ViewModel简化为:
public class MainViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler? PropertyChanged; private ObservableCollection<CheckableItem> miscellaneousImports; public ObservableCollection<CheckableItem> MiscellaneousImports { get { return miscellaneousImports; } set { miscellaneousImports = value; OnPropertyChanged(nameof(MiscellaneousImports)); } } public MainViewModel() { LoadCheckableItems(); } private void LoadCheckableItems() { List<CheckableItem> lstItems = new List<CheckableItem>(); CheckableItem item = new CheckableItem { Name = "Coffee", Children = new ObservableCollection<CheckableItem>() { new CheckableItem { Name="Medium" }, new CheckableItem {Name="Dark" } } }; lstItems.Add(item); MiscellaneousImports = new ObservableCollection<CheckableItem>(lstItems); } protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
核心逻辑说明
- 父项联动子项:父项
IsChecked变化时,遍历所有子项同步状态,通过_isUpdating标记避免循环触发。 - 子项联动父项:每个子项的
PropertyChanged事件被父项监听,当子项勾选状态变化时,父项通过All方法判断所有子项是否都已勾选,从而更新自身状态。 - 递归联动:父项状态更新后,会继续触发其自身父项的状态检查,实现多级树形结构的完整联动。
内容的提问来源于stack exchange,提问作者Vinod
相关产品推荐
相关产品推荐

