This question already has answers here:
Disabling .NET progressbar animation when changing value?
(5 answers)
Closed 7 years ago.
I'm doing my first WinForms application featuring a background worker and progress bar. When I run my code it looks like the progress bar is delayed, because its animation -- approaching 100% -- is not completed when the code for the RunWorkerCompleted is executed. (A few hundred milliseconds the progress bar is filled.)
Is there a way to make things more synchronized?
From a user perspective I'm expecting the progress bar to reach 100% before other things start to happen.
componentList.Count, below, is usually 5-15, meaning the progress animation jumps roughly 10-20% each increment.
My code:
private void ExecuteButton_Click(object sender, EventArgs e)
{
ExecuteButton.Enabled = false;
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int filesRun = 0;
foreach(string file in componentList)
{
api.ExecuteInstructions(file);
int progress = ++filesRun * 100 / componentList.Count;
backgroundWorker1.ReportProgress(progress);
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
ExecuteButton.Enabled = true;
}
My current workaround, giving the impression of simultaneousness:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int filesRun = 0;
foreach(string file in componentList)
{
api.ExecuteInstructions(file);
int progress = ++filesRun * 100 / componentList.Count;
if(progress == 100)
progress--;
backgroundWorker1.ReportProgress(progress);
}
Thread.Sleep(650); // cosmetic paus
backgroundWorker1.ReportProgress(100);
}
UPDATE:
Using the trick suggested in the question "Disabling .NET progressbar animation when changing value?" I managed to solve my problem by using the existing implementation and then adding the following logic:
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
if (e.ProgressPercentage == 100)
{
progressBar1.Value = 101;
progressBar1.Maximum = 100;
progressBar1.Value = 100;
}
}
Do a simple trick, instead of solving the UI thread synchronization problem.
Simply set progress bar to 100% in _RunWorkerCompleted.
void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar1.Value = 100;
Application.DoEvents();
ExecuteButton.Enabled = false;
}
Related
My code that works:
private void textBox1_TextChanged(object sender, EventArgs e)
{
progressBar1.Visible = true;
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
progressBar1.Visible = false;
}
If I add something for the computer to do, as seen in the following code example, the computer does not show the progress bar until it's done doing the computation. What I want it to do is show the progress bar first, then do the computation, then on some other event I want to hide the progress bar. Why can't I do it this way?
private void textBox1_TextChanged(object sender, EventArgs e)
{
progressBar1.Visible = true;
FindPrimeNumber(50000);
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
progressBar1.Visible = false;
}
The requested FindPrimeNumber code:
public int FindPrimeNumber(int n)
{
int count = 0;
int a = 2;
while (count < n)
{
int b = 2;
int prime = 1;// to check if found a prime
while (b * b <= a)
{
if (a % b == 0)
{
prime = 0;
break;
}
b++;
}
if (prime > 0)
count++;
a++;
}
return (--a);
}
the FindPrimeNumber code is just something to make the computer do a task for a while, so I can test to see if my progress bar is going to show.
I figured it out. In this example, a user enters a 5 digit number in a text box, then a progress bar shows up on the form as it is processing the number in a math function, then the result is put into a second text box and the progress bar goes away.
private void textBox1_TextChanged(object sender, EventArgs e)
{
if (textBox1.TextLength == 5)
{
progressBar1.Visible = true;
int textFromTextBox1 = Int32.Parse(textBox1.Text);
backgroundWorker1.RunWorkerAsync(textFromTextBox1);
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
e.Result = FindPrimeNumber((int)e.Argument);
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
textBox2.Text = e.Result.ToString();
backgroundWorker1.CancelAsync();
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
progressBar1.Visible = false;
}
Note: This example does not handle all exceptions, but it works great. As you can probably see in the code I am passing a value into the BackgroundWorker which gets passed into the FindPrimeNumber method, then I am retrieving the result out of the BackgroundWorker.
More notes for newbies:
I have the BackgroundWorker property WorkerSupportsCancellation set to True.
In WinForms, after dropping the BackgroundWorker onto the form, when you double click it, it will generate the DoWorkEventHandler for you, then in the Solution Explorer go to the Events and double click on RunWorkerCompleted so it can generated that for you as well. Otherwise, you will have to do a lot of manual code entry.
This is the code that I am trying to execute, but stepping through my code I never see any progress indicated or updated on my windows form showing progressbar1. This is my 1st attempt in getting a background worker to function properly, and all I have is a windows form with one button on it and this is all of the code involved in the project.
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private int i = 0;
public Form1()
{
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = false;
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
}
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
ReadySteadyGo();
worker.ReportProgress((i * 10));
FinalizeAndFinish();
worker.ReportProgress((i * 10));
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar1.Text = "Done!";
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Text = (e.ProgressPercentage.ToString() + "%");
}
private void ReadySteadyGo()
{
Thread.Sleep(100000);
}
private void FinalizeAndFinish()
{
Thread.Sleep(1000);
}
}
It appears that you are using Thread.Sleep() to simulate a long-running operation. There are a few things you should consider based on your code example:
When the backgroundWorker1.RunWorkerAsync(); is executed, it starts working on another thread. Thus, if you are debugging interactively and you have not set a breakpoint in the backgroundWorker1_DoWork method, you are not likely to see this code execute.
When the Thread.Sleep(100000) executes, it essentially means that the background worker will pause for 100 seconds - so you need to make sure you are waiting at least that long to see the UI updated.
Also, as per Hans Passant's comment, consider the following:
Nor can you see it doing anything, there's no point to assigning the
ProgressBar.Text property since it doesn't display text. Set Value
instead.
I recreated your example in Visual Studio and am hitting a breakpoint in backgroundWorker1_DoWork so the multi-threading is working properly, you just need to do proper processing?
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 100; i++)
{
backgroundWorker1.ReportProgress(i);
Thread.Sleep(100);
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
When the following code executes, the progress bar doesn't go upto the end and when its 90% complete, the message box is shown. I find the problem with the for loop but don't know what the real problem inside is. Please help. I searched the same kind of questions here in stackoverflow too. But didn't find any solution.
Here is my code:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{
if (backgroundWorker1.CancellationPending)
{
e.Cancel = true;
break;
}
backgroundWorker1.ReportProgress(i);
Thread.Sleep(100);
}
}
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void button2_Click(object sender, EventArgs e)
{
backgroundWorker1.CancelAsync();
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("Cancelled");
progressBar1.Value = 0;
}
else
{
progressBar1.Value = 0;
MessageBox.Show("Done");
}
}
I have reproduced your problem. The problem lies in the DoWork method that is too fast and the progress bar repaint of the bar cannot keep up. If you try to change that Thread.Sleep to a bigger interval (I have tried 300ms) you could be see the full painting.
However, you could help the progress bar in its repainting if you move the MessageBox.Show("Done") before the setting to zero of the progressbar.Value
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("Cancelled");
progressBar1.Value = 0;
}
else
{
MessageBox.Show("Done");
progressBar1.Value = 0;
}
}
Change backgroundWorker1.ReportProgress(i);
To:
backgroundWorker1.ReportProgress(i+1);
Or change the loop to:
for (int i = 1; i <= 100; i++)
What I want is when some method is doing some task UI keeps itself active and I want to show the progress of the work in a progress-bar.
I have a method, a BackGroundWorker and a Progressbar. I want to call the method when BackGroundWorker starts running and show the progress. The method contains a loop. So, it can report the progress.
So, what can be done?
private void Form1_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'dataSet1.TBLMARKET' table. You can move, or remove it, as needed.
myBGWorker.WorkerReportsProgress = true;
}
private void myBGWorker_DoWork(object sender, DoWorkEventArgs e)
{
parseFiles();
}
private void myBGWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
myProgressBar.Value = e.ProgressPercentage;
}
private void myBGWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Done");
}
private void parseButton_Click(object sender, EventArgs e)
{
myBGWorker.RunWorkerAsync();
}
public void parseFiles()
{
for()
{
//parsing
myBGWorker.ReportProgress(...);
}
}
But it's not working. The Progressbar is not updating. Only a small progress is showing after the MessageBox "Done".
Instead of using one ParseFiles method (which should depend on myBGWorker) use loop and method which parse one file. Report progress percentage in that loop:
private void parseButton_Click(object sender, EventArgs e)
{
parseButton.Enabled = false;
myBGWorker.RunWorkerAsync();
}
private void myBGWorker_DoWork(object sender, DoWorkEventArgs e)
{
for(int i = 0; i < filesCount; i++)
{
ParseSingleFile(); // pass filename here
int percentage = (i + 1) * 100 / filesCount;
myBGWorker.ReportProgress(percentage);
}
}
void myBGWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
myProgressBar.Value = e.ProgressPercentage;
}
void myBGWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
parseButton.Enabled = true;
MessageBox.Show("Done");
}
To. soham.m17
using with sender argument
private void myBGWorker_DoWork(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
for(int i = 0; i < filesCount; i++)
{
ParseSingleFile(); // pass filename here
int percentage = (i + 1) * 100 / filesCount;
worker.ReportProgress(percentage); // use not myBGWorker but worker from sender
}
}
I am sorry about the question. Actually the code works fine. It was not showing the Progressbar as the argument in myBGWorker.ReportProgress() was fraction and not percentage. So, it was not showing it. Sorry for the inconvenience.
Moderator may delete this thread. Otherwise it can be a tutorial for others.
I have a progress bar and want to fill it in using a separate thread, because the main thread is put to sleep for a few seconds in a loop. I'm using a timer so that the progress bar fills up over a certain amount of time.
Thread creation:
private void PlayButton_Click(object sender, EventArgs e)
{
progressBar1.Value = 0;
int playTime = getPlayTime();
int progressInterval = playTime / 100;
Thread progressThread = new Thread(barfiller=>fillBar(progressInterval));
progressThread.Start();
//Loops through the collection and plays each note one after the other
foreach (MusicNote music in this.staff.Notes)
{
music.Play(music.Dur);
Thread.Sleep(music.getInterval(music.Dur));
}
progressThread.Abort();
}
As is, nothing happens to the progress bar, if however I call fillbar() within the main thread, it works BUT it fills after the for loop is complete and not before/during the for loop even though I call fillbar() before the loop.
Thread methods:
private void fillBar(int progressInterval)
{
progressTimer = new System.Windows.Forms.Timer();
progressTimer.Tick += new EventHandler(clockTick);
progressTimer.Interval = progressInterval; //How fast every percentage point of completion needs to be added
progressTimer.Start();
}
public void clockTick(object sender, EventArgs e)
{
if (progressBar1.Value < 100)
{
progressBar1.Value++;
}
else
{
progressTimer.Stop();
}
}
You're doing it the wrong way. The main thread is reponsible of updating the user interface. So if you're blocking it with your calculations, it won't be able to draw the progress bar. Move your computing code in another thread and it should be fine.
always the main thread for manage user interface. use backgroundworker for this purpose.
to enable progress feature in backgroundworker set WorkerReportProgress(property) to true and
set WorkerSupportCancellation for stopping backgroundworker if needed.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// also use sender as backgroundworker
int i = 0;
foreach (MusicNote music in this.staff.Notes)
{
if(backgroundWorker1.CancellationPending) return;
music.Play(music.Dur);
Thread.Sleep(music.getInterval(music.Dur));
int p = (int) (i*100/ staff.Notes.Count); /*Count or Length */
backgroundWorker1.ReportProgress(p);
i++;
}
backgroundWorker1.ReportProgress(100);
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}