views:

529

answers:

3

I understand how to use delegates to update controls on the main control thread, works like a charm. My problem here is if I'm adding a large dataset (say 2000 items) to a bound datagridview it takes 5-8 seconds for the grid to populate and during that 5-8 seconds the whole gui is locked. How can I update the datagridview such that it doesn't lock the user interface?

To be clear, the problem isn't that I'm doing a slow query to a database and the UI is blocking on that, I already have the dataset object[] and adding the array of objects to a BindingList which the datagrid is bound to so:

BindingList<object> dataProvider = new BindingList<object>();
DataGridView gridView = new DataGridView();
gridView.DataSource = dataProvider;

...stuff happens...

object[] source = dataSet; //of 2000 items
foreach (object item in source) {  //this foreach blocks
    dataProvider.Add(item);
}

I tried various things (that I knew wouldn't work but figured I'd see) like creating a delegate that did the dataProvider.Add(), but that didn't matter since it still had to happen on the control thread.

Any help would be much appreciated, thanks! Sam

EDIT: A couple good suggestions revolved around building the BindingList first and then setting the gridView.DataSource. While this works (it updates the grid instantly) the only way I see to add more data is to create another new BindingList, do a gridView.DataSource.copyTo() (to get the existing data) and add the new data on top of that then set the gridView.DataSource to the new BindingList. This won't work for me since the objects in my list are not static, they are each uploading data to a server async. and copying them to a new bindinglist would cause problems.

A: 

Look into using a BackgroundWorker. I don't know much about them myself, but a little searching on Google reveals that as a good possibility.

Nick Lewis
+1  A: 

You are adding records while the GridView is linked to the DataSource. This means it will update the layout each time.

How about you first fill your DataSource and only then set the DataSource property?

gridView.DataSource = null;
...stuff happens...

object[] source = dataSet; //of 2000 items
foreach (object item in source) {  //this foreach blocks
    dataProvider.Add(item);
}

gridView.DataSource = dataProvider;

The foreach loop could go to another thread but I don't think you will need that.

Henk Holterman
The foreach loop should go to another thread, but this is still the right idea.
It depends a little but Adding 2000 items is usually acceptable for a normal event. I gather they are already in memory.
Henk Holterman
I tried putting the foreach loop in a different thread but that doesn't help since each .Add still has to happen on the control thread which means the whole foreach is still happening on the control thread.Henk is right that if I add all the items to the BindingList, then set the gridView.DataSource to the BindingList afterwards its an instant update. The problem here is if I want to add more items to the gridView I have to create a new BindingList, copy all the existing items to it and add more to it. This breaks any operations I'm running on the current data (and feels like a hack).
Shizam
See, the list is a bunch of objects that upload a file, copying them over to a new list would interrupt the uploads in progress and have other nasty effects on the upload threads.
Shizam
Even if it happens in batches, you could null the DataSource property, run the loop, fill the property again. Filling the GUI part isn't suitable to put on a thread.
Henk Holterman
Great minds think alike Henk, I just tried that to see if setting it to an empty array would update the BindingList with an empty set but it doesn't have a chance to so the solution works even if it feels a little wrong :) Thanks!
Shizam
+1  A: 

Like Nick said BackgroundWorker is probably your best bet.

Here is a very simple example

  public partial class Form1 : Form
{
    BackgroundWorker b = new BackgroundWorker();

    public Form1()
    {
        InitializeComponent();
        b.RunWorkerCompleted += new RunWorkerCompletedEventHandler(b_RunWorkerCompleted);
        b.DoWork += new DoWorkEventHandler(b_DoWork);
    }

    void b_DoWork(object sender, DoWorkEventArgs e)
    {
        //build dataset here
        //and assing it to results
        e.Result=dataset

    }

    void b_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        //assign the dataset you built in DoWork in the gridview and update it
        dataGridView1.DataSource = e.Result;
        dataGridView1.Update();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        b.RunWorkerAsync();
    }


}
cptScarlet
This is another good suggestion but it involves replacing the DataSource every time I add more data instead of adding more data to the existing DataSource. This is a problem since objects in the dataSource are uploading data to a server so I can't just do a BindingList.copyTo() since each object isn't static.
Shizam
Shizam, did you try this? As long as the elements are classes (not structs) there should be no problem.
Henk Holterman