0

In this simple test program, I want to see the text "DONE!" after the progress bar has been filled in 100%. Currently, it displays "DONE!" way before 100% - I am even able to get a screen shot of it. I guess, that the problem is related to Windows' message queue, but even "Application.DoEvents()" before setting the label does not cure the problem. A "Thread.Sleep(500)" will make the problem disappear, but it will also waste half a second of my life ;-) I would like to know WHY the text appears before 100% is reached and what I can do (better than sleep) to avoid it?

    private void button1_Click(object sender, EventArgs e)
    {
        button1.Enabled = false;
        label1.Text = "In progress";
        label1.Refresh();
        int CountDown = 10000;
        progressBar1.Value = 0;
        progressBar1.Maximum = CountDown;
        while  (CountDown > 0)
        {
            CountDown--;
            progressBar1.Value = progressBar1.Maximum - CountDown;
            progressBar1.Refresh();
        }
        label1.Text = "DONE!";
        button1.Enabled = true;
    }

Progress bar not complete yet, but "DONE!" already displayed:

Progress bar not complete yet, but "DONE!" already displayed

Version 2.0 with updates inspired by Alexandru and Jimi (see comments below)

public partial class Form1 : Form
{
    const int cStartCountDown = 1000;

    public Form1()
    {
        InitializeComponent();
        backgroundWorker1.WorkerReportsProgress = true;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        progressBar1.Value = 0;
        progressBar1.Maximum = cStartCountDown;
        label1.Text = "In progress";
        label1.Refresh();
        if (backgroundWorker1.IsBusy != true)
            backgroundWorker1.RunWorkerAsync();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;

        int CountDown = cStartCountDown;
        while (CountDown > 0)
        {
            if (worker.CancellationPending == true)
            {
                e.Cancel = true;
                break;
            }
            else
            {
                CountDown--;
                worker.ReportProgress(CountDown);
            }
        }
    }
    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        progressBar1.Value = progressBar1.Maximum - e.ProgressPercentage;
        // progressBar1.Refresh();
        Task wait = Task.Delay(1);
        wait.Wait();
    }

    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Error != null)
        {
            label1.Text = "Error: " + e.Error.Message;
        }
        else
        {
            label1.Text = "DONE!";
        }
    }
}
Michael Rovinsky
  • 6,807
  • 7
  • 15
  • 30
  • What's the point of this exercise? I can think of easier ways to make a progress bar fill up as fast as possible... – canton7 May 11 '21 at 11:06
  • Avoid doing long-running work or tight loops on the UI thread. Dispatch any updates to the UI to the UI thread. Check https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.backgroundworker?view=netframework-4.7.2 You can also look into SynchronizationContext, though I think that is no longer a thing in newer versions of the framework. – Alexandru Clonțea May 11 '21 at 11:22
  • With the latest .NET 5.0 the "DONE!" label always displays correctly. However I suppose you somehow do multiple clicks on the button, which messes up the event handler somehow. As the above comment states, this is because the main UI thread is blocked by the loop. – antanta May 11 '21 at 11:35
  • 2
    The ProgressBar has an animation that takes some time to complete (it also takes some time to start). In a very quick loop as the one shown here, the animation is not complete when the Label's text is set. You can appreciate it better if you make the Click handler async, add `await Task.Delay(1);` (*virtual* value, you cannot delay 1 millisecond) and loop 1000 times. Or 100 times with a delay of 50ms. -- The ProgressBar doesn't *suffer* the tight loop, since it refreshes itself (more than one may think :) – Jimi May 11 '21 at 12:15
  • @canton7: This is an excerpt of a larger program, only to show my problem without confusing with many other details. – Ole Kullmann May 11 '21 at 12:51
  • @Alexandru Clonțea: Thanks. I modified my program to use BackgroundWorker, but it did not solve the problem. I agree, though, that if the user can have any use of the UI while the loop is running, a BackgroundWorker or similar thread is necessary. – Ole Kullmann May 11 '21 at 12:54
  • @atanta: Interesting that it works under NET 5.0. However, I am using Visual Studio 2019 and I cannot select NET higher than 4.7.2. – Ole Kullmann May 11 '21 at 12:56
  • @jimi: Thanks for the information. I will try it out and be back... – Ole Kullmann May 11 '21 at 12:58
  • @jimi: Your idea of adding Task.Delay() (see version 2.0 code above) improved the visual result very much, but also slowed down the program very much. Avoiding too many updates of the progress bar is probably the solution for that. Anyway, your explanation of the ProgressBar's internal work has helped me understand the problem. Thank you very much! – Ole Kullmann May 11 '21 at 13:31
  • 1
    Yes, of course I suggested to set a delay to better *appreciate* (let's call it that :) the effect. You don't want to delay the loop, you may want to delay the presentation of the Label, since the animation timer is always the same... -- Setting the maximum to the default 100 and updating every 1/100 of the total loop counter can make it better (the final delay caused by the animation is still present, but you can handle it). – Jimi May 11 '21 at 13:39

0 Answers0