views:

30

answers:

3

I have a Tree View control for a Windows Application that uses the CheckBoxes property.

Sometimes (often) when a Tree Node is either checked or unchecked, I get Stack Overflow Exceptions in my static methods below.

Could someone point out why? Maybe even show me how to do this the right way?

In the After Check Event, I have written the following:

void TreeNode_AfterCheck(object sender, TreeViewEventArgs e) {
  if (0 < e.Node.Nodes.Count) {
    if (e.Node.Checked) {
      e.Node.Expand();
      TreeNodes_SetChecksTo(e.Node, true);
    } else {
      if (!TreeNode_SomethingChecked(e.Node)) {
        e.Node.Collapse(false);
      }
    }
  }
}

Generally, the Exception is thrown when something in a static method fires the After Check Event above and trickles into one of the static methods below:

static void TreeNodes_SetChecksTo(TreeNode node, bool value) {
  if (node != null) {
    if (node.Checked != value) node.Checked = value;
    if (0 < node.Nodes.Count) {
      foreach (TreeNode sub in node.Nodes) {
        TreeNodes_SetChecksTo(sub, value);
      }
    }
  }
}

static bool TreeNode_SomethingChecked(TreeNode node) {
  if (node != null) {
    if (node.Checked) return true;
    if (0 < node.Nodes.Count) {
      foreach (TreeNode sub in node.Nodes) {
        if (TreeNode_SomethingChecked(sub)) {
          return true;
        }
      }
    }
  }
  return false;
}
A: 

The tree is iterated recursively every time a (sub-) node gets checked or unchecked and this happens pretty often as you react on every check state change with your AfterCheck callback. Either "forbid" the callback while an "instance" of it is already processing or do it without explicit recursion and let the callback do the recursion implicitly.

Flinsch
Not sure I follow how I'd "forbid" a callback ...or rewrite the method to have a callback do the recursion. :(
jp2code
Kent and Hans each gave an example of how to "forbid" the callback in case it is already running. Another solution could be to process only the directly subordinated child nodes (not recursively in SetChecksTo) and let the callback do its work for each of them child nodes automatically (well, that's some kind of "recursion" as well).
Flinsch
+2  A: 

Setting IsChecked inside TreeNodes_SetChecksTo is resulting in the AfterCheck event being raised and thus the TreeNode_AfterCheck method being called. I suspect you want to disable/ignore the event whilst processing it:

private bool latch;

void TreeNode_AfterCheck(object sender, TreeViewEventArgs e) {
  if (latch)
      return;

  latch = true;

  try
  {
      if (0 < e.Node.Nodes.Count) {
        if (e.Node.Checked) {
          e.Node.Expand();
          TreeNodes_SetChecksTo(e.Node, true);
        } else {
          if (!TreeNode_SomethingChecked(e.Node)) {
            e.Node.Collapse(false);
          }
        }
      }
  }
  finally
  {
      latch = false;
  }
}

HTH,
Kent

Kent Boogaart
This looks like I'd be getting into race conditions. No?
jp2code
+2  A: 
if (node.Checked != value) node.Checked = value;

This is the statement that probably causes it. It fires the AfterCheck event. Your event handler will get called again, while it is already running. You need to protect yourself against that and break the recursion with a private field. Something like this:

private bool updatingChecks;

void TreeNode_AfterCheck(object sender, TreeViewEventArgs e) {
  if (updatingChecks) return;
  updatingChecks = true;
  try {
    // etc..
  }
  finally {
    updatingChecks = false;
  }
}
Hans Passant
Same as Kent's idea - almost to the letter. That's good support for that technique! Is that better than using lock(object)?
jp2code
@jp2 - yes, considering that locks are re-entrant on the same thread so cannot block anything. This code only ever runs on the UI thread.
Hans Passant