Overview
Embarcadero Delphi provides us with great opportunities for drawing directly on the form (Canvas). We can simply fill the form with color, or draw lines, various geometric shapes, sprites (images), etc. All of this is possible thanks to the Canvas property.
In FireMonkey (FMX), the Canvas property is available in the form (TForm), the TImage component, and TPaintBox. It means that with the help of these components, we can programmatically draw various graphic objects (lines, rectangles, ellipses, images, etc.).
There are specific rules for drawing graphics in Embarcadero Delphi FMX applications. Unlike VCL, drawing can only be performed in the OnPaint method of the corresponding component (for example, TForm). Forced invocation of the form redraw method can be triggered using the Invalidate method.
In this article, we will demonstrate how to draw an analog clock on a form in our Embarcadero Delphi FMX application.
Step-by-Step Guide on Drawing the Analog Clock
The entire logic for drawing the analog clock will be implemented in our custom-designed class dubbed TMyClock.
With the constructor of the TMyClock class, we will configure many parameters for drawing our analog clock. The fields of the Real type, such as FLs, FLm, FLh, will contain the lengths of the second, minute, and hour hands. The fields of the Integer type (FTs, FTm, FTh, FTc1, FTc2) will store the thickness of the second, minute, and hour hands, as well as the thickness of the hour and minute scale marks, respectively.
The fields FPointMinute and FPointHour will contain the lengths of the minute and hour scale divisions of our analog clock.
The fields of the TAlphaColor type, such as FSColor, FMColor, FHColor, FC1Color, and FC2Color, will hold the colors of the second, minute, and hour hands, as well as the colors of the hour and minute scale divisions of our analog clock, respectively. The fields FRHour and FRMinute will store the distances from the center of the analog clock to the hour and minute scales, respectively (radii).
The fields of the TPathData type, namely, FSecArrowPath, FHourArrowPath, FMinArrowPath, FCircle1Path, and FCircle2Path, will contain the coordinates for drawing the second, hour, and minute hands, as well as the coordinates for the hour and minute scale divisions of our analog clock.

All input parameters of the constructor won’t contain the letter “F” in their names (for example, Ls, Lm, etc.). However, they will hold the values that should be passed to the fields. The values will be simply assigned to the corresponding fields (we have highlighted three fields for demonstration, the assignment of the other values is done similarly).

The function CurrentTimeDecoding will return the value of the date, system time, and day of the week as a string. It will also calculate the rotation angles for the second, minute, and hour hands (fields FFis, FFim, FFih). Additionally, it will define the angles for one division of the minute and hour scales (fields FFLarge and FFSmall).

The function DayToStr will return the name of the day of the week based on its value (for example, Monday, Tuesday, etc.).

The method DrawArrows will allow us to calculate and store an array of coordinates for drawing our clock hands (second, minute, and hour) in TPathData (FSecArrowPath, FMinArrowPath, FHourArrowPath). We will add the coordinates for the hands to the TPathData objects with the help of the MoveTo and LineTo methods. MoveTo will move the current position in TPathData. LineTo will add a line to TPathData in accordance with the specified coordinates. For example, we can write FMinArrowPath.MoveTo(PMove), where PMove is the point with the required coordinates.

The method DrawPoints1 can allow us to determine the array of coordinates for drawing the hour scale on the dial of our analog clock. The coordinates will also be stored in TPathData (FCircle1Path).

The method DrawPoints2 will make it possible to calculate the array of coordinates for drawing the minute and second scales on the dial of our analog clock. The coordinates will be stored in TPathData (FCircle2Path).

The method DrawPathOnCanvas will draw our analog clock on the form. It will take a Form of the TForm class as an input parameter. In FireMonkey, drawing is performed strictly within the BeginScene and EndScene blocks. The color and thickness of the line should be set using the Canvas attributes Stroke.Color and Stroke.Thickness, respectively. The drawing will be performed by the DrawPath method, which will take the TPathData object (for example, FMinArrowPath) as its first parameter. The second parameter will be the opacity of the line. In our case, the line won’t be transparent (we will set this parameter to 1.0).

The source code of the TMyClock class is presented below:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 | unit MyClockClass;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs;
const
HOURS = 12;
MINUTES = 60;
type
TMyClock = class
private
FFis, FFim, FFih, FHrad, FAngle, FLs, FLm, FLh: Real;
FNs, FNm, FNh, Fn, FMs: Word;
FDay: Integer;
FFSmall, FPointMinute, FFLarge, FPointHour: Real;
FSecArrowPath: TPathData;
FHourArrowPath: TPathData;
FMinArrowPath: TPathData;
FCircle1Path: TPathData;
FCircle2Path: TPathData;
FSColor: TAlphaColor;
FMColor: TAlphaColor;
FHColor: TAlphaColor;
FC1Color: TAlphaColor;
FC2Color: TAlphaColor;
FTs, FTm, FTh, FTc1, FTc2: Integer;
FRHour, FRMinute: Integer;
public
constructor Create(Ls, Lm, Lh: Real; Ts, Tm, Th, Tc1, Tc2: Integer;
PointMinute, PointHour: Real;
SColor, MColor, HColor, C1Color, C2Color: TAlphaColor;
RHour, RMinute: Integer); overload;
destructor Destroy; override;
function CurrentTimeDecoding: string; overload;
procedure DrawArrows(Form: TForm); overload;
procedure DrawPoints1(Form: TForm); overload;
procedure DrawPoints2(Form: TForm); overload;
procedure DrawPathOnCanvas(Form: TForm); overload;
function DayToStr(DayNum: Integer): string;
end;
implementation
constructor TMyClock.Create(Ls, Lm, Lh: Real; Ts, Tm, Th, Tc1, Tc2: Integer;
PointMinute, PointHour: Real;
SColor, MColor, HColor, C1Color, C2Color: TAlphaColor;
RHour, RMinute : Integer);
begin
FRHour := RHour;
FRMinute := RMinute;
FLs := Ls;
FLm := Lm;
FLh := Lh;
FTs := Ts;
FTm := Tm;
FTh := Th;
FTc1 := Tc1;
FTc2 := Tc2;
FPointMinute := PointMinute;
FPointHour := PointHour;
FSColor := SColor;
FMColor := MColor;
FHColor := HColor;
FC1Color := C1Color;
FC2Color := C2Color;
FAngle := 0;
FHrad := ((2 * PI) / MINUTES);
FCircle1Path := TPathData.Create;
FCircle2Path := TPathData.Create;
FSecArrowPath := TPathData.Create;
FMinArrowPath := TPathData.Create;
FHourArrowPath := TPathData.Create;
end;
function TMyClock.CurrentTimeDecoding: string;
var
TCur, DCur: TDateTime;
begin
TCur := Time;
DCur := Date;
with Self do
begin
DecodeTime(TCur, FNh, FNm, FNs, FMs);
FDay := DayOfWeek(DCur);
FFis := FNs * FHrad;
FFim := FNm * FHrad;
if FNh >= 12 then
Fn := FNh – HOURS
else
Fn := FNh;
case FNm of
0..11: FAngle := 0;
12..23: FAngle := FHrad;
24..35: FAngle := 2 * FHrad;
36..47: FAngle := 3 * FHrad;
48..59: FAngle := 4 * FHrad;
60: FAngle := 5 * FHrad;
end;
FFih := ((2 * pi) / HOURS) * Fn + FAngle;
FFLarge := ((2 * pi) / HOURS);
FFSmall := ((2 * pi) / MINUTES);
end;
Result := DateToStr(DCur) + ' ' + TimeToStr(TCur) + ' ' + DayToStr(FDay);
end;
function TMyClock.DayToStr(DayNum: Integer): string;
begin
case DayNum of
1: Result := 'Sunday';
2: Result := 'Monday';
3: Result := 'Tuesday';
4: Result := 'Wednesday';
5: Result := 'Thursday';
6: Result := 'Friday';
7: Result := 'Saturday';
end;
end;
destructor TMyClock.Destroy;
begin
FCircle1Path.Free;
FCircle2Path.Free;
FSecArrowPath.Free;
FMinArrowPath.Free;
FHourArrowPath.Free;
inherited;
end;
procedure TMyClock.DrawArrows(Form: TForm);
var
PMove, PPaint: TPointF;
begin
with Self do
begin
PMove.X := Form.Width div 2;
PMove.Y := Form.Height div 2;
FSecArrowPath.MoveTo(PMove);
PPaint.X := Form.Width div 2 + Round(FLs * Cos(FFis – (PI / 2)));
PPaint.Y := Form.Height div 2 + Round(FLs * Sin(FFis – (PI / 2)));
FSecArrowPath.LineTo(PPaint);
PMove.X := Form.Width div 2;
PMove.Y := Form.Height div 2;
FMinArrowPath.MoveTo(PMove);
PPaint.X := Form.Width div 2 + Round(FLm * Cos(FFim – (PI / 2)));
PPaint.Y := Form.Height div 2 + Round(FLm * Sin(FFim – (PI / 2)));
FMinArrowPath.LineTo(PPaint);
PMove.X := Form.Width div 2;
PMove.Y := Form.Height div 2;
FHourArrowPath.MoveTo(PMove);
PPaint.X := Form.Width div 2 + Round(FLh * Cos(FFih – (PI / 2)));
PPaint.Y := Form.Height div 2 + Round(FLh * Sin(FFih – (PI / 2)));
FHourArrowPath.LineTo(PPaint);
end;
end;
procedure TMyClock.DrawPoints1(Form: TForm);
var
J: Integer;
PMove, PPaint: TPointF;
begin
for J := 1 to HOURS do
begin
with Self do
begin
PMove.X := Form.Width div 2 + Round(Self.FRHour * Cos(J * FFLarge));
PMove.Y := Form.Height div 2 – Round(Self.FRHour * Sin(J * FFLarge));
PPaint.X := Form.Width div 2 + Round(Self.FRHour * Cos(J * FFLarge)) +
Round(FPointMinute * Cos(J * FFLarge));
PPaint.Y := Form.Height div 2 – Round(Self.FRHour * Sin(J * FFLarge)) –
Round(FPointMinute * Sin(J * FFLarge));
FCircle1Path.MoveTo(PMove);
FCircle1Path.LineTo(PPaint);
end;
end;
end;
procedure TMyClock.DrawPoints2(Form: TForm);
var
I: Integer;
PMove, PPaint: TPointF;
begin
for I := 1 to MINUTES do
begin
with Self do
begin
PMove.X := Form.Width div 2 + Round(Self.FRMinute * Cos(I * FFSmall));
PMove.Y := Form.Height div 2 + Round(Self.FRMinute * Sin(I * FFSmall));
PPaint.X := Form.Width div 2 + Round(Self.FRMinute * Cos(I * FFSmall)) +
Round(FPointHour * Cos(I * FFSmall));
PPaint.Y := Form.Height div 2 + round(Self.FRMinute * Sin(I * FFSmall)) +
Round(FPointHour * Sin(I * FFSmall));
FCircle2Path.MoveTo(PMove);
FCircle2Path.LineTo(PPaint);
end;
end;
end;
procedure TMyClock.DrawPathOnCanvas(Form: TForm);
begin
with Form.Canvas do
begin
BeginScene;
Stroke.Dash := TStrokeDash.Solid;
Stroke.Thickness := Self.FTm;
Stroke.Color := Self.FMColor;
DrawPath(Self.FMinArrowPath, 1.0);
Stroke.Thickness := Self.FTh;
Stroke.Color := Self.FHColor;
DrawPath(Self.FHourArrowPath, 1.0);
Stroke.Thickness := Self.FTs;
Stroke.Color := Self.FSColor;
DrawPath(Self.FSecArrowPath, 1.0);
Stroke.Thickness := Self.FTc2;
Stroke.Color := Self.FC2Color;
DrawPath(Self.FCircle2Path, 1.0);
Stroke.Thickness := Self.FTc1;
Stroke.Color := Self.FC1Color;
DrawPath(Self.FCircle1Path, 1.0);
EndScene;
end;
Self.FSecArrowPath.Clear;
Self.FMinArrowPath.Clear;
Self.FHourArrowPath.Clear;
Self.FCircle1Path.Clear;
Self.FCircle2Path.Clear;
end;
end. |
Let’s implement the drawing of our analog clocks in our Embarcadero Delphi FMX application with the help of the TMyClock class. We will use the TTimer component for timekeeping.

We will set the TTimer parameter Interval to 1000, which will correspond to 1 second. Thus, the OnTimer event handler will be called every second.

In it, we will be able to invoke a forced redraw of the clock hands and other elements of our analog clock every second. The actual drawing will be performed in the OnPaint form method.
The Fill.Kind property of the form should be set to Solid to ensure the drawing of our analog clocks.

We will declare the field FMyNewClock of the TMyClock class. Then, we will call the constructor of the class in the event handler of the main form OnCreate with our clock’s appearance settings (hand sizes, minute and hour scale divisions, colors, etc.).

In the OnTimer event handler of the Timer1 component, we will implement the preparation for the drawing of the analog clock, as well as for the demonstration of the system time, date, and current day of the week in TLabel. The DrawPoints1 and DrawPoints2 methods will prepare the hour and minute scales for drawing, respectively. The DrawArrows method will prepare all the hands of our analog clock for further drawing. To force the redraw of our clock on the form every second, we will call the Invalidate method.

And finally, the drawing of the analog clock will take place in the event handler of the main form OnPaint (the DrawPathOnCanvas method).

The source code of the main form module can be found below.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253 | unit Main;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, MyClockClass,
FMX.Edit, FMX.Controls.Presentation, FMX.StdCtrls, FMX.ScrollBox, FMX.Memo;
type
TForm1 = class(TForm)
Timer1: TTimer;
ToolBar1: TToolBar;
Label1: TLabel;
Label2: TLabel;
procedure FormCreate(Sender: TObject);
procedure FormPaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF);
procedure Timer1Timer(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
FMyNewClock: TMyClock;
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
procedure TForm1.FormCreate(Sender: TObject);
begin
FMyNewClock := TMyClock.Create(120, 80, 50, 2, 8, 8, 3, 3, 20, 5,
TAlphaColors.Blueviolet, TAlphaColors.Aqua, TAlphaColors.Brown,
TAlphaColors.Green, TAlphaColors.Darkgrey, 150, 150);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FMyNewClock.Free;
end;
procedure TForm1.FormPaint(Sender: TObject; Canvas: TCanvas;
const ARect: TRectF);
begin
FMyNewClock.DrawPathOnCanvas(Form1);
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
with FMyNewClock do
begin
Label1.Text := CurrentTimeDecoding;
DrawPoints1(Form1);
DrawPoints2(Form1);
DrawArrows(Form1);
end;
Form1.Invalidate;
end;
end. |
Let’s check the work of our Embarcadero Delphi FMX application.

Bottom Line
Creating an analog clock in an Embarcadero Delphi FMX application provides a practical demonstration of the FMX framework’s power for custom UI design. With the TMyClock class, developers can easily configure and render a fully functional clock, using the Canvas property for precise graphical drawing.
The example of analog clocks showcases Delphi’s versatility in both visual and functional programming.
For any questions or assistance with Delphi development, feel free to reach out to the Softacom team — we have over 15 years of experience in Delphi projects and are happy to help.