All control property change in loop - c#

I'm writing a code to apply transparent background to all of my LinkLabel controls. I don't wanna do it with hardcoded label id and repeating the same code.
foreach (Control cType in this.Controls)
{
if (cType.GetType() == typeof(LinkLabel))
{
LinkLabel linkTemp = cType as LinkLabel;
Point pos = this.PointToScreen(linkTemp.Location);
pos = pictureBox1.PointToClient(pos);
linkTemp.Parent = pictureBox1;
linkTemp.Location = pos;
linkTemp.BackColor = Color.Transparent;
}
}
When I'm trying the above piece of code it make odd numbered buttons to be transparent i.e.
Button 1 - transparent
Button 2 - NOT
Button 3 - transparent
But while I'm repeating the same code applied on different linklabel ids manually its working fine. What I'm missing here?

This is an inevitable side-effect of you changing the Parent property of the control. That modifies the Controls collection since the LinkLabel is moved from the form's Controls collection to the pictureBox1.Controls collection. Modifying a collection while iterating it normally generates an InvalidOperationException but this was not done for the Controls collection.
You need to solve this by iterating the collection backwards. Like this:
for (int ix = this.Controls.Count-1; ix >= 0; --ix) {
var linkTemp = this.Controls[ix] as LinkLabel;
if (linkTemp == null) continue;
// etc..
}
Some programmers like using Linq, that's okay too but obfuscates the issue a bit and isn't nearly as efficient. Key is to make a copy that cannot be affected by modifying the collection:
foreach (var linkTemp in this.Controls.OfType<LinkLabel>().ToList()) {
// etc..
}

You can use this 'odd' construction:
int i = 1;
foreach (LinkLabel item in this.Controls.OfType<LinkLabel>())
{
if ((i = 1 - i) == 0)
{
LinkLabel linkTemp = cType as LinkLabel;
Point pos = this.PointToScreen(linkTemp.Location);
pos = pictureBox1.PointToClient(pos);
linkTemp.Parent = pictureBox1;
linkTemp.Location = pos;
linkTemp.BackColor = Color.Transparent;
}
}
Some tips for code formatting
Change
if (cType.GetType() == typeof(LinkLabel))
to
if (cType is LinkLabel)
Change
foreach (Control cType in this.Controls)
{
if (cType.GetType() == typeof(LinkLabel))
{
LinkLabel linkTemp = cType as LinkLabel;
Point pos = this.PointToScreen(linkTemp.Location);
pos = pictureBox1.PointToClient(pos);
linkTemp.Parent = pictureBox1;
linkTemp.Location = pos;
linkTemp.BackColor = Color.Transparent;
}
}
to
foreach (LinkLabel linkTemp in this.Controls.OfType<LinkLabel>())
{
Point pos = this.PointToScreen(linkTemp.Location);
pos = pictureBox1.PointToClient(pos);
linkTemp.Parent = pictureBox1;
linkTemp.Location = pos;
linkTemp.BackColor = Color.Transparent;
}

Related

C# Win Forms issues deleting dynamically created controls

I have a function that reads info from a db and dynamically creates checkbox controls when a menu item is selected from a combobox.
When the user selects a different item from the combobox, I am attempting to delete any existing checkboxs already on the page. However, the delete function only deletes every other checkbox. Any idea what im doing wrong?
The form is super basic, just one form, no tabs, only this single combobox and the dynamically created checkboxes.
private void serverModels_SelectedIndexChanged(object sender, EventArgs e)
{
DeleteBoxes();
List<string> eqpIDs = new List<string>();
try
{
DataExtractor dataExtract = new DataExtractor(DataBase.TCDBServerName, DataBase.TCDBname, DataBase.TCDBUserID, DataBase.TCDBPassword);
eqpIDs = dataExtract.GetToolsByServerModel(Convert.ToString(serverModels.SelectedItem));
}
catch (Exception ex)
{
Logger.InsertSystemLog(e.ToString());
MessageBox.Show(ex.ToString());
return;
}
for(int i = 0; i < eqpIDs.Count; i++)
{
CheckBox box = new CheckBox();
box.Checked = true;
box.Tag = "TOOL";
box.Text = eqpIDs[i];
box.AutoSize = true;
box.Location = new Point(50 + 75 * (i / 17), (i % 17) * 25 + 120);
this.Controls.Add(box);
}
}
private void DeleteBoxes()
{
foreach (Control c in this.Controls)
{
if (c is CheckBox && c.Tag.ToString() == "TOOL" )
c.Dispose();
}
}
Dispose is removing the control from the Controls collection as well as disposing of the control.
As noted here,
When a Control is removed from the control collection, all subsequent controls are moved up one position in the collection.
That is why your logic is skipping every other CheckBox.
Try using a for loop in reverse like this:
for (int ii = Controls.Count - 1; ii >= 0; ii--)
{
if (Controls[ii] is CheckBox && Controls[ii].Tag.ToString() == "TOOL")
Controls[ii].Dispose();
}
This appears to be happening because you are modifying your control's Controls collection while iterating over it. Try something like this instead:
private void DeleteBoxes()
{
List<Control> toDispose = new List<Control>();
foreach (Control c in this.Controls)
{
if (c is CheckBox && c.Tag.ToString() == "TOOL" )
toDispose.Add(c);
}
foreach (Control c in toDispose)
{
c.Dispose();
}
}

Remove controls of flowlayoutpanel and recreating it in C#

I had been experimenting on writing a code to generate images inside a FlowLayoutPanel.
This is what i had done so far, when i click on a button for the first time (by using a checkboxes - read in number of images to open), it will generate the images, when i click on the button on second try, it will not update the flowlayoutpanel.
Even though i tried to remove the controls inside the FlowLayoutPanel, it still doesn't show the second try of the images.
This is the code snippet of the method:
FlowLayoutPanel fwPanel = null;
private void btnOpenFile_Click(object sender, EventArgs e)
{
//if there is content inside the flowpanel, dump it
if (fwPanel != null)
{
listOfFile.Clear();
}
//create a new FLP
fwPanel = new FlowLayoutPanel();
int panelWidth = width * 4 + 50;
int panelHeight = height * 4 + 50;
fwPanel.Size = new Size(panelWidth, panelHeight);
fwPanel.Location = new Point(0, 0);
this.Controls.Add(fwPanel);
//each checked item would be stored into an arraylist
foreach(object itemChecked in clbFile.CheckedItems)
{
listOfFile.Add((clbFile.Items.IndexOf(itemChecked)+1).ToString());
}
int noOfCheckedFile = listOfFile.Count;
PictureBox[] listOfPicture = new PictureBox[noOfCheckedFile];
int positionX = 0, positionY = 0;
int maxPaddingX = (width * MATRIX_SIZE) - 1;
int maxPaddingY = (height * MATRIX_SIZE) - 1;
//dynamically create images.
for (int i = 0; i < noOfCheckedFile; i++)
{
listOfPicture[i] = new PictureBox();
listOfPicture[i].Image = resizeImage((Image)show_picture(Convert.ToInt32(listOfFile[i])), new Size(width, height));
listOfPicture[i].Size = new Size(width, height);
if (positionX > maxPaddingX)
{
positionX = 0;
positionY += height;
}
if (positionY > maxPaddingY)
{
positionY = 0;
}
listOfPicture[i].Location = new Point(positionX,positionY);
listOfPicture[i].Visible = true;
fwPanel.Controls.Add(listOfPicture[i]);
positionX += width;
}
}
show_picture is a method that takes in and integer and returns a bitmap image.
listOfFile is to trace which file to return.
listOfPicture is to store each images.
i tried replacing this lines
//if there is content inside the flowpanel, dump it
if (fwPanel != null)
{
listOfFile.Clear();
}
i have added this line into it, when i do a second click, everything just gone missing, but this is not what i want, because it does not re-populating the FlowLayoutPanel.
if (fwPanel != null)
{
fwPanel.SuspendLayout();
if (fwPanel.Controls.Count > 0)
{
for (int i = (fwPanel.Controls.Count - 1); i >= 0; i--)
{
Control c = fwPanel.Controls[i];
c.Dispose();
}
GC.Collect();
}
fwPanel.ResumeLayout();
listOfFile.Clear();
}
I also tried this, but on second click, nothing will happen.
if (fwPanel != null)
{
List<Control> listControls = fwPanel.Controls.Cast<Control>().ToList();
foreach (Control control in listControls)
{
fwPanel.Controls.Remove(control);
control.Dispose();
}
listOfFile.Clear();
}
I wonder if i miss out anything, can someone enlighten me on what did i miss out ? Or guide me for the best practice of doing this.
as Suggested, i shifted the creation outside (credit to Sinatr for spotting it)
FlowLayoutPanel fwPanel = new FlowLayoutPanel();
private void createFLP()
{
int panelWidth = width * 4 + 50;
int panelHeight = height * 4 + 50;
fwPanel.Size = new Size(panelWidth, panelHeight);
fwPanel.Location = new Point(0, 0);
this.Controls.Add(fwPanel);
}
that solves the nothing happen part. Followed by using this to remove controls
if (fwPanel != null)
{
List<Control> listControls = fwPanel.Controls.Cast<Control>().ToList();
foreach (Control control in listControls)
{
fwPanel.Controls.Remove(control);
control.Dispose();
}
listOfFile.Clear();
}
and everything works like a charm, hope that this answer will be able to help others who are facing the same problem too.

For each control loop only affecting one Control

I will try to keep this as specific as possible.
This is my code:
foreach (Control c in this.Controls)
{
if (c.Height > 25)
{
c.BackColor = Color.Red;
pictureBox1.Location = new Point(x1, y1);
if (pictureBox1.Bounds.IntersectsWith(c.Bounds))
{
isCollide = true;
label1.Text = "true";
c.BackColor = Color.Green;
}
else
{
isCollide = false;
label1.Text = "false";
}
}
}
My goal is to loop through all of the controls on my form, and detect all of which have more than 25 pixels in height, which determines whether or not they are a wall in my game.
The "isCollide" variable which is being set to "true" simply makes it so that I cannot run through it.
The problem is that it only affects 1 picture box (wall). However, I have chosen to change the colour of all the selected controls to green as a check to see which controls are affected by the loop, and yet this affects all walls as it should..
My question is, why does the color affect all the controls that my code looped through, but the collision detection doesn't work on them all? Is it because the "isCollide" variable is set to "false" and "true" at the same time due to colliding with one wall and simultaneously NOT colliding with another?
How may I remedy this problem?
This is the code which uses "isCollide":
public void move(string Direction)
{
if (Direction == "Up")
{
//b = x / a = y
reset();
x1 = x;
y1 = y - 5;
isCollided();
if (isCollide == false)
{
y -= 5;
}
}
*Note: Not the end of the code, similar code has been applied to all directions (top, left, right, bottom)
The final value of isCollide will be based on the collision of the last control that is iterated through in the foreach loop. If you don't care which object the collision occurs with, I recommend breaking out of the loop once a collision is found.
Something like this.
foreach (Control c in this.Controls)
{
if (c.Height > 25)
{
c.BackColor = Color.Red;
pictureBox1.Location = new Point(x1, y1);
if (pictureBox1.Bounds.IntersectsWith(c.Bounds))
{
isCollide = true;
label1.Text = "true";
c.BackColor = Color.Green;
break;
}
else
{
isCollide = false;
label1.Text = "false";
}
}
}
Its because isCollide is just a local variable and not a property of your control like your c.BackColor is.
You need to define isCollide on your Control class to retain the state of isCollide for each control object in your Controls list:
public class Control
{
public bool isCollide {get; set;}
public string BackColor {get; set;}
public int Height {get; set;}
}
foreach (Control c in this.Controls)
{
if (c.Height > 25)
{
c.BackColor = Color.Red;
pictureBox1.Location = new Point(x1, y1);
if (pictureBox1.Bounds.IntersectsWith(c.Bounds))
{
c.isCollide = true;
label1.Text = "true";
c.BackColor = Color.Green;
}
else
{
c.isCollide = false;
label1.Text = "false";
}
}
}
A different reason could be that the controls on your form are nested into containers (e.g. GroupBox).
In this case you would have to recursively check the elements in their Control property as well.

How to change properties of a Control that is in List<UIControl> without using Loop?

I have the following code where a click event will dynamically create additional Canvas to the WrapPanel, and each Canvas contains a TextBox and a Button. Once the Button on one Canvas is click, TextBox.Text and Button.Content change from "Foo" to "Jesus".
The below code works, but it's not ideal. Because each property Change ("Foo" to "Jesus), I have to run a loop. I have to run two loops just to change the text on the TextBox and Button. Is there a direct way to change the Properties other then a Loop? My actually application contains 30+ controls in a Canvas, I don't want to run 30+ loops each time just to change some text.
List<Canvas> cvList = new List<Canvas>();
List<TextBox> tbList = new List<TextBox>();
List<Button> FooList = new List<Button>();
WrapPanel wp = new WrapPanel();
private void createbtn1_Click(object sender, RoutedEventArgs e)
{
Canvas cv = new Canvas();
StackPanel sp = new StackPanel();
TextBox tb = new TextBox();
Button Foo = new Button();
sp.Orientation = Orientation.Vertical;
sp.Children.Add(tb);
sp.Children.Add(Foo);
cv.Children.Add(sp);
wp.Children.Add(cv);
cvList.Add(cv);
tbList.Add(tb);
FooList.Add(Foo);
cv.Width = 100;
cv.Height = 100;
tb.Text = "#" + (cvList.IndexOf(cv)+1);
tb.Width = 50;
tb.Height = 30;
Foo.Content = "Foo";
Foo.Click += destroy_Click;
}
private void Foo_Click(object sender, RoutedEventArgs e)
{
Button b = sender as Button;
var bIndex = FooList.IndexOf(b);
foreach (TextBox t in tbList)
{
if (tbList.IndexOf(t) == bIndex)
{
t.Text = "Jesus";
}
}
foreach (Button f in FooList)
{
if (FooList.IndexOf(t) == bIndex)
{
t.Content = "Jesus";
}
}
}
Just access the text boxes by index and set the content of the button directly:
if(bIndex < tbList.Count && bIndex != -1)
tbList[bIndex].Text = "Jesus";
if(b != null && bIndex != -1)
b.Content = "Jesus";
why can't you just get the item at the index and set that items text:
tbList[bindex].Text="Jesus";
As for setting the buttons content, you already have the button from the click event, so just use that:
b.Content = "Jesus";
You current code just loops through each item in the list and gets the index of the item and sees if it is the index you want. Accessing by the indexer of the list directly will give you what you want.
You will probably want to do some error checking, but that is not currently done in your existing code either.
Some info on using indexers from MSDN

Removing Elements Programmatically in Silverlight

I am starting to learn silverlight and to practice I am doing a simple space invaders type videogame.
My issue is I am creating custom controls (bullets) programmatically like so:
if(shooting)
{
if(currBulletRate == bulletRate)
{
Bullet aBullet = new Bullet();
aBullet.X = mouse.X - 5;
aBullet.Y = mouse.Y - Ship.Height;
aBullet.Width = 10;
aBullet.Height = 40;
aBullet.Tag = "Bullet";
LayoutRoot.Children.Add(aBullet);
currBulletRate = 0;
}
else
currBulletRate++;
}
However I am having trouble removing them once they go off bounds (leave the LayoutRoot).
I tried looping throught the LayoutRoot.Children and removing but I can't seem to get it right.
UIElement[] tmp = new UIElement[LayoutRoot.Children.Count];
LayoutRoot.Children.CopyTo(tmp, 0);
foreach (UIElement aElement in tmp)
{
Shape aShape = aElement as Shape;
if (aShape != null && aShape.Tag != null)
{
if (aShape.Tag.ToString().Contains("Bullet"))
{
if (Canvas.GetTop(aShape) + aShape.ActualHeight < 0) // This checks if it leaves the top
{
LayoutRoot.Children.Remove(aElement);
}
else if(Canvas.GetTop(aShape) > Canvas.ActualHeight) // This condition checks if it leaves the bottom
{
LayoutRoot.Children.Remove(aElement);
}
}
}
}
The code you pasted was only checking if the bullet left the top of the canvas.

Categories