Solved

Timer and a ProgressBar

Posted on 2009-09-29
18
3,552 Views
Last Modified: 2012-08-13
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.
0
Comment
Question by:QC20N
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 6
  • 5
  • 3
  • +2
18 Comments
 
LVL 12

Expert Comment

by:Hypo
ID: 25448895
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

0
 
LVL 22

Expert Comment

by:8080_Diver
ID: 25449019
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 ;-).
0
 
LVL 37

Accepted Solution

by:
Geert Gruwez earned 125 total points
ID: 25449045
a timer will only fire when the time is reached

i copied the code from my article and adjusted it

it uses a thread to update a progressbar so your form doesn't freeze
and adds a line to a memo after every interval of 5 minutes
unit Unit1; 
 
interface 
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, StdCtrls; 
 
type
  TProgressProc = procedure (aProgress: Integer) of object; 
 
  TProgressThread = class(TThread)
  private
    FProgressProc: TProgressProc;
    FProgressValue: integer;
    procedure SynchedProgress;
  protected
    procedure Progress(aProgress: integer); virtual;
  public
    constructor Create(aProgressProc: TProgressProc; CreateSuspended: Boolean = False); reintroduce; virtual;
  end; 
 
  TMyThread = class(TProgressThread)
  protected
    procedure Execute; override;
  end; 
 
  TForm1 = class(TForm)
    btnStart: TButton;
    Memo1: TMemo;
    ProgressBar1: TProgressBar;
    procedure btnStartClick(Sender: TObject);
  private
    fMyThread: TMyThread;
    procedure UpdateProgressBar(aProgress: Integer);
    procedure CatchStartProc(var Msg: TMessage); message WM_USER;
    procedure RunProc; 
  end; 
 
var
  Form1: TForm1; 
 
implementation 
 
{$R *.dfm} 
 
{ TProgressThread } 
 
constructor TProgressThread.Create(aProgressProc: TProgressProc; CreateSuspended: Boolean = False);
begin
  inherited Create(CreateSuspended);
  FreeOnTerminate := True;
  FProgressProc := aProgressProc;
end; 
 
procedure TProgressThread.Progress(aProgress: Integer);
begin
  FProgressValue := aProgress;
  Synchronize(SynchedProgress);
end; 
 
procedure TProgressThread.SynchedProgress;
begin
  if Assigned(FProgressProc) then
    FProgressProc(FProgressValue);
end; 
 
{ TMyThread } 
 
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);
  until Terminated;
end; 
 
{ TForm1 } 
procedure TForm1.UpdateProgressBar(aProgress: Integer);
begin
  ProgressBar1.Position := aProgress;
  ProgressBar1.Update; // Make sure to repaint the progressbar
  if aProgress >= 100 then
    PostMessage(Handle, WM_USER, 0, 0);
end; 
 
procedure TForm1.btnStartClick(Sender: TObject);
begin
  if not Assigned(fMyThread) then
    fMyThread := TMyThread.Create(UpdateProgressBar);
end; 
 
procedure TForm1.CatchStartProc(var Msg: TMessage); 
begin
  RunProc;
end;
 
procedure TForm1.RunProc; 
begin
  Memo1.Lines.Add('Running procedure');
end;
 
end.

Open in new window

0
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 

Author Comment

by:QC20N
ID: 25449070
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?
0
 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 25449082
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;
0
 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 25449175
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;
0
 
LVL 22

Expert Comment

by:8080_Diver
ID: 25449227
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.
0
 
LVL 12

Expert Comment

by:Hypo
ID: 25450256
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

0
 
LVL 22

Expert Comment

by:8080_Diver
ID: 25451073
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 ;-).
0
 
LVL 12

Expert Comment

by:Hypo
ID: 25451196
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
0
 
LVL 22

Expert Comment

by:8080_Diver
ID: 25451703
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.
0
 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 25451709
>>although it's an interesting solution
lol, i thought my posts didn't get noticed
0
 

Author Comment

by:QC20N
ID: 25453832
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. :)
0
 
LVL 22

Expert Comment

by:8080_Diver
ID: 25453872
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. ;-)
0
 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 25455898
>>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
0
 
LVL 22

Expert Comment

by:senad
ID: 25466249
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;


0
 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 25466255
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
0
 

Author Closing Comment

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

Featured Post

Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

The uses clause is one of those things that just tends to grow and grow. Most of the time this is in the main form, as it's from this form that all others are called. If you have a big application (including many forms), the uses clause in the in…
Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
In this video, viewers are given an introduction to using the Windows 10 Snipping Tool, how to quickly locate it when it's needed and also how make it always available with a single click of a mouse button, by pinning it to the Desktop Task Bar. Int…
Monitoring a network: why having a policy is the best policy? Michael Kulchisky, MCSE, MCSA, MCP, VTSP, VSP, CCSP outlines the enormous benefits of having a policy-based approach when monitoring medium and large networks. Software utilized in this v…

724 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question