user1477388
user1477388

Reputation: 21430

Showing Dialog After Long Process

In the following code, I have a long running process called GetExcelData. When it's complete, I want to show a dialog to save it's contents into a TXT file.

The problem is, when debugging, I get the following error:

Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it. This exception is only raised if a debugger is attached to the process.

This is my code. The error occurs on the line that reads saveFileDialog1.ShowDialog();

FileInfo existingFile = new FileInfo("C:\\MyExcelFile.xlsx");

ConsoleApplication2.Program.ExcelData data = ConsoleApplication2.Program.GetExcelData(existingFile, _worker);

var json = new JavaScriptSerializer().Serialize(data);

SaveFileDialog saveFileDialog1 = new SaveFileDialog();

saveFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
saveFileDialog1.ShowDialog();

if (saveFileDialog1.FileName != "")
{
    File.WriteAllText(saveFileDialog1.FileName, json);
}

I have tried adding the [STAThread] attribute to the method I am calling this from but it didn't seem to work.

Please let me provide more code for additional clarity as to what I am trying to do:

The following exists in a WPF project which references my Console project:

private BackgroundWorker _backgroundWorker = new BackgroundWorker();

public MainWindow()
{
    InitializeComponent();

    // Set up the BackgroundWorker.
    this._backgroundWorker.WorkerReportsProgress = true;
    this._backgroundWorker.WorkerSupportsCancellation = true;
    this._backgroundWorker.DoWork += new DoWorkEventHandler(bw_DoWork);
    this._backgroundWorker.ProgressChanged +=
                              new ProgressChangedEventHandler(bw_ProgressChanged);
}

private void Button_Click(object sender, RoutedEventArgs e)
{
    if (this._backgroundWorker.IsBusy == false)
    {
        this._backgroundWorker.RunWorkerAsync();
    }
    e.Handled = true;
}

void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    // Set the Value porperty when porgress changed.
    this.progressBar1.Value = (double)e.ProgressPercentage;
}

void bw_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker _worker = sender as BackgroundWorker;
    if (_worker != null)
    {
        FileInfo existingFile = new FileInfo("C:\\MyExcelFile.xlsx");

        ConsoleApplication2.Program.ExcelData data = ConsoleApplication2.Program.GetExcelData(existingFile, _worker);

        var json = new JavaScriptSerializer().Serialize(data);

        SaveFileDialog saveFileDialog1 = new SaveFileDialog();

        saveFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
        saveFileDialog1.ShowDialog();

        if (saveFileDialog1.FileName != "")
        {
            File.WriteAllText(saveFileDialog1.FileName, json);
        }
    }
}

Upvotes: 2

Views: 209

Answers (3)

Steve
Steve

Reputation: 216293

Move the code that interacts with the UI to the same thread that handle your UI elements. The easiest way to do so it through the RunWorkerCompleted event

  this._backgroundWorker.RunWorkerCompleted +=
                          new RunWorkerCompletedEventHandler(bw_WorkComplete);

  ....

void bw_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker _worker = sender as BackgroundWorker;
    if (_worker != null)
    {
        FileInfo existingFile = new FileInfo("C:\\MyExcelFile.xlsx");
        ConsoleApplication2.Program.ExcelData data = ConsoleApplication2.Program.GetExcelData(existingFile, _worker);

        e.Result = new JavaScriptSerializer().Serialize(data);
     }
 }

 private void bw_WorkComplete(object sender, RunWorkerCompletedEventArgs e)
 {
    SaveFileDialog saveFileDialog1 = new SaveFileDialog();
    saveFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
    saveFileDialog1.ShowDialog();

    if (saveFileDialog1.FileName != "")
    {
       string json = e.Result.ToString();
       File.WriteAllText(saveFileDialog1.FileName, json);
    }
}

In the DoWork method, save the json string in the e.Result property of the DoWorkEventArgs class and retrieve it in the RunWorkerCompleted event from the RunWorkerCOmpletedEventArgs property with the same name.

Upvotes: 1

oleksii
oleksii

Reputation: 35905

Why?

Basically what happens is that you call saveFileDialog1.ShowDialog(); from bw_DoWork. And that's not right. Dialog is the UI control and should run from the UI thread and bw_DoWork method is executed in a separate thread (which is non-UI).

How to fix this?

Move the dialog show code away from the bw_DoWork method and pass the needed string instead. So the algorithm would look like

  • Click a button or whatever action to show the dialog [UI thread]
  • Open dialog [UI thread]
  • Verify you get a valid string from the dialog [UI thread]
  • Start background worker and pass a file path string [UI thread]
  • Write to file [background worker thread]

Upvotes: 1

Thorsten Dittmar
Thorsten Dittmar

Reputation: 56697

Modify your Program.cs so the declaration of the Main method looks like this:

[STAThread]
static void Main()

Upvotes: 0

Related Questions