TProgressBar Not Updating Fast Enough?

progressbar-slow-update
Do users of your Delphi application actually see the progress bar moving?

The TProgressBar control provides visual feedback about the progress of some actions within your application. The Position, Max and Min properties determine the current position of the progress bar within its minimum and maximum values.

In most processing situations of some kind, where the progress bar is used, I have code that looks like:

ProgressBar1.Max := NUMBER_OF_ELEMENTS;
try
  for i  := 1 to NUMBER_OF_ELEMENTS do
  begin
    begin
     // do some operation
    end;

    //update progress bar
    ProgressBar1.StepIt; //if Step = 1
    //OR
    ProgressBar1.Position := i;
  end;
finally
  ProgressBar1.Position := 0;
end;

I guess the above way of using a progress bar is also what you have in your application. Set the Max value, run some loop, update the progress bar position to notify the user of the progress of the loop.

Note that after the operation is finished the progress bar position is set to zero, to make it ready for the next operation requiring visual feedback where the same progress bar control is used. In fact, I have a single progress bar positioned in the status bar of the main form of my application – this single progress bar is used for all progress-type actions.

Now, would you expect, using the above code, to actually see the progress bar progressing?

The answer to that questions depends on two other questions: how fast will the loop finish and on what version of Windows (themed or not) the application is being run!

The TProgressBar as implemented in Delphi (as the case is with most other controls) is a direct implementation of the Windows common control – and therefore Windows (the operating system) is responsible for painting it.

Progress Animation on Vista, Windows 7, 8 ….

If your application is run under Windows Vista or Windows 7 (and later) with Aero enabled, the operating system draws the progress bar in a way that it smoothly scrolls from the previous position to the new position. Since this painting takes time, it can take more time then needed in your code to step to the next position.

As a result, and depending on the time needed to process your loop, you might notice that the progress bar is sometimes not painted at all, sometimes it will go to 30%, sometimes to 80% (whatever number or position).

Here’s a real code example you can use to see this (should be running on Windows 7, 8 or Vista with Aero):

procedure TProgressForm.Button1Click(Sender: TObject);
var
  dirFiles : TStringDynArray;
  aFile : string;
begin
  dirFiles := TDirectory.GetFiles('c:\SomeFolderWithPasFiles');

  ProgressBar1.Max := Length(dirFiles);
  ProgressBar1.Position := 0;

  for aFile in dirFiles do
  begin
    if TPath.MatchesPattern(ExtractFileName(aFile),'*.pas',false) then
    begin
      //do something with .PAS files that could take time
    end;

    ListBox1.Items.Add(aFile);

    ProgressBar1.Position := 1 + ProgressBar1.Position;
  end;

  ProgressBar1.Position := 0;
end;

The code loops through all the files in a specified folder and places their names in a list box, a progress bar is used for the visual feedback. If a file is a .PAS file some additional action will be done (whatever). Depending on the number of files and the number of .PAS files (and what you would do to them) it might appear that the progress bar is not being painted at all or it will reach some random position before being reset to position zero (initial position).

Similarly, if the progress bar is at 0% and you set it to 75% then 100% (very fast) – the progress bar does not “jump” to that position – it “slowly” and smoothly fills in its area – possibly too slow for the purpose!.

ProgressBar.ProperlyPaint!

Now, enough with the intro to the problem. The thing is that the way to solve the painting problem is rather simple and involves a trick.

When you set the progress position backwards the (slow) animation does not take place, rather the progress bar jumps quickly to that position.

Having this in mind, I have a simple procedure “ProgressBarStepItOne” implemented as:

procedure TProgressForm.ProgressBarStepItOne;
begin
  ProgressBar1.StepBy(1);
  ProgressBar1.StepBy(-1);
  ProgressBar1.StepBy(1);

  //same as
  (*
    ProgressBar1.Position := 1 + ProgressBar1.Position;
    ProgressBar1.Position := -1 + ProgressBar1.Position;
    ProgressBar1.Position := 1 + ProgressBar1.Position;
  *)
end;

And my code looking as below, ensures the visibility of the progress bar actually moving even when the time to process something is less than it would be needed for Windows to paint (animate) the progress bar moving from 0 to 100!

procedure TProgressForm.Button1Click(Sender: TObject);
var
  dirFiles : TStringDynArray;
  aFile : string;
begin
  dirFiles := TDirectory.GetFiles('C:\SomeFolderWithPasFiles');

  ProgressBar1.Max := Length(dirFiles);
  ProgressBar1.Position := 0;

  for aFile in dirFiles do
  begin
    if TPath.MatchesPattern(ExtractFileName(aFile),'*.pas',false) then
    begin
      //do something with .PAS files that could take time
    end;

    ListBox1.Items.Add(aFile);

    ProgressBarStepItOne;
  end;

  ProgressBar1.Position := 0;
end;

If you use the TProgressBar’s StepIt procedure to advance the Position property with the current value of M by some value > 1, let’s say N , make sure you then set it to M+N-1 and back to M+N.

Now, is that a trick you would never think of or?

8 thoughts on “TProgressBar Not Updating Fast Enough?

  1. himselfv

    Yep, stumbled into this just some weeks ago. In the end I decided that as long as what I’m passing is in all senses right, it’s Windows fault for not displaying it properly. Any hacks I implement might turn into something even worse on, say, Windows 9 where progress bar is rendered yet again differently.

    Reply
  2. Stuart

    I use:


    ProgressBar1.Position := aValue;
    ProgressBar1.Update ;

    and haven’t noticed any weird behaviour. Maybe I’m just lucky ?

    Reply
    1. zarkogajic Post author

      Stuart, I guess you are lucky, as this does not work work for me (Win 7, Aero, fast loop, setting position to 0 when “finished”).

      Reply
  3. CCRDude

    If you refresh that often that the progress bar cannot follow in time, I would think about the concept, not about tricks. And calling Update is just another dirty trick. These unnecessarily delay the whole process, which by its nature (otherwise you wouldn’t need a progress bar) is already taking precious time from the user.

    Why waste that much CPU time on the UI, plus let the user wait longer?

    Put your processing code into a thread (that’s what they are for!) instead, update a TMultiReadExclusiveWriteSynchronizer (or similar) with the status, and let the UI pull status update once every 100 or 250 ms. That way, your processing won’t take longer than necessary and the UI still gets updated, without too much flicker or CPU just for that.

    Reply
  4. Wodzu

    Thanks for the tip Zarko 🙂 I am glad to see your blogging!
    I might stubled on this problem a year ago or so. Personally I hate progress bars that provide false information to the user. (It is common under Windows to notice a progress bar that goes to 99% or even 100% and stays at this point for ages. ) \

    My rule of thumb is to not show progress bar when I am not able to divide my process to given amount of parts. In such case it is better to notice unser in different way.

    Reply
  5. grzesiekyogi

    Continuing your topic in new delphi you can use class helpers for changing position property. In this way you can use standard component, but animation will be off:

    type
    test = class helper for TProgressBar
    private
    function getpos: integer;
    procedure setpos(value: integer);
    public
    property position: integer read getpos write setpos;
    end;

    function test.getpos: integer;
    begin
    Result:= inherited Position;
    end;

    procedure test.setpos(value: integer);
    begin
    inherited position:= value;
    StepBy(-1);
    StepBy(1);
    end;

    Reply
  6. grzesiekyogi

    Continue your ‘dirty trick’ you can use class helper (in delphi > 7) to modify position property. In this way you can use standard component, but it will be modified by mentioned trick.

    unit Unit1;

    interface

    uses
    Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
    Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.StdCtrls;

    type
    test = class helper for TProgressBar
    private
    function getpos: integer;
    procedure setpos(value: integer);
    public
    property position: integer read getpos write setpos;
    end;

    TForm1 = class(TForm)
    ProgressBar1: TProgressBar;
    Button1: TButton;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
    private
    { Private declarations }
    public
    { Public declarations }
    end;

    var
    Form1: TForm1;

    implementation

    {$R *.dfm}

    { test }

    function test.getpos: integer;
    begin
    Result:= inherited Position;
    end;

    procedure test.setpos(value: integer);
    begin
    inherited position:= value+1;
    if value max then
    begin
    StepBy(-1);
    StepBy(0);
    end else
    begin
    StepBy(-1);
    StepBy(1);
    end;
    end;

    procedure TForm1.Button1Click(Sender: TObject);
    var
    licznik: integer;
    begin
    for licznik := 1 to 10 do
    begin
    progressbar1.position:= licznik*10;
    Edit1.Text:= IntToStr(progressbar1.position);
    sleep(100);
    end;
    end;

    end.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *