user9945420
user9945420

Reputation:

clear datagridview duplicate rows and keep unique rows

so when filling my datagridview I normally do something like

    public void FillTable(CoBRAMetaField[] metaFields)
    {
        dataGridView.Rows.Clear();

        // do something with metaFields
    }

Important:

My grid gets filled correctly (sorry, the language is german)

enter image description here

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

enter image description here

the second run seems to crash, some fields remain empty

enter image description here

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

https://pastebin.com/BFmr2ps9

After that fill the forms code with some test data

https://pastebin.com/Yz84Akkj

and now setup the DataGridView logic

https://pastebin.com/qH6kZKZv

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

Answers (1)

Ivan Stoev
Ivan Stoev

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

Related Questions