views:

544

answers:

3

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?

A: 

Try

dataGridView1.Refresh()
Michael Baker
Thanks, Michael, but it gives the same result. I tried replacing the Invalidate call with Refresh, and also tried leaving the Invalidate in and adding Refresh before, and then after. The all still throw the same exceptions.
Dave Hanna
A: 

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.

Dave Hanna
+2  A: 

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.

Andrew Watt