Jump to content


Check out our Community Blogs

Register and join over 40,000 other developers!


Recent Status Updates

View All Updates

Photo
- - - - -

Digital Clock Control

timer clock

  • Please log in to reply
2 replies to this topic

#1 Luthfi

Luthfi

    CC Leader

  • Expert Member
  • PipPipPipPipPipPipPip
  • 1320 posts
  • Programming Language:PHP, Delphi/Object Pascal, Pascal, Transact-SQL
  • Learning:C, Java, PHP

Posted 21 December 2011 - 05:36 AM

In this tutorial we will learn the basics of creating new custom control. For exercise example, we will try to build a custom control simulating a digital clock.

What to do/code in our custom control?
In our digital clock control we want to address the following problems, consider them our project requirements.
  • We want to show the current time, down to seconds precision
  • We want to show background image to achieve closer look and feel to real digital clock.
  • We want to be able to adjust the location of the text

Before proceed with doing actual coding, we should consider which class is the most appropriate for our control. in VCL, in developing custom control we usually have two options for the base class. They're:

The main differences between the two are that TWinControl descendants have the ability to contain another control and also able to receive focus. Since none of our requirements require any of those, we can safely choose TGraphicControl for our base class.

[1]Base class: TGraphicControl.

Drawing
Analyzing the requirements, we can summary the task as:

Draw the background image, if available, to the screen, and then draw the current time on top of it.


To properly draw something with TGraphicControl, we want to do it in TGraphicControl's Paint virtual method. Paint will be called any time the control's parent (i.e. the container) needs to update its display. Therefore we will override this Paint method, and do our drawing there.

[2]Override Paint method and do the drawing there.

How to draw?
Now that we know when to draw, time to find out how to do the drawing. Fortunately TGraphicControl already provided nearly anything we need to draw anything on top of it. It has a property named Canvas with type of TCanvas (click to see official documentation). I think you will find the names are very intuitive, since we are indeed doing all the drawing using it.

We can draw text onto a Canvas by using TCanvas.TextOut method, like this:
  Canvas.TextOut(X, Y, 'Hello world');

X and Y defined the coordinate where the text should started. X defined how many pixels from the left boundary of the control, and Y defined how many pixels from the top boundary.

To draw a graphic/image onto a Canvas, we can use TCanvas.Draw method, like this:
  Canvas.Draw(X, Y, MyImage);

MyImage is an instance of TGraphic class containing the image to be drawn onto the canvas.

[3]Drawing is done by using TGraphicControl.Canvas.

What to Draw?
We have two entities to draw,
  • String containing the current time information
  • Background image

1. String of the current time
We get this string from Now function from SysUtils unit. Of course we need to process the information returned by Now to get it into string format. We can use either TimeToStr from SysUtils if we want to use default time format of the computer, or FormatDateTime, also from SysUtils unit, if we want to get special format for the time.

[4]Use Now combined with TimeToStr routine to get string of current time default formatted, or Now combined with FormatDateTime to get string of current time with specific format.

2. Background image
The main problem here is how to specify image that we want to use as the background image. We want the background image could be specified at design time and still available at runtime without complex code. Here we will use the help of an instance of TPicture. Using TPicture we can store any kind of image (that was supported) into our control, instead of just one kind of image if we use a specific descendant of TGraphic. For example, if we decide to use TBitmap instead of TPicture, then we can only properly store and load images of TBitmap type. We can't store JPGs.

Our TPicture must be instantiated in the constructor, to make sure it's available to receive stream of image data stored in the dfm.

When drawing, we don't directly use the TPicture. But we will use the graphic stored in the TPicture's Graphic. So when we are drawing the background image we will go something like:
  Canvas.Draw(X, Y, FPicture.Graphic);

[5]Use TPicture to manage background image
[6]Background image actually is in TPicture.Graphic.

Time to Code!
Let's put together our findings. Note that in this tutorial we will name the custom control: TDigitalClock.
  • Base class: TGraphicControl. This should turn into code like:
    unit MyClock;
    uses
      Controls
      ;
      
    type
      TDigitalClock=class(TGraphicControl)
      end;
    
  • Override Paint method and do the drawing there. According to this, we should update the class into:
    type
      TDigitalClock=class(TGraphicControl)
      protected
        procedure Paint; override;
      end;
      
    implementation
    
    procedure TDigitalClock.Paint;
    begin
    end;
    
  • Drawing is done by using TGraphicControl.Canvas. Okay, we'll incorporate this with the next findings.
  • Use Now combined with TimeToStr routine to get string of current time default formatted, or Now combined with FormatDateTime to get string of current time with specific format.

    Let's assume that we want to support specific time format. So we have to add a field to store the format, we name the field FFormat, and then publish it as a property with name Format. If this property is empty, we will be using the computer's default format for time. So let's update our class declaration into:
      TDigitalClock=class(TGraphicControl)
      private
        procedure SetFormat(const Value: string);
      protected
        procedure Paint; override;
      published
        property Format: string read FFormat write SetFormat;
        property Font;
      end;
    end;
    

    We have to use a setter method for Format property since we want to clock to update its display immediately when the format is changed. The code for SetFormat is:
    procedure TDigitalClock.SetFormat(const Value: string);
    begin
      if FFormat <> Value then
      begin
        FFormat := Value;
        Invalidate;  // ask the control to update its display asap
      end;
    end;
    
    Note the Invalidate method.

    And now we can update our overrided Paint implementation into something like this:
    var
      vText: string;
      vLeft: Integer;
      vTop : Integer;
    begin
      // get into "transparent" background mode
      Canvas.Brush.Style := bsClear;
      
      if FFormat='' then
        // if no format specified, use default format
        vText := TimeToStr(Now)
      else
        // use the specified time format
        vText := FormatDateTime(FFormat, Now);
    
      // clear the drawing area	
      Canvas.FillRect(BoundsRect);
    
      Canvas.Font := Self.Font;
      vLeft := GetLeftPos(vText);
      vTop  := GetTopPos(vText);
      Canvas.TextOut(2, 2, vText);
    
  • Use TPicture to manage background image. Let's create a field named FBackground of type TPicture, and create it in our digital clock's constructor. We publish this field as property with setter method, since we'll use its Assign method to change the content instead of directly change the object reference.

    Our class declaration to be something like this:
      TDigitalClock=class(TGraphicControl)
      private
        FFormat: string;
        FBackground: TPicture;
        procedure SetFormat(const Value: string);
        procedure SetBackground(const Value: TPicture);
      protected
        procedure Paint; override;
      public
        constructor Create(AOwner: TComponent); override;
        destructor Destroy; override;
      published
        property Format: string read FFormat write SetFormat;
        property Background: TPicture read FBackground write SetBackground;
        property Font;
      end;
    

    implementations of the contructor, destructor, and SetBackground:
    constructor TDigitalClock.Create(AOwner: TComponent);
    begin
      inherited;
      Width := 200;
      Height := 80;
      FBackground := TPicture.Create;
    end;
    
    destructor TDigitalClock.Destroy;
    begin
      FBackground.Free;
      inherited;
    end;
    
    procedure TDigitalClock.SetBackground(const Value: TPicture);
    begin
      FBackground.Assign(Value);
      DoAutoSize;
      Invalidate;
    end;
    

    Note the call to Invalidate in SetBackground. The purpose is to tell the control to update its display asap.
  • Background image actually is in TPicture.Graphic. With this finding, we can finally draw the background image. So let's update our Paint method into:
    procedure TDigitalClock.Paint;
    var
      vText: string;
      vLeft: Integer;
      vTop : Integer;
    begin
      // get into "transparent" background mode
      Canvas.Brush.Style := bsClear;
      if FFormat='' then
        // if no format specified, use default format
        vText := TimeToStr(Now)
      else
        // use the specified time format
        vText := FormatDateTime(FFormat, Now);
    
      if FBackground.Graphic<>nil then
        // draw the background, if available
        Canvas.Draw(0, 0, FBackground.Graphic)
      else
        // if no background graphic available, clear the drawing area
        Canvas.FillRect(BoundsRect);
    
      // Make sure canvas uses the same font with the control
      Canvas.Font := Self.Font;
    
      vLeft := GetLeftPos(vText);
      vTop  := GetTopPos(vText);
    
      // draw the time text
      Canvas.TextOut(vLeft, vTop, vText);
    end;
    

Okay, this is it!! Well, not!! If you run the control up to this version, you will get the clock to update the displayed time only when it was uncovered from another window. Because only at this time normally Paint will be called. So in order to synchronize the display with the actual current time, we have to force our control to update its display at specified interval.

To force the update, we can call Invalidate like we already use in several locations. But to be sure to call it at specified interval, we need the help of TTimer. Enabled TTimer allows execution of codes after specified time elapsed. The execution would be repeatable as long as it stays Enabled.

So let's add a TTimer to our digital clock. The class declaration would become:
type
  TDigitalClock=class(TGraphicControl)
  private
    FFormat: string;
    FTimer : TTimer;
    FBackground: TPicture;
    procedure SetFormat(const Value: string);
    procedure SetBackground(const Value: TPicture);
    procedure HandleTimer(ASender: TObject);
  protected
    procedure Paint; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Format: string read FFormat write SetFormat;
    property Background: TPicture read FBackground write SetBackground;
    property Font;
  end;

HandleTimer method is the handler of our TTimer FTimer's OnTimer event. OnTimer event will be fired after an interval time specified in Interval property of the TTimer elapsed since enabling the TTimer or since the time OnTimer fired. In our constructor we have to create the TTimer and then set proper values so it works properly.

Our constructor would become:
constructor TDigitalClock.Create(AOwner: TComponent);
begin
  inherited;
  Width := 200;
  Height := 80;
  FTimer := TTimer.Create(Self);
  FTimer.Enabled := False;
  FTimer.Interval := 500;
  FTimer.OnTimer := HandleTimer;
  FTimer.Enabled := True;
  FBackground := TPicture.Create;
end;

There you are! We now actually have a working digital clock controls. We just need another routine to reqister the control to make it available in component pallette. For this, just add this line somewhere in the interface section of the unit:
  procedure Register;

and this in the implementation:
procedure Register;
begin
  RegisterComponents('Samples', [TDigitalClock]);
end;

After you put the unit into a package and register the package, you will get a new control in your component pallette, under tab Samples. You can drop this control from component pallette into your form to get a digital clock. See to it that you can change the background easily at design time, and the background available both in design time and runtime.

AssigningBkgImage001.png

The complete source code of the digital clock is Attached File  Demo.zip   14.57KB   452 downloads, along with demo project. There are some improvements you will find in the attached code compared to the codes in this tutorial. So, enjoy!

Edited by LuthfiHakim, 06 January 2012 - 10:27 PM.
fix typo

  • 1

#2 Alexander

Alexander

    YOL9

  • Moderator
  • 3963 posts
  • Location:Vancouver, Eh! Cleverness: 200
  • Programming Language:C, C++, PHP, Assembly

Posted 22 December 2011 - 04:12 PM

A digital clock element is usually the first thing I'd whip up on such language (certainly for Visual Basic).

This tutorial is pretty fun to follow!
  • 0

All new problems require investigation, and so if errors are problems, try to learn as much as you can and report back.


#3 Luthfi

Luthfi

    CC Leader

  • Expert Member
  • PipPipPipPipPipPipPip
  • 1320 posts
  • Programming Language:PHP, Delphi/Object Pascal, Pascal, Transact-SQL
  • Learning:C, Java, PHP

Posted 06 January 2012 - 10:40 PM

Thanks Alex! Yeah, I'm aiming the tutorial for very beginner in programming (especially Delphi programming). So I used very detailed steps. I hope I was not making more advance delphi coders bored :-P.
  • 0





Also tagged with one or more of these keywords: timer, clock

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download