Delete dynamically created controls - c#

I keep getting stuck on this part of my program.
whenver i call an listbox.selectitemchange event, i want the proper amount of trackbar and labels to be displayed.
now, it does not work properly.
Some of them get removed when the event is called, some of them don't.
foreach (Label label in Controls.OfType<Label>())
{
if (label.Tag != null && label.Tag.ToString() == "dispose")
{
label.Dispose();
}
}
foreach (TrackBar trackBar in Controls.OfType<TrackBar>())
{
if (trackBar.Tag != null && trackBar.Tag.ToString() == "dispose")
{
trackBar.Dispose();
}
}
for (int i = 0; i < calc; i++)
{
//string[] LineWidthSplitted = lines[lineWidth].Split(' ');
//Int32.TryParse(LineWidthSplitted[2], out WidthValue);
Label Label = new Label();
Label.Name = "TrackbarWidth" + LabelName++;
Label.Tag = "dispose";
Label.Text = "Board -" + LabelName + "- Height:";
Label.Location = new Point(10, 450 + (50 * LabelName));
Label.Size = new System.Drawing.Size(100, 25);
this.Controls.Add(Label);
TrackBar trackBar = new TrackBar();
trackBar.Name = "TrackbarWidth" + trackbarName++;
trackBar.Tag = "dispose";
trackBar.Maximum = 85;
trackBar.Minimum = 65;
trackBar.SmallChange = 5;
trackBar.TickFrequency = 5;
trackBar.Value = 65;
trackBar.Location = new Point(150, 450 + (50 * trackbarName));
trackBar.Size = new System.Drawing.Size(100, 25);
this.Controls.Add(trackBar);
lineWidth += 4;
}
while, when i remove the foreach for the trackbar, all labels get properly displayed.
they all get deleted, en recreated for the pricese amount needed to be created, no exceptions.
Any reason why?
thank you.

Don't use "Dispose" on the labels right away. First remove them. Note that you can't modify the Controls collection inside the foreach so you have to do something like this:
List<Label> itemsToRemove = new List<Label>();
foreach (Label label in Controls.OfType<Label>())
{
if (label.Tag != null && label.Tag.ToString() == "dispose")
{
itemsToRemove.Add(label);
}
}
foreach (Label label in itemsToRemove)
{
Controls.Remove(label);
label.Dispose();
}
If you want to remove all different kinds of controls in one swoop:
List<Control> itemsToRemove = new List<Control>();
foreach (Control ctrl in Controls)
{
if (ctrl.Tag != null && ctrl.Tag.ToString() == "dispose")
{
itemsToRemove.Add(ctrl);
}
}
foreach (Control ctrl in itemsToRemove)
{
Controls.Remove(ctrl);
ctrl.Dispose();
}

I can't test this now, but I think you should also remove the controls from the Form Controls collection where you have added them. By the way, in your case I think you could avoid the OfType extension and use the old fashioned for..loop that will allow to execute just one loop....
for(int x = this.Controls.Count - 1; x >= 0; x--))
{
Control ctr = this.Controls[x];
if (ctr Is Label && ctr.Tag != null && ctr.Tag.ToString() == "dispose")
{
this.Controls.Remove(ctr);
ctr.Dispose();
}
if(ctr Is TrackBar && ctr.Tag != null && ctr.Tag.ToString() == "dispose")
{
this.Controls.Remove(ctr);
ctr.Dispose();
}
}
Notice how removing elements from a collection with a for..loop should be done in reverse order, from the end to start of the collection

Related

TableLayoutPanel update lag

I have a WinForms app which is updating a TableLayoutPanel from a BackgroundWorker. Depending whether a device input is On (1) or off (0) the colour and some text is changed accordingly.
try
{
//Define a new TLP to hold the search tablelayoutpanel.
//search for the iterated card number.
//get the status label using GetControl from Position.
TableLayoutPanel TLP = new TableLayoutPanel();
string IO_Card_Name = "ED527_" + i.ToString();
TLP = TLP_IO_Info.Controls.Find(IO_Card_Name, true).FirstOrDefault() as TableLayoutPanel;
try { lbl = TLP.GetControlFromPosition(4, 0) as Label; } catch { lbl = null; };
//if card is found (because input is active, colour the TLP (card) according to its state.
if (TLP != null && lbl != null && INPUTS[i - 1] == 1)
{
TLP.BackColor = Color.Green;
foreach (Label l in TLP.Controls)
{
l.BackColor = Color.Green;
}
lbl.Invoke((MethodInvoker)delegate { lbl.Text = "ON"; });
}
else if (TLP != null && lbl != null && INPUTS[i - 1] == 0)
{
TLP.BackColor = Color.White;
foreach (Label l in TLP.Controls)
{
l.BackColor = Color.White;
}
lbl.Invoke((MethodInvoker)delegate { lbl.Text = "OFF"; });
}
}
catch (Exception exception)
{
MessageBox.Show(exception.Message);
};
TLP holds 5 labels in it. The update shows some noticeable lag when the line updates. Is there a way I can carry out something akin to SuspendLayout() / ResumeLayout on the main UI thread?
****EDIT to show the before and after - the IOLabel column updates slightly before the Status column.
Sounds you have a nested design. Each row is 5 Labels are hosted by different TableLayoutPanels and the TLP_IO_Info which is a TableLayoutPanel hosts the other TableLayoutPanels. In the DoWork event of the BackgroundWorker you have a for..loop to change the Backcolor of the inner controls according to the current state of the devices which you read it from the INPUT int array. Please correct me.
I'd like to suggest this:
foreach (var tlp in TLP_IO_Info.Controls.OfType<TableLayoutPanel>()
.Where(x => x.Name.StartsWith("ED527_")))
{
if (tlp.GetControlFromPosition(4, 0) is Label lbl)
{
var state = // get the state of the current device from INPUT array
var stateColor = state == 1 ? Color.Green : Color.White;
var stateText = state == 1 ? "ON" : "OFF";
this.Invoke(new Action(() =>
{
tlp.BackColor = stateColor;
tlp.Controls.OfType<Label>().ToList().ForEach(l => l.BackColor = stateColor);
lbl.Text = stateText;
}));
}
}
Or this to eliminate the redundant code:
var stateColors = new[] { Color.White, Color.Green };
var stateTexts = new[] { "OFF", "ON" };
foreach (var tlp in TLP_IO_Info.Controls.OfType<TableLayoutPanel>()
.Where(x => x.Name.StartsWith("ED527_")))
{
if (tlp.GetControlFromPosition(4, 0) is Label lbl)
{
var state = // get the state of the current device from INPUT array
this.Invoke(new Action(() =>
{
tlp.BackColor = stateColors[state];
tlp.Controls.OfType<Label>().ToList()
.ForEach(l => l.BackColor = stateColors[state]);
lbl.Text = stateTexts[state];
}));
}
}
Note that, I've removed the expensive try..catch blocks since this code won't throw any exceptions.
As for the INPUT array, I suggest that you replace it with a Dictionary<string, int> to store the current state of each device since (according to the link you've provided) each device has a unique IOLineNumber so you can easily set/get the current state of each one.
⍰ Maybe there is already something like this in the library?

C# Checkbox-Dialog How to uncheck

I have a Windows Forms Dialog in C# which shows Checkboxes for each Element in a Dictionary. The Dialog returns a List with all selected Elements(Checkboxes). However I noticed that if I select a Checkbox and then uncheck it again, the Element is still in the returned List of Selected Elements.
How can I fix this?
My Dialog looks like this:
public SelectDialog(Dictionary<string, string> Result)
{
int left = 45;
int idx = 0;
InitializeComponent();
for (int i = 0; i < Result.Count; i++)
{
CheckBox rdb = new CheckBox();
rdb.Text = Result.Values.ElementAt(i).Equals("") ? Result.Keys.ElementAt(i) : Result.Values.ElementAt(i);
rdb.Size = new Size(100, 30);
this.Controls.Add(rdb);
rdb.Location = new Point(left, 70 + 35 * idx++);
if (idx == 3)
{
idx = 0; //Reihe zurücksetzen
left += rdb.Width + 5; // nächste Spalte
}
rdb.CheckedChanged += (s, ee) =>
{
var r = s as CheckBox;
if (r.Checked)
this.selectedString.Add(r.Text);
};
}
}
//Some more Code
}
As per the comment:
You need to remove the items from the list if the raised event is unchecked, I think you have to check for already added items to avoid duplicates, and remove the items if exists. so the handler would be like this:
rdb.CheckedChanged += (s, ee) =>
{
var r = s as CheckBox;
var itemIndex = this.selectedString.IndexOf(r.Text)
if (r.Checked && itemIndex == -1)
this.selectedString.Add(r.Text);
else if(!r.Checked && itemIndex != -1)
{
this.selectedString.RemoveAt(itemIndex);
}
};

How to use variable names that are created in a loop and stored in List<string>?

I have a List object that gets automatically filled with the names of textboxes. Now I'd like to cycle through all these text boxes.
How can I let C# know that the string-names in the List are actually TextBox objects with that string-name?
List<string> txtOppsNames = new List<string>();
for (int i = 1; i < numOpps; i++)
{
txtOppsNames.Add("txtOpp" + i);
}
foreach (var txtName in txtOppsNames)
{
if (txtName.Text != "")
{
// do stuff
}
}
The current code reads txtName as a string. I would like it to read as a TextBox.
Edit - the below code contains the solution for me.
List<string> txtOppsNames = new List<string>();
for (int i = 1; i < numOpps; i++)
{
txtOppsNames.Add("txtOpp" + i);
}
foreach (var txtName in txtOppsNames)
{
TextBox textBox = this.Controls.Find(txtName, true).FirstOrDefault() as TextBox;
if (textBox.Text != "")
{
MessageBox.Show("Thanks Amir Popovich");
}
}
Try this :
List<string> txtOppsNames = new List<string>();
for (int i = 1; i < numOpps; i++)
{
txtOppsNames.Add("txtOpp" + i);
}
foreach (var txtName in txtOppsNames)
{
var cntrl= FindControl(txtName);
if (cntrl!=null && cntrl is TextBox)
// do something with
((TextBox)cntrl)
}
Use Control.ControlCollection.Find:
string textBoxName = "txtOpp1";
TextBox textBox = this.Controls.Find(textBoxName, true).FirstOrDefault() as TextBox;
In your case:
List<string> txtOppsNames = new List<string>();
for (int i = 1; i < numOpps; i++)
{
txtOppsNames.Add("txtOpp" + i);
}
foreach (var txtName in txtOppsNames)
{
var control = this.Controls.Find(txtName, true).FirstOrDefault();
if(control != null && control is TextBox)
{
TextBox textBox = control as TextBox;
if(textBox.Text != string.Empty)
{
//logic
}
}
}
Assuming it is Winform, There seems to be no pretty way to do it.
But you could use Loop through Textboxes.
once you have the text box collection, check the names with your string.

add listboxes to dynamically created tabpages

I am trying to programmatically add listboxes to dynamically created tabpages within tabcontrols. I don't understand where is the problem as I it doesn't give me any error when compiling. I used breakpoints on the if statementif (c is TabPage)and it shows correctly the tab pages which are created.
public void createControls()
{
TabControl tabcontrol = new TabControl();
panel1.Controls.Add(tabcontrol);
tabcontrol.Dock = DockStyle.Fill;
int n = 1;
do
{
tabcontrol.Controls.Add(new TabPage() { Name = "Property #" + n + "", Text = "Property #" + n + "" });
n++;
} while (n == pnum);
foreach (Control c in panel1.Controls)
{
if (c is TabPage)
{
ListBox list = new ListBox();
list.Items.AddRange(new object[] {
"Id",
"Name",
"Entity"});
list.Location = new System.Drawing.Point(20, 38);
list.Name = "listBox1";
list.ScrollAlwaysVisible = true;
list.SelectionMode = System.Windows.Forms.SelectionMode.MultiSimple;
list.Size = new System.Drawing.Size(134, 147);
c.Controls.Add(list);
}
}
}
You need to make sure that you access the controls of the immediate container.
foreach (Control c in panel1.Controls)
Should be
foreach (Control c in tabcontrol.Controls)

How do I find and use a programatically-created control?

I've created a number of buttons on a form based on database entries, and they work just fine. Here's the code for creating them. As you can see I've given them a tag:
for (int i = 0; i <= count && i < 3; i++)
{
btnAdd.Text = dataTable.Rows[i]["deviceDescription"].ToString();
btnAdd.Location = new Point(x, y);
btnAdd.Tag = i;
this.Controls.Add(btnAdd);
}
I use these buttons for visualising a polling system. For example, I want the button to be green when everything is fine, and red when something is wrong.
So the problem I'm running into is referencing the buttons later so that I can change their properties. I've tried stuff like the following:
this.Invoke((MethodInvoker)delegate
{
// txtOutput1.Text = (result[4] == 0x00 ? "HIGH" : "LOW"); // runs on UI thread
Button foundButton = (Button)Controls.Find(buttonNumber.ToString(), true)[0];
if (result[4] == 0x00)
{
foundButton.BackColor = Color.Green;
}
else
{
foundButton.BackColor = Color.Red;
}
});
But to no avail... I've tried changing around the syntax of Controls.Find() but still have had no luck. Has anyone encountered this problem before or know what to do?
If you name your buttons when you create them then you can find them from the this.controls(...
like this
for (int i = 0; i <= count && i < 3; i++)
{
Button btnAdd = new Button();
btnAdd.Name="btn"+i;
btnAdd.Text = dataTable.Rows[i]["deviceDescription"].ToString();
btnAdd.Location = new Point(x, y);
btnAdd.Tag = i;
this.Controls.Add(btnAdd);
}
then you can find it like this
this.Controls["btn1"].Text="New Text";
or
for (int i = 0; i <= count && i < 3; i++)
{
//**EDIT** I added some exception catching here
if (this.Controls.ContainsKey("btn"+buttonNumber))
MessageBox.Show("btn"+buttonNumber + " Does not exist");
else
this.Controls["btn"+i].Text="I am Button "+i;
}
Put these buttons in a collection and also set the name of the Control rather than using its tag.
var myButtons = new List<Button>();
var btnAdd = new Button();
btnAdd.Text = dataTable.Rows[i]["deviceDescription"].ToString();
btnAdd.Location = new Point(x, y);
btnAdd.Name = i;
myButtons.Add(btnAdd);
To find the button use it.
Button foundButton = myButtons.Where(s => s.Name == buttonNumber.ToString());
Or Simply
Button foundButton = myButtons[buttonNumber];
In your case I would use a simple Dictionary to store and retrieve the buttons.
declaration:
IDictionary<int, Button> kpiButtons = new Dictionary<int, Button>();
usage:
Button btnFound = kpiButtons[i];
#Asif is right, but if you really want to utilize tag you can use next
var button = (from c in Controls.OfType<Button>()
where (c.Tag is int) && (int)c.Tag == buttonNumber
select c).FirstOrDefault();
I'd rather create small helper class with number, button reference and logic and keep collection of it on the form.

Categories

Resources