1

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

Breakwin
  • 84
  • 6
  • Not sure what your issue is. The only thing I see wrong is when rootNode.Nodes is null. So you need an IF statement to return from CheckAllNodes/UnCheckAllNodes when rootNode.Nodes is null. – jdweng Dec 23 '20 at 10:57

2 Answers2

2

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;
}
dr.null
  • 4,032
  • 3
  • 9
  • 12
  • Hey, thank you so much for this. Almost does exactly what I want. Apologies for the late reply as I have been away on holiday. How would I go about making it check/uncheck on the fly? Rather than having a button doing that on the selected nodes? – Breakwin Jan 02 '21 at 16:38
  • @Breakwin Welcome back. If you check the first version of my answer, you'll see that part. I thought you don't need it thus I remove it. I'll add it again. – dr.null Jan 03 '21 at 00:22
  • 1
    brilliant, this did it. thank you very much. I just had to add the aftercheck event additionally. Apparently this did not get added to treeview when i created it. https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.treeview.aftercheck?view=net-5.0 – Breakwin Jan 04 '21 at 13:17
0

I agree with @jdweng , you are using recursion in checkChildren(). The base case is missing. In recursion checkChildren , add base case before
foreach (TreeNode node in rootNode.Nodes)
if node is Null : rootNode=isChecked

amol goel
  • 149
  • 3