0
votes

I am new to using tree views and I want to be able to make sure the tree view can only have one child node checked and if someone tries to check more then one it stops the check event and deselects all parent and child nodes. How would I go about doing this? So far this is what I've got but it is acting quirky. Any suggestions?

Update: To clarify some things this is a win form treeview and the parent node is a category and each category can contain multiple items. I only want the user to be able to select one category and one item from the category at a time.

private void tvRecipes_BeforeCheck(object sender, TreeViewCancelEventArgs e)
{
    int checkedNodeCount = 0;

    if (e.Node.Parent != null && !e.Node.Parent.Checked)
        e.Cancel = true;
    else
    {
        foreach (TreeNode node in tvRecipes.Nodes)
        {
            if (node.Checked)
                ++checkedNodeCount;

            if (checkedNodeCount > 2)
            {
                MessageBox.Show("Only one recipe can be selected at a time, please deselect the current recipe and try again.", "Too Many Recipes Selected", MessageBoxButtons.OK, MessageBoxIcon.Error);

                e.Cancel = true;
            }
        }
    }

After some messing around I figured out the solution I was after. I have posted it below:

private bool CheckNumOfSelectedChildern(TreeViewEventArgs e)
{
    bool Valid = true;
    int selectedChildern = 0;

    foreach (TreeNode node in tvRecipes.Nodes)
    {
        if (node.Checked)
        {
            foreach (TreeNode child in node.Nodes)
            {
                if (child.Checked)
                    ++selectedChildern;
            }
        }
    }

    if (selectedChildern > 1)
    {
        MessageBox.Show("Only one recipe per category can be selected at a time, please deselect the current recipe and try again.", "Too Many Recipes Selected", MessageBoxButtons.OK, MessageBoxIcon.Error);
        e.Node.Checked = false;
        e.Node.Parent.Checked = false;
        Valid = false;
    }       
    return Valid;
}

private bool CheckNumOfSelectedParents(TreeViewEventArgs e)
{
    bool Valid = true;
    int selectedParent = 0;

    foreach (TreeNode root in tvRecipes.Nodes)
    {
        if (root.Checked)
            ++selectedParent;
    }

    if (selectedParent > 1)
    {
        MessageBox.Show("Only one recipe category can be selected at a time, please deselect the current recipe and try again.", "Too Many Recipes Selected", MessageBoxButtons.OK, MessageBoxIcon.Error);
        e.Node.Checked = false;
        Valid = false;
    }
    return Valid;
}

private void tvRecipes_BeforeCheck(object sender, TreeViewCancelEventArgs e)
{
    if (e.Node.Parent != null && !e.Node.Parent.Checked)
        e.Cancel = true;
    else if (e.Node.Checked)
    {
        foreach (TreeNode child in e.Node.Nodes)
        {
            if (child.Checked)
                e.Cancel = true;
        }
    }
}

private void tvRecipes_AfterCheck(object sender, TreeViewEventArgs e)
{
    if (CheckNumOfSelectedParents(e))
    {
        if (e.Node.Parent != null && e.Node.Parent.Checked)
        {
            if (e.Node.Checked)
            {
                if (CheckNumOfSelectedChildern(e))
                {
                    RecipeDs = RetrieveRecipe.FillRecipeDs(e.Node.Text);
                    DataBindControls();
                }                    
            }
            else
            {
                RemoveLabelsFromLayout();
                RemoveDataBindings();
                RecipeDs.Clear();
                this.Refresh();
            }
        }
    }
}
4
@Nathan, Hi, I see you accepted my answer which is really more of a long "comment" than a specific answer to your question. I'm not sure my "long comment 'posing' as an answer" deserves acceptance, so please, if you get a "better answer" don't hesitate to select it. Once again, may I (gently) encourage you to clarify your question a bit. best,BillW
@Nathan, a few questions : 1. by design the end-user cannot check or uncheck category nodes ? 2. if user can check a category (root) node : but doesn't select an item (child node) within checked category node : is this an error condition ? 3. only the act of checking a child node will automatically check the root node which is the category-parent of the child node you just checked ? 4. if I am allowed to uncheck a category node and I have an item node within its parent (category) node checked, when I uncheck the item ndoe : the category node (parent node) must also be unchecked ?BillW
@Nathan, Hi, once we know exactly what can and can't be done with category nodes (parentless, root nodes), this situation can easily be managed with just one or two boolean flags, or, perhaps, only by using two references to the currently checked category node (parentless, root), and currently checked item node (child node). best,BillW
I have updated my question with an answer I came up with over the weekend.Nathan

4 Answers

1
votes

Your if (checkedNodeCount > 2)... should be outside the foreach loop.

Inside the for loop, it gets run each time. I'm assuming this is what you mean by "acting quirky"

Perhaps you should give some more detail about what the incorrect behavior is.

1
votes

This is too long for just a comment.

I assume you are describing the standard Windows Forms TreeView : correct ?

When you say in your question :

if someone tries to check more then one it stops the check event and deselects all parent and child nodes.

To me that implies you have a treeview capable of multiple selection, and the standard Windows Forms TreeView is not capable of this. You can "hack it" so it does multi-selection : for example : CodeProject : "Multiselect Treeview Implementation", but it's non-trivial to do so.

So I am going to assume you meant "uncheck" parent and child nodes : but then : that is confusing because your question appears to ask clearly how you can have only one child TreeNode checked at one time : that implies to me that it would never be the case that you would have to "uncheck" any other child nodes.

Reading your code, it's obvious that you are planning for the possibility that one parent node is checked, but you do not specifically exclude the case that more than one "parentless" (root) node could be checked.

So, suggest re-stating your question a little more clearly to clarify :

  1. more than one parentless (root) node can be checked ?

  2. only one parentless (root) node can be checked at one time ?

  3. for each parentless (root) node checked, one, and only one, child node can be checked at any one time ?

  4. if child nodes are "deeply nested" : does that make any difference to you ?

Note : because of the great limitations of the native WinForms TreeView control, a lot of us have purchased 3rd. party TreeView controls :

I purchased the Integral UI TreeView from Lidor Systems, and have been delighted with it. A built-in property of the Lidor TreeView is the 'CheckedNodes collection containing all currently checked nodes, and you have your choice of three selection modes, including one "MultiExtended" that allows you to have very complex selections that include child and parent nodes in any order, at any level of "nesting" :

And of course you have a 'SelectedNodes collection available at any time containing all selected Nodes.

With the Lidor TreeView you can hide the CheckBox for any one Node at any time (at design time, or at run-time), and the CheckBox supports three states : 'indeterminate as well as 'checked and 'unchecked.

I assume other 3rd. party TreeView controls also offer similar features, but when I reviewed and tried "trial versions" of most of the major ones two years, ago : for me the Lidor TreeView offered the most value and was not expensive. In terms of the ability to richly "style" TreeNodes, I didn't see anything close to the capability of the Lidor TreeView in the "WinForms Universe." Disclosure : I do not work for Lidor, and have no business relationship with them : I'm just a happy customer who's pleased to recommend.

1
votes

Hope this works:

private int _callCountUp;

        private int _callCountDn;

private void tvwPermissions_AfterCheck(object sender, System.Windows.Forms.TreeViewEventArgs e)
        {
            bool anyChecked = false;

            if (_callCountDn == 0 && e.Node.Parent != null)
            {
                anyChecked = false;
                foreach (TreeNode childNode in e.Node.Parent.Nodes)
                {
                    if (childNode.Checked)
                    {
                        anyChecked = true;
                        break;
                    }
                }
                _callCountUp += 1;

                if (anyChecked)
                    e.Node.Parent.Checked = true;

                _callCountUp -= 1;
            }

            if (_callCountUp == 0)
            {
                foreach (TreeNode childNode in e.Node.Nodes)
                {
                    _callCountDn += 1;
                    childNode.Checked = e.Node.Checked;
                    _callCountDn -= 1;
                }
            }
        }
0
votes

It was asked 1 year ago but there is the tip, how to select only one node in Treeview