I want to loop through a DataGridView that is created on the main form in a BackgroundWorker to export the data to a CSV file. The BackgroundWorker is created on a separate form where the progress of the export will be displayed via a progress bar. Here is the code on the export form that calls the BackgroundWorker:
private DataGridView exportGrid;
public void ExportCSV(DataGridView mainGrid)
{
this.exportGrid = mainGrid;
//Set progress bar maximum
progressBar1.Maximum = mainGrid.Rows.Count;
if (backgroundWorker1.IsBusy != true)
{
//Start the asynchronous operation
backgroundWorker1.RunWorkerAsync();
}
//Show the form
this.ShowDialog();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
//Write data rows
foreach (DataGridViewRow row in exportGrid.Rows)
{
//Check if the background worker has been cancelled
if (worker.CancellationPending == true)
{
e.Cancel = true;
break;
}
else
{
foreach (DataGridViewCell cell in row.Cells)
{
if (cell.Visible)
{
//Do CSV writing here...
}
}
//Report current progress to update UI
worker.ReportProgress(row.Index + 1);
}
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//Update progress bar
this.progressBar1.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//Close the form once the background worker is complete
this.Close();
}
This code has been causing the following errors:
- BindingSource开发者_StackOverflow社区 cannot be its own data source. Do not set the DataSource and DataMember properties to values that refer back to BindingSource.
- Cross-thread operation not valid: Control 'mainGrid' accessed from a thread other than the thread it was created on.
I assume that these are because I am accessing the DataGridView in a thread that did not create it. What is the best way to go about doing this? Is it even possible?
Update:
The reason I am looping through the DataGridView instead of the datasource is that the users will be changing the column order, sort order and showing/hiding columns of the grid and they want these changes reflected in the exported data. Is there a different way to handle this?
Jeff in my opinion you are doing at minimum two mistakes in here:
- Exporting data from a UI control instead of doing it from the data source;
- Trying to access a UI control from a background thread;
I just would not try to access a UI control (Grid in your case) in a form which is not even the form where the background thread is declared, code will be so unclear and unreadable...
then consider that UI controls are used to render data in the UI; whenever you need to access the data for anything else than rendering in the screen you'd better access directly the datasource used to populate the UI.
I have ran into this before. Here's how I did this. This is taken straight out of the project I did this in. You should be able to get the idea of how to set up the threading to make this work. I didn't include the methods that actually write the CSV file, I'm assuming your main problem is with the threading. And as Davide Piras said, it's probably not a good idea to write the data directly from a control.
The BackGroundWorker's EventHandlers:
#region TableWorker Events
void TableWorker_DoWork(object sender, DoWorkEventArgs e)
{
bool done = false;
GetSwitch();
ProgressLabel.Visible = true;
while (!done)
{
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(100);
TableWorker.ReportProgress(i);
}
done = Export.ExportDataTable(SaveFile, DataTable);
}
}
void TableWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Progress.Style = ProgressBarStyle.Blocks;
Progress.Value = e.ProgressPercentage;
ProgressLabel.Text = "Writing File: " + e.ProgressPercentage.ToString() + "% Complete";
}
void TableWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Progress.Value = 100;
ProgressLabel.Visible = false;
Progress.Visible = false;
//MessageBox.Show("Export Completed!");
TableWorker.Dispose();
ExportButton.Enabled = true;
this.Close();
}
#endregion
Event that starts the BackgroundWorker.
private void EntireTableButton_Click(object sender, EventArgs e)
{
dialogResult = Export.SetSaveDialog(SaveFile, ".csv", "csv file (*.csv)|*.csv");
if (dialogResult == DialogResult.OK)
{
TableWorker.RunWorkerAsync();
this.Hide();
ProgressLabel.Visible = true;
ProgressLabel.Text = "Retrieving Data...";
Progress.Style = ProgressBarStyle.Marquee;
Progress.Visible = true;
ExportButton.Enabled = false;
while (TableWorker.IsBusy)
{
Application.DoEvents();
}
Progress.Visible = false;
}
}
The Background worker's ReportProgress method will allow you to pass the progress to the ProgressChanged event. By doing this you can update the progress bar on another form.
精彩评论