You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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));
    }
}

核心逻辑说明

  1. 父项联动子项:父项IsChecked变化时,遍历所有子项同步状态,通过_isUpdating标记避免循环触发。
  2. 子项联动父项:每个子项的PropertyChanged事件被父项监听,当子项勾选状态变化时,父项通过All方法判断所有子项是否都已勾选,从而更新自身状态。
  3. 递归联动:父项状态更新后,会继续触发其自身父项的状态检查,实现多级树形结构的完整联动。

内容的提问来源于stack exchange,提问作者Vinod

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 05:40:57