WPF TreeView級聯複選

关关长语發表於2024-03-10

blog-hbh-hc-header

目標效果

初始載入

選中葉子節點

操作中間節點

實現思路

TreeView資料來源集合的子項ViewModel部分

BindableBasePrism中對應的Vm基類,讀者也可以按自身需求實現屬性通知介面(INotifyPropertyChanged)的基類。

    public class ModuleNode:BindableBase
    {

        //public bool IsChecked { get; set; } //屬性需要設定為可空型別
		//核取方塊狀態
        private bool? isChecked;
        public bool? IsChecked
        {
            get { return isChecked; }
            set { SetProperty(ref isChecked, value); }
        }
        
        // 子節點集合
        public List<ModuleNode> Children { get; set; }
		// 父節點物件
        public ModuleNode Parent { get; set; }
    }

主頁面ViewModel

public class MainViewModel:BindableBase
{
	public DelegateCommand<ModuleNode> ModuleCkCommand { get; set; }
	public MainViewModel()
	{
		ModuleCkCommand = new DelegateCommand<ModuleNode>(ModuleCkMethod);
	}
	
	/// <summary>
    /// 模組選中函式
    /// </summary>
    /// <param name="node">操作選中物件</param>
    private void ModuleCkMethod(ModuleNode node)
    {
        // 子節點level-1總數
        int childCount = node.Children.Count;
        // 選中節點狀態
        Debug.WriteLine($"更新選中項:{node.Name} {node.IsChecked}");
        // 選中子節點更新
        if (childCount > 0)
        {
            // 節點狀態
            bool? ischeck = node.IsChecked;
            foreach (var child in node.Children)
            {
                child.IsChecked = ischeck;
                Debug.WriteLine($"更新子項:{child.Name} {child.IsChecked}");
                // 遞迴操作子節點下層
                ModuleCkMethod(child);
            }
        }
        // 父級節點更新
        ModuleParentCkMethod(node.Parent);
    }
    
   /// <summary>
   /// 更新父級節點狀態
   /// </summary>
   /// <param name="parent">父級物件</param>
   private void ModuleParentCkMethod(ModuleNode parent)
   {
       if (parent == null)
       {
           return;
       }

       // 子集深度level-1 選中項 非選中項 null狀態
       int childCount = parent.Children.Count;
       int ckCount = parent.Children.Count(child => child.IsChecked == true);
       int uckCount = parent.Children.Count(child => child.IsChecked == false);
       int nullCount = parent.Children.Count(child => child.IsChecked == null);
       // 子節點是否存在null,若存在則父級節點直接設定為null
       if (nullCount != 0)
       {
           parent.IsChecked = null;
       }
       else // 判定選中和非選中是否與下級子節點總數一致,一致時,則更新父節點狀態true/false
       {
           if (ckCount == childCount)
           {
               parent.IsChecked = true;
           }
           else if (uckCount == childCount)
           {
               parent.IsChecked = false;
           }
           // 判定選中和非選中是否與下級子節點總數一致,不一致時,則更新父節點狀態為null
           else
           {
               parent.IsChecked = null;
           }
       }
       Debug.WriteLine($"更新父級:{parent.Name} {parent.IsChecked}");
       // 遞迴更新父級狀態
       ModuleParentCkMethod(parent.Parent);
   }
}

UI部分

其中需要注意的是CheckBox對應的啟用三狀態(True、False、null)屬性IsThreeState,預設為False,不啟用。

        <TreeView x:Name="TreeData" ItemsSource="{Binding TreeItemSources}">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                    <StackPanel Orientation="Horizontal">
                        <CheckBox IsChecked="{Binding Path=IsChecked}" Command="{Binding DataContext.ModuleCkCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeView}}}" 
      CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self},Path=DataContext}">
                        </CheckBox>
                        <ContentPresenter VerticalAlignment="Center" Content="{Binding Path=Name}" Margin="5,0"/>
                    </StackPanel>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>

相關文章