I am trying to update 2 labels and 2 progress bars on the UI.
However it does not work, the form just freezes and nothing happens until the saving is over.
In windows forms I am used to using Invoke and making it update. How can I achieve updating in WPF?
Thanks!
public void EncryptFiles(string saveFileLocation, string saveFileTitle, string saveFileExtension)
{
using (ZipFile zip = new ZipFile())
{
zip.Password = passwordField1.Password;
zip.Encryption = EncryptionAlgorithm.WinZipAes256;
foreach (File file in dataStorage.listOfFiles)
{
zip.AddFile(file.fileLocation, String.Empty);
}
zip.SaveProgress += SaveProgress;
zip.Save(System.IO.Path.Combine(saveFileLocation, saveFileTitle + saveFileExtension));
}
}
public void SaveProgress(object sender, SaveProgressEventArgs e)
{
if (e.EventType == ZipProgressEventType.Saving_Started)
{
System.Windows.Forms.MessageBox.Show("Begin Saving: " + e.ArchiveName);
}
else if (e.EventType == ZipProgressEventType.Saving_BeforeWriteEntry)
{
labelCompressionStatus.Content = "Writing: " + e.CurrentEntry.FileName + " (" + (e.EntriesSaved + 1) + "/" + e.EntriesTotal + ")";
labelFilename.Content= "Filename:" + e.CurrentEntry.FileName;
progressBar2.Maximum = e.EntriesTotal;
progressBar2.Value = e.EntriesSaved + 1;
}
else if (e.EventType == ZipProgressEventType.Saving_EntryBytesRead)
{
progressBar1.Value = (int)((e.BytesTransferred * 100) / e.TotalBytesToTransfer);
}
else if (e.EventType == ZipProgressEventType.Saving_Completed)
{
System.Windows.Forms.MessageBox.Show("Done: " + e.ArchiveName);
}
}
Dispatcher.Invoke(new Action(() => {
// code here for updating GUI
}), DispatcherPriority.ContextIdle);
Found this and it works for me, thanks everyonne
Edit: My answer only applies if you're running your unzip operation on a thread that is different than the UI thread (does not have to be a background worker). Otherwise, furkle's answer is what you want.
Check out the WPF dispatcher. It's designed for this usage case, and is roughly analogous to Control.Invoke() in Winforms.
Unless you execute your code in a separate thread, the UI will not update until you complete your method. Changing the value of progressBar1 won't end up mattering, because the changes won't be visible until after all the work completes. The easiest way to do this would probably be to assign the work of EncryptFiles to a BackgroundWorker:
BackgroundWorker bw = new BackgroundWorker() { WorkerReportsProgress = true };
bw.DoWork += EncryptFiles;
bw.ProgressChanged += ReportEncryptSaveProgress;
Then, keep reference to bw and call bw.ReportProgress() each time the task is updated, passing the percentage of completion to the method. Given that ReportProgress occurs on the UI thread, you should put your updates to your ProgressBars therein, and the user should see the progress of saving.
Note that I'm not 100% sure how you'd want to move your work into the BackgroundWorker - I'm not positive I know how the ZipFile object's events work, so you might have to work with it a bit. It might end up easier to make the BackgroundWorker a field of your class rather than trying to pass it to SaveProgress, given that I'm assuming you can't alter SaveProgress's method signature.
You could accomplish the UI updates by using the Dispatcher, but your UI will still end up totally locked for the entirety of the operation, and that's generally good to avoid.
Related
I am trying to run a series of tasks in the background of my Telnet / SSH program. sadly it seems I am not able to find out how to get this to work on the background.
I have tried using Tasks :
Task ReindexStock = new Task(delegate
{
this.Invoke(new Action(() =>
{
btnReindexStock.PerformClick();
txtBoxInput.Text = command[1];
ExecuteCommand();
}
));
});
ReindexStock.Start();
ReindexStock.Wait();
Task Product_attribute = new Task(delegate
{
this.Invoke(new Action(() =>
{
btnReindexProduct_Attribute.PerformClick();
txtBoxInput.Text = command[2];
ExecuteCommand();
}
));
});
Product_attribute.Start();
Product_attribute.Wait();
I also tried threads :
new Thread(() =>
{
btnReindexStock.PerformClick();
txtBoxInput.Text = command[1];
ExecuteCommand();
}).Start();
new Thread(() =>
{
btnReindexProduct_Attribute.PerformClick();
txtBoxInput.Text = command[2];
ExecuteCommand();
}).Start();
as well as this ( plucked this one from the net, hoped it would work ):
ThreadPool.QueueUserWorkItem(delegate
{
btnReindexStock.PerformClick();
txtBoxInput.Text = command[1];
ExecuteCommand();
});
ThreadPool.QueueUserWorkItem(delegate
{
btnReindexProduct_Attribute.PerformClick();
txtBoxInput.Text = command[2];
ExecuteCommand();
});
But for some reason my program still freezes when it's doing ExecuteCommand (
var cmd = SSH.client.CreateCommand(txtBoxInput.Text);
var result = cmd.Execute();
this.Invoke(new Action(() =>
{
rTxtBoxOutput.Text += result;
var reader = new StreamReader(cmd.ExtendedOutputStream);
rTxtBoxOutput.Text += "\n" + reader.ReadToEnd();
}
));
)
I have tried several things in the backgroundworker_doWork aswell but none of them seemed to work.. I tried to start a new thread like this
new Thread(new ThreadStart(ReindexAll)).Start();
also like this but I guess this is practically the same but larger
Thread t = new Thread(new ThreadStart(ReindexAll));
t.Start();
t.IsBackground = true;
and
Task.Factory.StartNew(() => ReindexAll());
as well as the plain and simple
ReindexAll();
but as I said before none of it seems to work, the moment I execute my command the program freezes.
Now my question is if someone is able to tell me what I am doing wrong and hopefully help me
From most of your examples, it looks like you're trying to click a button on your background thread to start performing whatever task this is.
all UI is only able to work on the UI thread. Most of this would almost definitely cause an error, I would think. If you're trying to do something strictly off of the UI thread, you can. However, you shouldn't be triggering a button on another thread to do so. Typically speaking, the button should be triggered by your operator only.
If you need to do some work without the operator's involvement, you can either do it in the start of the app when everything is loading up, or you can set a timer and trigger the work via that. Again, this shouldn't trigger a button, but the work itself.
Okay, first up - I'd suggest simplifying the stuff where you're creating Tasks. Instead of trying to write an inline delegate, just break out the Task's code into a separate function, and then have the Task refer to it. Much simpler, much cleaner.
Task myTask = new Task(TaskCode);
// elsewhere ...
private void TaskCode()
{
// stuff
}
At that point, after creating myTask, you can simply call myTask.Start() and you're done.
Now, that's only one half of your problem. If you tried this:
private void TaskCode()
{
SomeGuiControl.Text = "something";
}
... you're going to get an error that you can't update a GUI control from the non-gui thread. However, you can use Invoke or BeginInvoke to kick over a message to the GUI thread to handle an update (see: Writing to a TextBox from another thread?) The simplest being something like:
txtResults.BeginInvoke(new Action(() => txtResults.Text = DateTime.Now.ToString() + " Hello World"));
Finally, there's one additional caveat that's very important. You shouldn't do a Task.Wait() on the GUI thread. Why? Because all the GUI events won't fire off until the Task.Wait() finishes - which means, if you're doing updating of the GUI card throughout the task, it won't show up until everything is done! Keep in mind, the idea is to keep the GUI thread's code as quickly-done as possible. Kick off the thread and exit out - keep the GUI thread free to process other events (user or otherwise.)
Putting it all together? Here's what my sample code looks like on a WinForm I created for this problem:
private void btnSmallJob_Click(object sender, EventArgs e)
{
Task myTask = new Task(SmallTaskCode);
myTask.Start(); // NOTE: I'm NOT doing a wait() on this task; don't want to hold up the GUI thread.
}
private void SmallTaskCode()
{
System.Threading.Thread.Sleep(1000);
txtResults.BeginInvoke(new Action(() => txtResults.Text += DateTime.Now.ToString() + " Small Job" + Environment.NewLine));
System.Threading.Thread.Sleep(1000);
}
Task singleInstanceOfLargeJob;
private void btnLargeJob_Click(object sender, EventArgs e)
{
if (this.singleInstanceOfLargeJob == null || this.singleInstanceOfLargeJob.Status != TaskStatus.Running)
{
singleInstanceOfLargeJob = new Task(LargeTaskCode);
singleInstanceOfLargeJob.Start(); // NOTE: I'm NOT doing a wait() on this task; don't want to hold up the GUI thread.
return;
}
MessageBox.Show("Sorry, you can only have one instance of the large job running at once!");
// this job should only have one instance running at a time!
}
private void LargeTaskCode()
{
System.Threading.Thread.Sleep(1000);
txtResults.BeginInvoke(new Action(() => txtResults.Text += DateTime.Now.ToString() + " Big Job A" + Environment.NewLine));
System.Threading.Thread.Sleep(1000);
txtResults.BeginInvoke(new Action(() => txtResults.Text += DateTime.Now.ToString() + " Big Job B" + Environment.NewLine));
System.Threading.Thread.Sleep(1000);
txtResults.BeginInvoke(new Action(() => txtResults.Text += DateTime.Now.ToString() + " Big Job C" + Environment.NewLine));
System.Threading.Thread.Sleep(1000);
}
Basically, I've got it two different ways. The first half is just a simple, kick-off-the-thread-and-done. The second keeps track of the Task, and doesn't allow two of the job to be running at the same time.
Anyway, hope that helps!
I have a function, when the function starts, I want to display some UI components and then start working and at the end, I want to erase those components from there. Problem is that I don't see the change in UI on the form.
The function in which I am doing this is:
public void Processor()
{
// ------------- SETTING UI COMPONENTS
lblProgress.Visible = true;
progressBar.Visible = true;
btnStop.Visible = true;
// ------------- WORKING
int counter = 0, percent = 0;
foreach (string url in Urls)
{
.... WORKING THAT TAKES TIME
counter += 1;
percent = ((counter * 100) / Urls.Count());
// ------------- MODIFYING UI COMPONENTS
// Modification doesn't appear on the form while running
lblProgress.Text = "Progress: " + (percent > 100 ? 100 : percent) + "%";
progressBar.Value = percent;
}
// ------------- SETTING UI COMPONENTS
lblProgress.Visible = false;
progressBar.Visible = false;
btnStop.Visible = false;
lblDone.Visible = true;
}
Can someone help with this. Kindly let me know if I am doing something wrong.
#user2831683, I think you know now that using Application.DoEvents is not much advisable. Here is another alternative you can use:
async public void Processor() //see the "async" keyword
{
//SETTING UI COMPONENTS
foreach (string url in Urls)
{
await Task.Run(() =>
{
//.... WORKING THAT TAKES TIME
});
//MODIFYING UI COMPONENTS
}
}
While DoEvents makes your code seem to work, your UI is still blocked by your work most of the time. This approach will allow your UI to make more smooth updates (for ex, while moving your window etc.)
PS: The only change in the original code is the part //.... WORKING THAT TAKES TIME, which is replaced by a Task
As an addition to Vimalan's response:
Application.DoEvents() forces to pump the message on the windows message queue. This means that any pending request(s) for UI update are invoked via Application.DoEvents() so this is a brute-force method. I would suggest to make your UI responsive by removing the block of code which is taking time to process and run it on a seprate thread. Right now the code that takes time is also running on the main thread (where the UI is running too.) which I would say choke the main thread and is heaving difficulty to swallow and digest it ;)
Try reading the following asynchronous programming pattern to learn more:
https://msdn.microsoft.com/en-us/library/jj152938(v=vs.110).aspx
Or you can use a background worker:
https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
Please do not use .DoEvents(). It can cause all sorts of re-entrancy issues in your code and can break third-party code. Actually it can break the built-in Windows Forms code too, depending on what you're doing.
I would certainly suggest BackgroundWorker as the stock standard approach to resolve this issue.
I, however, prefer to use Microsoft's Reactive Framework for this kind of thing. It makes things like this very simple.
Here's how I would have tackled this:
public void Processor()
{
// ------------- SETTING UI COMPONENTS
lblProgress.Visible = true;
progressBar.Visible = true;
btnStop.Visible = true;
// ------------- WORKING
Urls
.ToObservable(Scheduler.Default) // Goes to background thread
.Do(url =>
{
/* .... WORKING THAT TAKES TIME */
})
.Select((url, counter) => counter * 100 / Urls.Count())
.DistinctUntilChanged()
.ObserveOn(this) // back to the UI thread
.Subscribe(
percent => // Done each change in percent
{
lblProgress.Text = "Progress: " + (percent > 100 ? 100 : percent) + "%";
progressBar.Value = percent;
},
() => // Done when finished processing
{
lblProgress.Visible = false;
progressBar.Visible = false;
btnStop.Visible = false;
lblDone.Visible = true;
});
}
You can NuGet "Rx-WinForms" to get all of this goodness.
I am trying to change a label on the form during a background worker process However it says unhandled exception. I've looked at the error and it said to invoke it like the accepted answer says here: Update label text in background worker winforms
I have done this successfully with changing values inside a checkbox list, but I've used the same method and for the label it won't invoke, I get the red error line in the code when I type it.
My Background Worker:
private void bw2_DoWork(object sender, DoWorkEventArgs args)
{
BackgroundWorker worker = sender as BackgroundWorker;
func.sshConnect();
for (int num = 0; num < checklist.Items.Count; num++)
{
if (checklist.GetItemChecked(num))
{
string project = checklist.Items[num].ToString();
lblStatus.Text = "Opening " + project + "..."; //error here
if (func.svnCheckoutProject(project))
{
lblStatus.Text = project + " Opened"; //same error here
func.sshRunCommand("echo " + project + " >> " + Properties.Settings.Default.serverUserFilesPath + Properties.Settings.Default.Username);
}
else
{
//error message
}
}
worker.ReportProgress(num * (100 / checklist.Items.Count));
}
}
I've tried replacing the lines with errors with this, but in visual studio it gives it a red line under invoke and won't let me build it.
lblStatus.Invoke((MethodInvoker)delegate { lblStatus.Text = "Opening " + project + "..."; });
Visual studio points me to here when the error appears: MSDN
I used this method for the checkbox list and it worked but try it for the label and it doesn't work.
Any ideas of why it isn't working or another way of doing this?
Don't update your UI controls from the DoWork event - you're on a separate thread from the UI thread. You can call Invoke, but it's really not the right place for it.
The BackgroundWorker already provides a construct for periodically updating the UI thread while the worker is running, and you're already using it - it's the ReportProgress method. That's the place you should be updating your Label from. Anything in there runs back on the main UI thread.
You can pass any object you want to the ReportProgess method:
worker.ReportProgress(num * (100 / checklist.Items.Count),
string.Format("Opening {0} ...", project));
Then cast the value back and use it in the ProgressChanged event:
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var message = e.UserState.ToString();
lblStatus.Text = message;
}
In most cases, you would want to use BeginIvoke rather than Invoke
public void changeLabelText(System.Windows.Forms.Label lib, String whateva)
{
if (lib.InvokeRequired)
{
lib.BeginInvoke(new MethodInvoker(() => changeLabelText(lib, whateva)));
}
else
{
lib.Text = whateva;
}
}
and the call the method in your DoWork
private void bw_DoWork(object sender, DoWorkEventArgs args)
{
//do something
//change the text of a label lb1
changeLabelText(lb1, "I'm reporting some progress....");
}
I made an application that passes trough an XML file and "extracts" the entries that have a specific value of a certain attribute. The application works fine, but the XML file has over 52,000 entries and only 3 values to sort by (so naturally it takes some time to execute it). Because of that I want to implement a progress bar so I can follow the progress my application made. I googled it, but none of the solutions work for me (the application does it's work, but the progress bar doesn't move at all), so if you could help me that would be grate.
The code that extracts the entries is this:
XmlNodeList nodes;
string ExtractedXML = "";
private void extractingXMLToolStripMenuItem_Click(object sender, EventArgs e)
{
if (nodes.Count != 0)
{
foreach (XmlNode node in nodes)
{
if (String.Compare(node.ChildNodes[6].ChildNodes[0].Attributes.GetNamedItem("code").Value.ToString(), "CodeValue") == 0)
{
tempXML = node.InnerXml;
temp = "<" + node.Name + " code=\"" + node.Attributes.GetNamedItem("code").Value + "\">" + Environment.NewLine;
temp += tempXML + Environment.NewLine;
temp += "</" + node.Name + ">";
ExtractedXML += temp + Environment.NewLine;
temp = "";
}
}
textBox1.Text = ExtractedXML;
}
}
There is a ToolStripMenue item for each value with similar code, only the "CodeValue" changes. How can I implement the progress bar to show me how far did the application get?
Best thing to do, is to not process the XML on the UI thread.
The reason why you don't see the progressbar increasing, is because processing that XML probably uses all resources and the UI thread has no chance of updating it's controls, until processing the XML has finished.
You can use a BackgroundWorker to do the processing, and use the ProgressChanged event to let the UI know that it should update the progressbar.
By doing so, your application will remain responsive, and the UI will not freeze. The UI thread will be handed some CPU time as well, and it is able to update his controls and respond to events.
You should do your long running process inside of a separate thread, BackgroundWorker would be useful here. When kicking off your process call:
bgWorker.RunWorkerAsync(//pass in object to process)
Then have a ReportProgress event where you increment your progress bar e.g.
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
}
And finally, inside of the do work event you'll want to call report progress with your formula for getting the percentage completed, as well as passing in a processed result, here's an example i've used:
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
bgWorker.ReportProgress((outputGrid.Rows.Count * 100) / importGrid.Rows.Count, result);
}
I have a method that is meant to display output on a RichTextBox in a form.
public void LogTextEvent(RichTextBox TextEventLog, Color TextColor, string EventText)
{
string nDateTime = DateTime.Now.ToString("hh:mm:ss tt") + " - ";
// color text.
TextEventLog.SelectionStart = TextEventLog.Text.Length;
TextEventLog.SelectionColor = TextColor;
// newline if first line, append if else.
if (TextEventLog.Lines.Length == 0)
{
TextEventLog.AppendText(nDateTime + EventText);
TextEventLog.ScrollToCaret();
TextEventLog.AppendText(System.Environment.NewLine);
}
else
{
TextEventLog.AppendText(nDateTime + EventText + System.Environment.NewLine);
TextEventLog.ScrollToCaret();
}
}
The problem arises when I call LogEventText() from another method running inside a thread:
Thread thListening = new Thread(new ThreadStart(StartListening));
thListening.Start();
Inside the StartListening method (it is a thread created that handles new tcp sockets to be created for clients on a main listening socket for a small http server), I make use of LogTextEvent to log some data, but I receive an InvalidOperationException was unhandled error in the LogTextEvent method at the 2nd line, TextEventLog.SelectionStart = TextEventLog.Text.Length;
The error reads, Cross-thread operation not valid: Control 'rchEventLog' accessed from a thread other than the thread it was created on.
Can anyone help break down what's going on, why, and how the error could be fixed?
Try something like this:
public void LogTextEvent(RichTextBox TextEventLog, Color TextColor, string EventText)
{
if (TextEventLog.InvokeRequired)
{
TextEventLog.BeginInvoke(new Action(delegate {
LogTextEvent(TextEventLog, TextColor, EventText);
}));
return;
}
string nDateTime = DateTime.Now.ToString("hh:mm:ss tt") + " - ";
// color text.
TextEventLog.SelectionStart = TextEventLog.Text.Length;
TextEventLog.SelectionColor = TextColor;
// newline if first line, append if else.
if (TextEventLog.Lines.Length == 0)
{
TextEventLog.AppendText(nDateTime + EventText);
TextEventLog.ScrollToCaret();
TextEventLog.AppendText(System.Environment.NewLine);
}
else
{
TextEventLog.AppendText(nDateTime + EventText + System.Environment.NewLine);
TextEventLog.ScrollToCaret();
}
}
If the LogTextEvent method is called from a thread other than the rich text box's UI thread, it will post a message to the UI thread which will cause the LogTextEvent method to be called in that thread.
By using BeginInvoke instead of Invoke, it is posted asynchronously which means that it returns immediately to the caller instead of waiting for the method to be executed on the other thread. For a logging scenario like this, this is probably what you want. If you needed to get a value from a control or something you would probably need to use Invoke instead.
new Thread(new ThreadStart(delegate {
//do some worker-thread processing here
...
//now update the gui from within the gui thread
MyRichTextBox.Invoke(new Action(delegate {
MyRichTextBox.SelectionStart = CalculateTheSelectionStart();
}));
})).Start();