C# TreeView becomes missmatched between data and display - c#

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);.

Related

How to link textbox and checkbox in C#

I started making a small program. The form contains checkbox1,2,3,4,.... and textbox1,2,3,4,5.... there is a code that looks at which of the checkboxes are marked. If there is any possibility to link textbox and checkbox. So that when a code marked with a checkbox is detected, the text is taken from the textbox given to it and transferred to the RichTextBox, using AppendText. Below is a sample code with a cyclic check of all the checkboxes on the form for the presence of checked on my form.
foreach (Control control in this.tabControl1.TabPages[0].Controls) //цикл по форме с вкладками
{
if (control as CheckBox != null) // проверка на пустое значение
{
if (control.Visible == true)// проверка на видимость
{
if ((control as CheckBox).Checked)// проверка на чек
{
}
else if ((control as CheckBox).Checked == false)
{
}
}
}
Use the following method to get CheckBox controls.
public static class ControlExtensions
{
public static IEnumerable<T> Descendants<T>(this Control control) where T : class
{
foreach (Control child in control.Controls)
{
if (child is T thisControl)
{
yield return (T)thisControl;
}
if (child.HasChildren)
{
foreach (T descendant in Descendants<T>(child))
{
yield return descendant;
}
}
}
}
}
In the form, use a Dictionary to pair CheckBox to TextBox. You can also check for visibility in the Where.
public partial class Form1 : Form
{
private readonly Dictionary<string, TextBox> _dictionary =
new Dictionary<string, TextBox>();
public Form1()
{
InitializeComponent();
_dictionary.Add("checkBox1", textBox1);
_dictionary.Add("checkBox2", textBox2);
_dictionary.Add("checkBox3", textBox3);
_dictionary.Add("checkBox4", textBox4);
_dictionary.Add("checkBox5", textBox5);
}
private void button2_Click(object sender, EventArgs e)
{
var list = tabControl1.Descendants<CheckBox>().ToList();
var #checked = list.Where(x => x.Checked).ToList();
var notChecked = list.Where(x => !x.Checked).ToList();
foreach (var checkBox in #checked)
{
TextBox textBox = _dictionary[checkBox.Name];
}
}
}
Create a UserControl with CheckBox and TextBox components. Create properties Checked and TextForAdd:
namespace Sort.UserPanel
{
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public bool Checked { get { return checkBox1.Checked; } }
public string TextForAdd { get { return textBox1.Text; } }
}
}
On the main form we add UserControl1 the necessary number of times.
private void testCheckBoxes(object obj)
{
if (obj is UserControl1 control)
{
string text = control.TextForAdd;
// .....
}
}
private void button1_Click(object sender, EventArgs e)
{
foreach (Control control in this.LeftPanel.Controls)
{
if (control as UserControl1 != null)
{
if (control.Visible == true )
{
testCheckBoxes(control);
}
}
}
}

Prevent user from altering Check boxes in TreeView

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;
}
}

Beginner: TreeView with checkboxes and recursion in Winforms

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

TreeView with CheckBoxes in c#

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.

Checking TreeView Control Nodes and SubNodes - StackOverflowException!

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.

Categories

Resources