Question

After 2 hours of researching, i still couldn't find a solution to my problem.

The task I do is process some files in the BackGroundWorker thread. However, sometimes I need to use ShowDialog to let the user choose the SaveFile location but i'm getting the STA/MTA error.

MainForm code:

private void button2_Click(object sender, EventArgs e)
{
            button1.Enabled = false;
            ProcessReportWorker.RunWorkerAsync();
}

DoWork Code:

void ProcessReportWorker_DoWork(object sender, DoWorkEventArgs e)
{
    int ReportCount = Reports.Count();
    foreach (string Report in Reports)
    {
            ProcessReport NewReport = new ProcessReport(Report);
        string result = NewReport.Start();
    }
} 

ProcessReport.Start() Code:

class ProcessReport
{
    public string Start() 
    {
        if(File.Exists(ResultPath))
        {
            SaveFileDialog SaveReport = new SaveFileDialog();
                    SaveReport.InitialDirectory = "c:\somepath";
                    SaveReport.CheckPathExists = true;
                    SaveReport.DefaultExt = ".xls";
                    SaveReport.OverwritePrompt = true;
                    SaveReport.ValidateNames = true;
                    if (SaveReport.ShowDialog() == DialogResult.OK)
                    {
                        ResultPath = SaveReport.FileName;
                        if (File.Exists(ResultPath)) File.Delete(ResultPath);
                    }
        }
    }
}

As you can see, the ShowDialog is needed in some cases. I believe this can be done using delegates but i'm not much familiar with delegates. I did try the solution by Jon in Calling ShowDialog in BackgroundWorker but i couldn't get it to work. (maybe i'm doing something wrong with delegates?)

Someone please help me with this. Please provide me the code for delegates if needed for this. Thanks!

EDIT: Solution given by PoweredByOrange worked. HOwever, i had to make a small change to it:

this.Invoke((MethodInvoker)delegate{....}); did not work because - the intention is to refer to the MainForm instance but this code exists in the ProcessReport Class. So the "this" here is referring to the ProcessReport class instance, but it must refer to the GUI instance (MainForm instance) to work.

My Fix: I sent an instance of the MainForm to the ProcessReport class and made the changes as mentioned below:

IN DoWork:

ProcessReport NewReport = new ProcessReport(Report, this); //CHANGE: Sending 'this'
//this sends reference of MainForm(GUI) to the ProcessReport Class

In ProcessReport Class:

 class ProcessReport
    {
        MainForm MainFormInstance;
        public ProcessReport(string report, MainForm x)
        {
            MainFormInstance = x;
        }
        public string Start() 
        {
            MainFormInstance.Invoke((MethodInvoker)delegate //changed this.Invoke to MainFormInstance.Invoke
                {
                   SaveFileDialog SaveReport = new SaveFileDialog();
                    SaveReport.InitialDirectory = "c:\somepath";
                    SaveReport.CheckPathExists = true;
                    SaveReport.DefaultExt = ".xls";
                    SaveReport.OverwritePrompt = true;
                    SaveReport.ValidateNames = true;
                    if (SaveReport.ShowDialog() == DialogResult.OK)
                    {
                        ResultPath = SaveReport.FileName;
                        if (File.Exists(ResultPath)) File.Delete(ResultPath);
                    }
                });
        }
    }

So the above thing finally worked. I understood this pretty well, thanks to PoweredByOrange.

Was it helpful?

Solution

The reason you're getting the exception is because only the thread that owns a control is allowed to modify/access it. In this case, the SaveFileDialog belongs to your main thread, but the Start() method is running in a different (i.e. background) thread. Therefore, the background thread in this case needs to ask the main thread to open up its SaveFileDialog.

public string Start() 
    {
        if(File.Exists(ResultPath))
        {
          this.Invoke((MethodInvoker)delegate
                {
                   SaveFileDialog SaveReport = new SaveFileDialog();
                    SaveReport.InitialDirectory = "c:\somepath";
                    SaveReport.CheckPathExists = true;
                    SaveReport.DefaultExt = ".xls";
                    SaveReport.OverwritePrompt = true;
                    SaveReport.ValidateNames = true;
                    if (SaveReport.ShowDialog() == DialogResult.OK)
                    {
                        ResultPath = SaveReport.FileName;
                        if (File.Exists(ResultPath)) File.Delete(ResultPath);
                    }
                });
        }
    }

To make it more clear, assume you want your friend to give you one of his textbooks. You are NOT allowed to go to your friend's room and steal the book. What you could do, is call your friend (invoke) and ask for a favor (delegate).

OTHER TIPS

Unsure if this will help but here is the simplest delegate / event code I can provide you;

public static class CacheManager
{
    private static CacheEntryRemovedCallback callback = null;
    public delegate void CacheExpiredHandler(string key);
    public static event CacheExpiredHandler CacheExpired;

    static CacheManager()
    {
        // create the callback when the cache expires.
        callback = new CacheEntryRemovedCallback(MyCachedItemRemovedCallback);
    }

    private static void MyCachedItemRemovedCallback(CacheEntryRemovedArguments arguments)
    {
        if (CacheExpired != null)
            CacheExpired(arguments.CacheItem.Key);
    }


public static class DataManager
{
    static DataManager()
    {
        // when a cached list expires, notify me with the key of the list.
        CacheManager.CacheExpired += new CacheManager.CacheExpiredHandler(CacheManager_CacheExpired);

    }

    /// <summary>
    /// When a chached list expires, this is the callback method that is called.
    /// </summary>
    /// <param name="key">The key of the list that just expired.</param>
    static void CacheManager_CacheExpired(string key)
    {
        // Do something now because the cache manager has raised an event that it has expired.
    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top