A Clock Component for Delphi

The Complete Source Code...

The listing is the complete source code to just such a component. The new component class is TClock, and it is derived from TCustomLabel. TCustomLabel is essentially a fully-functional TLabel except that all of its properties are defined in the Protected section rather than the Published section. This means that none of the properties appear in the Object Inspector but are available for use in objects that descend from it. TLabel is simply a descendant of TCustomLabel that moves all these property declarations from the Protected to the Published section.

Our TClock component is the same. It descends from TCustomLabel and publishes all the same properties that TLabel does, with the exception of the Caption property. We need to do it this way (rather than descend from TLabel), because a property cannot be hidden once it has been published by an ancestor.

Indeed, this is the reason that TCustomLabel exists at all: to allow new descendants to be created that do not publish all its properties.

In addition, we have defined three new published properties OnTimer, Interval and DisplayFormat.

We have also defined a private object field called Timer of type TTimer. This is a private timer that will be used to keep the time updated. In the overridden Constructor for the component, we create an instance of a TTimer component and assign it (or actually a pointer to it) to our Timer field. We also set its OnTimer event to point to our own private method InternalTimerEvent. In that procedure, we set our Caption to the current time formatted according to the DisplayFormat property, which is a string as required for the FormatDateTime built-in function that we initialise in our constructor.

  unit Clocku;
  interface
  uses
    SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, StdCtrls, ExtCtrls, Menus;
  type
    TClock = class(TCustomLabel)
    private
    {Private declarations}
    Timer : TTimer;
      FOnTimer : TNotifyEvent;
      FDisplayFormat : String;
        procedure Internal
        TimerEvent(Sender : TObject);
      procedure SetInter
  val(I: word);
      function  GetInterval: word;
      procedure SetDisplay
  Format(F : String);
    protected
      { Protected declarations }
    public
      { Public declarations}
      constructor Create 
  (AOwner: TComponent); 
  override;
      destructor Destroy; override;
    published
  { Published declarations }
      property Align;
      property Alignment default taCenter; {Tell OI of new default}
      property AutoSize;
  {   property Caption;  (* commented out to avoid publishing *) }
      property Color;
      property DragCursor;
      property DragMode;
      property Enabled;
      property FocusControl;
      property Font;
      property ParentColor;
      property ParentFont;
      property ParentShowHint;
      property PopupMenu;
      property ShowAccelChar;
      property ShowHint;
      property Transparent;
      property Visible;
      property WordWrap;
      property OnClick;
      property OnDblClick;
      property OnDragDrop;
      property OnDragOver;
      property OnEndDrag;
      property OnMouseDown;
      property OnMouseMove;
      property OnMouseUp;
      property OnTimer : TNotifyEvent read FOnTimer 
  write FOnTimer;
      property Interval : Word  read GetInterval write SetInterval;
      property DisplayFormat : string read FDisplayFor
  mat write SetDisplayFor
  mat;
    end;
  procedure Register;
  implementation
  procedure TClock.InternalTimerEvent   (Sender: TObject);
  begin 
    Caption := FormatDate
  Time(DisplayFormat,Now);
    If Assigned(FOnTimer) Then FOnTimer(Self);
  end;
  procedure TClock.SetInterval
  (I : word);
  begin
    Timer.Interval := I;
  end;
  function TClock.GetInterval: word;
  begin
    Result := Timer.Interval;
  end;
  procedure TClock.SetDisplayFormat(F : String);
  begin
    If F <> FDisplayFormat then begin
       FDisplayFormat := F;
       Caption := FormatDateTime(DisplayFormat,
  Now);
       end;
  end; 
 constructor TClock.Create(AOwner: TComponent);
  begin
    inherited Create(AOwner);
    Alignment := taCenter; { implement new default }
    DisplayFormat := 'h:nn ampm';
    Caption := FormatDateTime(DisplayFormat, 
  Now);
    Timer := TTimer.Create(Self);
    Timer.OnTimer := InternalTimerEvent;  
  end;
  destructor TClock.Destroy; 
  begin
    Timer.Free;
    inherited Destroy;
  end;
  procedure Register;
 begin
  RegisterComponents('Samples', [TClock]);
  end;
  end.

Note how the Caption property is still available for us even though it is not available to the Object Inspector. It will not, however, be accessible by the end user.

As we created the timer, we must be sure to destroy it. This is done in the overridden Destructor method using a call to Timer.Free before calling our inherited destructor.

We have already seen how the DisplayFormat property is used to control how the time will be displayed. The format string itself is stored in FDisplayFormat, and we use a write procedure (SetDisplayFormat) to trigger an immediate update of the caption when the format string is changed.

The Interval property uses the SetInterval procedure and GetInterval function to actually store and retrieve the Interval directly to and from the Timer.

The OnTimer property is an event property, of type TNotifyEvent. The read and write parts of this property definition cause the Object Inspector to store the address of any user-defined event handler in the FOnTimer field of the object. Event properties almost always use this form of property definition. When the event occurs, we check the related object field and, if it is assigned, we call it. This is what the line:

  If Assigned(FOnTimer) then FOnTimer(Self); 

in InternalTimerEvent does. This allows the users of TClock to use our component for their own timer-related processing without the need to use another Timer.

The only other thing to notice about this control is the handling of the default value for Alignment. TCustomLabel has an Alignment property with a default value of taLeftJustify. We reassign the default to taCenter because this is what we will want it to be most times. To do this, we need to do two things. First we specify the directive default taCenter when we publish the Alignment property. This does not actually change the default — it simply tells the Object Inspector that whenever a new TClock component is created, it starts out with an Alignment property with a value of taCenter. Using this information, the Object Inspector can determine whether to bother streaming out the property to the DFM file. This makes the resulting EXE smaller and the control loads faster. The actual assignment of the default value is done in the overriden Create constructor. It is very important that the constructor actually set whatever default values you specify in property declarations.


Image of arrow to previous article

Image of line
[HOME] [TABLE OF CONTENTS] [SEARCH]