Other Projects
till.gif (646 bytes) Till  Lab Report
Count-Down Timer
ScreenTill.jpg (13467 bytes)

Purpose
The purpose of this project is to create a count-down timer to count down in days, hours, minutes, and seconds to any specified target date and time.

Materials and Equipment

Software Requirements
Windows 95/98
Delphi 3/4/5 (to recompile)
Till.EXE

Hardware Requirements
VGA display

Procedure

  1. Double click on the Till.EXE icon to start the program.
  2. Press the minimize button at the upper right to minimize the application.  While minimized, the remaining time will display in the taskbar.

TillTaskBar.jpg (1583 bytes)

  1. If desired, change the target time by pressing the Setup button.  Note the Target Date and Target Time are in your local time.  The time shown in the example below is for U.S. Eastern Time.  For the central time zone, the Target Time should be 11:00:00; for Mountain Time, the Target Time should be 10:00:00; and for the West Coast, the Target Time should be 9:00:00, etc.

ScreenTillSetup.jpg (24051 bytes)

ScreenTillClintonIsGone.jpg (14580 bytes)

  1. When the target time has been reached, the display will show a flashing "0 0:00:00", either in "normal" mode, or on the task bar.

ScreenTillElapsed.jpg (12025 bytes)

Discussion
Several actions are taken in the FormCreate method for TCountDownForm.   One of these actions is to establish OnMinimize and OnRestore methods for when the form is minimized:

Application.OnRestore := AppOnRestore;
Application.OnMinimize := AppOnMinimize;
DisplayState := wsNormal;

The TILL.INI file contains several important data times, including the Title, Target Time, Font Size, and the Form's Top and Left position properties.

// Default if no .INI file is present
TopMessage.Caption := 'Till Next Year';
TargetTime := EncodeDate(StrToInt(FormatDateTime('yyyy',Now))+1,1,1);

// Read .INI file with defaults
Filename := ChangeFileExt(ParamStr(0), '.INI');
IF   FileExists(Filename)
THEN BEGIN
  IniFile := TIniFile.Create(Filename);
  TRY
    Top := IniFile.ReadInteger(FORMSection, FORMTop, Top);
    Left := IniFile.ReadInteger(FORMSection, FORMLeft, Left);

    FontSize := IniFile.ReadInteger(OPTIONSSection,  OPTIONSFontSize,
                      Days.Font.Size);
    UpdateFontSize(FontSize);

    TopMessage.Caption := IniFile.ReadString(OPTIONSSection,OPTIONSTitle,
                                           'Till Next Year');
    DateTimeString := Inifile.ReadString(OPTIONSSection,OPTIONSTargetDateTime,'');

    IF   LENGTH(DateTimeString) > 0
    THEN TargetTime := StrToFloat(DateTimeString)
  FINALLY
    IniFile.Free
  END
END;

The FormClose updates the Top and Left properties in the TILL.INI in case the screen was moved.

The main work of the program is performed in the TimerTickTimer method of the TimerTick object:

procedure TCountDownForm.TimerTickTimer(Sender: TObject);
  VAR
    dd          :  Cardinal;
    Fraction    :  TDateTime;
    hh          :  WORD;
    Milliseconds:  WORD;
    mm          :  WORD;
    ss          :  WORD;
    TimeDiff    :  TDateTime;
begin
  TimeDiff := TargetTime - Now;
  IF   TimeDiff < 0
  THEN BEGIN
    UpdateNumericDisplay (0, 0, 0, 0);
    TimerTick.Enabled := FALSE;             // Turn off timer messages
    TimerDone.Enabled := TRUE;              // Separate processing when done
    DoneState := 0;
    // Use clRed to indicate time has elapsed
    Days.Font.Color    := clRed;
    Hours.Font.Color   := Days.Font.Color;
    Colon1.Font.Color  := Days.Font.Color;
    Minutes.Font.Color := Days.Font.Color;
    Colon2.Font.Color  := Days.Font.Color;
    Seconds.Font.Color := Days.Font.Color
  END
  ELSE BEGIN
    dd       := Trunc(TargetTime - Now);    // Full days remaining
    Fraction := Frac(TargetTime - Now);     // Fractional days remaining
    DecodeTime (Fraction , hh, mm, ss, Milliseconds);
    UpdateNumericDisplay (dd, hh, mm, ss);
  END
end; 

The UpdateNumericDisplay method updates the main form, or changes the caption shown on the Task Bar icon when the form is minimized.  A lot of extra logic is present to dynamically change the main form for different sizes of numeric values.

PROCEDURE TCountDownForm.UpdateNumericDisplay (CONST dddd:  Cardinal;
                                               CONST hh, mm, ss:  WORD);
  VAR
    j           :  INTEGER;
    WidthTitle   :  INTEGER;
    WidthNumerics:  INTEGER;
BEGIN
  IF   DisplayState = wsMinimized
  THEN BEGIN
    Application.Title := Format('%2d %2d:%2.2d:%2.2d', [dddd,hh,mm,ss]);
  END
  ELSE BEGIN
    // Use separate fields to avoid flicker when values don't change
    CountDownForm.Days.Caption    := Format('%d', [dddd]);
    CountDownForm.Hours.Caption   := Format('%d', [hh]);
    CountDownForm.Minutes.Caption := Format('%2.2d', [mm]);
    CountDownForm.Seconds.Caption := Format('%2.2d', [ss]);

    // Adjust geometry of labels -- vertical first
    // Top position of Days/Hours/Minutes/Seconds labels
    // Size of TopMessage is fixed.  Top of numeric labels is fixed.
    j := TopMessage.Height + Days.Height;
    DaysLabel.Top := j;
    HoursLabel.Top := j;
    MinutesLabel.Top := j;
    SecondsLabel.Top := j;
    CountDownForm.ClientHeight := j + DaysLabel.Height;

    // Adjust geometry of labels -- horizontally
    WidthTitle := SpeedButtonSetup.Width +
                  TopMessage.Width       +
                  SpeedButtonSetup.Width;
    WidthNumerics := Colon1.Width DIV 2 +
                     Days.Width         +
                     Colon1.Width       +
                     Hours.Width        +
                     Colon1.Width       +
                     Minutes.Width      +
                     Colon2.Width       +
                     Seconds.Width      +
                     Colon1.Width DIV 2;
    IF   WidthTitle > WidthNumerics
    THEN ClientWidth := WidthTitle
    ELSE ClientWidth := WidthNumerics;

    TopMessage.Left := (ClientWidth - TopMessage.Width) DIV 2;

    Days.Left    := Colon1.Width DIV 2;
    Hours.Left   := Days.Left + Days.Width + Colon1.Width;
    Colon1.Left  := Hours.left + Hours.Width;
    Minutes.Left := Colon1.Left + Colon1.Width;
    Colon2.Left  := Minutes.Left + Minutes.Width;
    Seconds.Left := Colon2.Left + Colon2.Width;

    DaysLabel.Left    := Days.Left    + (Days.Width    - DaysLabel.Width) DIV 2;
    HoursLabel.Left   := Hours.Left   + (Hours.Width   - HoursLabel.Width) DIV 2;
    MinutesLabel.Left := Minutes.Left + (Minutes.Width - MinutesLabel.Width) DIV 2;
    SecondsLabel.Left := Seconds.Left + (Seconds.Width - SecondsLabel.Width) DIV 2
  END
END {UpdateDisplay};

Before using the approach shown above, I considered using a Form.OnResize and adjusting the font so the text would fit.  I concluded setting the font and adjusting the size of all the TLabels was an easier approach.

The FormCreate of the FormSetup definition included special DateTime_SetFormat calls to establish a 4-digit year and a 24-hour clock with the two TDateTimePickers used on this form:

USES
  CommCtrl;  // DateTime_SetFormat
procedure TFormSetup.FormCreate(Sender: TObject);
begin
  // 4-digit year
  DateTime_SetFormat(DateTimePickerDate.Handle, pChar('M-dd-yyy'));
  // 24-hour clock
  DateTime_SetFormat(DateTimePickerTime.Handle, pChar('H:mm:ss'));
end;

See additional information about TDateTimePicker on the Delphi Dates and Times page.

A ShellExecute call is used to place a hyperlink to the Computer Lab on the Setup screen:

USES
  ShellAPI;  // ShellExecute
procedure TFormSetup.LabelLab2Click(Sender: TObject);
begin
  ShellExecute(0, 'open', pchar('http://www.efg2.com/Lab'), NIL, NIL, SW_NORMAL)
end;                                 

For the remaining details of this program, study the source code.

Conclusions
"Till" is a great utility for any clock watcher.


Keywords
TTimer, Now, TDateTime, TDateTimePicker, DateTime_SetFormat,  Application.OnRestore, Application.OnMinimize, Application.Title, Format, ClientWidth, ClientHeight, Trunc, Frac, DecodeTime, EncodeDate, TIniFile, WritePrivateProfileString, ShellExecute

Download
Delphi 3/4/5 Source and EXE (141 KB):  Till.ZIP


Updated 27 Nov 2002


since 5 Dec 1999