Search code examples
c#winformsrecursiontreeviewchild-nodes

Beginner: TreeView with checkboxes and recursion in Winforms


first post and a new coder so bare with me if you need more info than I am giving. I am trying to create a treeview with checkboxes in a hierarchy (see pic). My issue is i want to create some sort of recursion which deselects and selects child nodes when parent nodes are checked or vice versa.

I am using VS with winforms and been googling for 2 days on how to do this, unfortunately the examples online are either too advanced for me or dont work. I found a Tutorial on how to do this exactly with indeterminate checkboxes as well which would be a big bonus but it is for WPF.

I managed to create buttons which are able to (un)check all buttons with some examples online. Please can someone guide, a beginner who is finding programming AMAZING so far, in the right direction :)

 private void button_checkAllNodes_Click(object sender, EventArgs e)
    {
        checkAllNodes(treeView1.Nodes);
    }

    private void button_uncheckAllNodes_Click(object sender, EventArgs e)
    {
        UncheckAllNodes(treeView1.Nodes);
    }

    public void checkAllNodes(TreeNodeCollection nodes)
     {
         foreach (TreeNode node in nodes)
         {
             node.Checked = true;
             checkChildren(node, true);
         }
     }
    public void UncheckAllNodes(TreeNodeCollection nodes)
    {
        foreach (TreeNode node in nodes)
        {
            node.Checked = false;
            checkChildren(node, false);
        }
    }

    private void checkChildren(TreeNode rootNode, bool isChecked)
     {
         foreach (TreeNode node in rootNode.Nodes)
         {
             checkChildren(node, isChecked);
             node.Checked = isChecked;
         }
     }

    private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
    {
        
    }

Picture Treeview with (un)check All buttons


Solution

  • Let's create a couple of extension methods for the TreeNode type, one that gets all the children of a node, and another that gets it's parents.

    // Within your project's namespace...
    static class TreeViewExtensions
    {
        public static IEnumerable<TreeNode> Children(this TreeNode node)
        {
            foreach (TreeNode n in node.Nodes)
            {
                yield return n;
    
                foreach (TreeNode child in Children(n))
                    yield return child;
            }
        }
    
        public static IEnumerable<TreeNode> Parents(this TreeNode node)
        {
            var p = node.Parent;
    
            while (p != null)
            {
                yield return p;
    
                p = p.Parent;
            }
        }
    }
    

    Now, all what you need to do is to handle the TreeView.AfterCheck event to toggle the Checked property of the nodes that the extension methods yield.

    // +
    using System.Linq;
    
    private void treeView1_AfterCheck(object sender, TreeViewEventArgs e)
    {
        if (e.Action == TreeViewAction.Unknown) return;
    
        foreach (TreeNode n in e.Node.Children())
            n.Checked = e.Node.Checked;
    
        // Comment this if you don't need it.
        foreach (TreeNode p in e.Node.Parents())
            p.Checked = p.Nodes.OfType<TreeNode>().Any(n => n.Checked);
    }
    

    Soon, you'll notice sometimes this solution won't work as it should when you click rapidly over the check boxes since they don't receive the mouse double click messages by default. Then, follow this post or this to solve this problem. For now click slowly.

    If you prefer though to use buttons to toggle the check state, then delete the AfterCheck handler and do instead:

    private void btnCheckAll_Click(object sender, EventArgs e)
    {
        ToggleCheck(treeView1.SelectedNode, true);
    }
    
    private void btnUncheckAll_Click(object sender, EventArgs e)
    {
        ToggleCheck(treeView1.SelectedNode, false);
    }
    
    private void ToggleCheck(TreeNode node, bool state)
    {
        node.Checked = state;
    
        foreach (TreeNode n in node.Children())
            n.Checked = state;
    
        // Optional...
        foreach (TreeNode n in node.Parents())
            n.Checked = state;
    }