Reputation:
so when filling my datagridview I normally do something like
public void FillTable(CoBRAMetaField[] metaFields)
{
dataGridView.Rows.Clear();
// do something with metaFields
}
Important:
CoBRAMetaField
is a object with a ID and other stuff
Each row in the grid holds a metafield object
My grid gets filled correctly (sorry, the language is german)
When I fill the grid another time I only want to remove the rows with metaFields that don't exist in the new metaFields array. I want this behaviour because when a user selected a value for this row I don't want it to get removed and created again because then the selected value is removed too.
I came up with this
public void FillTable(CoBRAMetaField[] metaFields)
{
for (int i = 0; i < dataGridView.Rows.Count; i++) // loop through the grid rows
{
double metaFieldID = (dataGridView.Rows[i].Cells[0].Tag as CoBRAMetaField).ID; // get the ID from the row metaField
if (metaFields.Any(field => field.ID == metaFieldID)) // Does it exist?
metaFields = metaFields.Where(field => field.ID != metaFieldID).ToArray(); // Remove it from the new array
else // it doesn't exist
dataGridView.Rows.Remove(dataGridView.Rows[i]); // remove the row
}
// Fill the grid with the remaining metaFields
}
The first run gets initialized correctly
the second run seems to crash, some fields remain empty
when I press the button on this row I get a nullpointer exception. I only get this error when using the "new code" so am I missing something? Is there something I didn't think about?
I will provide a full example here
At first create a DataGridView
and Button
on the form. Create a file for all required classes and take this snippet
After that fill the forms code with some test data
and now setup the DataGridView
logic
I added
dataGridView.AllowDrop = false;
dataGridView.AllowUserToAddRows = false;
dataGridView.AllowUserToDeleteRows = false;
dataGridView.AllowUserToOrderColumns = false;
dataGridView.AllowUserToResizeRows = false;
if you just want to copy paste but you can also do it by the forms designer. Have a look at dataGridView.Rows.Clear();
this provides a working example. Comment it out and use the code above to test the incorrect example
Upvotes: 2
Views: 167
Reputation: 205629
The main problem is in the code for adding rows (taken from the link):
// Fill the grid with the remaining metaFields
for (int i = 0; i < metaFields.Length; i++)
{
MetaField currentMetaField = metaFields[i];
dataGridView.Rows.Add(currentMetaField.Name, null);
DataGridViewRow newRow = dataGridView.Rows[i]; // <-- Problem!
DataGridViewCell metaFieldCell = newRow.Cells[0];
metaFieldCell.Tag = currentMetaField;
(newRow.Cells[1] as DataGridViewAllocationCell).Initialize(releaseSetupData);
}
In the marked line you are assuming that the index of the added row is the same as i
, which is true when you start with empty grid, bit not when the grid is updated and some old records are kept.
The proper way of handling it is to not assume the new row index - it is returned by the Add method:
int rowIndex = dataGridView.Rows.Add(currentMetaField.Name, null);
DataGridViewRow newRow = dataGridView.Rows[rowIndex];
This will solve the original issue from the question.
There is also a problem in the remove part of the code - the for
loop will miss checking the rows next to the removed ones. Anytime you want to iterate some list and remove items during the iteration, use reverse for
loop and RemoveAt
:
for (int i = dataGridView.Rows.Count - 1; i >= 0; i--) // <--
{
double metaFieldID = (dataGridView.Rows[i].Cells[0].Tag as MetaField).ID;
if (metaFields.Any(field => field.ID == metaFieldID))
metaFields = metaFields.Where(field => field.ID != metaFieldID).ToArray();
else
dataGridView.Rows.RemoveAt(i); // <--
}
The removal code could further be improved (currently looks inefficient with these Any
, Where
+ ToArray
), but at least with the above changes it will work correctly.
Upvotes: 4