Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refreshing DataGridView bindings to a list when a row is deleted

I have a WinForm app with multiple DataGridViews bound to SortableBindingLists.

Under some circumstances, I need to programmatically delete an item from the list that grid is bound to.

I can't seem to get the DGV to recognize that it's data has changed, or, specifically, that it has fewer rows. I'm calling dataGridView1.Invalidate(), and it's repainting the grid, but it tries to repaint as many rows as their were before, and throws a series of exceptions that "Index does not exist", one exception for each column.

Here's a simplified code sample that exhibits the problem: (just a WinForm with a DGV and a button.)

    private List<Employee> list;
    private void Form1_Load(object sender, EventArgs e)
    {
        list = new List<Employee>();
        for (int ix = 0; ix < 3; ix++)
        {
            list.Add(ObjectMother.GetEmployee(ix+1));
        }

        dataGridView1.DataSource = list;
    }

    private void cmdDeleteARow_Click(object sender, EventArgs e)
    {
        list.Remove(list[0]);
        dataGridView1.Invalidate();

    }

In ASP.NET, when using a GridView control, there's a "DataBind()" method you can call to force it to refresh it's data. There does not seem to be any such thing in WinForms, or am I missing something?

like image 587
Dave Hanna Avatar asked Dec 02 '22 07:12

Dave Hanna


2 Answers

In order for a DataGridView to pick up on changes to its DataSource, the source should implement IBindingList. List<T> doesn't, so it doesn't broadcast its changes, and the DataGridView doesn't know it needs to be updated.

An easy fix in this case is to put a BindingSource between the list and the DataGridView, and then call Remove() on it instead:

private List<Employee> list;
private BindingSource bindingSource;
private void Form1_Load(object sender, EventArgs e)
{
    list = new List<Employee>();
    for (int ix = 0; ix < 3; ix++)
    {
        list.Add(ObjectMother.GetEmployee(ix+1));
    }

    dataGridView1.DataSource = bindingSource;
    bindingSource.DataSource = list;
}

private void cmdDeleteARow_Click(object sender, EventArgs e)
{
    bindingSoruce.Remove(list[0]); // or, RemoveAt(0)

    // Probably not necessary:
    // dataGridView1.Invalidate();
}

Alternatively, you could use BindingList<T> instead of List<T>, or create your own list class that implements IBindingList.

like image 56
Andrew Watt Avatar answered Mar 15 '23 16:03

Andrew Watt


Well, since I'm not getting any useful responses, I'm going to go ahead and use the kludge I've come up with.

If you use reflection to go into the DataGridView.DataSource property, you'll see that the binding methods are only called if the DataSource changes. Note that a change to the contents of the DataSource (e.g, adding, changing, or deleting a list element) are not recognized as a change to the DataSource. In order to force the data binding methods to be called, what I have done successfully is to reassign the DataSource to some other object, then assign it back to the list. Seems incredibly kludgy, and a monumental waste of CPU cycles, but it appears to work. So the code becomes:

    private void cmdDeleteARow_Click(object sender, EventArgs e)
    {
        list.Remove(list[0]);
        dataGridView1.DataSource = new List<Employee>();
        dataGridView1.DataSource = list;
        dataGridView1.Invalidate();
    }

If anyone has any better ideas (and I'm sure there have to be some out there), please let me know.

like image 23
Dave Hanna Avatar answered Mar 15 '23 16:03

Dave Hanna