Firstly, let me say that I’ve been on this issue for pretty much 2 days now: tried various alternatives and read tons of questions from your site with similar issues.
First let me paste my code so you get the whole picture.
EDIT: Removed some code that might be unnecessary to the problem, based on request by forum member.
Form 1.cs
namespace TestApp
{
public partial class Form1 : Form
{
//global declarations
private static Form1 myForm;
public static Form1 MyForm
{
get
{
return myForm;
}
}
List<DataSet> DataSets = new List<DataSet>();
int typeOfDataset = 0;
//delegate for background thread to communicate with the UI thread _
//and update the metadata autodetection progress bar
public delegate void UpdateProgressBar(int updateProgress);
public UpdateProgressBar myDelegate;
//***************************
//**** Form Events ********
//***************************
public Form1()
{
InitializeComponent();
if (myForm == null)
{
myForm = this;
}
DataSets.Add(new DSVDataSet());
DataSets[typeOfDataset].BWorker = new BackgroundWorker();
DataSets[typeOfDataset].BWorker.WorkerSupportsCancellation = true;
myDelegate = new UpdateProgressBar(UpdateProgressBarMethod);
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
//e.Cancel = true;
if (DataSets[typeOfDataset].BWorker != null || DataSets[typeOfDataset].BWorker.IsBusy)
{
Thread.Sleep(1000);
DataSets[typeOfDataset].BWorker.CancelAsync();
}
Application.Exit();
}
//***************************
//*** Menu Items Events ***
//***************************
private void cSVToolStripMenuItem_Click(object sender, EventArgs e)
{
LoadFillDSVData(',');
}
//***************************
//*** DataGridViews Events **
//***************************
private void dataGridView1_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e)
{
using (SolidBrush b = new SolidBrush(this.dataGridView1.RowHeadersDefaultCellStyle.ForeColor))
{
e.Graphics.DrawString(e.RowIndex.ToString(System.Globalization.CultureInfo.CurrentUICulture), this.dataGridView1.DefaultCellStyle.Font, b, e.RowBounds.Location.X + 20, e.RowBounds.Location.Y + 4);
}
int rowHeaderWidth = TextRenderer.MeasureText(e.RowIndex.ToString(), dataGridView1.Font).Width;
if (rowHeaderWidth + 22 > dataGridView1.RowHeadersWidth)
{
dataGridView1.RowHeadersWidth = rowHeaderWidth + 22;
}
}
private void dataGridView2_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e)
{
using (SolidBrush b = new SolidBrush(this.dataGridView2.RowHeadersDefaultCellStyle.ForeColor))
{
e.Graphics.DrawString(e.RowIndex.ToString(System.Globalization.CultureInfo.CurrentUICulture), this.dataGridView2.DefaultCellStyle.Font, b, e.RowBounds.Location.X + 20, e.RowBounds.Location.Y + 4);
}
int rowHeaderWidth = TextRenderer.MeasureText(e.RowIndex.ToString(), dataGridView2.Font).Width;
if (rowHeaderWidth + 22 > dataGridView2.RowHeadersWidth)
{
dataGridView2.RowHeadersWidth = rowHeaderWidth + 22;
}
}
//***************************
//****** Other Methods ******
//***************************
private void LoadFillDSVData(char delimiter)
{
//load file through openFileDialog
//some more openFileDialog code removed for simplicity
if (DataSets[typeOfDataset].BWorker != null || DataSets[typeOfDataset].BWorker.IsBusy)
{
Thread.Sleep(1000);
DataSets[typeOfDataset].BWorker.CancelAsync();
DataSets[typeOfDataset].BWorker.Dispose();
}
//if file was loaded, instantiate the class
//and populate it
dataGridView1.Rows.Clear();
dataGridView2.Rows.Clear();
typeOfDataset = 0;
DataSets[typeOfDataset] = new DSVDataSet();
DataSets[typeOfDataset].DataGrid = this.dataGridView1;
DataSets[typeOfDataset].BWorker = new BackgroundWorker();
DataSets[typeOfDataset].InputFile = openFileDialog1.FileName;
DataSets[typeOfDataset].FileName = Path.GetFileName(DataSets[typeOfDataset].InputFile);
DataSets[typeOfDataset].InputPath = Path.GetDirectoryName(DataSets[typeOfDataset].InputFile);
DataSets[typeOfDataset].Delimiter = delimiter;
//read file to get number of objects and attributes
DataSets[typeOfDataset].LoadFile(DataSets[typeOfDataset].InputFile, DataSets[typeOfDataset].Delimiter);
//ask to autodetect metadata
DialogResult dr = MessageBox.Show("Auto detect attributes?", "TestApp", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
switch (dr)
{
case DialogResult.Yes:
toolStripStatusLabel1.Text = "Autodetecting attributes...";
toolStripProgressBar1.Value = 0;
toolStripProgressBar1.Maximum = 100;
toolStripProgressBar1.Style = ProgressBarStyle.Continuous;
DataSets[typeOfDataset].AutoDetectMetadata(DataSets[typeOfDataset].Attributes);
break;
case DialogResult.No:
break;
default:
break;
}
}
public void UpdateProgressBarMethod(int progress)
{
if (progress > 99)
{
toolStripProgressBar1.Value = progress;
toolStripStatusLabel1.Text = "Done.";
}
else
{
toolStripProgressBar1.Value = progress;
}
}
}
}
And one more class:
DSVDataSet.cs
namespace TestApp
{
public class DSVDataSet : DataSet
{
static Form1 myForm = Form1.MyForm;
//constructor(s)
public DSVDataSet()
{
InputType = DataSetType.DSV;
}
//autodetects metadata from the file if
//the user wishes to do so
public override void AutoDetectMetadata(List<Attribute> attributeList)
{
BWorker.WorkerReportsProgress = true;
BWorker.WorkerSupportsCancellation = true;
BWorker.DoWork += worker_DoWork;
BWorker.ProgressChanged += worker_ProgressChanged;
BWorker.RunWorkerCompleted += worker_RunWorkerCompleted;
//send this to another thread as it is computationally intensive
BWorker.RunWorkerAsync(attributeList);
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
List<Attribute> attributeList = (List<Attribute>)e.Argument;
using (StreamReader sr = new StreamReader(InputFile))
{
for (int i = 0; i < NumberOfAttributes; i++)
{
Attribute a = new Attribute();
attributeList.Add(a);
}
for (int i = 0; i < NumberOfObjects; i++)
{
string[] DSVLine = sr.ReadLine().Split(Delimiter);
int hoistedCount = DSVLine.Count();
string str = string.Empty;
for (int j = 0; j < hoistedCount; j++)
{
bool newValue = true;
str = DSVLine[j];
for (int k = 0; k < attributeList[j].Categories.Count; k++)
{
if (str == attributeList[j].Categories[k])
{
newValue = false;
}
}
if (newValue == true)
{
attributeList[j].Categories.Add(str);
//removed some code for simplicity
}
}
int currentProgress = (int)((i * 100) / NumberOfObjects);
if (BWorker.CancellationPending)
{
Thread.Sleep(1000);
e.Cancel = true;
return;
}
BWorker.ReportProgress(currentProgress); //report progress
}
}
e.Result = 100; //final result (100%)
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
int update = e.ProgressPercentage;
myForm.BeginInvoke(myForm.myDelegate, update);
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
return;
}
if (e.Cancelled)
{
return;
}
else
{
int update = (int)e.Result;
myForm.Invoke(myForm.myDelegate, update);
for (int i = 0; i < Attributes.Count; i++)
{
DataGridViewRow item = new DataGridViewRow();
item.CreateCells(DataGrid);
item.Cells[0].Value = Attributes[i].Name;
switch (Attributes[i].Type)
{
case AttributeType.Categorical:
item.Cells[1].Value = "Categorical";
break;
//removed some cases for simplicity
default:
item.Cells[1].Value = "Categorical";
break;
}
item.Cells[2].Value = Attributes[i].Convert;
DataGrid.Rows.Add(item);
}
BWorker.Dispose();
}
}
}
}
Long story short, I have a backGroundWorker in DSVDataset.cs that does some ‘heavy computation’ so the UI doesn’t freeze (new to this), and I use a delegate to have the background thread to communicate with the UI thread to update some progress bar values. If the user decides to make a new DSVDataSet (through cSVToolStripMenuItem_Click or tSVToolStripMenuItem_Click) then I’ve added some ifs wherever I’ve found appropriate that check if there’s already a backGroundWorker running: if there is, call CancelAsync so it stops whatever it’s doing and then .Dispose it.
Now, sometimes when I try to exit the Form1 while a backGroundWorker might be running (check for example Form1_FormClosing) I get the error that is in the title of this post. The error takes me to this chunk of code in DSVDataSet.cs:
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
int update = e.ProgressPercentage;
myForm.BeginInvoke(myForm.myDelegate, update);
}
Pressing F10 takes me to Program1.cs:
Application.Run(new Form1());
Based on another stackoverflow post I read (http://stackoverflow.com/questions/513131/c-sharp-compile-error-invoke-or-begininvoke-cannot-be-called-on-a-control-unti , look at the answer), I implemented that logic by exposing the instance of the Main class itself so it always points to this instance of main.
So in Form1.cs:
private static Form1 myForm;
public static Form1 MyForm
{
get
{
return myForm;
}
}
public Form1()
{
InitializeComponent();
if (myForm == null)
{
myForm = this;
}
}
And in DSVDataset.cs:
static Form1 myForm = Form1.MyForm;
and I use myForm wherever needed.
Still, I’m not able to get past that error, so I can only assume I haven’t implemented that solution correctly, or I’m doing something wrong with handling the backGroundWorkers.
Any help would be greatly appreciated and I did my best to be as detailed as a could (hope this wasn’t an overkill 🙂
Regards
As dBear says, it’s likely that your background worker is still firing progress changed events after your form has been disposed. It’d be a good idea to either block the form from closing until the background worker is finished, or to kill the background worker before you close the form. Regardless of which of these options you choose, it’s better to do this kind of communication using events. Add the following event definition to DSVDataSet, and modify the progress changed event handler:
Once you’ve done that, you’ll need to make a change in Form1, so that once you’ve created a new instance of DSVDataSet, you add an event handler:
Put whatever code you need to display the progress into the body of
dsv_ProgressChanged, something like: