views:

68

answers:

3

Tree View control's AfterCheck event checks all child nodes below it and enables the Run button if something is checked.

1346 void TreeNode_AfterCheck(object sender, TreeViewEventArgs e) {
1347   if (!e.Node.Checked) return;
1348   foreach (TreeNode sub in e.Node.Nodes) {
1349     sub.Checked = e.Node.Checked;
1350   }
1351   RunButton.Enabled = IsANodeChecked();
1352 }

1429 static bool IsANodeChecked(TreeNode node) {
1430   if (node.Checked) return true;
1431   foreach (TreeNode sub in node.Nodes) {
1432     if (IsANodeChecked(sub)) {
1433       return true;
1434     }
1435   }
1436   return false;
1437 }

Checking the root node when there are 4881 sub nodes will hang the GUI for about 7 seconds.

I only need to call IsANodeChecked (on Line 1351) once, but I don't know how to disable it until after all of the tree nodes have been processed.

And I do not want to have a timer on my form devoted to monitoring this.

Does anyone see a simple/obvious solution?

+1  A: 

I sometimes use a Timer to handle such cases. Add a timer and set up the Tick event handler to call IsANodeChecked and enable/disable the button. Give it a short interval (~100 ms perhaps), and leave it disabled. Then, you call Stop followed by Start on the timer in your AfterCheck event handler. This will cause the timer to be restarted for each call to AfterCheck, but the Tick event handler will be invoked only when a certain time has elapsed after the Start call, which means that it will not be invoked until after the last call to AfterCheck.

100 ms is a very long time for the computer to work, but will seem immediate for the user.

You can see similar behavior in the Windows Explorer. If you use the keyboard to quickly navigate around in the folder tree, the right hand pane with the folder contents will not update unless you stay on a folder in the tree for a brief moment.

Fredrik Mörk
If nothing better comes around, I may have to implement yet another timer in this application. I'd rather not, though.
jp2code
@jp2code: If the application is already timer intense, I see your reluctance. I tend to avoid them as well and save them for those special occasions when they will *really* help me.
Fredrik Mörk
+2  A: 

Put an event handler on your checkboxes that enables or disables the RunButton as opposed to having something that iterates over the whole thing to find out.

Add the checkbox to a list of checked checkboxes when it get's checked first so you don't disable the RunButton until the list of checked checkboxes is empty. Remove it from the list when it's unchecked, etc.

Here's kind of how I would write it out, this is just winging it so sorry if I miss something:

private int _checkedCheckboxes;

void AddCheckBox()
{
    if (_checkedCheckBoxes++ == 1) RunButton.Enabled = true;
}

void RemoveCheckBox()
{
    if (_checkedCheckBoxes-- == 0) RunButton.Enabled = false;
}

void TreeNode_AfterCheck(object sender, TreeViewEventArgs e) 
{
    if (e.Node.Checked)
    {
        AddCheckBox();
        return;
    }

    RemoveCheckBox();
}
Jimmy Hoffa
The Tree Nodes are manually created based on model numbers (from serial numbers) in a database. Are you saying create a new event for each manually created node? (like 5000 events) If the root node is checked and all 5000 child nodes fire at once, how is this going to be any better than what I'm doing now? Your idea would take some time to design, so I'd like to know more before committing to it.
jp2code
A: 

These ideas where helpful, but I used something different that worked by adding a single boolean variable:

bool _treeNodeFirst = false;

...and a Before Checked event that temporarily modifies the Back Color on the control to serve as a flag for the control that started the chain of events:

1273 void TreeNode_BeforeCheck(object sender, TreeViewCancelEventArgs e) {
1274   if (!_treeNodeFirst) {
1275     _treeNodeFirst = true;
1276     e.Node.BackColor = Color.Silver;
1277   }
1278 }

1346 void TreeNode_AfterCheck(object sender, TreeViewEventArgs e) {
1347   if (e.Node.Checked) {
1348     foreach (TreeNode sub in e.Node.Nodes) {
1349       sub.Checked = e.Node.Checked;
1350     }
1351   }
1352   if (e.Node.BackColor == Color.Silver) {
1353     e.Node.BackColor = Color.Empty;
1354     RunButton.Enabled = IsANodeChecked();
1355     _treeNodeFirst = false;
1356   }
1357 }

1429 static bool IsANodeChecked(TreeNode node) {
1430   if (node.Checked) return true;
1431   foreach (TreeNode sub in node.Nodes) {
1432     if (IsANodeChecked(sub)) {
1433       return true;
1434     }
1435   }
1436   return false;
1437 }

This seems to be the best way (that I can see right now) to ensure that IsANodeChecked(TreeNode) is only run once when a group of nodes is selected all at once.

I do, however, really like Jimmy Hoffa's idea of using a count, though. I will probably add that to my code.

Thanks to all! ~Joe

jp2code