I found my answer. Thanks to those who responded to my original question!
As Nicholas Paladino pointed out, you have to disconnect a control from a
bound object before you update the object on a worker thread. Then update
it, and when the worker thread completes, rebind the resulting object.
In my demo project, I am using a dataGridView control bound to a
bindingSource control. The binding source control is bound to a WidgetList.
Here's how I fixed my code:
(1) I threw out the WidgetList member variable. Instead, I created it as a
local variable in the FormLoad event handler and set it as the
bindingSource.DataSource.
private void this_Load(object sender, EventArgs e)
{
WidgetList widgetList = new WidgetList();
widgetListBindingSource.DataSource = widgetList;
}
(2) In the button handler that triggers the background worker, I get the
WidgetList from the bindinmgSource, then clear bindingSource.DataSource. I
also show a label and progress bar to show the progress of the operation. I
pass the WidgetList to the worker thread as the argument to
RunWorkerAsync():
private void toolBarButtonGo_Click(object sender, EventArgs e)
{
statusStripLabel1.Visible = true;
statusStripProgressBar1.Visible = true;
WidgetList widgetList = (WidgetList)widgetListBindingSource.DataSource;
widgetListBindingSource.DataSource = null;
backgroundWorker1.RunWorkerAsync(widgetList);
}
(3) The backgroundWorker.DoWork event fires. I get the WidgetList from the
event args and pass it to my worker method. The worker method returns the
loaded WidgetList, which I put into the event args:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Call method to be run on worker thread
WidgetList argumentList = (WidgetList)e.Argument;
WidgetList resultList = this.PopulateList(argumentList);
e.Result = resultList;
}
Here is the worker method from my demo:
private WidgetList PopulateList(WidgetList widgetList)
{
for (int i = 0; i < 100; i++)
{
// Add new widget to the list
WidgetItem widget = new WidgetItem(i);
widgetList.Add(widget);
// Pause to simulate slow process
Thread.Sleep(100);
// Report progress
backgroundWorker1.ReportProgress(i);
}
return widgetList;
}
(4) When the worker thread completes, I get the WidgetList from the
backgroundWorker.RunWorkerCompleted event args and re-bind the binding
source to it. I also hide the progress bar and label:
private void backgroundWorker1_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
WidgetList widgetList = (WidgetList)e.Result;
widgetListBindingSource.DataSource = widgetList;
statusStripLabel1.Visible = false;
statusStripProgressBar1.Visible = false;
}
(5) While the operation is in progress, my worker method periodically
instructs the background worker to report its progress (see worker method
above).
(6) The backgroundWorker.ProgressChanged event fires each time
backgroundWorkerReportProgress() is called. It simply updates the progress
bar:
private void backgroundWorker1_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
statusStripProgressBar1.Value = e.ProgressPercentage;
}
The overall result is that I have preserved the identity of my WidgetList
and updated it on a worker thread, without making any significant changes to
my data binding scheme.
--
David Veeneman
Foresight Systems