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);
}
Related
I am working on a Windows Form in Visual Studio.
The form takes 2 excel files (say A.xlsx und B.xlsx), processes them and produces "n" number of files at the end.
The "n" is always "total number of sheets in A.xlsx minus 2"
How can I program a progress bar in the windows form which shows the progress (% Complete) ?
private void progressBarTimer_Tick(object sender, EventArgs e)
{
int progressBarValue = 0;
progressBarValue += 1;
lblprogressBar.Text = progressBarValue.ToString() + "% complete";
progressBar.Value = progressBarValue;
if(progressBarValue>= progressBar.Maximum)
{
progressBarTimer.Stop();
}
}
I've tried the above block but I don't know where to include this block in windows form. This block is currently in Form.cs. I have very less experience working with windows Forms in Visual Studio. Any help is ver much appreciated.
There are two problems you need to solve.
How to measure the progress
How to show the progress
To measure the progress you need whatever method that is doing the work to tell how it is going. The typical way to do this would be to take a IProgress parameter in your method. I usually prefer to use a double in the 0-1 range for progress.
public void MyMethod(IProgress<double> progress){
for(int i = 0; i < count; i++){
progress.Report( i / (double)count);
}
}
You will need to run this method on a background thread to ensure the UI can update smoothly. If you lack any such loop where you can report progress, you might just have to use an indeterminate progress-bar and accept that you have no idea when the work will complete.
To do the actual update you would typically register an event handler on the ProgressChanged event for the shared progress object. This should also ensure that the UI is only updated on the UI thread.
public void MyProgressEventHandler(object sender, double progress){
lblprogressBar.Text = (progress * 100).ToString("0.#") + "% complete";
progressBar.Value = progressBar.Minimum + progressBar.Maximum * progress;
progressBar.Invalidate(); // don't know if this is needed or not
}
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 have a textbox and a listbox in my app. Also i have a text file with many players. What I want is whenever the user enters some text, look in the players file and add the matching players to the matching list which is the data source for the listbox. The problem is that it seems to be very slow and UI freezes a short time but it's quite annoying.
This is the code i have:
private void tb_playername_TextChanged(object sender, EventArgs e)
{
//This method is used to show user the options he can choose with the text he has entered
List<string> matching_players = new List<string>();
foreach (var item in all_players)
{
string player = item.f_name + " " + item.l_name;
if ((player.IndexOf(tb_playername.Text, StringComparison.OrdinalIgnoreCase) >= 0))
{
matching_players.Add("(" + item.rating + ") " + item.f_name + " " + item.l_name);
}
}
if (tb_playername.Text.Length >= 4)
{
matching_players.Sort();
matching_players.Reverse()
listbox_matchingplayers.DataSource = matching_players;
}
}
The problem is that you are doing a relatively time consuming task in the event handler. Event handlers operate on the same thread which takes care of rendering your application and handle any other visual aspects of it, so if this thread is busy, it will not be in a position to react to user input immediately, hence freezing.
The standard approach to this problem is to offload the time consuming tasks to a Background Worker. The background worker will operate in a new thread thus allowing the main thread to continue handling UI events. This example should hopefully put you on the right track when it comes to using a background worker.
EDIT: As per your question, what you could do would be to start searching only when a particular amount of characters is entered, for instance 3, this would reduce the amount of time the background worker runs. If the user keeps on typing, you could stop the current background worker if running and launch a new one.
The background worker will fire an event when finished. You could use the RunWorkerCompletedEventArgs.Result to then extract the returned list act upon it.
private async void tb_playername_TextChanged(object sender, EventArgs e)
{
var text = (sender as TextBox).Text;
// Check length of the text
if (string.IsNullOrEmpty(text) || text.Length <= 3)
return;
// Check timer to not process if user still typing, by measuring the key stoke time
...
// Filtering
List<string> matching_players = await PlayerFilter(text);
// Minimize listbox layout time
listbox_matchingplayers.SuspendLayout();
listbox_matchingplayers.DataSource = matching_players;
listbox_matchingplayers.ResumeLayout();
}
//Time consuming method
private async Task<List<string>> PlayerFilter(string text)
{
//This method is used to show user the options he can choose with the text he has entered
return matching_players;
}
For details of the user typing check wait for user to finish typing in a Text Box
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.
I am new to Visual C#, using Visual Studio. I have some calculations that are done in a parallel for loop, and I want to output something to the output text box inside this loop (keeps all output from the calculations on screen). I simply want to be able to append text to the text box (or add text to what is already there).
I found the following link on thread safe calls to windows forms. They show two examples both work for the case they present. They are simply displaying a message in the text box. I want to append data to the text box. I am able to get the following code (first way) to work:
private void setTextSafeBtn_Click(object sender, EventArgs e)
{
this.demoThread =
new Thread(new ThreadStart(this.ThreadProcSafe));
this.demoThread.Start();
}
private void ThreadProcSafe()
{
int length = 10;
Parallel.For(1, length, j =>
{
//this.textBox1.Text = this.textBox1.Text + "Simulation Replication " + j.ToString() + "\r\n";
this.SetText("Simulation Replication " + j.ToString() + "\r\n");
}); // Parallel.For
}
private void SetText(string text)
{
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text = this.textBox1.Text + text;
}
}
This will produce the desired output:
Simulation Replication 4
Simulation Replication 2
Simulation Replication 7
...
However, if I try to use the first line inside (currently commented) the parallel for loop instead of the second line, I get the following error:
"Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on."
First question is, being new to parallel computing (done some in Matlab) and being new to Visual C#, what exactly is this error trying to tell me? Is there another way to accomplish what I am trying to do? Is it easier than the method that is working right now?
Now, I also have another issue. I attempted to use the other strategy suggested for this, which is using a background worker, with very similar code. If I just display a message and do not append, everything works fine. If I attempt to append, it does 5 parallel calculations and displays them and then the GUI gangs... just freezes, no error or anything, just stuck. The code I am using looks like this (the SetText is the same as in the code above):
private void setTextBackgroundWorkerBtn_Click(object sender, EventArgs e)
{
this.backgroundWorker2.RunWorkerAsync();
}
private void backgroundWorker2_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
int length = 10;
Parallel.For(1, length, k =>
{
//this.textBox1.Text = this.textBox1.Text + "Simulation Replication " + k.ToString() + "\r\n";
this.SetText("Simulation Replication " + k.ToString() + "\r\n");
}); // Parallel.For
}
It actually outputs the text for the first 5 and the indexes are 1,2,4,6,8. Is there something to the fact that these are strictly increasing?
I would like to understand why this code works in the above case, but seems to screw up using the background worker. I read that using background workers is the better way to go. Can someone show me a way to get the desired behavior using the background worker?
Finally, I actually just want to know what the best way to do this is. If I am executing a parallel for loop with many calculations (say it takes about 5 seconds to do all the calcs for one iteration), and I want to display the current index number when the iteration begins, what is the best way to do that within the parallel for loop? I want to keep all the data that was in the text box before getting to these parallel calculations...hence the need to append..
Any help or advice here would be much appreciated. It was easy to just tell it to write some output to the console in a console app, that is no problem (using both Matlab and C#). Using the text boxes seems to create an issue. Please Help!