Foreach loop for disposing controls skipping iterations - c#

Code to create textboxes...
private void btnAddIncrement_Click(object sender, EventArgs e)
{
SmartTextBox dynamictextbox = new SmartTextBox();
dynamictextbox.BackColor = Color.Bisque;
dynamictextbox.Width = this.tbWidth;
dynamictextbox.Left = (sender as Button).Right + this.lastLeft;
dynamictextbox.K = "Test";
this.lastLeft = this.lastLeft + this.tbWidth;
dynamictextbox.Top = btnAddStart.Top;
this.Controls.Add(dynamictextbox);
}
Code for to remove all text boxes.
foreach (Control c in this.Controls)
{
if (c.GetType() == typeof(BnBCalculator.SmartTextBox))
{
count++;
//MessageBox.Show((c as SmartTextBox).K.ToString());
c.Dispose();
}
// else { MessageBox.Show("not txtbox"); }
}
When I click the btnAddIncrement I get the following as expected...
But when I click reset it misses every second textbox. See below...
No idea what's going on here but this is the same no matter how may text boxes I add. It always misses every second box.

You should use a reverse standard for loop to dispose the SmartTextBoxes from its container
for(int x = this.Controls.Count - 1; x >= 0; x--)
{
BnBCalculator.SmartTextBox c = this.Controls[x] as BnBCalculator.SmartTextBox;
if (c != null)
{
count++;
c.Dispose();
}
}
According to this question/answer you don't need to remove them from the container and of course this avoids two loops (explicit or implicit). Also in the accepted answer you could see the reason why your code jumps a control every two.
if (parent != null)
{
parent.Controls.Remove(this);
}
The control that you want to dispose is removed from the collection that you are iterating over. (Not clear why this doesn't throw the standard exception).
Instead looping with a simple for in reverse avoid any problem in the ordered access to the controls to dispose.

When you remove an item form this.Controls the collection is modified and so the next item is not what you expect. Yo should copy the this.Controls to a new list. For example you can use ToArray to make a copy of this.Controls
foreach (Control c in this.Controls.ToArray())
{
...
}

Your removal code is incorrect as you are modifying the Controls collection by calling Dispose() which is why you get the skipping of controls.
Easiest option to remove those of a specific type is to do the following:
var smartTbs = this.Controls.OfType<BnBCalculator.SmartTextBox>().ToList();
smartTbs.ForEach(x => x.Dispose());

You have to remove controls from Form.Controls at first and then dispose it.
var controlsToRemove = new List<Control>();
foreach (Control c in this.Controls)
{
if (c is BnBCalculator.SmartTextBox)
controlsToRemove.Add(c);
}
foreach (Control c in controlsToRemove)
{
Controls.Remove(c);
}

Try to select all the SmartTextBox controls first and dispose them on another loop. Pseudocode:
SmartTextBoxes = Select From this.Controls Where (c.GetType() == typeof(BnBCalculator.SmartTextBox));
foreach(stb in SmartTextBoxes) { stb.Dispose(); }

Related

Check which Controls have Borders c#

I am making a Winforms application. Because I want to redraw some borders I want to loop through the controls and check which controls have a border. Unfortunately I have no idea how to accomplish this.
I know panels and textboxes, etc. have a property BorderStyle but I can not access it while looping through Controls. I use the function from this link : https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.control.controls?view=netframework-4.8 , to loop through the controls.
If you have a panel you can foreach on the panel. I use form load as an event
private void Form1_Load(object sender, EventArgs e)
{
foreach (var item in this.Controls)
{
switch (item.GetType().Name)
{
case "Label":
if (((Label)item).BorderStyle == BorderStyle.None)
{
//Your commands
}
break;
case "TextBox":
if (((TextBox)item).BorderStyle == BorderStyle.None)
{
//Your commands
}
break;
}
}
}
Or you can check them dynamic
I recommend you to use the dynamic way.
in this way, your app wouldn't encounter exceptions or errors
foreach (var item in this.Controls)
{
//Get item from form
dynamic element = item;
//check if there is any property called Borderstyle exists
var res = item.GetType().GetProperties().Where(p => p.Name.Equals("BorderStyle")).FirstOrDefault();
//if it exists and value is not equal None(Control have border)
if (res !=null && !(res.GetValue(item).Equals("None")))
{
res.SetValue(item, BorderStyle.FixedSingle, null);
//your other commands
}
}
You could use a foreach for every type of control you use (TextBox, Label, etc.)
var controlsWithoutBorders = new List<Control>();
// Check textboxes
foreach (var control in controls.OfType<TextBox>())
{
if (control.BorderStyle != BorderStyle.None)
{
controlsWithoutBorders.Add(control);
}
}
//Check labels
foreach (var control in controls.OfType<Label>())
{
if (control.BorderStyle != BorderStyle.None)
{
controlsWithoutBorders.Add(control);
}
}
Alternatively, you could use a single foreach on all the controls, and try to cast the control to each type. The cast will be successful if the control is actually what you want to cast it to, otherwise it will return null (e.g. trying to cast a Control that is a TextBox to a Label will return null)
var controlsWithoutBorders = new List<Control>();
foreach (var control in controls)
{
var controlAsTextBox = control as TextBox;
var controlAsLabel = control as Label;
if (controlAsTextBox != null && controlAsTextBox.BorderStyle != BorderStyle.None)
{
controlsWithBorders.Add(control);
}
if (controlAsLabel != null && controlAsLabel.BorderStyle != BorderStyle.None)
{
controlsWithBorders.Add(control);
}
}
Though this method is not the fastest at run time, your problem could be cleanly solved with the use of C#'s dynamic typing feature. Consider the below snippet of code.
public void DealWithBorder(List<Control> lsControls) {
foreach(var element in lsControls){
dynamic dynamicElement = element;
try{
BorderStyle style = dynamicElement.BorderStyle;
// Handle if property does exist in the control
}
catch{
// Handle if property doesnt exist in the control
}
}
}
In English, it will try to act as if the property exists in the object but if it does not, an exception will thrown. For more info on dynamic typing, click here.

How to increase or decrease the tag value in C#?

I have dynamically added controls to my flowLayoutPanel and I'm offering the user to choose which pair of label and richTextBox does he wants to delete (label text is simply 1.,2.,3.,...) and tags are just numbers(1,2,3,...). This is how I did deleting the controls:
pairToDelete = Convert.ToInt32(textBox.Text);
foreach (Control ctrl in flowLayoutPanel1.Controls.OfType<Label>())
{
if (ctrl.Tag.ToString() == pairToDelete.ToString())
{
Controls.Remove(ctrl);
ctrl.Dispose();
}
}
foreach (Control ctrl in flowLayoutPanel1.Controls.OfType<RichTextBox>())
{
if (ctrl.Tag.ToString() == pairToDelete.ToString())
{
Controls.Remove(ctrl);
ctrl.Dispose();
}
}
Now, what I want is to change the tags of next pairs of controls. For example, if the user wants to delete 2nd pair of label and RTBox then I want to change tags of label3 and RTBox3 from 3 to 2, tags of label4 and RTBox4 from 4 to 3 etc. How can I do this?
I modified a little bit the mechanism to find a control to remove. After that I removing the control the way you remove it. After that I'm lowering the tag number of any control that has the tag higher then the removed control. Assumption is that the tags are numbers. I leave for you to do appropriate checks.
public void Delete()
{
var pairToDelete = Convert.ToInt32(textBox1.Text);
// Find what to remove.
var lblToDelete = this.Controls.OfType<Label>()
.FirstOrDefault(l => l.Tag.ToString() == pairToDelete.ToString());
var txtToDelete = this.Controls.OfType<RichTextBox>()
.FirstOrDefault(c => c.Tag.ToString() == pairToDelete.ToString());
// Can be removed?
if (lblToDelete != null)
{
// Remove.
this.Controls.Remove(lblToDelete);
lblToDelete.Dispose();
// Lower tag number for labels with tag higher then the removed one.
foreach (var c in this.Controls.OfType<Label>()
.Where(l => Convert.ToInt32(l.Tag) > pairToDelete))
{
var newTag = Convert.ToInt32(c.Tag) - 1;
c.Tag = newTag;
}
}
// Can be removed?
if (txtToDelete != null)
{
// Remove.
this.Controls.Remove(txtToDelete);
txtToDelete.Dispose();
// Lower tag number for rich textvbox with tag higher then the removed one.
foreach (var c in this.Controls.OfType<RichTextBox>()
.Where(r => Convert.ToInt32(r.Tag) > pairToDelete))
{
var newTag = Convert.ToInt32(c.Tag) - 1;
c.Tag = newTag;
}
}
}
In these situations where different controls are related, I prefer to rely on an additional storage (on top of the Controls collection) to ease all the actions among controls. For example, a class like the following one:
public class LabelRTB
{
public Label label;
public RichTextBox rtb;
public LabelRTB(Label label_arg, RichTextBox rtb_arg)
{
label = label_arg;
rtb = rtb_arg;
}
}
It can be populated on form load:
List<LabelRTB> allLabelRTB = new List<LabelRTB>();
allLabelRTB.Add(new LabelRTB(label1, rtb1));
allLabelRTB.Add(new LabelRTB(label2, rtb2));
this.Tag = allLabelRTB;
The requested deletion is now straightforward and the indices are updated automatically. For example:
private void removeItem(int index)
{
LabelRTB curItem = ((List<LabelRTB>)this.Tag)[index];
Controls.Remove(curItem.label);
Controls.Remove(curItem.rtb);
((List<LabelRTB>)this.Tag).Remove(curItem);
}
As far as the number of controls is quite limited and the complexity of the associated actions might be reduced notably, this a-priori-less-efficient approach (it stores the same information twice) might even make the application more efficient than in case of exclusively relying on Controls.
This is also one of the solutions:
foreach (Control ctrl in flowLayoutPanel1.Controls.OfType<Label>())
{
if (ctrl.Tag.ToString() == pairToDelete.ToString())
{
Controls.Remove(ctrl);
ctrl.Dispose();
}
if (Convert.ToInt32(ctrl.Tag) > pairToDelete)
{
int decrease = Convert.ToInt32(ctrl.Tag) - 1;
ctrl.Tag = decrease;
ctrl.Text = decrease + ".";
}
}
foreach (Control ctrl in flowLayoutPanel1.Controls.OfType<RichTextBox>())
{
if (ctrl.Tag.ToString() == pairToDelete.ToString())
{
Controls.Remove(ctrl);
ctrl.Dispose();
}
if (Convert.ToInt32(ctrl.Tag) > pairToDelete)
{
int decrease = Convert.ToInt32(ctrl.Tag) - 1;
ctrl.Tag = decrease;
}
}

how to check if a control of a certain type?

When I use th below code, it works. All the controls are hidden.
foreach (Control ctr in eItem.Controls)
{
ctr.visible = false;
}
However, I want to hide only labels and dropdownlists. That why I'm trying to use the below code without success
foreach (Control ctr in eItem.Controls)
{
if(ctr is Label | ctr is DropDownList)
{
ctr.visible = false;
}
}
EDIT
Here's the whole method
private void HideLabelAndDDLOnPageLoad()
{
foreach (ListViewItem eItem in lsvTSEntry.Items)
{
foreach (Control ctr in eItem.Controls)
{
if (ctr is Label || ctr is DropDownList)
{
ctr.Visible = false;
}
}
}
}
When I remove the if, all the controls get hidden. When I put it back, nothing happens.
Thanks for helping
I think what you are after is || change it to ||...that is the logical or operator.
foreach (Control ctr in eItem.Controls)
{
if(ctr is Label || ctr is DropDownList)
{
ctr.Visible = false;
}
}
| = bitwise operator
|| = logical or operator
Based on your edit
It appears your controls are inside an updatepanel, if that is the case you want to loop for all controls within the updatepanel's content template container.
Here you go:
foreach (Control ctr in UpdatePanel1.ContentTemplateContainer.Controls)
{
// rest of code
if(ctr is Label || ctr is DropDownList)
{
ctr.Visible = false;
}
}
The | is the bitwise or operator.
You are looking for ||, the logical or operator.
if(ctr is Label || ctr is DropDownList)
Without your exact markup we can only guess the solution here.
You must be using another container to wrap your controls inside your ItemTemplate in the ListView, something like a Panel or other containers. When you get the Controls on the list view item you actually get the warping container and not its children(labels, dropdowns etc.)
One solution to this is something like:
foreach (ListViewItem item in lsvTSEntry.Items)
{
item.FindControl("myLabel").Visible = false;
item.FindControl("myDropdownList").Visible = false;
}
Basically you try to find the controls by id and hide them. Notice there is no error checking there so you could get a NullReferenceException if FindControl returns null.
In case you have nested containers in your ItemTemplate and you want to hide all the labels and dropdowns regardless of where they are you can implement your own recursive FindControl that will look like:
private Control FindControlRecursive(Control rootControl, string controlId)
{
if (rootControl.ID == controlId)
{
return rootControl;
}
foreach (Control controlToSearch in rootControl.Controls)
{
Control controlToReturn = FindControlRecursive(controlToSearch, controlId);
if (controlToReturn != null)
{
return controlToReturn;
}
}
return null;
}
Not the most elegant but.... You can change this to take an array of Id's of course for speed purposes.
Based on this of course you can implement the search by control type which instead of taking a controlId as a parameter will take the types of controls to find.

find properties that has an attribute in a control

I have a page with a number of controls.
As the page is rendering I want to loop through all controls on the page and find any control that has a property with certain attribute. I am attempting to do this with c# - any ideas how I might achieve this?
I dont know how big is your control tree. This is what I would do. I'm not promissing the best performance.
Case 1. Looking for .NET Attributes
IEnumerable<Control> GetMarkedControls(ControlCollection controls)
{
foreach(Control c in controls)
{
var props = c.GetType().Properties();
if(props.Count(x => x.GetCustomAttributes(false).OfType<YourAttribute>().Count() > 0) > 0)
yield return c;
foreach (Control ic in GetMarkedControls(c.Controls))
yield return ic;
}
}
Case 2. Looking for HTML attributes
IEnumerable<WebControl> GetMarkedControls(ControlCollection controls)
{
foreach(Control c in controls)
{
if(c is WebControl)
{
var wc = c as WebControl;
if (wc.Attributes.FirstOrDeafult(x => x.Name == "yourAttribute") != null)
yield return c;
}
foreach (Control ic in GetMarkedControls(c.Controls))
yield return ic;
}
}
Now you can call it this way: var controlsWAttribute = GetMarkedControls(this.Controls); from your page or any control. This way you are not forced to call it at the page level.
With this method you explore the whole control tree in your page or control recursively.
You need to use Reflection
http://msdn.microsoft.com/en-us/library/system.reflection.aspx
using reflection you can get all the attributes of an object
Every control that is on the page has a "controls" property which contains all of its children controls. I have written recursive functions to loop through these before but do not have any on hand. Let me try to write one real quick:
public Collection<Control> findControlsWithAttributes(Control startingControl)
{
Collection<Control> toReturn = new Collection<Control>();
foreach (Control curControl in startingControl.controls)
{
if (DO COMPARISON HERE WITH CURCONTROL) toReturn.add(curControl);
if (curControl.Count() > 0) findControlsWithAttributes(curControl, toReturn);
}
return toReturn;
}
private void findControlsWithAttributes(Control startingControl, Collection<Control> inputCollection)
{
foreach (Control curControl in startingControl.controls)
{
if (DO COMPARISON HERE WITH CURCONTROL) inputCollection.add(curControl);
if (curControl.Count() > 0) findControlsWithAttributes(Control startingControl, Collection<Control> inputCollection);
}
}
Its been a little while since i've done this and I can't remember off the top of my head if Collection.Count is a method or property so make sure you check that first, but if you pass the page in then this will check against every server-visible control on your page and return a collection containing controls that match your comparison.
Lastly, Control.Attributes will return an AttributeCollection which you should be able to subsequently compare against.
Not sure what attributes you are after but if class attributes is what you are after look to #user751975 other wise you can do something like ...
page.Controls.Cast<System.Web.UI.WebControls.WebControl>().First().Attributes["class"]

How do I get all controls of a form in Windows Forms?

I have a Form named A.
A contains lots of different controls, including a main GroupBox. This GroupBox contains lots of tables and others GroupBoxes. I want to find a control which has e.g. tab index 9 in form A, but I don't know which GroupBox contains this control.
How can I do this?
With recursion...
public static IEnumerable<T> Descendants<T>( this Control control ) where T : class
{
foreach (Control child in control.Controls) {
T childOfT = child as T;
if (childOfT != null) {
yield return (T)childOfT;
}
if (child.HasChildren) {
foreach (T descendant in Descendants<T>(child)) {
yield return descendant;
}
}
}
}
You can use the above function like:
var checkBox = (from c in myForm.Descendants<CheckBox>()
where c.TabIndex == 9
select c).FirstOrDefault();
That will get the first CheckBox anywhere within the form that has a TabIndex of 9. You can obviously use whatever criteria you want.
If you aren't a fan of LINQ query syntax, the above could be re-written as:
var checkBox = myForm.Descendants<CheckBox>()
.FirstOrDefault(x=>x.TabIndex==9);
Recursively search through your form's Controls collection.
void FindAndSayHi(Control control)
{
foreach (Control c in control.Controls)
{
Find(c.Controls);
if (c.TabIndex == 9)
{
MessageBox.Show("Hi");
}
}
}
void iterateControls(Control ctrl)
{
foreach(Control c in ctrl.Controls)
{
iterateControls(c);
}
}
You can make a method like this:
public static Control GetControl(Control.ControlCollection controlCollection, Predicate<Control> match)
{
foreach (Control control in controlCollection)
{
if (match(control))
{
return control;
}
if (control.Controls.Count > 0)
{
Control result = GetControl(control.Controls, match);
if (result != null)
{
return result;
}
}
}
return null;
}
...that is used like this:
Control control = GetControl(this.Controls, ctl => ctl.TabIndex == 9);
Note however that TabIndex is a tricky case, since it starts at 0 within each container, so there may be several controls in the same form having the same TabIndex value.
Either way, the method above can be used for checking pretty much any property of the controls:
Control control = GetControl(this.Controls, ctl => ctl.Text == "Some text");
I hate recursion, so I always use a stack for this sort of thing. This assigns a common event handler to the CheckedChanged event of every RadioButton control in the current control hierarchy:
Stack<Control> controlStack = new Stack<Control>();
foreach (Control c in this.Controls)
{
controlStack.Push(c);
}
Control ctl;
while (controlStack.Count > 0 && (ctl = controlStack.Pop()) != null)
{
if (ctl is RadioButton)
{
(ctl as RadioButton).CheckedChanged += new EventHandler(rb_CheckedChanged);
}
foreach (Control child in ctl.Controls)
{
controlStack.Push(child);
}
}
You could easily retrofit Josh Einstein's extension method to work this way.

Categories