Rodolfo Neuber
Rodolfo Neuber

Reputation: 3451

Progress bar value is not synchronized with what gets rendered in windows 7

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.

Upvotes: 1

Views: 7349

Answers (3)

quetzalcoatl
quetzalcoatl

Reputation: 33536

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.

Upvotes: 1

Luis G
Luis G

Reputation: 21

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.

Upvotes: 2

Rodolfo Neuber
Rodolfo Neuber

Reputation: 3451

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.

Upvotes: 8

Related Questions