1

It seems that in windows 7, there is some animation that takes place when setting a progress bar's value. Setting the value does not seem to wait for the animation to complete. Is there a way to be notified of when the progress bar has finished animating?

I have a sample program. Please see the comments.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Threading;

namespace Testing
{
   static class Program
   {
      [STAThread]
      static void Main()
      {
         Application.EnableVisualStyles();
         Application.SetCompatibleTextRenderingDefault(false);
         var form = new Form1();
         form.Run();
      }
   }

   public partial class Form1 : Form
   {
      public Form1()
      {
         InitializeComponent();
      }

      public void Run()
      {
         Thread thread = new Thread
         (
            delegate()
            {
               ProgressBarMax = 10;
               ProgressValue = 0;

               for (int i = 0; i < 10; i++)
               {
                  Thread.Sleep(1000);
                  ProgressValue++;
               }
            }
         );
         EventHandler show = delegate
         {
            thread.Start();
         };

         Shown += show;
         ShowDialog();
         Shown -= show;
      }

      public int ProgressBarMax
      {
         set
         {
            Invoke
            (
               (MethodInvoker)
               delegate
               {
                  progressBar1.Maximum = value;
               }
            );
         }
      }

      public int ProgressValue
      {
         get
         {
            return progressBar1.Value;
         }
         set
         {
            Invoke
            (
               (MethodInvoker)
               delegate
               {
                  label1.Text = value.ToString();
                  progressBar1.Value = value;

                  // setting the value is not blocking until the
                  // animation is completed. it seems to queue
                  // the animation and as a result a sleep of 1 second
                  // will cause the animation to sync up with the UI
                  // thread.

                  // Thread.Sleep(1000); // this works but is an ugly hack

                  // i need to know if there is a callback to notify me
                  // when the progress bar has finished animating.
                  // then i can wait until that callback is handled 
                  // before continuing.

                  // if not, do i just create my own progress bar?
               }
            );
         }
      }
   }
}

My google kung foo seems to be dead today. Thanks.

Julien Lebosquain
  • 40,639
  • 8
  • 105
  • 117
Rodolfo Neuber
  • 3,321
  • 1
  • 19
  • 12
  • i just want the progress bar's value to be sync-ed up with the other ui components. there are 10 steps, by the time the 10th step is hit, the progress is displaying 90% instead of 100%. – Rodolfo Neuber Sep 18 '11 at 17:09
  • why are you calling form.Run instead of Application.Run(new Form1()); in the main method? things are not going to work in the same way if you do as you are doing... to my understanding Application.Run must be called by a windows forms application, _always_ !! – Davide Piras Sep 18 '11 at 20:20
  • to davide: this is a sample application to show the issue. the run method could have easily been placed in the program class. i don't really care about Application.Run; i know better. The problem is that the progress bar renders some time after `progressBar1.Value = value;` is set. – Rodolfo Neuber Sep 18 '11 at 22:31
  • to gertarnold: i don't normally care about being notified about what a progress bar does, however, windows 7 is rendering things badly on a different thread. i have two options: 1) implement my own progress bar--which is rediculous, or 2) figure out a simple mechanism to wait until the progress bar has finished rendering the updated value. Its not my fault microsoft did something silly, but i have to deal with it now. – Rodolfo Neuber Sep 18 '11 at 22:39

3 Answers3

8

In the end this is the same issue as C# progress bar not synced with download (WebClient class).

Also this issue is similar to ProgressBar is slow in Windows Forms. The solution here is to move the value forward and back, i.e. progressBar1.Value = value + 1; progressBar1.Value = value;. The first update begins the animation sequence, while the second update forces the animation sequence to stop early. Its a good fix but can introduce problems that are hard to reproduce. It just doesn't seem like there is a real solution.

Finally, Disabling progress bar animation on Vista Aero suggests to turn off themes. This seems to work, but the progress bar loses its look and feel. Ultimately, the best thing to do is just make my own progress bar.

Microsoft should just make the animation an option instead of forcing developers to re-invent the progress bar.

Community
  • 1
  • 1
Rodolfo Neuber
  • 3,321
  • 1
  • 19
  • 12
  • 1
    This works, but not for the final change to 100%, since setting value higher than maxvalue gives an out of range exception. The dumb thing is that there is a `MarqueeAnimationSpeed` setting meant exactly to specify the update delay, but it seems to have no effect on this. – Nyerguds Aug 27 '14 at 10:13
  • 2
    I found a workaround for the last value: set the maximum of the progressbar to the maximum value + 1, then set the value to that same maximum, then set the maximum back to its original maximum value. This will trigger an immediate update while the bar is at 100% – Nyerguds Aug 27 '14 at 10:24
2

This did the trick for me:

if (progressBar1.Value <= progressBar1.Maximum - 2)
{
    progressBar1.Value += 2;
    progressBar1.Value--;
}                    
else if (progressBar1.Value <= progressBar1.Maximum)
{                    
    progressBar1.Maximum--;                   
}   

It updates the progress bar pretty quick and takes care of the last step to update it to 100%. Hope it helps someone.

Luis G
  • 21
  • 1
  • Adjusting MAX-- has some sense - probably internal renderer rescales the colored bar instead of firing animation. However, I have no idea why +=2 and -- actually does the trick, but hell, it does, at least on Windows10. That is truly insane. – quetzalcoatl Mar 28 '18 at 20:46
  • I got it. Damn, I got it. With +=2 -=1 trick it is about not doing animations when progressbar's state is decreased. The dreadful animation **is only applied on increasing** the value. Decreasing value is redendered instantly, with no delay. – quetzalcoatl Mar 28 '18 at 20:52
  • This has to be the stupidist fix, but it works!!! Really appreciate it. Thanks. This also applies to VB.NET as well. – vr_driver Oct 20 '21 at 14:04
1

Thanks to LuisG's answer, I noticed that the "filling animation" in ProgressBar is applied only on when actually increasing the value. When decreasing the value the animation is not used and the bar is updated instantly.

This allows us to exploit it without using several conditions - we just need to ensure that the last Value update that sets our desired value is an act of decreasing the Value:

var tmp = progress.Maximum;
progress.Maximum = int.MaxValue;
progress.Value = int.MaxValue;
progress.Value = desiredValue;
progress.Maximum = tmp;

Above trick works because increasing the MAX and VALUE triggers the animation, so it is NOT rendered instantly. Immediately then we decrease the value, and that omits the (not yet performed) animation and renders new state. Coincidentally, that's the state we wanted in the first place.

Above trick works only if desiredValue is less than int.MaxValue. If you want full integer range, you must use LuisG's trick with detecting the final case of 100% and doing Maximum-- instead of raising the Value.

quetzalcoatl
  • 32,194
  • 8
  • 68
  • 107