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.
Related
I have a winforms treeview with nodes added and check state set programmatically based on the database values. I am trying to prevent users from altering the check status and am having trouble. I am not sure what event to fire to keep the check state unaltered.
Below is my code:
private void BuildRolesTree(int ParentID, TreeNode pNode, DataSet SourceDS)
{
DataView dvwData = new DataView(SourceDS.Tables[0]);
dvwData.RowFilter = "[parent_id] = " + ParentID;
if (this.InvokeRequired)
{
BuildReportTreeDelegate d = new BuildReportTreeDelegate(BuildRolesTree);
this.Invoke(d, new object[] { ParentID, pNode, SourceDS });
}
else
{
foreach (DataRowView Row in dvwData)
{
TreeNode zNode;
if (pNode == null)
zNode = tv_Permissions.Nodes.Add(Row["node_id"].ToString(), Row["display_access_description"].ToString().Trim());
else zNode = pNode.Nodes.Add(Row["node_id"].ToString(), Row["display_access_description"].ToString().Trim());
if (Convert.ToInt32(Row["is_selected"]) == 1)
zNode.Checked = true;
else if (Convert.ToInt32(Row["is_selected"]) == 0)
zNode.Checked = false;
BuildRolesTree(Convert.ToInt32(Row["node_id"].ToString()), zNode, SourceDS);
}
}
}
private void PermissionsNode_AfterCheck(object sender, TreeViewEventArgs e)
{
if(e.Action != TreeViewAction.Unknown)
{
if(e.Node.Checked) //leave it checked
e.Node.Checked = e.Node.Checked????
//I am looking for something like the below
//e.Checked.NewValue = e.Checked.CurrentValue;
}
}
}
ANy help is appreciated.
You can handle BeforeCheck and set e.Cancel = true to prevent changing the check:
private void treeView1_BeforeCheck(object sender, TreeViewCancelEventArgs e)
{
e.Cancel = true;
}
However, there is a problem in TreeView when double click on CheckBoxes the check events not work as expected. Follow the solution that I've shared in the linked post or use the following ExTreeView which has a CheckBoxEdit property (similar to LabelEdit) which allows you enable or disable checking on CheckBoxes:
public class ExTreeView : TreeView
{
private const int WM_LBUTTONDBLCLK = 0x0203;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_LBUTTONDBLCLK)
{
var info = this.HitTest(PointToClient(Cursor.Position));
if (info.Location == TreeViewHitTestLocations.StateImage)
{
m.Result = IntPtr.Zero;
return;
}
}
base.WndProc(ref m);
}
[DefaultValue(true)]
public bool CheckBoxEdit { get; set; } = true;
protected override void OnBeforeCheck(TreeViewCancelEventArgs e)
{
base.OnBeforeCheck(e);
e.Cancel = !CheckBoxEdit;
}
}
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
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 want to add the items of a listBox to a list inside Properties.Settings.Default but it doesn't work at all. I add items to my listBox with a messagebox i added from the Microsoft.VisualBasic library and then add the input to the listBox:
private void Button_additem_Click(object sender, EventArgs e)
{
string itemname = Interaction.InputBox("Item hinzufügen", "Gib unten den Namen des Items ein.", "");
if (itemname.Length > 0)
{
listbox_items.Items.Add(itemname);
}
else { }
Save();
Restore();
}
After that i am using my Save() and Restore() voids:
private void Save()
{
if (Properties.Settings.Default.myList == null)
{ }
else
{
Properties.Settings.Default.myList.Clear();
}
foreach (object item in listbox_items.Items)
{
Properties.Settings.Default.myList.Add(item);
}
Properties.Settings.Default.Save();
}
private void Restore()
{
combobox_montag.Items.Clear();
combobox_dienstag.Items.Clear();
combobox_mittwoch.Items.Clear();
combobox_donnerstag.Items.Clear();
combobox_freitag.Items.Clear();
combobox_samstag.Items.Clear();
combobox_sonntag.Items.Clear();
listbox_items.Items.Clear();
foreach (object item in Properties.Settings.Default.myList)
{
combobox_montag.Items.Add(item);
combobox_dienstag.Items.Add(item);
combobox_mittwoch.Items.Add(item);
combobox_donnerstag.Items.Add(item);
combobox_freitag.Items.Add(item);
combobox_samstag.Items.Add(item);
combobox_sonntag.Items.Add(item);
listbox_items.Items.Add(item);
}
}
The error comes here with a 'System.NullReferenceException' 'Planer.Properties.Settings.myList.get returned null.':
foreach (object item in listbox_items.Items)
{
Properties.Settings.Default.myList.Add(item); <---------
}
There is a part of code that can make this list null
if (Properties.Settings.Default.myList == null)
{ }
else { Properties.Settings.Default.myList.Clear();
this if makes nothing, if the list is null it can not add an item, null is not
the same as empty null means inexsintent, you will need to create a new one seeing as you only cear the list in the else so:
if (Properties.Settings.Default.myList == null)
{Properties.Settings.Default.myList = new List<?> etc... }
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.