I am trying to write a separate unit for my main form to call, all of my other units are working except for one that uses TTimer
.
Basically what the function is supposed to be doing is that the main form uDataReceived
calls BlinkRect(Gateway)
which is processed in rRectControl
unit and the according Rectangle will blink in the main form.
Here are the codes:
unit uRectControl;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes,
System.Variants, System.IOUtils, FMX.Graphics, FMX.Types, FMX.Objects;
var
Blinks: array [0 .. 2] of record Rectangle: TRectangle;
Timer: TTimer;
end;
type
TMyClass = Class(TObject)
private
Timer1: TTimer;
procedure Timer1Timer(Sender: TObject);
public
procedure BlinkRect(Gateway: integer);
end;
procedure AssignRectangles;
implementation
uses uDataReceived;
// Error shows "Cannot resolve unit name 'uDataReceived'
{ TMyClass }
procedure AssignRectangles;
var
i: integer;
begin
Blinks[0].Rectangle := TC_Theft_Detection.rect1;
// Error shows Undeclared Identifier TC_Theft_Detection (which is the name of the main form)
Blinks[0].Timer := nil;
Blinks[1].Rectangle := TC_Theft_Detection.rect2;
Blinks[1].Timer := nil;
Blinks[2].Rectangle := TC_Theft_Detection.rect3;
Blinks[2].Timer := nil;
for i := 0 to 2 do
Blinks[i].Rectangle.Fill.Color := TAlphacolors.blue;
end;
procedure TMyClass.BlinkRect(Gateway: integer);
begin
Blinks[Gateway].Rectangle.Fill.Color := TAlphacolors.Red;
Blinks[Gateway].Rectangle.Fill.Kind := TBrushKind.Solid;
Blinks[Gateway].Rectangle.Stroke.Thickness := 0.3;
Blinks[Gateway].Rectangle.Stroke.Color := TAlphacolors.Black;
if Blinks[Gateway].Timer = nil then
begin
Blinks[Gateway].Timer := TTimer.Create(nil);
Blinks[Gateway].Timer.OnTimer := Timer1Timer;
Blinks[Gateway].Timer.Interval := 500;
Blinks[Gateway].Timer.Tag := Gateway;
Blinks[Gateway].Timer.Enabled := True;
end;
end;
procedure TMyClass.Timer1Timer(Sender: TObject);
var
Timer: TTimer;
begin
Timer := TTimer(Sender);
Blinks[Timer.Tag].Rectangle.Visible := not Blinks[Timer.Tag]
.Rectangle.Visible;
end;
end.
I know there must be something wrong with the unit shown above, and my question is:
How to work with TTimer
in a separate unit and how to call the procedure BlinkRect(Gateway)
on the main form.
Thanks a lot!!
Your code in uRectControl works provided AssignRectangles is called before you attempt to call BlinkRect. However there are a number of issues to be addressed.
1) Cross dependency of units
The form (uDataReceived) apparently uses uRectControl and that is fine. The way uRectControl is written it needs to use (uses uDataReceived in the implementation) the form and this is not good. This error is simple to correct, because the AssignRectangles procedure is the only place where the form is referred to. AssignRectangles could just as well be in the form, since the Blinks[] array is global (in the interface of uRectControl) and can therefore be accessed by the form.
2) Global variables
Global variables should be avoided as much as possible. You have defined both the Blinks[] array and the Timer to be global, so you might by mistake access and modify them from anywhere in your program just by adding uRectControl to a uses clause. In future development you might add new forms that have indicators you want to blink and add TRectangles to the Blinks[] array possibly overwriting value that are already there and you end up in a mess. I will address this issue in my suggestion below.
3) Hardcoded entities
In Proof Of Concept code it is acceptable (or not) to hardcode constants, sizes of arrays etc. but not in production code. Just think about all changes you need to do just to add one more blinking rectangle to the form. Dynamical arrays or better TList and its derivatives etc. comes to rescue here. You have also limited yourself to only TRectangles. What if you would like to have circular indicators in your form?
4) Unsyncronized blinking
It may look cool (not really) when indicators are blinking all over the place, but actually it is just distracting. I guess you tried to change this with the timer in TMyClass, but you still left the individual timers in the Blinks records. I will address this also in my suggestion below.
Here is a suggestion
I have a background in Fire Alarm Systems, and it is common to have three states; off, blinking and steady lit. TBlinkState represents these.
Then comes a class that represent indicators in the UI. An indicator can be any TShape derivative like TRectangle, TCircle, TPath etc. Each state can have its own color.
The field FShape holds a reference to a TShape derivative. Through this reference we have access to the actual component on the UI form and can change its color. We will see later how the TShape is passed to the constructor.
Then the second class which manages a collection of TBlinkingShape, timing and actual color changes of the indicators on the form.
FBlinkingShapes is the object list that holds instances of TBlinkingShapes. FBlinkPhase syncronizes blinking of the indicators so that all blinking indicators change to the BlinkColor simultaneously. FTimer is common for all indicators. Procedure RegisterShape is called by the UI when it wants to add an indicator to the list. UnRegister is called when an indicator is to be removed from the list. SetBlinkState is used to change state and GetBlinkState to retrieve the state of an indicator.
The unit is designed to be usable by any number of forms, synchronizing blinking for all of them. This requires that the TShapeBlinker is a singleton. It is therefore created in the initialization section of the unit, and freed in the finalization. The instance is held by a var in the implementation, thus inaccessible directly from any other unit. Access is provided by a function declared as the last item in the interface of the unit:
This effectively prevents a mistake to accidentally call ShapeBlinker.Create.
Instead of commenting on each method I just copy the implementation here:
Finally a few words about usage. Consider a form, say TAlarmView, with 2 TRectangle and 1 TCircle. In FormCreate you might register these for blinking as follows
and then test them with button clicks like
As you see I just go through the different states for each click.