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
Related
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()
I am currently writing a simple WPF 3.5 application that utilizes the SharePoint COM to make calls to SharePoint sites and generate Group and User information. Since this process takes awhile I want to show a ProgressBar while the groups are being generated. The desired process is as follows:
User enters url and clicks button to fetch site data.
ProgressBar begins animation
Groups are generated and names are added to a ListView
Upon completion ProgressBar animation ends
The problem I am running into is that the UI is never updated. Neither the ProgressBar or the ListView makes any changes. If anyone has any ideas to help with the code below it would be greatly appreciated.
private void GetGroupsAndUsersButton_Click(object sender, RoutedEventArgs e)
{
siteUrl = "";
if (SiteURLTextBox.Text.Length > 0)
{
FetchDataProgressBar.IsIndeterminate = true;
mWorker = new BackgroundWorker();
mWorker.DoWork += new DoWorkEventHandler(worker_DoWork);
mWorker.WorkerSupportsCancellation = true;
mWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
mWorker.RunWorkerAsync();
}
else
{
System.Windows.MessageBox.Show("Please enter a URL for the SharePoint site you wish to retrieve data");
}
}
private void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
siteUrl = SiteURLTextBox.Text;
GroupListView.ItemsSource = null;
try
{
using (SPSite site = new SPSite(siteUrl))
{
SPWeb web = site.OpenWeb();
SPGroupCollection collGroups = web.SiteGroups;
if (GroupNames == null)
GroupNames = new List<string>();
foreach (SPGroup oGroup in collGroups)
{
GroupListView.Items.Add(new ListViewItem() { Content = oGroup.Name });
}
foreach (ListViewItem item in GroupListView.Items)
{
item.MouseLeftButtonUp += item_MouseLeftButtonUp;
}
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show("Unable to locate a SharePoint site at: " + siteUrl);
}
}
private void worker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
FetchDataProgressBar.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(
delegate()
{
FetchDataProgressBar.IsIndeterminate = false;
}
));
}
At first you need to support ProgressChanged events.
Update your BackgroundWorker initialization to:
GroupListView.ItemSource = null;
mWorker = new BackgroundWorker();
mWorker.DoWork += new DoWorkEventHandler(worker_DoWork);
mWorker.WorkerSupportsCancellation = true;
mWorker.WorkerReportsProgress = true;
mWorker.ProgressChanged += OnProgressChanged;
mWorker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
mWorker.RunWorkerAsync(SiteURLTextBox.Text);
After that you have to add a OnProgressChanged handler:
private void OnProgressChanged(object sender, ProgressChangedEventArgs e)
{
FetchDataProgressBar.Value = e.ProgressPercentage;
ListViewItem toAdd = (ListViewItem)e.UserState;
toAdd.MouseLeftButtonUp += item_MouseLeftButtonUp;
GroupListView.Items.Add(toAdd);
}
Therefore you have to change your DoWork:
private void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender;
try
{
using (SPSite site = new SPSite((String)e.Argument))
{
SPWeb web = site.OpenWeb();
SPGroupCollection collGroups = web.SiteGroups;
if(GroupNames == null)
GroupNames = new List<string>();
int added = 0;
foreach(SPGroup oGroup in collGroups)
{
added++;
ListViewItem tmp = new ListViewItem() {
Content = oGroup.Name
};
worker.ReportProgress((added * 100)/collGroups.Count,tmp);
}
}
}
catch (Exception ex)
{
MessageBox.Show("Unable to locate a SharePoint site at: " + siteUrl);
}
}
That's because you're not allowed to change GUI on DoWork.
After that, each ListViewItem is added separately to your ListView. I would also recommend, that your URL is passed as an argument to RunWorkerAsync.
Edit: Add percentage to OnProgressChanged.
In your DoWork method, you are manipulating WPF controls in code on a background thread, which you are not supposed to do. Actually, you should receive errors like "Cannot access control from other thread". Probably those exceptions are caught by your catch-all error handler, and maybe even the MessageBox doesn't work from the background thread.
As a quick fix, you would have to make siteURL and collGroups class fields, move everything before the using block to your GetGroupsAndUsersButton_Click method, and everything starting with the first foreach loop to the RunworkerCompleted event, so that all code which accesses controls runs on the UI thread.
Another thing you should change is that you should not create ListViewItems in code, but use a DataTemplate instead... this is not connected to your problem, though.
You'll need:
mWorker.WorkerReportsProgress = true;
mWorker.ProgressChanged +=
new ProgressChangedEventHandler(worker_ProgressChanged);
Then in your DoWork you'll need to call:
var worker = (BackgroundWorker)sender;
worker.ReportProgress(progressAmount);
Good worked example here: http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx
i got some really simple code, but cant get it to work. I'm using BackgroundWorker. Problem is that RunWorkerCompleted is fired way to fast. Instantly after running i get message "Work completed", but application remains frozen for couple of seconds as 'DataType data = new DataType(path);' is beign executed. After that i got all my DataGridViews etc filled correctly. If i swap this single line with Thread.Sleep everything seems to work well. Any ideas?
public frmWindow(string path)
{
InitializeComponent();
DataType d;
backgroundWorker1.RunWorkerAsync(path);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
string path = e.Argument as string;
DataType data = new DataType(path);
e.Result = data;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
d = e.Result as DataType;
MessageBox.Show("Work completed");
}
How about you use Debug.Write instead of MessageBox.Show with timers to show when the Methods are entered and exited.
While it is possible for this same background thread to act on your UI, its almost always NOT a good thing to do--UI is not threadsafe.
BackgroundWorker backGroundWorker1;
public frmWindow(string path)
{
InitializeComponent();
DataType d;
backGroundWorker1 = new BackgroundWorker();
backGroundWorker1.DoWork += (s, e) =>
{
System.Diagnostics.Debug.Write("Work started at: " + DateTime.Now + Environment.NewLine);
string path = e.Argument as string;
DataType data = new DataType(path);
e.Result = data;
};
backGroundWorker1.RunWorkerCompleted += (s, e) =>
{
d = e.Result as DataType;
System.Diagnostics.Debug.Write("Work completed at: " + DateTime.Now + Environment.NewLine);
};
backGroundWorker1.RunWorkerAsync();
}
what is wrong why is that the richtextbox doesnt get the stream of Process output? theres no text display in richtextbox..
private void button1_Click(object sender, EventArgs e)
{
Process sortProcess;
sortProcess = new Process();
sortProcess.StartInfo.FileName = "sort.exe";
sortProcess.StartInfo.Arguments = this.comboBox1.SelectedItem.ToString();
// Set UseShellExecute to false for redirection.
sortProcess.StartInfo.CreateNoWindow = true;
sortProcess.StartInfo.UseShellExecute = false;
// Redirect the standard output of the sort command.
// This stream is read asynchronously using an event handler.
sortProcess.StartInfo.RedirectStandardOutput = true;
sortOutput = new StringBuilder("");
// Set our event handler to asynchronously read the sort output.
sortProcess.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);
// Redirect standard input as well. This stream
// is used synchronously.
sortProcess.StartInfo.RedirectStandardInput = true;
// Start the process.
sortProcess.Start();
// Start the asynchronous read of the sort output stream.
sortProcess.BeginOutputReadLine();
sortProcess.WaitForExit();
richTextBox1.AppendText(sortOutput.ToString());
}
private static void SortOutputHandler(object sendingProcess,
DataReceivedEventArgs outLine)
{
sortOutput.Append(Environment.NewLine +
"[" + numOutputLines.ToString() + "] - " + outLine.Data);
}
}
so when sort.exe launches, it displays text, i want all those text be displayed also in richtextbox in RealTime (i dont want to wait for the process to exit, and then read all output)
how can i do it? any wrong part of my code? thanks
UPDATE #botz
i added this in my code
private void SortOutputHandler(object sendingProcess,
DataReceivedEventArgs outLine)
{
sortOutput.Append(Environment.NewLine +
"[" + numOutputLines.ToString() + "] - " + outLine.Data);
richTextBox1.AppendText(sortOutput.ToString());
}
but it throws this exception
Cross-thread operation not valid: Control 'richTextBox1' accessed from a thread other than the thread it was created on.
WaitForExit() blocks your UI Thread, so you don't see the new output.
Either wait for the process in a separate thread or replace WaitForExit() with something like this:
while (!sortProcess.HasExited) {
Application.DoEvents(); // This keeps your form responsive by processing events
}
In your SortOutputHandler, you can now directly append output to your textbox. But you should remember to check if you need to invoke it on the UI Thread.
You can check if it's on the UI thread this way in your handler:
if (richTextBox1.InvokeRequired) { richTextBox1.BeginInvoke(new DataReceivedEventHandler(SortOutputHandler), new[] { sendingProcess, outLine }); }
else {
sortOutput.Append(Environment.NewLine + "[" + numOutputLines.ToString() + "] - " + outLine.Data);
richTextBox1.AppendText(sortOutput.ToString());
}
This is working for me:
private void button1_Click(object sender, EventArgs e)
{
using (Process sortProcess = new Process())
{
sortProcess.StartInfo.FileName = #"F:\echo_hello.bat";
sortProcess.StartInfo.CreateNoWindow = true;
sortProcess.StartInfo.UseShellExecute = false;
sortProcess.StartInfo.RedirectStandardOutput = true;
// Set event handler
sortProcess.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);
// Start the process.
sortProcess.Start();
// Start the asynchronous read
sortProcess.BeginOutputReadLine();
sortProcess.WaitForExit();
}
}
void SortOutputHandler(object sender, DataReceivedEventArgs e)
{
Trace.WriteLine(e.Data);
this.BeginInvoke(new MethodInvoker(() =>
{
richTextBox1.AppendText(e.Data ?? string.Empty);
}));
}
The example you started with was a console application, which doesn't care much about multithreaded access. For Windows Forms when you update a control this has to be done from the main UI thread, which is why BeginInvoke is needed. If you want to check rapidly if a handler like SortOutputHandler is working properly you can use System.Diagnostics.Trace.Write*, which doesn't need BeginInvoke.
EDIT: echo_hello.bat simply echoes the "hello" string:
#echo off
echo hello
If you are going to update the ui from another thread, you need to make sure you are on the main ui thread. In the method check for InvokeRequired. See InvokeRequired
Complete application and source code available from this external link of codeproject :
http://www.codeproject.com/Articles/335909/Embedding-a-Console-in-a-C-Application
This is tutorial of implementation of https://github.com/dwmkerr/consolecontrol.
As I said in the comment I posted to the question, by definition of what a sort does, it is impossible for there to be any output until all the input has been read. So the sort program is a bad example of getting output in realtime. So the following is for anyone in the future that wants to do something like this for console programs in general. The following uses a BackgroundWorker to get the output asynchronously and put it into a TextBox. A RichTextBox could easily be used instead.
public partial class MainWindow : Window
{
const string Path = #"C:\Windows\system32\sort.exe";
BackgroundWorker Processer = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
Processer.WorkerReportsProgress = true;
Processer.WorkerSupportsCancellation = true;
Processer.ProgressChanged += Processer_ProgressChanged;
Processer.DoWork += Processer_DoWork;
}
private void Processer_DoWork(object sender, DoWorkEventArgs e)
{
StreamReader StandardOutput = e.Argument as StreamReader;
string data = StandardOutput.ReadLine();
while (data != null)
{
Processer.ReportProgress(0, data);
data = StandardOutput.ReadLine();
}
}
private void Processer_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
string data = e.UserState as string;
if (data != null)
DataBox.Text += data + "\r\n";
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DataBox.Text = string.Empty;
ProcessStartInfo StartInfo = new ProcessStartInfo(Path);
StartInfo.RedirectStandardOutput = true;
StartInfo.RedirectStandardInput = true;
StartInfo.UseShellExecute = false;
Process p = null;
try { p = Process.Start(StartInfo); }
catch (Exception ex)
{
MessageBox.Show($"Error starting {Path}: {ex.Message}");
return;
}
// Get the output
Processer.RunWorkerAsync(p.StandardOutput);
// Put the input
p.StandardInput.WriteLine("John");
p.StandardInput.WriteLine("Alice");
p.StandardInput.WriteLine("Zoe");
p.StandardInput.WriteLine("Bob");
p.StandardInput.WriteLine("Mary");
// Tell the program that is the last of the data
p.StandardInput.Close();
}
}
For the sort program it is not necessary to call ReportProgress until after all the data has been read but this is a more generalized sample.
I have written code to save an image which is generated by the application. The size of the image is around 32-35 MB. While saving the image to a BMB file, it is taking a long time, around 3-5 secs. For this purpose, I have used a background worker but when running the background worker, it shows an error like..."can't access the object as it is created on different thread".
Following is the code:
private void btnSaveDesign_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.SaveFileDialog sfd = new Microsoft.Win32.SaveFileDialog();
sfd.Title = "Save design as...";
sfd.Filter = "BMP|*.bmp";
if (sfd.ShowDialog() == true)
{
ww = new winWait();
ww.Show();
System.ComponentModel.BackgroundWorker bw = new System.ComponentModel.BackgroundWorker();
bw.DoWork += new System.ComponentModel.DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
fName = sfd.FileName;
cache = new CachedBitmap((BitmapSource)imgOut.Source, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
bw.RunWorkerAsync();
}
}
void bw_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
ww.Close();
}
void bw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(cache)); //here... it says cant access...
using (FileStream file = File.OpenWrite(fName))
{
encoder.Save(file);
}
}
I have declared "cache" as a global object. (A similar trick worked when I was programming in Windows Forms with VB.NET.)
ww is the wait window that I want to be displayed while the precess is being executed.
How to do this? Is there any other simple method for multi threading in WPF?
When WPF objects are created they are assigned to a Dispatcher object. This disallows any threads other than the creating thread to access the object. This can be circumvented by freezing the object by calling the freeze method. You would need to call Freeze on your bitmapsource object. Once you have frozen your object it becomes uneditable
Your problem comes about because you are accessing an object which is not created by the background worker thread. Normally this would happen if you access a UI control which is created in the main thread and accessed from different thread.
Use the code below.
Dispatcher.Invoke
(
new Action(
delegate()
{
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(cache));
using (FileStream file = File.OpenWrite(fName))
{
encoder.Save(file);
}
}
)
);
I think you have to pass cache as a parameter to the new thread:
bw.RunWorkerAsync(cache);
and get it from the DoWork method:
var cache=(CacheType) e.Argument;
.NET framework provides a simple way to get started in threading with
the BackgroundWorker component. This wraps much of the complexity and
makes spawning a background thread relatively safe. In addition, it
allows you to communicate between your background thread and your UI
thread without doing any special coding. You can use this component
with WinForms and WPF applications. The BackgroundWorker offers
several features which include spawning a background thread, the
ability to cancel the background process before it has completed, and
the chance to report the progress back to your UI.
public BackgroudWorker()
{
InitializeComponent();
backgroundWorker = ((BackgroundWorker)this.FindResource("backgroundWorker"));
}
private int DoSlowProcess(int iterations, BackgroundWorker worker, DoWorkEventArgs e)
{
int result = 0;
for (int i = 0; i <= iterations; i++)
{
if (worker != null)
{
if (worker.CancellationPending)
{
e.Cancel = true;
return result;
}
if (worker.WorkerReportsProgress)
{
int percentComplete =
(int)((float)i / (float)iterations * 100);
worker.ReportProgress(percentComplete);
}
}
Thread.Sleep(100);
result = i;
}
return result;
}
private void startButton_Click(object sender, RoutedEventArgs e)
{
int iterations = 0;
if (int.TryParse(inputBox.Text, out iterations))
{
backgroundWorker.RunWorkerAsync(iterations);
startButton.IsEnabled = false;
cancelButton.IsEnabled = true;
outputBox.Text = "";
}
}
private void cancelButton_Click(object sender, RoutedEventArgs e)
{
// TODO: Implement Cancel process
this.backgroundWorker.CancelAsync();
}
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// e.Result = DoSlowProcess((int)e.Argument);
var bgw = sender as BackgroundWorker;
e.Result = DoSlowProcess((int)e.Argument, bgw, e);
}
private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
workerProgress.Value = e.ProgressPercentage;
}
private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
outputBox.Text = "Canceled";
workerProgress.Value = 0;
}
else
{
outputBox.Text = e.Result.ToString();
workerProgress.Value = 0;
}
startButton.IsEnabled = true;
cancelButton.IsEnabled = false;
}