Showing Dialog After Long Process - c#

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);
}
}
}

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.

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]

Modify your Program.cs so the declaration of the Main method looks like this:
[STAThread]
static void Main()

Related

Sending a class pointer to background worker?

I have made a small application that should download files from a website. I have a btn called btnDownload_Click. When it is clicked, a BackgroundWorker is created in order to keep the form functioning for the user while running the program. It then calls the void Downloadfiles(object sender, DoWorkEventArgs e) in order to download the file, with a bunch of settings specified in a struct called DownloadSettings.
The code for btnDownload_Click is shown below:
private void btnDownload_Click(object sender, EventArgs e)
{
//settings from the user (mostly)
DownloadSettings settings = new DownloadSettings();
settings.cond = txtSearchTerm.Text;
settings.count = Int32.Parse(txtNumberofStudies.Text);
settings.outputpath = txtFilePath.Text;
settings.fmt = cmbFormats.Text;
settings.flds = 10000;
if (settings.outputpath == "")
{
MessageBox.Show("Please select an output directory.", "Output directory needed");
}
else
{
//https://stuff.seans.com/2009/05/21/net-basics-do-work-in-background-thread-to-keep-gui-responsive/
SetAppState(AppStates.DownloadingFile);
// Set up background worker object & hook up handlers
_worker = new BackgroundWorker();
_worker.DoWork += new DoWorkEventHandler(Downloadfiles);
_worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted);
_worker.WorkerReportsProgress = true;
_worker.WorkerSupportsCancellation = true;
_worker.ProgressChanged += new ProgressChangedEventHandler(bgWorker_ProgressChanged);
// Launch background thread to do the work of reading the file. This will
// trigger BackgroundWorker.DoWork(). Note that we pass the filename to
// process as a parameter.
_worker.RunWorkerAsync(settings);
}
}
My problem is that I cannot use settings as an argument in Downloadfiles:
private void Downloadfiles(object sender, DoWorkEventArgs e) //string cond, string fmt, string outputpath
{
DownloadSettings settings = e.Argument as DownloadSettings;
}
I just get the error The as operator must be used with a reference type or nullable type ('DownloadSettings' is a non-nullable value type). How can I solve this? I got the idea for this solution from: https://stackoverflow.com/a/29011429/7502962

Savefiledialog locked file, change file name

How to keep the savefilediallog open when you write to a file which is in use by an other program so that you can change the file name and try to save again?
private void button1_Click_2(object sender, EventArgs e)
{
Cursor.Current = Cursors.WaitCursor;
CsvExport = Class_ExportData.DataTableToCSV(datatabelControle, csvSCheidingteken);
Cursor.Current = Cursors.Default;
saveFileDialog1.OverwritePrompt = true;
saveFileDialog1.Filter = "Komma gescheiden waarden (*.csv)|*.csv|Tekst bestanden (*.txt)|*.txt|Alle formaten (*.*)|*.*";
saveFileDialog1.DefaultExt = "csv";
saveFileDialog1.AddExtension = true;
saveFileDialog1.ShowDialog();
}
private void saveFileDialog1_FileOk(object sender, CancelEventArgs e)
{
try
{
string name = saveFileDialog1.FileName; // Get file name.
File.WriteAllText(name, CsvExport); // Write to the file name selected.
}
catch (Exception ex)
{
//file is locked, how to get back to the open save file dialog ???
}
}
Try this. Move the code associated with opening the saveFileDialog1 into its own function and invoke that function from button1_Click:
private void button1_Click_2(object sender, EventArgs e)
{
Cursor.Current = Cursors.WaitCursor;
CsvExport = Class_ExportData.DataTableToCSV(datatabelControle, csvSCheidingteken);
Cursor.Current = Cursors.Default;
ShowSaveFileDialog();
}
private void ShowSaveFileDialog()
{
saveFileDialog1.OverwritePrompt = true;
saveFileDialog1.Filter = "Komma gescheiden waarden (*.csv)|*.csv|Tekst bestanden (*.txt)|*.txt|Alle formaten (*.*)|*.*";
saveFileDialog1.DefaultExt = "csv";
saveFileDialog1.AddExtension = true;
saveFileDialog1.ShowDialog();
}
EDIT: On further consideration, I don't think you want/need the loop here, so I've removed it. You still want to invoke the ShowSaveFileDialog method here in case of exceptions, though:
private void saveFileDialog1_FileOk(object sender, CancelEventArgs e)
{
try
{
string name = saveFileDialog1.FileName; // Get file name.
File.WriteAllText(name, CsvExport); // Write to the file name selected.
return;
}
catch (Exception ex)
{
//file is locked, how to get back to the open save file dialog ???
// maybe display an error message here so that the user knows why they're about to see the dialog again.
}
ShowSaveFileDialog();
}
Technically, this can probably lead to a StackOverflowException if the user tries repeatedly (and I mean thousands of times) to retry the save after an exception, but that's pretty unlikely.

How to delete the file after 30 seconds/ or delete the file once after the job is completed

I am working on a C# project and i need the file to deleted after 30 seconds. So once the file sent to the machine i need the software to count till 30 seconds and at same time show a splash form and once 30 seconds crossed close the splash screen and then delete the file.
I have added a splash screen called "image". So now what happens is, the data is only sent to the printer after the splash screen is closed. I need to multi thread the job. I mean the data should print in one side while the splash screen should show at the same time. Is there a way i can come out!!.. Please help me out.
So in my case i am copying the file to the bin/debug folder. then sending data to the machine simultaneously show the splash screen for 30 seconds and close the splash screen and then i need to delete the file..
codes:
private void button4_Click(object sender, EventArgs e)
{
//string filePath = image_print();
// MessageBox.Show(filePath, "path");
string s = image_print() + Print_image();
if (String.IsNullOrEmpty(s) || img_path.Text == "")
{
return;
}
else
{
//here its coming to the splash screen code, But data is transferred to the machine only after the splash screen is close :-(
this.Hide();
omg = new image();
omg.ShowDialog();
this.Show();
//splash screen closed and then data is transferred.. which i don't need.. i need simultaneous job to be done at the same time..
PrintFactory.sendTextToLPT1(s);
}
}
private string image_print()
{
OpenFileDialog ofd = new OpenFileDialog();
string path = "";
string full_path = "";
string filename_noext = "";
ofd.InitialDirectory = #"C:\ZTOOLS\FONTS";
ofd.Filter = "GRF files (*.grf)|*.grf";
ofd.FilterIndex = 2;
ofd.RestoreDirectory = true;
if (ofd.ShowDialog() == DialogResult.OK)
{
filename_noext = System.IO.Path.GetFileName(ofd.FileName);
path = Path.GetFullPath(ofd.FileName);
img_path.Text = filename_noext;
//MessageBox.Show(filename_noext, "Filename"); - - -> switching.grf
// MessageBox.Show(full_path, "path");
//move file from location to debug
string replacepath = #"\\bin\Debug";
string fileName = System.IO.Path.GetFileName(path);
string newpath = System.IO.Path.Combine(replacepath, fileName);
// string newpath = string.Empty;
if (!System.IO.File.Exists(filename_noext))
System.IO.File.Copy(path, newpath);
filename_noext = img_path.Text;
MessageBox.Show(filename_noext, "path");
}
if (string.IsNullOrEmpty(img_path.Text))
return "";//
StreamReader test2 = new StreamReader(img_path.Text);
string s = test2.ReadToEnd();
return s;
}
private string Print_image()
{
//some codes
return s;
}
In image form: I have the following codes
public partial class image : Form
{
string filePath;
public image()
{
InitializeComponent();
// this.filePath = FileToDeletePath;
System.Timers.Timer timer1 = new System.Timers.Timer();
timer1.Interval = 30000;
timer1.Elapsed += timer1_Elapsed;
timer1.Start();
}
private void image_Load(object sender, EventArgs e)
{
}
void timer1_Elapsed(object sender, ElapsedEventArgs e)
{
//delete the file using "filePath"
string Filename = img_path.Text; // here i cannot pass the old string file name with extension to this form.. Any ways please help me out
if (string.IsNullOrEmpty(Filename))
return;
if (Filename.ToCharArray().Intersect(Path.GetInvalidFileNameChars()).Any())
return;
File.Delete(Path.Combine(#"\\bin\Debug", Filename));
}
}
something like this????
Task waitfordelete = Task.Run(() =>
{
image im = new image();
});
Assumptions: window image should be shown as a dialog (modal), and only while the call to PrintFactory.sendTextToLPT1 is in progress.
If that's correct, then something like this could work for you:
// Don't forget, you need to dispose modal dialogs
image omg = new image();
// Ensure the dialog has been shown before starting task. That
// way the task knows for sure the dialog's been opened and can
// be closed.
omg.Loaded += (sender, e) =>
{
// Run the print task in a separate task
Task.Run(() =>
{
PrintFactory.sendTextToLPT1(s);
// But get back onto the main GUI thread to close the dialog
Dispatcher.Invoke(() => omg.Close());
});
};
this.Hide();
omg.ShowDialog();
this.Show();
Apologies in advance for any typos/syntax errors/etc. Hopefully the above is sufficient to express the general idea.
The answer given by Narzul and Peter both are correct. You can implement any one. But, I know your next question will be how to implement that method in your code.
you can use Thread or Task class object to separate the process. So when one process is running then other process can perform their taks at that time. There are two process in your login. The first one is send the file to the printer and the second one is the show dialog for 30 seconds and then delete the file. You should create the another thread to invoke the any one of the process so other process can perform asynchronously.
1st: make the seperate process for Print file.
Task waitfordelete = Task.Run(() =>
{
PrintFactory.sendTextToLPT1(s);
});
this.Hide();
omg = new image();
omg.ShowDialog();
this.Show();
2nd: make the seperate process for show dialog and delete the file. But, I think you may get the error in this method. You cannot change the UI from other thread
Task waitfordelete = Task.Run(() =>
{
Dispatcher.Invoke(() => this.ShowSplashScreen());
});
PrintFactory.sendTextToLPT1(s);
private void ShowSplashScreen()
{
this.Hide();
omg = new image();
omg.ShowDialog();
this.Show();
}
if you don't want to use the thread or task then just simply handle the close event of Image form
this.Hide();
omg = new image();
omg.Show();
PrintFactory.sendTextToLPT1(s);
omg.FormClosed += (object sender, EventArgs e) => {
File.Delete(Path.Combine(Application.StartupPath, Path.GetFileName(img_path.Text));
this.Show();
};
and modify the code in timer_tick event in Image form and add the this.Close() after delete file statement.
void timer1_Elapsed(object sender, ElapsedEventArgs e)
{
....
//File.Delete(Path.Combine(#"\\bin\Debug", Filename)); comment this line
this.Close();
}
Another hidden question I have found here. here i cannot pass the old string file name with extension to this form.. Any ways please help me out
void timer1_Elapsed(object sender, ElapsedEventArgs e)
{
//delete the file using "filePath"
string Filename = img_path.Text; // here i cannot pass the old string file name with extension to this form.. Any ways please help me out
for that, you can create the property in Image class and assign the file name from the parent form.
Image omg = new Image()
omg.FileName = Path.Combine(Application.StartupPath, Path.GetFileName(img_path.Text));
omg.Show();
and the property in Image form will be created like this
public class Image : Form
{
public string FileName { get; set; }
public Image()
{
}
void timer1_Elapsed(object sender, ElapsedEventArgs e)
{
....
File.Delete(Path.Combine(Application.StartupPath, this.Filename));
this.Close();
}
}
NOTE: Use the Application.StartupPath istead of \\bin\debug

C# Updating progress bars without blocking form thread

I am trying to update the components on my form with blocking its thread.
My program uses DotNetZip to add files into an archive and I am trying to update the progress bars to illustrate the progress made.
The SaveProgress method is called when the Save() starts. Before and after each entry has been written and when the Save() is finished.
At the moment the labels are not being updated and the progressBar1 does not update?
private void buttonCompress_Click(object sender, EventArgs e)
{
if ((folderBrowserDialog1.ShowDialog() == DialogResult.OK) && (saveFileDialog1.ShowDialog() == DialogResult.OK))
{
buttonCompress.Enabled = false;
String DirectoryToZip = folderBrowserDialog1.SelectedPath;
String ZipFileToCreate = saveFileDialog1.FileName;
using (ZipFile zip = new ZipFile())
{
zip.CompressionLevel = Ionic.Zlib.CompressionLevel.Default;
zip.SaveProgress += SaveProgress;
zip.StatusMessageTextWriter = System.Console.Out;
zip.AddDirectory(DirectoryToZip); // recurses subdirectories
zip.Save(ZipFileToCreate);
}
}
}
Compression is very CPU-intensive, of course it would freeze your UI thread, use a background thread for it instead:
private void buttonCompress_Click(object sender, EventArgs e)
{
if ((folderBrowserDialog1.ShowDialog() == DialogResult.OK) && (saveFileDialog1.ShowDialog() == DialogResult.OK))
{
buttonCompress.Enabled = false;
String DirectoryToZip = folderBrowserDialog1.SelectedPath;
String ZipFileToCreate = saveFileDialog1.FileName;
// fire off zipping job in a background thread
Task.Factory.StartNew(() => StartZipping(DirectoryToZip, ZipFileToCreate), TaskCreationOptions.LongRunning);
}
}
private object StartZipping(string DirectoryToZip, string ZipFileToCreate)
{
using (ZipFile zip = new ZipFile())
{
zip.CompressionLevel = Ionic.Zlib.CompressionLevel.Default;
zip.SaveProgress += SaveProgress;
zip.StatusMessageTextWriter = System.Console.Out;
zip.AddDirectory(DirectoryToZip); // recurses subdirectories
zip.Save(ZipFileToCreate);
}
}
Also since the SaveProgress event handler will now be called from the background thread, you have to change it to marshall UI updates to the UI thread.

File not getting saved in specified location in winforms c#

Eventhough i specify a different location the file gets saved in mydocuments. How to resolve this issue. Pls share your ideas if any.Here is the code.
if (externalButton.Checked == true)
{
// int i = 1;
saveFileDialog.Title = "Save the Proofer Report";
saveFileDialog.Filter = "Document Files (*.doc)|*.doc|Document Files (*.docx)|*.docx";
saveFileDialog.FilterIndex = 0;
saveFileDialog.InitialDirectory = "MyDocuments";
saveFileDialog.FileName = "Proofer Report -- " + Path.GetFileName((string)fileName) + ".doc";
//i.tostring()
saveFileDialog.DefaultExt = ".doc";
saveFileDialog.ShowHelp = true;
// saveFileDialog.ShowDialog();
var thread = new Thread(new ParameterizedThreadStart(param => { saveFileDialog.ShowDialog(); }));
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
fname = saveFileDialog.FileName;
You are showing dialog assynchronously on new thread and code after starting the thread executes before dialog is shown (most of the time).
Either wait for thread completion or move saving to that thread after dialog is closed.
Why You Are Showing saveFileDialog in different thread?
if you show save dialog in diffrent thread fname = saveFileDialog.FileName; is always return null.dont use separate thread.or Call this event after thread start
saveFileDialog1.FileOk += new CancelEventHandler(saveFileDialog1_FileOk);
void saveFileDialog1_FileOk(object sender, CancelEventArgs e)
{
string fname = null;
fname = saveFileDialog1.FileName;
}
Edited
Example
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
_SaveFileDialog.FileOk += new CancelEventHandler(_SaveFileDialog_FileOk);
}
string filename = null;
SaveFileDialog _SaveFileDialog = new SaveFileDialog();
private void savebtn_Click(object sender, EventArgs e)
{
_SaveFileDialog.Title = "Save the Proofer Report";
_SaveFileDialog.Filter = "Document Files (*.doc)|*.doc|Document Files (*.docx)|*.docx";
_SaveFileDialog.FilterIndex = 0;
_SaveFileDialog.InitialDirectory = "MyDocuments";
_SaveFileDialog.FileName = "Proofer Report -- .doc";
_SaveFileDialog.DefaultExt = ".doc";
_SaveFileDialog.ShowHelp = true;
var thread = new Thread(new ParameterizedThreadStart(param => { _SaveFileDialog.ShowDialog(); }));
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
void _SaveFileDialog_FileOk(object sender, CancelEventArgs e)
{
filename = _SaveFileDialog.FileName;
}
}

Categories

Resources