I've a datagridview in which values are inserted.The gridview is like this.
Item PRID
------ ------
Item1 1
Item2 2
Item3 2
I am trying to compare the PRID with a variable which holds the selected row PRID.
What I've done so far.
foreach (DataGridViewRow dgv_r in PurchaseOrder_dgv.Rows)
{
if (dgv_r.Cells[1].Value.ToString() == CurrentSelected_PRID_ForPurchaseOrder.ToString())
{
PurchaseOrder_dgv.Rows.Remove(dgv_r);
}
}
But it deletes the bottom row not the second row.and gives the following error.What I want is if the value of CurrentSelected_PRID_ForPurchaseOrder is equal to 2 then it should delete both the rows.I've tried it using for loop also but it gives me Index out of range error.It is giving the following error.
Object Reference Not set to an instance of object
The are a couple of ways around this. One is to do the following
for (int i = dataGridView1.RowCount - 1; i >= 0; i--)
if (String.Compare(dataGridView1.Rows[i].Cells[1].Value.ToString(), "2") == 0)
dataGridView1.Rows.Remove(dataGridView1.Rows[i]);
This is looping from the bottom end of the DataGridView and avoids the problem with removing rows whilst iterating.
I hope this helps.
As mellamokb pointed out the reason is because your editing the collection during the foreach. One solution would be to store the rows with the same PRID in a list first and then remove them after. Something like this
var rowsToRemove = new List<int>();
foreach (DataGridViewRow dgv_r in PurchaseOrder_dgv.Rows)
{
if (dgv_r.Cells[1].Value.ToString() == CurrentSelected_PRID_ForPurchaseOrder.ToString())
{
rowsToRemove.Add(dgv_r.Index);
}
}
rowsToRemove.Reverse();
rowsToRemove.ForEach(i => PurchaseOrder_dgv.Rows.RemoveAt(i));
Another solution is to use a while loop instead which takes into account the possiblity of the collection size changing.
int i = 0;
while (i < PurchaseOrder_dgv.Rows.Count)
{
if (PurchaseOrder_dgv.Rows[i].Cells[1].Value.ToString() == CurrentSelected_PRID_ForPurchaseOrder.ToString())
{
PurchaseOrder_dgv.Rows.RemoveAt(i);
}
i++;
}
Related
I have a list of strings that I'm trying to use to control the columns shown in a gridview, but can't seem to figure out how to get it to work. Here's an example List<string> selectedHeaders = new List<string>(new string[] { "header1", "header2", "header3", "header4" });
How can I loop through the gridview columns and compare them to the values in selectedHeaders and set the visibility to false for all columns that don't match. Also note that the number of selectedHeaders can be different than the total number of columns within the gridview.
Here is what I have so far:
foreach (GridViewRow row in gvEmployees)
{
for (int i = 0; i < gvEmployees.Columns.Count; i++)
{
if (gvEmployees.Column[i].HeaderText != selectedHeaders[i])
{
gvEmployees.Column[i].Visible = false;
}
}
}
I'm not sure how to refractor this it gives me an index out of range error, because the gridview has 6 columns, but selectedHeaders could contain 1-6 values.
Your loops don't make sense for what you're trying to accomplish.
What you're doing: looping through every row in the GridView, and looping through every column within that, and looking for a string in your selectedHeaders with a matching index
What you need to be doing: looping through every column, and checking to see if there's a corresponding record in selectedHeaders by value, not by index position.
Change your code to this:
for (int i = 0; i < gvEmployees.Columns.Count; i++)
{
if (!selectedHeaders.Any(h => h == gvEmployees.Column[i].HeaderText))
{
gvEmployees.Column[i].Visible = false;
}
}
I am trying to update an asp:ListView once after some conditions on the items are true. The problem is that the update happens on the loop and therefore I end up adding the wrong values.
This is my asp:listview:
Item 1 Item 2 Item 3 Item 4 ERROR
john 490 1 0 Message
peter 4 0 0 Message
veronica 2 3 1 Message
Oscar 2 0 0 Message
Caroline 1 0 0 Message
The conditions are as follow:
1.- I have a total number of 499
2.- The total number of Item 2 (sum of all rows) must match exactly 499
3.- Item 2 cannot be lower than the result of minus Item 3 with Item 4
4.- Once all the conditions are correct, the update must be done globally for all rows in the listview.
This is what I am doing:
<asp:label id="lbl_error_outside_view" runat="server"/>
<asp:ListView ID="gv_browse" runat="server" AutoGenerateColumns="False">
...
</asp:ListView>
<asp:Button ID="btnupdate" Text="Update All" runat="server" OnClick="btnupdate_Click" />
protected void btnupdate_Click(object sender, EventArgs e)
{
int total_at = 499;
int total_global = 0;
int total_items = 0;
//Get total value for all Item2 column = total_global
foreach (ListViewItem itemRow in this.gv_browse.Items)
{
var item2 = itemRow.FindControl("tbx_item2") as TextBox;
int item2_int = Convert.ToInt32(item2.Text);
total_global = total_global + item2_int;
}
foreach (ListViewItem itemRow in this.gv_browse.Items)
{
var item1 = itemRow.FindControl("hdn_item1") as HiddenField;
var item2 = itemRow.FindControl("tbx_item2") as TextBox;
var item3 = itemRow.FindControl("lbl_item3") as Label;
var item4 = itemRow.FindControl("lbl_item4") as Label;
Label lbl_error = itemRow.FindControl("lbl_error") as Label;
int result_item3_4 = (Convert.ToInt32(item3.Text) - Convert.ToInt32(item4.Text));
int item2_int = Convert.ToInt32(item2.Text);
total_items = total_items + item2_int;
if (item2_int < result_item3_4)
{
//ERROR, DO NOT UPDATE
lbl_error.Text="...";
}
else if (item2_int >= result_item3_4)
{
lbl_error.Text = "---"; //Clear error message
}
if (total_items > total_at)
{
//ERROR, DO NOT UPDATE
lbl_error_outside_view.Text ="...";
}
else if (total_global < total_at)
{
//ERROR, DO NOT UPDATE
lbl_error_outside_view.Text="...";
}
}
My question is where I put the update line exactly?
//Update all in the DB
UpdateData(item1, item2);
If I call the UpdateData method in the foreach, I end up updating only the row(s) that are comply with the conditions. What I need is to be able to update all the rows ONLY after verifying that each row is correct? All the conditions work although most probably there is a better way of doing them. Could anyone see what I am doing wrong? Thank you so much.
UPDATE
Let me clarify why an ELSE inside the foreach loop does not work:
I populate the listview from the database with several different rows. I added 5 rows as an example. Each row has 3 columns with numerical values, item2, item3 and item4 columns. I have a global number, for this example, I said 499. The sum of item2 in all rows must much exactly this global number, 499. If it doesn't, then the update should not happen (i.e. error shows in the lbl_error_outside_view label). Therefore, here is when the first condition starts: "if total of all rows not equal to global number...".
Now, for the second condition, I need to check each individual row and make sure that Item2 column is not lower than (item3 - item4) columns. If it doesn't, then the update should also not happen (i.e. error shows in the lbl_error label for that particular row). Because I am inside the foreach loop, and because I am checking two different things, sum of total number of item2 rows and then individual rows, I cannot add the else { updateData(item1, item2); } because that will basically just update the row that the condition applies which is not what I need. That is why an else on the loop is not the solution.
All rows should be updated at once ONLY after we know that total number of item2 in all rows is equal to 499 AND each row in item2 is not lower than (item3-item4).
If anyone could share some insides that would be great thanks a lot
After several testing I found the best solution to my problem. Basically the solution was in front of my eyes but I was not able to see it until now.
foreach (ListViewItem itemRow in this.gv_browse.Items)
{
//run all the conditions here and add ONLY the correct results to a DataTable (if wrong result found, return;)
}
then I call the datatable and do the update here.
foreach (DataRow row in dt.Rows)
{
//call update method
}
In the end, was a pretty simple thing but it took me a while to realize that. I hope this helps someone else that faces the similar problem.
I made a code for delete the selected row, you can see here:
var grid = Players_DataGrid;
var mygrid = Players_DataGrid;
if (grid.SelectedIndex >= 0)
{
for (int i = 0; i <= grid.SelectedItems.Count; i++)
{
mygrid.Items.Remove(grid.SelectedItems[i]);
};
}
grid = mygrid;
But there's a problem. If the user select multiple with ctrl combination rows the program crash displaying this exception:
Argument out of range exception
on mygrid.Items.Remove(grid.SelectedItems[i]);
Is my code wrong? Isn't the best way to delete values?
you delete an item from the list you are iterating through.
Let's say your list has 10 items so you have an for loop from 0 to 9. If you delete 2 Items you still will iterate to 9 and the list has only 8 items so you get an:
Argument out of range exception
you can solve this by iterating backwards
for (int i = grid.SelectedItems.Count -1; i >= 0; i--)
Edit:
the removed item will be removed from grid.SelectedItems too.
I want to clear the datagridview starting from the row which user chose. I am trying a few ways to implement it, but they all generate some errors, now I have something like this:
foreach(DataGridViewRow row in this.dataGridView1.Rows)
{
if(row.Index >= odelementu - 1)
dataGridView1.Rows.RemoveAt(row.Index);
}
User need to choose from which row we should clear the datagridview and then click the button
odelementu //this variable represents the starting row
I don't know why the loop misses some rows. I would be grateful for any advices
It's because you modify the DataGridViewRowCollection in the foreach loop and make the index gotten becomes inexact. Try this instead:
while(dataGridView1.Rows.Count>=odelementu) {
dataGridView1.Rows.RemoveAt(odelementu-1);
}
public void RemoveRows()
{
var selectedrows = datagridview.SelectedRows.Cast<DataGridViewRow>();
if (selectedrows.Count() == 0)
return;
var fromIndex = selectedrows.Last().Index;
datagridview.Rows.Cast<DataGridViewRow>()
.Where(p=>p.Index > fromIndex)
.ToList()
.ForEach(p=>datagridview.Rows.Remove(p));
}
I have to delete some rows from a data table. I've heard that it is not ok to change a collection while iterating through it. So instead of a for loop in which I check if a row meets the demands for deletion and then mark it as deleted, I should first iterate through the data table and add all of the rows in a list, then iterate through the list and mark the rows for deletions. What are the reasons for this, and what alternatives do I have (instead of using the rows list I mean)?.
Iterating Backwards through the List sounds like a better approach, because if you remove an element and other elements "fall into the gap", that does not matter because you have already looked at those. Also, you do not have to worry about your counter variable becoming larger than the .Count.
List<int> test = new List<int>();
test.Add(1);
test.Add(2);
test.Add(3);
test.Add(4);
test.Add(5);
test.Add(6);
test.Add(7);
test.Add(8);
for (int i = test.Count-1; i > -1; i--)
{
if(someCondition){
test.RemoveAt(i);
}
}
Taking #bruno code, I'd do it backwards.
Because when you move backwards, the missing array indices do not interfere with the order of your loop.
var l = new List<int>(new int[] { 0, 1, 2, 3, 4, 5, 6 });
for (int i = l.Count - 1; i >= 0; i--)
if (l[i] % 2 == 0)
l.RemoveAt(i);
foreach (var i in l)
{
Console.WriteLine(i);
}
But seriuosly, these days, I'd use LINQ:
var l = new List<int>(new int[] { 0, 1, 2, 3, 4, 5, 6 });
l.RemoveAll(n => n % 2 == 0);
You can remove elements from a collection if you use a simple for loop.
Take a look at this example:
var l = new List<int>();
l.Add(0);
l.Add(1);
l.Add(2);
l.Add(3);
l.Add(4);
l.Add(5);
l.Add(6);
for (int i = 0; i < l.Count; i++)
{
if (l[i] % 2 == 0)
{
l.RemoveAt(i);
i--;
}
}
foreach (var i in l)
{
Console.WriteLine(i);
}
Since you're working with a DataTable and need to be able to persist any changes back to the server with a table adapter (see comments), here is an example of how you should delete rows:
DataTable dt;
// remove all rows where the last name starts with "B"
foreach (DataRow row in dt.Rows)
{
if (row["LASTNAME"].ToString().StartsWith("B"))
{
// mark the row for deletion:
row.Delete();
}
}
Calling delete on the rows will change their RowState property to Deleted, but leave the deleted rows in the table. If you still need to work with this table before persisting changes back to the server (like if you want to display the table's contents minus the deleted rows), you need to check the RowState of each row as you're iterating through it like this:
foreach (DataRow row in dt.Rows)
{
if (row.RowState != DataRowState.Deleted)
{
// this row has not been deleted - go ahead and show it
}
}
Removing rows from the collection (as in bruno's answer) will break the table adapter, and should generally not be done with a DataTable.
A while loop would handle this:
int i = 0;
while(i < list.Count)
{
if(<codition for removing element met>)
{
list.RemoveAt(i);
}
else
{
i++;
}
}
chakrit's solution can also be used if you are targetting .NET 2.0 (no LINQ/lambda expressions) by using a delegate rather than a lambda expression:
public bool IsMatch(int item) {
return (item % 3 == 1); // put whatever condition you want here
}
public void RemoveMatching() {
List<int> x = new List<int>();
x.RemoveAll(new Predicate<int>(IsMatch));
}
Deleting or adding to the list whilst iterating through it can break it, like you said.
I often used a two list approach to solve the problem:
ArrayList matches = new ArrayList(); //second list
for MyObject obj in my_list
{
if (obj.property == value_i_care_about)
matches.addLast(obj);
}
//now modify
for MyObject m in matches
{
my_list.remove(m); //use second list to delete from first list
}
//finished.
When I need to remove an item from a collection that I am enumerating I usually enumerate it in reverse.