This code checks and unchecks the child nodes of a treeview control.
What algorithm is used in this code?
private int _callCountUp;
private int _callCountDn;
private void tvwPermissions_AfterCheck(object sender, System.Windows.Forms.TreeViewEventArgs e)
{
bool anyChecked = false;
if (_callCountDn == 0 && e.Node.Parent != null)
{
anyChecked = false;
foreach (TreeNode childNode in e.Node.Parent.Nodes)
{
if (childNode.Checked)
{
anyChecked = true;
break;
}
}
_callCountUp += 1;
if (anyChecked)
e.Node.Parent.Checked = true;
_callCountUp -= 1;
}
if (_callCountUp == 0)
{
foreach (TreeNode childNode in e.Node.Nodes)
{
_callCountDn += 1;
childNode.Checked = e.Node.Checked;
_callCountDn -= 1;
}
}
}
Not so sure this has a name. It is quite standard, the _callCountUp/Dn fields avoid trouble when changing the Checked property of a node causes the AfterCheck event handler to run again. StackOverflow is a very typical outcome when the event handler recurses without bound.
The generic pattern resembles this:
private bool modifyingNodes;
private void treeview_AfterCheck(object sender, TreeViewEventArgs e) {
if (modifyingNodes) return;
modifyingNodes = true;
try {
// etc..
}
finally {
modifyingNodes = false;
}
}
The finally block ensures that a handled exception (such as through ThreadExceptionDialog) doesn't permanently leave the state variable set to true. It's optional of course.
Related
While using asp:TreeView control on web form and populating the nodes in code behind, it expands and collapses as expected. However, when I added OnTreeNodeExpanded event it caused a postback but never fired the event tired to this event and prevented the node from expanding. I am trying to save the nodes that are expanded in session and restore the treeview expansion state when the user revisit the page. Any ideas how to accomplish this with this approach.
Thanks!
Here is the code snippet:
<asp:TreeView ID="tvDevices" runat="server" OnTreeNodeExpanded="TvDevices_OnTreeNodeExpandedCollapsed" OnTreeNodeCollapsed="TvDevices_OnTreeNodeExpandedCollapsed"/>
protected void Page_Load(object sender, EventArgs e)
{
foreach (string s in devices)
{
TreeNode tn = new TreeNode();
tn.Text = s.toString();
tn.SelectAction = TreeNodeSelectAction.None;
tvDevices.Ndes.Add(tn);
}
if (IsPostBack)
{
if (Session["tvDevicesState"] == null)
tvDevices.CollapseAll(); //Default to collapsed state
else
{
RestoreTreeViewState(tvDevices.Nodes, Session["tvDevicesState"] as List<string>);
}
}
else
{
tvDevices.CollapseAll();
}
}
private void SaveTreeViewState(TreeNodeCollection treeNodes, List<string> expandedList)
{
foreach (TreeNode treeNode in treeNodes)
{
if (treeNode.ChildNodes.Count > 0)
{
if (treeNode.Expanded.HasValue && treeNode.Expanded == true)
{
expandedList.Add(treeNode.Text);
}
SaveTreeViewState(treeNode.ChildNodes, expandedList);
}
}
}
private void RestoreTreeViewState(TreeNodeCollection treeNodes, List<string> expandedList)
{
foreach (TreeNode treeNode in treeNodes)
{
treeNode.Expanded = false;
if (expandedList.Contains(treeNode.Text))
{
if (treeNode.ChildNodes.Count > 0)
{
treeNode.Expanded = true;
RestoreTreeViewState(treeNode.ChildNodes, expandedList);
}
}
}
}
protected void TvDevices_OnTreeNodeExpandedCollapsed(object sender, EventArgs e)
{
List<string> expandedList = new List<string>();
SaveTreeViewState(tvDevices.Nodes, expandedList);
Session["tvDevicesState"] = expandedList;
}
I figured out the reason why the OnTreeNodeExpanded event was not fired. In my case, the reason was that there was no value property attached to the tree nodes. After adding a value to each node, the event was being fired.
Below is the working code.
<asp:TreeView ID="tvDevices" runat="server" OnTreeNodeExpanded="TvDevices_OnTreeNodeExpandedCollapsed" OnTreeNodeCollapsed="TvDevices_OnTreeNodeExpandedCollapsed"/>
Code behind:
protected void Page_Load(object sender, EventArgs e)
{
List<TreeNode> root = new List<TreeNode>();
for(int i = 0; i < 5; i++)
{
root.Add(new TreeNode("Parent Node " + i, i.ToString()));
}
foreach (TreeNode tn in root)
{
tn.ChildNodes.Add(new TreeNode("First Child", "first"));
tn.ChildNodes.Add(new TreeNode("Second Child", "second"));
tvDevices.Nodes.Add(tn);
}
tvDevices.CollapseAll(); //Collapse all nodes
RestoreTreeViewState(tvDevices.Nodes, (List<string>)Session["tvState"] ?? new List<string>()); //Restore previously expanded nodes
}
private void SaveTreeViewState(TreeNodeCollection treeNodes, List<string> expandedList)
{
foreach (TreeNode treeNode in treeNodes)
{
if (treeNode.ChildNodes.Count > 0)
{
if (treeNode.Expanded.HasValue && treeNode.Expanded == true)
{
expandedList.Add(treeNode.Value);
}
SaveTreeViewState(treeNode.ChildNodes, expandedList);
}
}
}
private void RestoreTreeViewState(TreeNodeCollection treeNodes, List<string> expandedList)
{
foreach (TreeNode treeNode in treeNodes)
{
if(expandedList.Count == 0)
return;
if (expandedList.Contains(treeNode.Value))
{
if (treeNode.ChildNodes.Count > 0)
{
treeNode.Expand();
RestoreTreeViewState(treeNode.ChildNodes, expandedList);
}
}
}
}
protected void TvDevices_OnTreeNodeExpandedCollapsed(object sender, EventArgs e)
{
List<string> expandedList = new List<string>();
SaveTreeViewState(tvDevices.Nodes, expandedList);
Session["tvDevicesState"] = expandedList;
}
Since you added an event-handler for the treeview's Expanded/Collapsed event, it triggers a postback now. Before the tree view's event handler is called, the Page_Load event handler gets called. Within the Page_Load event handler, check for the Page.IsPostBack first before performing operations on the tree-view control, such as collapsing it by default.
I have a CheckedListBox with 10 items. On each item check a method is being called. I want to disable the checkbox of that particular item for which the method is being executed so that user cannot uncheck the item till the job is completed.
Note: Unchecking of an item calls another method.
Here is the code of ItemCheck Event:
private void host_listbox_ItemCheck(object sender, ItemCheckEventArgs e)
{
int index = e.Index;
try
{
string sitem = host_listbox.Items[index].ToString();
host_list[sitem].checked_event=e;
if (!host_list[sitem].is_busy)
{
host_config.listEnabled = false;
host_list[sitem].con_worker.RunWorkerAsync();
}
if (host_listbox.GetItemCheckState(index) == CheckState.Checked)
{
host_list[sitem].connected = false;
}
}
catch(Exception ex)
{
output_textbox.AppendText("connection failed!" +ex.ToString() +Environment.NewLine);
}
}
You can check/uncheck items in your checkedListBox with this code
checkedListBox.SetItemChecked(item, true);
for more informations go to microsoft documentation
private void host_listbox_ItemCheck(object sender, ItemCheckEventArgs e)
{
int index = e.Index;
try
{
string sitem = host_listbox.Items[index].ToString();
if (host_list[sitem].is_busy // or whatever indicates that background worker is running or any condition that specifies, that you do not want to let this item to be changed)
e.NewValue = e.CurrentValue; //Change the value back
else
{
//Let the checked state of the item change
This code prevent checked state change if associated background work is running:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
workerList = new List<BackgroundWorker>();
for (int i = 0; i < 10; i++)
{
var el = new BackgroundWorker();
el.DoWork += (s, e) =>
{
Thread.Sleep(5000);
};
workerList.Add(el);
checkedListBox1.Items.Add("el " + i);
}
}
private void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e)
{
var worker = workerList[e.Index];
if (worker.IsBusy)
{
e.NewValue = e.CurrentValue;
return;
}
if (e.NewValue == CheckState.Checked)
worker.RunWorkerAsync();
}
public List<BackgroundWorker> workerList { get; set; }
}
Think, that only funcion solution is set selection mode
CheckedListBox.SelectionMode = SelectionMode.None;
private void ItemCheck(object sender, ItemCheckEventArgs e)
{
if (busy)
e.NewValue = e.CurrentValue;
}
I got a windows form with around 140 numericUpDown elements and want all of them to do this:
private void numericUpDown_B1_RS_LS_Leave(object sender, EventArgs e)
{
if (numericUpDown_B1_RS_LS.Value < 1 || numericUpDown_B1_RS_LS.Value > 6)
{
numericUpDown_B1_RS_LS.BackColor = Color.Red;
}
else
{
numericUpDown_B1_RS_LS.BackColor = Color.White;
}
}
Is there a more comfortable way of doing this than just adding 140 functions to the form manually?
Assuming you want the same method for all NUDs on your form this will do it:
public Form1()
{
InitializeComponent();
foreach (Control ctl in this.Controls)
if (ctl.GetType() == typeof(NumericUpDown) ) ctl.Leave += numericUpDown_Leave;
}
private void numericUpDown_Leave(object sender, EventArgs e)
{
NumericUpDown NumericUD = (NumericUpDown) sender ;
if (NumericUD.Value < 1 || NumericUD.Value > 6)
{
NumericUD.BackColor = Color.Red;
}
else
{
NumericUD.BackColor = Color.White;
}
}
You can just as easily iterate over the Controls collection of another container, say a Panel or a GroupBox if the NUDs are not directly sitting on the Form. And if some NUDs should be excepted from that behaviour you could mark them in some way, maybe in their Tag or Name and check for that before adding their handlers ..
You should not write multiple functions for same code. Instead you can create just one function and assign it to the event method of your numericUpDown element i.e. "UpDown" event.
So whenever any of the element's "UpDown" event will fire, your same function will be executed. And as far as you want to write different methods for different elements, then consider that in your method
Method(object sender, EventArguments e)
sender is the actual sender object of your updownElement type, and that code will be executed for that particular object only.
You can take for loop to iterate your all 140 elements and assign this function to the "UpDown" element.
maybe you could write a static method to handle the adding of event handler recursively, something like:
private void frmMain_Load(object sender, EventArgs e)
{
AddHandleNumericUpdownLeave(this);
}
public static void AddHandleNumericUpdownLeave(Control theContrl)
{
if (theContrl.Controls != null && theContrl.Controls.Count > 0)
{
foreach (Control cControl in theContrl.Controls)
{
AddHandleNumericUpdownLeave(cControl);
}
}
else
{
NumericUpDown nudCtrl = theContrl as NumericUpDown;
if (nudCtrl != null)
{
nudCtrl.Leave += (object senderT, EventArgs eT) =>
{
var tmpCtrl = senderT as NumericUpDown;
if (tmpCtrl != null)
{
if (tmpCtrl.Value < 1 || tmpCtrl.Value > 6)
{
tmpCtrl.BackColor = Color.Red;
}
else
{
tmpCtrl.BackColor = Color.White;
}
}
}
}
}
}
but this might be costly if you have too many controls in a form...
Have a tab page with 2 panels, a data grid view and a 'clear' button.There are only textboxes in the panels and the grid is unbound. Data input is by user. The clear btn is disabled by default. My requirement is to enable it only if any of the textboxes is not empty or there is more than 1 row in the grid. This code isn't working. Please help.
public Form1()
{
InitializeComponent();
foreach (Control c in InvoiceTab.Controls)
{
if (c is DataGridView)
{
DataGridView dgv = c as DataGridView;
if (dgv.RowCount > 1)
{
EnableClearBtnBool = true;
btnClear.Enabled = true;
break;
}
else
{
EnableClearBtnBool = false;
btnClear.Enabled = false;
break;
}
}
}
foreach (Control c1 in panel1.Controls)
{
if (c1 is TextBox)
{
if (c1.Text != "")
{
EnableClearBtnBool = true;
c1.TextChanged -= EnableClearBtn;
c1.TextChanged += EnableClearBtn;
break;
}
else
EnableClearBtnBool = false;
}
}
foreach (Control c2 in panel2.Controls)
{
if (c2 is TextBox)
{
if (c2.Text != "")
{
EnableClearBtnBool = true;
c2.TextChanged -= EnableClearBtn;
c2.TextChanged += EnableClearBtn;
break;
}
else
EnableClearBtnBool = false;
}
}
}
bool EnableClearBtnBool = false;
private void EnableClearBtn(object sender, EventArgs e)
{
if (EnableClearBtnBool == true)
btnClear.Enabled = true;
else
btnClear.Enabled = false;
}
That code is almost certainly not working because of its location first and foremost. However, there are some fundamental changes we should be able to make as well. First we're going to need this code in a method that can be called frequently:
private void RefreshClearButton()
{
btnClear.Enabled = textBox1.Text.Length > 0 ||
textBox2.Text.Length > 0 || ...
dataGridView.RowCount > 1;
}
but, we also need to leverage the TextChanged event on all of the text boxes:
private void textBox_TextChanged(object sender, EventArgs e)
{
RefreshClearButton();
}
so you need to hook all of them up to this event handler. Now, we have two more events we need to consume, on the DataGridView, RowsAdded and RowsRemoved:
private void dataGridView_RowsAdded(object sender, DataGridViewRowsAddedEventArgs e)
{
RefreshClearButton();
}
private void dataGridView_RowsRemoved(object sender, DataGridViewRowsRemovedEventArgs e)
{
RefreshClearButton();
}
and so now we're notified every time something changes. If you must iterate through the panels rather than naming every single text box along the way then you might want to do something like this:
private bool HasTextBeenEntered(ControlCollection controls)
{
foreach (var c in controls)
{
if (c is TextBox && ((TextBox)c).Text.Length > 0) { return true; }
else if (c is Control && HasTextBeenEntered(((Control)c).Controls)) { return true; }
}
return false;
}
which would change the RefreshClearButton method slightly:
private void RefreshClearButton()
{
btnClear.Enabled = HasTextBeenEntered(this.Controls) || dataGridView.RowCount > 1;
}
DISCLAIMER: none of this code is compiled so don't be surprised if you have to tweak it, but it will get you more than 90% of the way.
I have a recursive function and two events (Going_in and Coming_out).
I use the Going_in event to increment a progressbar every time the function calls itself and I use Coming_out to decrement the progressbar every time the function returns from recursion.
Now i have to combine Going_in and Coming_out into one event.
How can I do that?
Thanks in advance!
Here is part of the code.
Form1.cs
.....
void ProgressBar_increment(object sender, EventArgs e)
{
progressBar1.Value++;
}
void ProgressBar_decrement(object sender, EventArgs e)
{
progressBar1.Value--;
}
public void button2_Click(object sender, EventArgs e)
{
initialize();
label3.Visible = false;
int wait_time = telltime();
int number = reading();
Facto mth;
mth = new Facto(label3, wait_time, progressBar1);
mth.Going_in += new EventHandler(ProgressBar_increment);
mth.Coming_out += new EventHandler(ProgressBar_decrement);
int result = mth.Factorial(number);
string display = result.ToString();
label3.Visible = true;
label3.Text = display;
}
Facto.cs
public event EventHandler Going_in;
public event EventHandler Coming_out;
........
public int Factorial(int number_to_calculate)
{
int Result;
if (Going_in != null)
{
Going_in(this, new EventArgs());
}
System.Threading.Thread.Sleep(wait_time);
if (number_to_calculate == 0)
{
if (Coming_out != null)
{
Coming_out(this, new EventArgs());
}
return 1;
}
else
{
Result = (number_to_calculate * Factorial(number_to_calculate - 1));
if (label_for_output != null)
{
label_for_output.Visible = true;
label_for_output.Text = Result.ToString();
label_for_output.Update();
}
else
Console.WriteLine(Result);
}
if (Coming_out != null)
{
Coming_out(this, new EventArgs());
}
System.Threading.Thread.Sleep(wait_time);
return Result;
}
That is what the EventArgs are for.
Put some variable in the EventArgs object (or create your own for this purpose) from where you can determine whether to increment or decrement (enum maybe?)
I.e. (possible pseudo code alert)
if (Going_in != null)
{
CustomEventArgs cea = new CustomEventArgs();
cea.Type = Type.Decrement;
Going_in(this, cea);
}
And in your event do something like:
void ProgressBar_DoProgress(object sender, CustomEventArgs e)
{
if(e.Type == Type.Increment)
progressBar1.Value++;
if(e.Type == Type.Decrement)
progressBar1.Value--;
}