With some of the knowledge I gained from yesterday and the day before, I have a general idea of what I want to do. I do have a general question about RichTextBoxes. I have a GUI already made of multiple RichTextBoxes aligned in different rows of one another. One row of boxes monitors the load of a product, the other monitors if one of the channels have been tripped and the other two rows displays the current and voltage of the product. The GUI has been premade and each row of product has been named StatusOutxxx, TripStatOutxxx, VoltageOutxxx and CurrentOutxxx (the xxx indicates a number of each RichTextBox, like CurrentOut001, for example).
The example gui is listed below:
From the image, you can see that I have 4 rows of RichTextBoxes. The monitoring backend of the product has already been established and I am trying to take the data and turn the product into a user friendly display to let the person now, if the load on a certain channel is okay or if the voltage of a product is too high/low or if a certain channel has been tripped.
Now, I know how to turn them all on at once via:
foreach(var rtb in this.Controls.OfType<RichTextBox>())
rtb.BackgroundColor = Color.Green;
and I know how to control which ones to turn on via if(rtb.Name.StartsWith("Insert Prefix Here")). But I am asking a bit of a harder question in terms of searching and manipulating an individual RichTextBox.
For example, if there is a load issue on Channel 17 of my product that drops my voltage, I want to be able to search for the StatusOut017, change the backcolor of that to Red and change the value of VoltageOut017 to the voltage display.
I know that the long way of doing it is doing a giant if statement for each load, trip and voltage channel to change it, but I am aiming for reducing the amount of lines of code and attempting to doing a for loop so my code will look something like this (in psuedocode):
for (i = 1; i < 25; i++)
{
if (StatusOut[i] == true)
{
StatusOut[i].BackColor == Color.Green;
}
else
{
StatusOut[i].BackColor == Color.Red;
}
if (TripStatOut[i] == true)
{
TripStatOut[i].BackColor == Color.Green;
}
else
{
TripStatOut[i].BackColor == Color.Red;
}
VoltageOut[i].Text = VoltageReading;
CurrentOut[i].Text = CurrentReading;
}
I am hoping for some help on this one.
I assume your naming convention is Name then 3 digits for the RichTextBox controls. You can use the controls collection to match the corresponding controls for that row. As noted a DataGrid would be better.
private void ChangeStatus()
{
//loop through the RichtextBoxes
foreach (RichTextBox rtb in this.Controls.OfType<RichTextBox>())
{
Color c = Color.Green; //default to good value
//only handle the status Out Control
if (rtb.Name.StartsWith("StatusOut"))
{ ///check the value and set the color if false
if (rtb.Text == "false")
c = Color.Red;
//update the controls
rtb.BackColor = c;
//string controlNum = rtb.Name.Replace("StatusOut", ""); //If it is not 3 just use replace since we know we are on the StatusOut control
string controlNum = rtb.Name.Substring(rtb.Name.Length - 3);
((RichTextBox)Controls["TripStateOut" + controlNum]).BackColor = c;
((RichTextBox)Controls["VoltageOut" + controlNum]).Text = VoltageReading;
((RichTextBox)Controls["CurrentOut" + controlNum]).Text = CurrentReading;
}
}
}
Related
I have a DataGridView with a Contact column full of numbers. Each number can reoccur multiple times, or just once.
I am trying to find a solution to re-colour each set of rows that contain matching numbers, but not recolour the ones with no duplicates.
Example of the outcome I am trying to achieve:
<p style="color:green;">2</p>
<p style="color:green;">2</p>
<p style="color:red;">3</p>
<p style="color:red;">3</p>
123 <br><br>
321 <br>
<p style="color:yellow;">4</p>
<p style="color:yellow;">4</p>
<p style="color:red;">3</p>
<p style="color:yellow;">4</p>
Here is my code, I think the problem is where I am incrementing the "duplicateCount" integer for the switch statement as I need it to increment only after every row containing the same value is coloured, but could not figure out a way to do that. Hoping I am clear enough and someone can guide me in the right direction. Thanks in advance
public void HighlightDuplicates(DataGridView grv)
{
List<String> alreadydone = new List<String>();
int duplicateCount = 1;
for (int currentRow = 0; currentRow < grv.Rows.Count - 1; currentRow++)
{
DataGridViewRow rowToCompare = grv.Rows[currentRow];
for (int otherRow = 0; otherRow < grv.Rows.Count-1; otherRow++)
{
DataGridViewRow row = grv.Rows[otherRow];
bool duplicateRow = true;
string rowToCompareString0 = rowToCompare.Cells[2].Value.ToString();
string rowString0 = row.Cells[2].Value.ToString();
if (rowToCompare.Cells[2].Value.ToString() != row.Cells[2].Value.ToString())
{
duplicateRow = false;
continue;
}
if (duplicateRow && currentRow != otherRow && !alreadydone.Contains(rowToCompare.Cells[2].Value.ToString() + rowToCompare))
{
switch (duplicateCount)
{
case 1:
rowToCompare.Cells[2].Style.BackColor = System.Drawing.Color.Red;
rowToCompare.Cells[2].Style.ForeColor = System.Drawing.Color.Black;
row.Cells[2].Style.BackColor = System.Drawing.Color.Red;
row.Cells[2].Style.ForeColor = System.Drawing.Color.Black;
alreadydone.Add(rowToCompare.Cells[2].Value.ToString() + rowToCompare);
break;
case 2:
rowToCompare.Cells[2].Style.BackColor = System.Drawing.Color.Yellow;
rowToCompare.Cells[2].Style.ForeColor = System.Drawing.Color.Black;
row.Cells[2].Style.BackColor = System.Drawing.Color.Yellow;
row.Cells[2].Style.ForeColor = System.Drawing.Color.Black;
alreadydone.Add(rowToCompare.Cells[2].Value.ToString() + rowToCompare);
break;
case 3:
rowToCompare.Cells[2].Style.BackColor = System.Drawing.Color.Green;
rowToCompare.Cells[2].Style.ForeColor = System.Drawing.Color.Black;
row.Cells[2].Style.BackColor = System.Drawing.Color.Green;
row.Cells[2].Style.ForeColor = System.Drawing.Color.Black;
alreadydone.Add(rowToCompare.Cells[2].Value.ToString() + rowToCompare);
break;
default:
rowToCompare.Cells[2].Style.BackColor = System.Drawing.Color.Purple;
row.Cells[2].Style.BackColor = System.Drawing.Color.Purple;
alreadydone.Add(rowToCompare.Cells[2].Value.ToString() + rowToCompare);
break;
}
}
}
duplicateCount++;
}
}
It appears you may be over complicating things. It is unclear why your current code is using two for loops for this. One for loop will work and it will be less complicated.
The approach below simply loops through the rows in the grid and checks adjacent rows for equality. The only caveat here is that we need to color the first row FIRST. We know the first row will be colored since there is no “previous” row to compare it with.
Once this first row is colored, then we will loop through each row in the grid… HOWEVER, the for loop will not iterate to the LAST row. The loop will iterate to the second to the last row. With each iteration of the loop, we will check the “second” row to see if it equals the “previous” row and if so, we can color the second rows cell with the same color. If the cell values are not equal, then we will simply change the color of that cell to the next color.
It is unknown how many “different” colors you may want to use and, in the example below I used four (4) different colors and if the number of “different” cell values is greater than four (4), then we will simply start over with the first color used. In other words, the first and fifth groups of rows with “different” values will be colored the same and will repeat for every group of five different cell values.
To help, we will create a simple method called SetCellColor that will take an int curColor and a DataGridViewCell cell. In this method we will simply color the cell based on the given curColor. This method may look something like…
private void SetCellColor(int curColor, DataGridViewCell cell) {
switch(curColor) {
case 1:
cell.Style.ForeColor = Color.Red;
cell.Style.BackColor = Color.Black;
break;
case 2:
cell.Style.ForeColor = Color.Black;
cell.Style.BackColor = Color.Yellow;
break;
case 3:
cell.Style.ForeColor = Color.Black;
cell.Style.BackColor = Color.Green;
break;
case 4:
cell.Style.ForeColor = Color.Wheat;
cell.Style.BackColor = Color.Purple;
break;
}
}
This method above will make it easier to color the cell when we compare the two row’s cell values. We will use the above method in the method below which loops through the grid rows as described above. Keep in mind we need to set the loop to go from row zero (0), to the “SECOND” to last row. NOTE: this assumes the grids AllowUsersToAddRows property is set to false. If it is set to true then you will need to set the ending condition to Rows.Count – 2 to avoid a guaranteed null exception.
private void ColorDuplicateCells() {
int curColorIndex = 1;
SetCellColor(curColorIndex, dataGridView1.Rows[0].Cells[2]);
for (int i = 0; i < dataGridView1.Rows.Count - 1; i++) {
if (dataGridView1.Rows[i].Cells[2].Value.ToString() == dataGridView1.Rows[i + 1].Cells[2].Value.ToString()) {
SetCellColor(curColorIndex, dataGridView1.Rows[i+1].Cells[2]);
}
else {
curColorIndex++;
if (curColorIndex > 4) {
curColorIndex = 1;
}
SetCellColor(curColorIndex, dataGridView1.Rows[i + 1].Cells[2]);
}
}
}
Walking through the method, we color the first cell before we enter the for loop. Once in the for loop we compare the current row cells value with the next rows cell value. If the values are equal, then we will use the same color. If the values are different, then we will change to the next color by incrementing curColorIndex and then call the SetCellColor for the second row. Then continue on to the next row. In the else portion of the if statement, we need to check and make sure curColorIndex stays in bounds of the four (4) different colors. If the curColorIndex gets greater than four (4), then we will simply set it back to 1 and repeat the same color scheme for the rest of the rows.
I hope this makes sense.
Edit update per OP comment...
There are a couple of things that appear to be left out of you question. One question would be what do you plan on doing if the number of “duplicate” rows exceeds the number of “different” colors you use. In your example, you can clearly see the data and surmise that only four different (4) colors are needed. However, this may not always be the case and in my first answer I simply “repeat” the colors by repeating the four colors. This would mean that two or more rows of “different” values may have the same color.
Since I was under the impression the “different” values would be grouped together, then this strategy of repeating colors technically would work and still remove some ambiguity by the different values with the same color. Unfortunately, if the values are NOT “grouped” together, then ambiguity IS going to be an issue. In this case, with repeated colors, you could end up with two or more consecutive cells where ALL the values are different but the colors are the same…? …
The only way to avoid this ambiguity is to ensure that we have enough colors such that no color is used more than once. This is going to be dictated strictly by the data. This clearly points to looping through the data twice.
The first loop would be to figure out how many “different” colors are needed to avoid any ambiguity. How you do this is your call and, in the example below, the code sticks with the same strategy and simply repeats the colors if there are more “different” repeated rows than there are available colors… therefore ambiguity is guaranteed if there are not enough colors.
Next, it is unclear “how” the grid is getting filled with data. Typically, setting the grids DataSource to a List<SomeClass> or a DataTable is the preferred method to fill the grid with data. I will assume your code is “manually” adding the data directly to the grid cells and the grid does not use a data source. If this is true, you should re-think that approach to use a data source…
Casting UI DataGridViewRows/Cell to an enumerable type so you can select/filter/group the data is only extra work for you. If you use a data source for the grid, then selecting/filtering/grouping the DataSource as opposed to UI grid cells… becomes much easier and you can usually avoid the unnecessary casting of the UI cells. Bottom line, you need to do this grouping and counting using the DATA SOURCE NOT the grid cells. This is just a recommendation, pick your own poison. The example below uses a DataTable as a DataSource to the grid.
Given these issues, one approach is to loop through the data source DataTable to get a list of “different” duplicate rows. The approach you attempted sounds like a good approach… is what we want to get is a list of the row cell values that ARE duplicates. If a cell value is only used once, then, we do not want that value in the list. We can use this list to then loop through the grid rows and check each cells value to see if it is in the list. If it IS in the list, then that is a duplicate value and the cell needs to be colored. If the cells value is NOT in the list, then it is not a duplicated value and does not need to be colored.
The only problem here, is that we know the row is a duplicate… but what color should it be?... When we get the list of duplicate values, we need to assign a color to each of the distinct duplicate values. We could obviously create something to calculate this and in the example below, I simply used the index of the value in the list plus 1. Example, if the duplicate list has 5 items, then the first items (duplicate value) would be 0 + 1, the second item would be 1 + 1 etc.… this will enable us to use the same SetCellColor method created in the original answer and we do not have to create any extra variables.
The only change made to the method is I added a “default” value for the cells that are not duplicated. In that case we set the forecolor to black and the background to white. This will allow the code to color the non-duplicated cells to the proper colors. This updated method may look something like…
private void SetCellColor(int curColor, DataGridViewCell cell) {
switch(curColor) {
case 1:
cell.Style.ForeColor = Color.Red;
cell.Style.BackColor = Color.Black;
break;
case 2:
cell.Style.ForeColor = Color.Black;
cell.Style.BackColor = Color.Yellow;
break;
case 3:
cell.Style.ForeColor = Color.Black;
cell.Style.BackColor = Color.Green;
break;
case 4:
cell.Style.ForeColor = Color.Wheat;
cell.Style.BackColor = Color.Purple;
break;
case 5:
cell.Style.ForeColor = Color.Black;
cell.Style.BackColor = Color.Aqua;
break;
// more colors ?
default:
cell.Style.ForeColor = Color.Black;
cell.Style.BackColor = Color.White;
break;
}
}
And finally, the code that puts all this together may look something like below. duplicateItems is a list of only the rows such that the values are duplicated… it gets its items using the LINQ statement with the DATATABLE… not the grid rows. This is the first loop through the data.
Then we start a second loop though the grids rows. dupRow will contain the item from the duplicate list that matches that rows value. If dupRow returns null then that value is not in the duplicateItems list and we set the color index to zero (0). If dupRow is NOT null, then that row is a duplicate and we can set the color index to the index of the item in the duplicateaList plus 1. Once the colorIndex is set we can call the SetColorCell method. This code may look something like below…
Note: GridDT is a DataTable that is used as a DataSource to the grid. The values column [2] is named “DupCol”.
private void ColorCells() {
var duplicateItems = GridDT.AsEnumerable().GroupBy(x => x.ItemArray[2]).Where(y => y.Count() > 1).ToList();
int colorIndex;
foreach (DataGridViewRow row in dataGridView1.Rows) {
var dupRow = duplicateItems.FirstOrDefault(x => x.Key.Equals(row.Cells["DupCol"].Value.ToString()));
if (dupRow != null) {
colorIndex = duplicateItems.IndexOf(dupRow) + 1;
}
else {
colorIndex = 0;
}
SetCellColor(colorIndex, row.Cells["DupCol"]);
}
}
You are trying to do too much in one go. Things get much easier when you split them up.
First get the distinct values (using linq and from memory):
var distinctValues = grv.Rows.Select(r=>r.Cells[2].Value.ToString()).Distinct;
Then write a method to assign the colours:
Dictionary<string, System.Drawing.Color> AssignColours(IEnumerable<string> values)
{
// Add as many colours as you need here
System.Drawing.Color[] colours = new System.Drawing.Color[] {System.Drawing.Color.Red, System.Drawing.Color.Yellow, System.Drawing.Color.Green, System.Drawing.Color.Purple};
var result = new Dictionary<string, System.Drawing.Color>();
int currentColour = 0;
foreach (var s in values)
{
result[s] = colours[currentColour++];
}
return result;
}
And finally call the method and set the colour of the cells:
var colourDict = AssignColours(distinctValues);
for (int row = 0; row < grv.Rows.Count - 1; row++)
{
var cell = grv.Rows[row].Cells[2];
var value = cell.Value.ToString();
var backColour = colourDict[value];
cell.Style.BackColor = backColour;
cell.Style.ForeColor = System.Drawing.Color.Black;
}
EDIT
To not colour the unique values you need to change the expression to this:
var values = grv.Rows.Select(r=>r.Cells[2].Value.ToString();
var distinctValues = values.GroupBy(g=>g).Where(h=>h.Count()>1).Select(f=>f.Key).Distinct();
There might be a way to shorten that, but it works.
Then in the for-loop you need to take into account that the value might not be in the list:
var colourDict = AssignColours(distinctValues);
for (int row = 0; row < grv.Rows.Count - 1; row++)
{
var cell = grv.Rows[row].Cells[2];
var value = cell.Value.ToString();
if (colourDict.ContainsKey(value))
{
var backColour = colourDict[value];
cell.Style.BackColor = backColour;
cell.Style.ForeColor = System.Drawing.Color.Black;
}
}
my winForms app has a tab control which consists of two tabs (tab1 & tab2). In tab2 data is fetched in a datagridview fron a database(Product infomations).
In tab1, I've a combobox [sales analyse]which makes a user to select an option.
I now want to get access to tab2 from tab1 on cb selection, displaying me a regional sales information from the data in tab2 datagrid.
Is it possible? I don't really know wher to start
tab1 image
tab2
Expectation:
if the combobox in tab1 is selected, it should then look through the datagridview in tab2 where the (regions) North, East, West ect are and then sum the sale 13, sales 14 .. and display in the textBoxes respectively.
As your controls all sit in one Form their methods can all reference each other without any additional help.
So you can write in the SelectedIndexChanged of the ComboBox cbAnalyse
cbAnalyse_SelectedIndexChanged(object sender, EventArgs e)
{
if (cbAnalyse.SelectedItem.ToStringndex == "Sales Analysis"
{
someTextbox1.Text = ColumnSum(yourDataGridView, someColumn1) + "$";
someTextbox2.Text = ColumnSum(yourDataGridView, someColumn2) + "$";
}
This uses a small helper function, which sums up all values from one column in a DataGridView:
decimal ColumnSum(DataGridView dgv, int columnIndex)
{
decimal sum = 0m;
for (int row = 0; row < DGV.Rows.Count; row++)
if (DGV[columnIndex, row].Value != null) sum += Convert.ToDecimal(DGV[1, row].Value);
return sum;
}
Folks often run into problems when they need to refer to controls that are not sitting in either the same Form but in a 2nd, 3rd etc Form. Or when they are part of a Usercontrol, which is a custome container for holding controls.
In both cases those controls are by default private members of the other Forms or of the UserObject.
In these cases one needs to create some kind of public accessor to them, usually by a Property. And in the case of Forms, one also need to provide a reference to the other forms, often stored when opening them.
In this case, the 2nd Form often also needs a back-refrence to the 1st Form; this is often pass in in the constructor.
But in your case none of these complications matter. All you need is the patience to wire up all those TextBoxes ;-)
Update: As you also seem to have a problem getting the intermediate sums and need to allow for repeating regions in the rows of Tab 2, you also want to use a function that will calculate with a where clause:
decimal ColumnSumWhere(DataGridView dgv, int columnIndex, string region)
{
decimal sum = 0m;
for (int row = 0; row < DGV.Rows.Count; row++)
if (DGV[columnIndex, row].Value != null) &&
(DGV[regionColumn, row].Value.ToString() == region)
sum += Convert.ToDecimal(DGV[1, row].Value);
return sum;
}
If I got it right, whenever you change the value in sales analyze combo, the tab page containing data grid should should be activate.
You can set the selected index of the tab control to the the data grid tab, and it should work
this.tabControl1.SelectedIndex = 1;//Index of data grid tab
Please welcome me as am a new member.
I have a datagridview with 7 columns on it. all this are xml file which get loaded to the datagridview. my columns are are follow
Department , Employment , Permanent , contract , Fontsize.
I have a xml file called employees which get loaded to my datagridview
The user will capture the data from the column cells
On Fontsize column. i want the user to enter only two numbers or one for incresing the font size of the current row.
if the user enters fontsize of 10, the current cell rows will change the font size(which mean department,employment,permanent and contract will change their font size).
the change will only happen on the current cell selected. e.g if you change for columnIndex 1. only column index row will change fontsize.
I have do the validations for allowing only numbers, but i want it to take two numbers only.
How can i change my fontsize of column index based on the number entered. I know of font dialog but thats not how i want it to be, i have use it for other client project.
I got this example but it is not how i want it as it got style. i only need fontsize
WinForms DataGridView font size
my validations
private void Gridview_Output_CellEndEdit_1(object sender, DataGridViewCellEventArgs e)
{
try
{
#region this one validate the font number/ restrctions
int RowIndex = e.RowIndex;
int columnIndex = e.ColumnIndex;
if (e.ColumnIndex == 2)
{
bool validation = true;
if (Gridview_.Rows[RowIndex].Cells[columnIndex].Value != null && Gridview_.Rows[RowIndex].Cells[columnIndex].Value.ToString().Trim() != "")
{
string DataToValidate = Gridview_.Rows[RowIndex].Cells[columnIndex].Value.ToString();
foreach (char c in DataToValidate)
{
if (!char.IsDigit(c))
{
validation = false;
break;
}
}
if (validation == false)
{
MessageBox.Show("Font must be numbers only", "Error Message", MessageBoxButtons.OKCancel,MessageBoxIcon.Warning);
Gridview_.Rows[RowIndex].Cells[columnIndex].Value = "";
}
}
}
loading the file
XmlDocument doc = new XmlDocument();
doc.Load(Employee);
my gridview column indexes
Gridview_.Rows[i].Cells[1].Value.ToString(); // for Department
Gridview_.Rows[i].Cells[2].Value.ToString(); // for Employment
Gridview_.Rows[i].Cells[3].Value.ToString(); //for Permanent
Gridview_.Rows[i].Cells[4].Value.ToString(); //for Contract
Gridview_.Rows[i].Cells[5].Value.ToString(); //for Fontsize
Thanks for your help and i hope my question is constructive enough
The post you linked is the correct way to set it for columns. A couple things I gather from your question:
You want to only change selected cells.
You want to only change the font size, not anything else.
To change a specific cell font, you would do something like this:
dgv.Rows[0].Cells[0].Style.Font = newFont;
To only set the size of the font, you need to bring over the properties of the previous font with something like this:
Font newFont = new Font(oldFont.FontFamily, 43, oldFont.Style, oldFont.Unit);
Having a bit trouble trying to figure out a way to swap 2 buttons. Let just dive in and show you the code:
int mCatID;
int mTempPos;
mCatID = mCatButList[mValidCatNumber - 1].mCatID - 1;
if (mTempOldPos.ToString() != txtCatPos.Text) // Attempt to change position
{
foreach (DataRow row in mIMenu.mCategoryList.Rows) // Check if space is taken
{
if (row[2].ToString() == txtCatPos.Text) // Space is taken
{
mCatOldIDX = mTempOldID - 2;
mTempDesc = mIMenu.mCategoryList.Rows[mCatOldIDX][1].ToString();
mIMenu.mCategoryList.Rows[mCatOldIDX][1] = row[1].ToString(); // desc
mIMenu.mCategoryList.Rows[mCatOldIDX][2] = (int)row[2]; // pos
row[1] = mTempDesc;
row[2] = mTempOldPos; //RP change new location to old
mCatOldIDX = 0;
mIMenu.mCategoryList.AcceptChanges();
break;
}
}
// mIMenu.mCategoryList.DefaultView.Sort = "POS asc";
CreateCatButList();
DisplayMenuCategories();
[EDIT] I have modified the code and now it seems that it does swap the position of the button controls once. But if I swap two of the controls and then swap one of the swapped buttons with another button it seems to mess up all the position values on all of the other buttons!? This is really starting to bug me.. why does it work for the first time and then not for others? Ive tried accept changes and still no effect.. Its all to do with the value in the Datatable mIMenu.mCategoryList where column [1] is Description and [2] is the Position. What is frustrating is why would it work once changing the value and then as soon as the state changes to modified decides to revert back to older position values?
I have a method in a windows form application that tries to remove 2 text boxes from a panel.
In the method, I loop through all the controls in the panel. There are always supposed to be 2 panels removed and added together, but when removing, it randomly removes 1 or 2 containers when I press the button.
Here is the code to remove the textboxes:
private void removeRows()
{
string descName = "Desc" + (textBoxCounter - 1).ToString();
string costName = "Cost" + (textBoxCounter - 1).ToString();
if (textBoxCounter >= 0)
{
foreach (Control c in costItems.Controls)
{
if (c.Name == descName)
{
// Remove the control from the panel and dispose of it
panel.Controls.Remove(c);
c.Dispose();
}
if(c.Name == costName)
{
// Remove the control from the panel and dispose of it
panel.Controls.Remove(c);
c.Dispose();
}
}
// Decrement the counter
// This happens only once since two controls need to be removed
if (textBoxCounter == 0)
textBoxCounter = 0;
else
textBoxCounter--;
}
else
MessageBox.Show("There are no more rows to remove", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
testlabel1.Text = textBoxCounter.ToString();
testlabel2.Text = panel.Controls.Count.ToString();
}
Here is the code to add a button:
private void addRows(string desc, string cost)
{
if (textBoxCounter >= maxExpenses)
{
MessageBox.Show("Maximum number of expenses entered", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
TextBox Desc = new TextBox();
TextBox Cost = new TextBox();
// Give the text boxes names
Desc.Name = "Desc" + textBoxCounter.ToString();
Cost.Name = "Cost" + textBoxCounter.ToString();
// Format the text boxes
Desc.Width = panel.Width / 2;
Cost.Width = panel.Width / 4;
// Add the items to the costItems panel
panel.Controls.Add(expenseDesc);
panel.Controls.Add(expenseCost);
// Add the items to the expenses dictionary
panel.Add(Desc, Cost);
// Increment the text box counter variable
textBoxCounter++;
testlabel1.Text = textBoxCounter.ToString();
testlabel2.Text = costItems.Controls.Count.ToString();
}
}
Some info to know.
There will always be 2 textboxes added and removed, they relate to each other.
The textBoxCounter is initialized to 0, so the first two boxe names will be "Desc0" and "Cost0".
When I press the button to remove rows the first time, one text box is removed, and then if i press it again it might remove 2, it might only remove 1.
I tried debugging and I noticed that the foreach loop that iterates over all the controls in the panel seems to loop one time short of the full number of controls.
Any help with my code would be great.
Your problem is caused by the foreach, modifying the collection in foreach may cause some unexpected behavior. You just want to remove the TextBoxes with names being known beforehand, so why not using the method ControlCollection.RemoveByKey?
If you want to remove the last added textBoxes (Desc... and Cost...) do this:
panel.Controls.RemoveByKey(descName);
panel.Controls.RemoveByKey(costName);
If you want to remove all the added textBoxes (suppose you have other kinds of TextBoxes, otherwise we can use a little LINQ to remove all the textboxes easily):
for(int i = 0; i < textBoxCounter; i++){
panel.Controls.RemoveByKey("Desc" + i);
panel.Controls.RemoveByKey("Cost" + i);
}
Your code has two problems: you are disposing something you cannot dispose and you are iterating through a collection (which you are modifying) in the wrong way. You can delete all the Controls by doing:
panel.Controls.Clear();
Or iterating backwards by relying on the indices:
for (int i = panel.Controls.Count - 1; i >= 0; i--)
{
panel.Controls.RemoveAt(i);
}
Regarding the Dispose, you can use it if you wish but don't need to use Remove:
for (int i = panel.Controls.Count - 1; i >= 0; i--)
{
panel.Controls[i].Dispose();
}
PS: I asked something identical to this and got -6. One of the reasons for maintaining this question was precisely being helpful to others (I saw the code you are using to delete controls in internet and I knew that quite a few people were using it). Pretty ironical, indeed.