Link to home
Start Free TrialLog in
Avatar of QC20N
QC20NFlag for Denmark

asked on

Timer and a ProgressBar

I have a timer on my form. The timerinterval is 5 min. I would like to have a progressbar that show me when it is time to run my procedure. You know, when the time reached 5 min.
Avatar of Hypo
Hypo
Flag of Sweden image

Since a Timer only fires when the timeout has expired, and you want it synced with a progress bar, you have to set a lower timeout on your timer, and then count the "ticks" of the timer until the correct timout is met while also updating the progress bar.

You can actually set the Max value of the Progressbar to the number of seconds you want on your actual timeout, and then use that as the trigger for your event.

So, set the timer timout to 1000 (1 sec) and then set Max value of your progress bar to 300 (5 mins = 300 sec), and use the Event from the snipet below for a small example.

/Hypo


procedure TForm1.Timer1Timer(Sender: TObject);
begin
  // Increase the ProgressBar...
  ProgressBar1.StepBy(1);
 
  // Check the progressBar...
  if ProgressBar1.Position >= ProgressBar1.Max then begin
    // Do your stuff here...
    Memo1.Lines.Add('5 minutes passed. Do your stuff');
 
    // Reset the ProgressBar for next tick
    ProgressBar1.Position := ProgressBar1.Min;
  end;
end;

Open in new window

While Hypo's approach will work, I would probably have a slightly different approach.  I'd set up a second timer that drove the ProgressBar (again using a 1 second timer interval) but I'd leave the first one with its 5 minute (300 secconds or 300,000 "ticks") setting.  When the 5 minute timer goes off, the first step of the event handler would be to disable both timers and then set the ProgressBar's progress to 300 (i.e. force it to completion ;-).
This avoids a slight discrepancy in the timer that would develop over the 300 events from the 1 second timer.  It also lets you use the second timer and the progress bar for other activities as needed (when the 5 minute timer isn't running ;-).
ASKER CERTIFIED SOLUTION
Avatar of Geert G
Geert G
Flag of Belgium image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of QC20N

ASKER

I forgot to tell you, that I need a checkbox to start the timer with. And if I uncheck the checkbox the timer should not stop, but keep on goind to the end of my procedure and then stop.

How do I accomplish that?
hmmm, it fires 2 times in the end

procedure TMyThread.Execute;
var
  Start: TDateTime;
  Interval: Int64;
  Temp: integer;
begin
  Start := Now;
  Interval := 300; // 5 * 60
  Progress(0);
  repeat  
    Sleep(500);
    Temp := Integer(SecondsBetween(Now, Start) div 3); // Seconds / 300 * 100 = procent
    Progress(Temp);
    if Temp >= 300 then
    begin
      Start := Now;
      Progress(-1);
    end;
  until Terminated;
end;

procedure TForm1.UpdateProgressBar(aProgress: Integer);
begin
  ProgressBar1.Position := aProgress;
  ProgressBar1.Update; // Make sure to repaint the progressbar
  if aProgress = -1 then
    PostMessage(Handle, WM_USER, 0, 0);
end;
type
  TForm1 = class(TForm)
    Checkbox1: TCheckbox;
    procedure Checkbox1Click(Sender: TObject);
  private
    fStopWhenFinished: boolean;
  end;

procedure TForm1.Checkbox1Click(Sender: TObject);
begin
  if TCheckBox(Sender).Checked then
  begin
    fStopWhenFinished := False;
    if not Asisgned(fMyThread) then
      fMyThread := TMyThread.Create(UpdateProgressBar);
  end else
    fStopWhenFinished := True;
end;

procedure TForm1.UpdateProgressBar(aProgress: Integer);
begin
  ProgressBar1.Position := aProgress;
  ProgressBar1.Update; // Make sure to repaint the progressbar
  if aProgress = -1 then
  begin
    PostMessage(Handle, WM_USER, 0, 0);
    if fStopWhenFinished then
    begin
      fMyThread.Terminate;
      fMyThread := nil;
    end;
  end;
end;
Re: Checkbox
When the user checks the box, in that event handler, start the timer if the checkbox is checked and ignore it if the checkbox is unchecked.  THe question then arises, though, if I check the box, wait 20 seconds before unchecking the box, and then wait another 40 seconds before checking the box again, should the timer restart when I check it again?
Re: Form Freezing
Guys, "sleep" is not a Timer.  A TTimer is a Timer.  If you use a TTimer component, you can start the timer and then go on with whatever you are doing with the form.  THe TTimer's event will interrupt things when it goes off and that will let you do whatever needs to be done based on the Timer's alarm going off, so to speak.
"Sleep" is a form of a "Let's pause for an interval, doing nothing, and then keep going."  For something like a 5-minute interval, a "Sleep" is a seriously poor solution.  You need to investigate the TTimer component.
First make sure that your Timer is disabled by defualt.
Then add a checkbox to your form, and set the OnClickEven of the checkbox to enable the timer if the box is checked.
Finally, in the OnTimer event, when the progress bar is full (when your 5 minutes have passed) you simply disable the timer if the checkbox is not set.

see sample below...

/Hypo
unit Unit1;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, ExtCtrls, StdCtrls;
 
type
  TForm1 = class(TForm)
    Timer1: TTimer;
    ProgressBar1: TProgressBar;
    Memo1: TMemo;
    CheckBox1: TCheckBox;
    procedure Timer1Timer(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.dfm}
 
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  // Increase the ProgressBar...
  ProgressBar1.StepBy(1);
 
  // Check the progressBar...
  if ProgressBar1.Position >= ProgressBar1.Max then begin
    // Do your stuff here...
    Memo1.Lines.Add('5 minutes passed. Do your stuff');
 
    // Reset the ProgressBar for next tick
    ProgressBar1.Position := ProgressBar1.Min;
 
    // The timer should not continue if Checkbox is not checked...
    Timer1.Enabled := CheckBox1.Checked;
  end;
end;
 
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  if CheckBox1.Checked then
    Timer1.Enabled := True;
end;
 
end.
 
 
--- DFM ---
 
object Form1: TForm1
  Left = 192
  Top = 114
  Width = 276
  Height = 252
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object ProgressBar1: TProgressBar
    Left = 8
    Top = 40
    Width = 150
    Height = 17
    Min = 0
    Max = 5
    TabOrder = 0
  end
  object Memo1: TMemo
    Left = 8
    Top = 64
    Width = 185
    Height = 89
    Lines.Strings = (
      'Memo1')
    TabOrder = 1
  end
  object CheckBox1: TCheckBox
    Left = 8
    Top = 8
    Width = 97
    Height = 17
    Caption = 'Enable timer'
    TabOrder = 2
    OnClick = CheckBox1Click
  end
  object Timer1: TTimer
    Enabled = False
    OnTimer = Timer1Timer
    Left = 8
    Top = 8
  end
end

Open in new window

Hypo,
I actually thought that was the process being discuessed in the first place. ;-)  
In your case, you have a single TTimer set to handle the 5 minute interval; however, that won't handle the progress bar.  If, as I suggested, a second TTimer (Timer2) is dropped on the form and its interval set to 1000 (i.e. 1 second), then Timer2 can be used to control the progress indicated by the ProgerssBar.  When Timer1 goes off, the event handler could disable Timer2 and force the max setting for the ProgressBar.  If Timer2 causes teh Progress Bar to reach the max setting before Timer1 goes off, then Timer2 can disable itself (because Timer1 should be firing momentarily ;-).
Oh, I just replied to QC20N's remark about the check box, but I'm at work so it took some time before I could post it, and so I didn't see your discussion... :)

At first I was thinking about using two timers as well, but I didn't like that the direct correlation between the timer and the progressbar got broken with two timers.

Then my only aproach to this problem is to make the code as easy to understand as possible (for the authors sake), and I think one timer is somewhat easier to understand than two timers (that could be debated though), but it's definatley easier than adding threads (although it's an interesting solution).

/Hypo
Hypo,
No worries. ;-)
I have done a very similar thing to this in the past and it is truly amazing how much the little bit of code that moves the progress bar and re-enables the timer adds to the over all execution time of the 5 minute wait.  That is why I would (as I did in the past) put the second timer in the picture.  The first timer handles the "real" task of the 5-minute wait while the second timer handles the "cosmetic" task of moving the progress bar.  
In the situation I was in in the past, I reused both timers and the progress bar for a variety of purposes but, by having the second timer set to a 1 second interval, the proress bar handling was reduces to setting the max, resetting the progress to 0, and enabling the timer.  Similarly, the primary timer's interval could be set for whatever was needed and then the Tag property set to an integer that would be tested in the event handler (Good old Case statements ;-) to process whatever was needed.
>>although it's an interesting solution
lol, i thought my posts didn't get noticed
Avatar of QC20N

ASKER

Geert:
Will it be possible to use a timer without using threads? As you know, I have another question here on EE about threads. I have looked this throug and thougt I might could use timers instead, cause my procedure only needs to run every 8th hour, so it will be 3 times on one day. And as you also know I can't figure out how threads works, sorry.

In fact in the end, my procedure is going to be run as a service and pleas don't tell me that it still needs to run as a thread. :)
QC20N,
If you need to have your process run every 8 hours, just use the Windows Task Scheduler and set it up to run at those times/that interval on the days you want it to run.
If you use a timer (or, heaven forbid, "Sleep") it is still going to consume resources because, technically, it is still running.
Using Timers doesn't require the use of threads, by the way . . . they sort of handle that aspect on their own. ;-)
>>going to be run as a service
ow, then let me show you this sample:
http://www.tolderlund.eu/delphi/service/service.htm

i used it for myself yesterday and i found it covered everything,
even logging in the windows event logger

>>pleas don't tell me
um yeah, in fact a service is a thread doing a loop processing messages until stop or finished
oh and if you want to run the service, you need to put /INSTALL in the run parameters
building a service is actually the step after you master creating/running threads

have you ever created a service application ?
if you create the thread in your other app and have it as standalone unit,
then creating the service app will only be a matter of starting and stopping that thread

i'm sorry to QC20N, but if you want to get somewhere with this,
you'll have to try and learn yourself threading in delphi
or find someone to do it for you
problem with service applications is that you MUST handle all exceptions....
regarding the question:
First create a procedure to set off the timer (Timer1):

.....
 private
  procedure Blink;
 { Private declarations }
  public
 { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.dfm}

procedure TForm1.Blink;
begin
Timer1.Enabled := True
end;

Now for the timer :

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  ProgressBar1.Visible:=True; //progres bar should be invisible at first

  Randomize;
  if Random(1) = 0 then ProgressBar1.Position:=ProgressBar1.Position+1;
  if (ProgressBar1.Position = 20) then   //use  Max
  begin
    Timer1.Enabled := False;
    ProgressBar1.Visible:=False;
    ShowMessage('Progress Bar Finished');
  end;

end;

Now to trigger off the timer1 use timer2.Set it to run at right interval:

procedure TForm1.Timer2Timer(Sender: TObject);
begin
ProgressBar1.Position:=0;
Blink;


hmmm ... service applications don't have forms (or progressbars ...)

so all the help we are providing needs to be reprogrammed anyway
it would be better to start in the service direction straight away

otherwise you will have to rethink nearly all your code again
Avatar of QC20N

ASKER

Reason for Partially is more my lack of knowledge, but I'm sure I will find out. :)