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();
}
}
Related
I am facing issue in tabcontrol of Windows application.
We have a tabcontrol on 1 windows form on which first tab is by default and other tabs we are adding dynamically at runtime.
In first case while we opening the form, we able to see all the controls in both the tabs i.e. (Default and other tabs)
Although, when we try opening the form for second time, data is being getting added into the form if we debug but while showing, it shows blank in other tabs which are getting added dynamically.
In CustomgroupControl class, i am adding controls dynamically like textbox, dropdown, grid and etc. Although this is working in first case, but not showing controls in second case. (Flow of code is same for both the cases)
Below is attached screenshot in link and shown sample source code
Screenshot for above query
public void renderTabpage(Dictionary<string, List<Field>> subHeaderMap, TabPage currentTabPage, AsynchTabRenderer asynchTabRenderer, DoWorkEventArgs e)
{
currentTabPage.SuspendLayout();
TableLayoutPanel headerTableLayout = createNewheaferTableLayoutPanel(currentTabPage.Name + TABLE_LAYOUT_NAME_SUFFIX);
currentTabPage.Controls.Add(headerTableLayout);
headerTableLayout.ColumnCount = 1;
headerTableLayout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
headerTableLayout.RowCount = 0;
headerTableLayout.SuspendLayout();
foreach (string subHeaderName in subHeaderMap.Keys)
{
if (asynchTabRenderer != null && asynchTabRenderer.CancellationPending == true)
{
e.Cancel = true;
return;
}
else
{
CustomGroupControl customGroupControl = createAndPopulateSubheaderGroupControl(subHeaderMap[subHeaderName], subHeaderName);
headerTableLayout.RowCount = headerTableLayout.RowCount + 1;
headerTableLayout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
headerTableLayout.Controls.Add(customGroupControl, 0, ++headerTableLayout.RowCount);
}
}
headerTableLayout.ResumeLayout(false);
headerTableLayout.PerformLayout();
currentTabPage.ResumeLayout(false);
currentTabPage.PerformLayout();
}
private void detailsTabControl_TabIndexChanged(object sender, EventArgs e)
{
tabIndexChangedEvent(((TabControl)sender).SelectedTab);
}
private void tabIndexChangedEvent(TabPage tabPage)
{
try
{
//WordApp.ScreenUpdating = false;
//WordApp.System.Cursor = Word.WdCursorType.wdCursorWait;
if (tabPage.Name == TabName.Replace(" ", "_").ToUpper())
{
FileUtilService service = FileUtilFactory.getInstance();
// service.clearIENetCache();
showForm(Window, forms);
}
//TabPage tabPage = ((TabControl)sender).SelectedTab;
if ((tabPage.Tag == null || true == (Boolean)tabPage.Tag) && !string.Equals(tabPage.Name, FieldConstants.TAB, StringComparison.CurrentCultureIgnoreCase))
{
var controls = WindowsFormControlUtils.GetControlHierarchy(tabPage);
foreach (Control control in controls)
{
if (control is CustomGroupControl)
{
CustomGroupControl customGroupControl = (CustomGroupControl)control;
customGroupControl.fieldRenderDelegater();
}
else
continue;
}
tabPage.Tag = false;
}
}
catch (Exception ex)
{
logger.Error(ex.Message);
}
}
internal TableLayoutPanel createNewheaferTableLayoutPanel(string headerName)
{
TableLayoutPanel subHeaderTableLayout = new TableLayoutPanel();
// resources.ApplyResources(subHeaderTableLayout, headerName.Replace(" ", "_").ToUpper());
subHeaderTableLayout.Name = headerName.Replace(" ", "_").ToUpper() + TABLE_LAYOUT_NAME_SUFFIX;
subHeaderTableLayout.Dock = DockStyle.Fill;
subHeaderTableLayout.AutoSize = true;
subHeaderTableLayout.AutoScroll = true;
subHeaderTableLayout.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly;
subHeaderTableLayout.GrowStyle = TableLayoutPanelGrowStyle.AddRows;
return subHeaderTableLayout;
}
#Jimi & #Lucky
It works. Got the correct direction. Thanks.
Hint - Don't create controls on worker thread
Tried solution and worked - Removed creation of controls from parameterized constructor and added on form_load event.
in c# form application, when i click the button i create a listBox.
and I add Item to the ListBox from a TextBox
When I click the button, I want the listBox to be created if it does not exist.
Therefore when assigning the ListBox creation code to an if block, the code to assign the data in the textBox to the listBox fails.
how can i fix this?
if (araclar_eklendi == false)
{
ListBox listB_X = new ListBox();
listB_X.******** = new Point(380, 45);
this.Controls.Add(listB_X);
araclar_eklendi=true;
}
listB_X.Items.Add(txtBox_X.text);
You can use foreach statement to traverse the form's Controls to check if a ListBox exists. And define a boolean to store the result.
Here is a demo you can refer to.
// bool to check if a listbox exists
bool flag = false;
private void button1_Click(object sender, EventArgs e)
{
Control control = new Control();
// traverse the form
foreach (Control c in this.Controls)
{
if (c is ListBox)
{
control = c;
flag = true;
break;
}
}
if (flag) // if true, access the listbox and add new item from tb
{
((ListBox)control).Items.Add(textBox1.Text);
}
else // if false, create a new listbox
{
ListBox listBox = new ListBox();
listBox.Location = new Point(380, 45);
this.Controls.Add(listBox);
listBox.Items.Add(textBox1.Text);
}
}
My problem is that when I compare two, absolutely the same tags, they turn out to be different instead.
The description of a code: I create 3 controls (button, label and a textbox) and assign the same tag (let's say, 0) to them all. When I press this newly created button, I want to delete all of the 3 controls with the same tag.
Code for adding the buttons (simplified):
int Count = 0; // This var changes, but for the example it is 0
Button newButton = new Button();
newButton.Tag = Count;
newButton.Click += new EventHandler(DeleteName);
Controls.Add(newButton);
Label newLabel = new Label();
newLabel.Tag = Count;
Controls.Add(newLabel);
And the same for a TextBox.
The coding behind deleting:
private void DeleteName(object sender, EventArgs e)
{
List<Control> toDelete = new List<Control>();
Button btn = sender as Button;
foreach (Control c in Controls)
{
if (c.Tag == btn.Tag)
toDelete.Add(c);
}
int tmp = toDelete.Count;
for (int i = 0; i < tmp; i++)
{
Controls.Remove(toDelete[i]);
}
}
It used to work perfectly when I did the same logic before, but now it just deletes the button and no other control (the textbox and label stay untouched). When I replace "if (c.Tag == btn.Tag)" with, for example, "if (c is TextBox)", it adds all the TextBoxes to the list and deletes them, so I believe the problem is in this comparison of Tags.
The likely reason you're running into this is that Tag is an object, so the equality comparison will do a reference comparison, but you're setting it to a value type. And even if you were setting it to a reference type, the comparison would only return true if they were pointing to the exact same object.
One way to solve this would be to cast the Tag to the appropriate type, or, more commonly, use the ToString() method for comparison. We should also add null conditional operators in the check, in case a Tag is null, to avoid a NullReferenceException:
if (c.Tag?.ToString() == btn.Tag?.ToString())
Another thing you might consider doing is creating a separate method to delete all controls with a particular tag, and another one that recursively finds all controls with that tag. This will help out if you are in a situation where you have some controls with the "delete" tag that are children of a different container.
For example:
// Returns all controls under the parent with the specified tag
private List<Control> GetTaggedControls(string tag, Control parent)
{
var taggedControls = new List<Control>();
foreach (Control control in parent.Controls)
{
if (control.Tag?.ToString() == tag)
{
taggedControls.Add(control);
}
// Recursively call this method in case this is a container
taggedControls.AddRange(GetTaggedControls(tag, control));
}
return taggedControls;
}
// Deletes all controls with the specified tag
private void DeleteControlsWithTag(string tag)
{
foreach (Control control in GetTaggedControls(tag, this))
{
Controls.Remove(control);
}
}
For example, if you add the code below to a new WinForm project, it will create 10 labels with every other one containing a Tag with "deleteMe". It also creates a button that, when clicked, calls the DeleteControlsWithTag method above:
private void Form1_Load(object sender, EventArgs e)
{
var padSpace = 5;
var height = 15;
// Add 10 labels, with a common tag on every other one
for (int i = 0; i < 10; i++)
{
Controls.Add(new Label
{
Text = $"Label {i}",
Top = padSpace * (i + 1) + height * i,
Height = height,
Visible = true,
Tag = i % 2 == 0 ? "deleteMe" : "saveMe"
});
}
// Add a button that will delete controls with deleteMe tag
var btn = new Button
{
Text = "Delete Even Labels",
Height = 20,
Width = 150,
Top = padSpace * 11 + height * 10,
Visible = true
};
btn.Click += (o, ea) => { DeleteControlsWithTag("deleteMe"); };
Controls.Add(btn);
}
I am dynamically generating Checkboxes on button click which I am adding to a panel in TableLayoutPanel.
Panel panel1=new panel();
CheckBox box = new CheckBox();
box.Name = "cb_" + count;
box.AutoSize = true;
panel1.Controls.Add(box);
tableLayoutPanel1.Controls.Add(panel1);
count++;
I need to check if these checkboxes are checked on Save button click. But when i try to retrieve the Checkbox it returns null. (But if I add the checkbox directly to the form instead of TablelayoutPanel I am able to retrieve it.)
for (int i = 0; i >= count; i++)
{
CheckBox cb = this.Controls["cb_" + i] as CheckBox;// Returns Null
if (cb.Checked)
{
//Add code
}
}
How can I get the checbox state?
You are looking in the form controls and not in the tableLayoutPanel1
change the code like this
for (int i = 0; i >= count; i++)
{
CheckBox cb = tableLayoutPanel1.Controls["cb_" + i] as CheckBox;
if (cb.Checked)
{
//Add code
}
}
Change your loop to this:
foreach(Control c in panel1.Controls)
{
CheckBox cb = c as CheckBox;
if (cb!=null)
{
if (cb.Checked)
{
//Add code
}
}
}
I'd like to do this:
CheckBox[] boxarray = new CheckBox[4];
boxarray[0] = checkBox1;
boxarray[1] = checkBox2;
boxarray[2] = checkBox3;
boxarray[3] = checkBox4;
using a for loop - because I have about 600 checkboxes. I thought about this:
for (int i = 0 ; i<= 600; i++)
{
boxes[i] = checkBox[i] ;
}
But whatever I tried - it didn't work. I hope you know what I mean.
Thanks for your help!
If you have these CheckBoxes on your form, then you can add them to an array (or List, which would be much easier to do) like that:
List<CheckBox> checkBoxesList = new List<CheckBox>();
foreach (Control ctrl in FormName.Controls)
{
if (ctrl.GetType() == typeof(CheckBox))
{
checkBoxesList.Add(ctrl as CheckBox);
}
}
Remember, simply writing:
checkBox1, checkBox2
won't make it possible to get them like checkBox[0] - it's not an array, it's just a name with '1' or '2' in the end.
Using the current setup, you'll have to use FindControl (assuming ASP.net, winforms and WPF work differently):
for(int i = 0; i <= 600; i++)
{
boxes[i] = Page.FindControl("checkbox" + i);
}
Update for windows forms: There is actually a method you can use to find all controls of a specific type:
How to get ALL child controls of a Windows Forms form of a specific type (Button/Textbox)?
Here's another option for you. I tested it by creating a sample
application, I then put a GroupBox and a GroupBox inside the initial
GroupBox. Inside the nested GroupBox I put 3 TextBox controls and a
button. This is the code I used (even includes the recursion you were
looking for)
public IEnumerable<Control> GetAll(Control control,Type type) {
var controls = control.Controls.Cast<Control>();
return controls.SelectMany(ctrl => GetAll(ctrl,type))
.Concat(controls)
.Where(c => c.GetType() == type);
}
To test it in the form load event I wanted a count of all controls inside
the initial GroupBox
private void Form1_Load(object sender, EventArgs e) {
var c = GetAll(this,typeof(TextBox));
MessageBox.Show("Total Controls: " + c.Count());
}
And it returned the proper count each time, so I think this will work perfectly for
what you're looking for :)
If you have other checkboxes on your form that you don't want to add, you'll have to use the following method:
for(int i = 0; i <= 600; i++)
{
boxes[i] = this.Controls.Find("checkbox" + i, true).FirstOrDefault() as CheckBox;
}
you are not able to specifiy the name of the checkboxes in the code like you have done, because i wont get changed.
you have to create new instances of CheckBox like:
for (int i = 0 ; i<= 600; i++)
{
//This will create a new instance of a CheckBox
CheckBox cb = new CheckBox()
//At this point the checkbox is empty and not visible anywhere
//So you have to change some properties:
cb.Text = "Check me!";
cb.Top = Ycoord; //you should change this depending on i like +(i*30)
cb.Left = Xcoord;
cb.name = "checkbox" + i.toString();
//To recognize if one of your Checkboxes is checked/unchecked you need to add
//an event like this (for the event see down below)
cb.CheckedChanged += checkedChangedEvent;
//then you need to add the checkbox to your Array
boxes[i] = cb;
//to make those checkboxes visible you need to add them to your form
//or Panel:
myForm.Controls.Add(cb);
}
The Event:
public void checkedChangedEvent(Object sender, EventArgs e)
{
MessageBox.Show(((CheckBox)sender).name + " is now Checked == " + ((CheckBox)sender).Checked.toString();
}
you have to specify which Checkbox was Checked/Unchecked by its name, because every CheckBox is firing the same event.
EDIT2:
to get the number of the checkbox you can do something like this in the event:
int cbNumber = 0;
try
{
cbNumber = System.Convert.toInt32(((CheckBox)sender).name.Replace("checkbox",""));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + "\n\n" + ex.StackTrace);
}