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
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;
}
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
Related
My procedure works fine with a Do-While but I think it must do a lot of iterations.
Is there a faster way to remove nodes without childs? Remember if you remove a node and it is a single node from its parent node then it must be removed later again.
int counter=0;
private void EliminaParentsWithoutChilds()
{
do
{
counter= 0;
foreach (TreeNode node in treeView1.Nodes)
{
RemoveEmptyNodes(node);
}
}
while (counter > 0);
}
private void RemoveEmptyNodes(TreeNode node)
{
if (node.Nodes.Count > 0)
{
foreach (TreeNode childNode in node.Nodes)
{
RemoveEmptyNodes(childNode);
}
}
else
{
node.Remove();
counter++;
}
}
You can create a method that tell you when a node is empty or has only one child in all descendants:
private static bool IsUniqueOrEmptyNode(TreeNode node)
{
return node.Nodes.Count == 0 ||
(node.Nodes.Count == 1 && IsUniqueOrEmptyNode(node.Nodes[0]));
}
Next method remove only the top parent that hasn't childs or that contains only one a single direct child:
private static void RemoveEmptyNodes(TreeNodeCollection nodes)
{
for (int i = nodes.Count - 1; i >= 0; i--)
{
if (IsUniqueOrEmptyNode(nodes[i]))
{
nodes.RemoveAt(i);
}
else
{
RemoveEmptyNodes(nodes[i].Nodes);
}
}
}
And a helper method to apply to a TreeView:
private static void RemoveEmptyNodes(TreeView tree)
{
RemoveEmptyNodes(tree.Nodes);
}
I have a TreeView with checkboxes like
I have added some code to show what is selected:
/// <summary>
/// Highlight checked nodes
/// </summary>
private void HighlightCheckedNodes()
{
foreach (TreeNode topNode in treeView1.Nodes)
{
if (topNode.Checked)
{
topNode.BackColor = Color.Yellow;
}
else
{
topNode.BackColor = Color.White;
}
foreach (TreeNode myNode in topNode.Nodes)
{
// Check whether the tree node is checked.
if (myNode.Checked)
{
// Set the node's backColor.
myNode.BackColor = Color.Yellow;
}
else
{
myNode.BackColor = Color.White;
}
}
}
}
This function is called from treeView1_AfterSelect and treeView1_AfterCheck.
If I click a few times I get some nodes that are marked as checked but not yellow (the data says it's not checked) or vice versa.
So how do I make sure the data and display are in sync?
Long story short, the TreeView is buggy!
Here's an implementation that uses NativeWindow instead of deriving from TreeView. This will allow you to use the existing TreeView on your Form:
public partial class Form1 : Form
{
private TreeViewSuppressDoubleClick treeViewHelper;
private void Form1_Load(object sender, EventArgs e)
{
treeViewHelper = new TreeViewSuppressDoubleClick(this.treeView1);
}
public class TreeViewSuppressDoubleClick : NativeWindow
{
public TreeViewSuppressDoubleClick(TreeView treeView)
{
if (treeView != null && treeView.IsHandleCreated)
{
this.AssignHandle(treeView.Handle);
}
}
protected override void WndProc(ref Message m)
{
if (m.Msg != 0x203)
base.WndProc(ref m);
}
}
private void treeView1_AfterCheck(object sender, TreeViewEventArgs e)
{
e.Node.BackColor = e.Node.Checked ? Color.Yellow : Color.White;
}
}
After this you'll notice that the TreeView isn't as responsive to clicks, but it does stay in sync now.
Note the simplified method for changing the BackColor on a check/uncheck.
Also, here's an alternate HighlightCheckedNodes() using recursion to get all the nodes:
private void HighlightCheckedNodes(TreeNode node)
{
if (node == null)
{
foreach (TreeNode topNode in treeView1.Nodes)
{
HighlightCheckedNodes(topNode);
}
}
else
{
node.BackColor = node.Checked ? Color.Yellow : Color.White;
foreach (TreeNode curNode in node.Nodes)
{
HighlightCheckedNodes(curNode);
}
}
}
You'd call it using HighlightCheckedNodes(null);.
I have a custom extension method that (is supposed to) find a control via a string, and perform a click to that control. I've set up a break point and it nevers into the if (c is ToolStripMenuItem) Anyone have any idea where I'm going wrong? This is on WinForms.
private void PerformClickfromString()
{
string item = File.ReadAllText(#"C:\controltest.txt");
foreach (var c in this.Controls)
{
if (c is ToolStripMenuItem)
{
var x = (ToolStripMenuItem)c;
if (x.Name == item)
{
x.PerformClick();
}
}
}
}
private void button1_Click(object sender, EventArgs e)
{
PerformClickfromString();
}
Any help is appreciated.
To find all nested items you need a recursive search. Here is an example; it collects all items in a List<ToolStripMenuItem> and the checks for the searched name. I have added that string to the signature of your call..:
private void PerformClickfromString(string s)
{
foreach (var c in this.Controls)
{
List<ToolStripMenuItem> items = new List<ToolStripMenuItem>();
if (c is MenuStrip)
{
foreach (ToolStripMenuItem tsItem in ((MenuStrip)c).Items)
{
GetAllMenuItems(items, tsItem);
}
}
ToolStripMenuItem found = items.Find(x => x.Name == s);
if (found != null) found.PerformClick();
}
}
void GetAllMenuItems(List<ToolStripMenuItem> items, ToolStripMenuItem menu)
{
items.Add(menu);
foreach(ToolStripMenuItem m in menu.DropDownItems)
GetAllMenuItems(items, m);
}
The ToolStripMenuItem is not considered a control, you must instead use its container
if(c is MenuStrip)
{
foreach(ToolStripMenuItem tsItem in ((MenuStrip)c).Items)
{
if (tsItem.Name == item)
{
tsItem.PerformClick();
}
}
}
I have a tree view with checkboxes in c#, I want that when the user checks one node all the nodes that there are on the levels below automatic checked also.
Does anyone know about way to do that without run with recorsive fnction on all the tree each time that the user checks some node?
Thanks
//this function returns the treeView.
public TreeView GetTreeView()
{
getSubject();
// fill the treeview with all subjects.
foreach (Subject subject in subjects)
{
//for each root subject fill all the his children.
if (subject.subjestId == subject.parentSubject)
{
TreeNode node = new TreeNode(subject.subjectString, subject.subjestId, subject.subjestId);
addChild(node, subject.subjestId);
tv.Nodes.Add(node);
}
}
return tv;
}
// for each subject return sub subjects.
private void addChild(TreeNode node, int parentId)
{
foreach (Subject subject in subjects)
{
if (subject.parentSubject == parentId && subject.parentSubject != subject.subjestId)
{
TreeNode childNode = new TreeNode(subject.subjectString, subject.subjestId, subject.subjestId);
addChild(childNode, subject.subjestId);
node.Nodes.Add(childNode);
}
}
}
Recursion. Like this:
bool busy = false;
private void treeView1_AfterCheck(object sender, TreeViewEventArgs e) {
if (busy) return;
busy = true;
try {
checkNodes(e.Node, e.Node.Checked);
}
finally {
busy = false;
}
}
private void checkNodes(TreeNode node, bool check) {
foreach (TreeNode child in node.Nodes) {
child.Checked = check;
checkNodes(child, check);
}
private void treeView1_AfterCheck(object sender, TreeViewEventArgs e) {
foreach (TreeNode child in e.Node.Nodes) {
child.Checked = e.Node.Checked;
}
}
This is a better solution
private void trvMenuList_AfterCheck(object sender, TreeViewEventArgs e)
{
SetChildrenChecked(e.Node, e.Node.Checked);
}
private void SetChildrenChecked(TreeNode treeNode, bool checkedState)
{
foreach (TreeNode item in treeNode.Nodes)
{
if (item.Checked != checkedState)
{
item.Checked = checkedState;
}
SetChildrenChecked(item, item.Checked);
}
}
As a number of the answers state, create a recursive 'set checked to children' function, then call it AfterCheck on the tree.
The framework unfortunately gives you a call back to AfterCheck even if you set the check value in code, and although this may not be noticeable in small trees adds a massive amount of exponential extra work for your app to do. To avoid it, filter AfterCheck to only fire your new function if it has been triggered by user.
private void tree_AfterCheck(object sender, TreeViewEventArgs e)
{
if (e.Action != TreeViewAction.Unknown)
{
SetChildrenChecked(e.Node);
}
}
private void SetChildrenChecked(TreeNode treeNode)
{
foreach (TreeNode item in treeNode.Nodes)
{
if (item.Checked != treeNode.Checked)
{
item.Checked = treeNode.Checked;
}
if (item.Nodes.Count > 0)
{
SetChildrenChecked(item);
}
}
}
I expanded on the answer a little; by updating the parent as well. [The DisplayException method inside the catch is just a popup window that I always use; you can do your own]
private bool _busy = false;
private void treeViewPassFail_AfterCheck(object sender, TreeViewEventArgs e)
{
try
{
if (_busy)
{
return;
}
_busy = true;
CheckNodes(e.Node, e.Node.Checked);
CheckParent(e.Node.Parent);
}
catch(Exception ex)
{
DisplayException(ex);
}
finally
{
_busy = false;
}
}
private void CheckNodes(TreeNode node, bool check)
{
foreach(TreeNode child in node.Nodes)
{
child.Checked = check;
CheckNodes(child, check);
}
}
private void CheckParent(TreeNode parent)
{
if (parent != null)
{
bool allChecked = true;
foreach (TreeNode node in parent.Nodes)
{
allChecked &= node.Checked;
}
parent.Checked = allChecked;
}
}
If you want to do it in WinForms then I think you have to do it manually by recursion - I don't know any better way.
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;
}
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;
}
}
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;
}
}
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.