3

I have a windows desktop application that I am writing using Windows Forms in visual basic. In this app I would like to display a simple progress bar, but I have come across a strange issue. The below is an example of a simple for loop which updates the progress bar:

pBar.Visible = true;
// pBar.Minimum and pBar.Step are both set to 1 using the properties pane in the design page
pBar.Maximum = 100;
for (int j = 0; j < 100; j++) {
    double pow = Math.Pow(j, j); //Calculation
    pBar.PerformStep();
}

The above works fine, however, I would like to be able to reset the progress bar to use it again later. I assumed I could simply set pBar.Value = 1 after the for loop, but strangely this breaks the progress bar. Instead of incrementing steadily like it did previously, the bar does not fill at all. If I set pBar.Value = 50, the bar begins at the halfway point and does not move. It is behaving like pBar.Value is executing before the for loop, or that pBar.Value is getting set during each loop iteration (I've made sure that I did not mistakenly put the pBar.Value statement inside the for loop).

The only reason I could think this is happening, is that C# is running my for loop asynchronously and the value is getting set immediately, then for some reason it isn't getting updated. I've tried looking around for answers to this, but so far all I have found are ways to create background workers (I'm not interested in that) and that my method of resetting the progress bar should be working...

Why is this happening?

Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321
ExecutionByFork
  • 206
  • 1
  • 5
  • 19
  • 1
    If you want to reset the progress bar, you can use a button to set progress bar =1. I can't understand why it is related to asynchronous. – Jack J Jun Jan 01 '20 at 07:59
  • @ChrisCatignani the code I included above is all of the code needed to reproduce the issue I am encountering. If you paste that into the function attached to the click event on a button, setup the progress bar like I described in the code comment, and also add `pBar.Value = 1` at the end, you should get the same result I am describing. – ExecutionByFork Jan 06 '20 at 15:28

3 Answers3

3

OK, so here is my attempt at answering your question:

I don't think that the progress bar does not 'update' when you reset its value to 1 after the loop. I think that the whole animation of the progress bar is quite a fake.

I took a look at the source code for ProgressBar here. We note that both ProgressBar.Value and ProgressBar.PerformStep utimately call into UpdatePos that in turn sends a PBM_SETPOS message to the underlying native control. Then MSDN tells us that the bar should redraw each time it receives this message.

However, I suspect the 'real' animation of the bar is somewhat virtualized by the control. My guess is that the PBM_SETPOS messages are immediately taken into account with respect to the position value (we wouldn't want our bar not to know its value at every time), but they must also be sent to some queue for the animation.

The animation can take quite a long time and it would be canceled if something happens that requires the animation to stop or to be modified, going backwards for instance... Suppose you alternate values 1 and 51 instead of incrementing the current value, what sort of animation do you expect?

I simulated this by replacing pBar.PerformStep(); in your code by:

pBar.Value = j % 2 * 50 + 1;

and, of course not having the trailing pBar.Value = 1;.

The resulting animation is not a sickening round-trip between 1 and 51 but a clean and smooth transition from 1 to 51 :)

I think this proves my point that the animation is picking what values it wants on its own, so that when quickly setting 1 just after 100, the animation engine decides not to render anything.

So if you want to avoid this effect, the simplest thing to do is to reset the bar value to 1 before you use it instead of just after (and btw, it may prove better programming practice not to assume the progress bar value is 1 when you use it).


PS: as a final note, the same phenomenon was used as a trick to force a high value on a progress bar before the animation has time to complete here.

In your case, you could have:

private void GoSlowlyTo100() => pBar.Value = 100;
private void GoQuicklyTo100()
{
    pBar.Maximum = 101;
    pBar.Value = 101;
    pBar.Maximum = 100;
    pBar.Value = 100;
}

Hope this answers your question!

odalet
  • 1,389
  • 10
  • 18
  • 1
    The value+1 trick seems in fact rather well known! here: https://inneka.com/windows/progressbar-lag-when-setting-position-with-pbm_setpos-duplicate/ And here: https://stackoverflow.com/a/5332770/107552 – odalet Jan 05 '20 at 03:36
  • Hmm, very interesting. I ran the code you provided and indeed the progress bar does not behave at all like you would expect. I had a suspicion that there was some out of order back end processing going on and I assumed things were getting handled asynchronously, but your explanation makes much more sense. – ExecutionByFork Jan 07 '20 at 00:34
2

pBar.Value = 1 after the for loop causes the progress bar to not update

Perhaps the control hasn't been invalidated/updated causing the paint event to occur, test the theory with this code:

Try:

for (int j = 0; j < 100; j++) {
...
}

pBar.Value = 1
pBar.Invalidate()  //<-- cause a paint event

I discuss this in one of the Progress Bar Background Worker threads you mentioned. I know you're not interested in those, however running Progress Bars in a single thread is exacerbating the problem. While its bad to spawn Controls on threads other than the GUI/main thread, its good to do computations in background threads and Invoke the main thread to update controls.

A less optimal and quite a hacky solution you will see many people in single thread scenario's is Invalidate() the progress bar during the for loop causing a Paint_Event to redraw itself (once each 5 loops), eg:

VB

pBar.Value = j
If j Mod 5 = 0 Then pBar.Invalidate()

C#

pBar.Value = j;
if (j % 5 == 0) pBar.Invalidate();

It's better using a second thread for a smoother display, although a single thread progress bar is perfectly valid.

Putting pBar.Invalidate(); at the end of the loop should answer your question, I wanted to cover a bit about why its happening with an example you could relate to.


Also note some people prefer using Update:

Causes the control to redraw the invalidated regions within its client area.

Instead of Invalidate:

Invalidates the entire surface of the control and causes the control to be redrawn.

I'm using the latter in the case for troubleshooting the entire surface of the control.

Ref: MSDN ProgressBar Class

Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321
  • Last TIP: Don't bother with `pBar.PerformStep();` I have a feeling that's causing the issue. Instead set the Value 1-100 in the loop and use hacky Invalidate yourself - just to test if this is the root cause and not a symptom of the problem. – Jeremy Thompson Jan 06 '20 at 04:38
1

use timer tread to monitor ProgressBar value does it hit maximum (100) then reset it back to 0 when true .You also need to use this small trick to by pass Win7\10 slow rendering issue. I just made a VIDEO that can help you better understand the solution.

Here is the full code base on your original code

        private void button1_Click(object sender, EventArgs e)
    {
        timer1.Enabled=true;

        pBar.Visible = true;
        pBar.Step = 1;
        pBar.Minimum = 0;

        pBar.Maximum = 100;
        for (int j = 1; j < 101; j++)
        {
            // double pow = Math.Pow(j, j); //Calculation
            pBar.Value = j;
            pBar.Value = j-1;
            pBar.PerformStep();
            Thread.Sleep(20);
        }



    }

    private void timer1_Tick(object sender, EventArgs e)
    {
        if (pBar.Value == 100)
        {
            pBar.Value = 0;
            timer1.Enabled = false; // stop timer process
        }
    }
HO LI Pin
  • 1,441
  • 13
  • 13
  • Thank you for taking the time to make that video for me, I found a different way to get it working without a timer. However, this answer still leaves the question as to why setting `pBar.Value = 1` after the for loop causes the progress bar to not update. Do you know what the cause for this could be? – ExecutionByFork Jan 03 '20 at 03:50
  • multi tread , windows use different tread to control UX and processing that is why you cant put pBar.Value = 1 after the for loop as both process was not run sequentially – HO LI Pin Jan 04 '20 at 05:28