1

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.

Community
  • 1
  • 1
Sabz
  • 361
  • 1
  • 7
  • 18
  • What is the exact message you're getting? Try wrapping your your code in `Start` in a delegate like this: `this.Invoke((MethodInvoker)delegate() { SaveFileDialog saveReport... });` – Arian Motamedi Aug 13 '13 at 21:59
  • @PoweredByOrange: Can you please provide the code for the delegate? Sorry for the trouble but this is my first application in c# and I don't know how to use the Invoke and Delegates. The error message is that the action cannot be done in STA thread (There cannot be GUI interactions which runs on main thread, from other threads) – Sabz Aug 13 '13 at 22:01
  • @Steve, I'm using WinForms – Sabz Aug 13 '13 at 22:02
  • If using WPF I would have recommended using a `Dispatcher` to execute the dialog on the UI thread, and a `ManualResetEvent` to signal back to the thread that it can continue. I'm not familiar enough with WinForms, and I don't think it has something like a Dispatcher. I just found a `Control.BeginInvoke`, though... – Steve Aug 13 '13 at 22:03
  • @Steve This is my first c# application, so I chose Winforms over WPF as WinForms is simpler and I have a deadline for this project :( – Sabz Aug 13 '13 at 22:05

2 Answers2

5

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).

Arian Motamedi
  • 7,123
  • 10
  • 42
  • 82
  • Will that block until the delegate is finished running? – Steve Aug 13 '13 at 22:06
  • 1
    @Steve Yes the thread will temporarily halt for getting info from the user (the SaveFile DialogBox) – Sabz Aug 13 '13 at 22:11
  • @PoweredByOrange this.Invoke didnt work, but the MainForm1.Invoke worked. MainForm1 is the instance of the Mainform i sent it to the ProcessReport class just in case...and it worked :) Thanks a lot! I still didnt understand how it works though. I gotta learn about delegates Thanks a lot! – Sabz Aug 13 '13 at 22:12
  • 1
    @Steve If you mean the caller thread, then yes it will block until the delegate is finished executing on the UI thread. If you don't want it to block, you could try `this.BeginInvoke`. – Arian Motamedi Aug 13 '13 at 22:12
  • @Sabz Updated my answer, hope it will make it a little bit more clear ;) – Arian Motamedi Aug 13 '13 at 22:22
  • @PoweredByOrange Thanks a LOT !! I edited my post with more details about this! Thanks again! – Sabz Aug 14 '13 at 07:13
0

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.
    }
griegs
  • 22,624
  • 33
  • 128
  • 205
  • Hello, thanks for your time. However, PoweredByOrange posted the thing that i was looking for. Your code helped me understand more about delegates, thanks for that :) – Sabz Aug 14 '13 at 07:09