如果在尝试调用 BlinkRect 之前调用了 AssignRectangles,则 uRectControl 中的代码可以正常工作。然而,有许多问题需要解决。
1) 单元的交叉依赖
表单 (uDataReceived) 显然使用 uRectControl,这很好。uRectControl 的编写方式需要使用(在实现中使用 uDataReceived)表单,这并不好。这个错误很容易纠正,因为 AssignRectangles 过程是唯一引用表单的地方。AssignRectangles 也可以在表单中,因为 Blinks[] 数组是全局的(在 uRectControl 的接口中),因此可以通过表单访问。
2) 全局变量
应尽可能避免使用全局变量。您已将 Blinks[] 数组和 Timer 都定义为全局的,因此您可能会错误地从程序中的任何位置访问和修改它们,只需将 uRectControl 添加到使用子句即可。在未来的开发中,您可能会添加具有要闪烁的指示器的新表单,并将 TRectangles 添加到 Blinks[] 数组中,这可能会覆盖已经存在的值,而您最终会陷入混乱。我将在下面的建议中解决这个问题。
3) 硬编码实体
在概念验证代码中,硬编码常量、数组大小等是可以接受(或不接受)的,但在生产代码中则不行。想一想您需要做的所有更改,以在表单中再添加一个闪烁的矩形。动态数组或更好的 TList 及其衍生物等在这里拯救。您还限制自己仅使用 TRectangles。如果您想在表单中添加圆形指示器怎么办?
4) 不同步闪烁
当指示灯到处闪烁时,它可能看起来很酷(不是真的),但实际上它只是分散注意力。我猜您尝试使用 TMyClass 中的计时器更改此设置,但您仍然将各个计时器留在了 Blinks 记录中。我也会在下面的建议中解决这个问题。
这是一个建议
unit ShapeBlinker;
interface
uses
System.SysUtils, System.UITypes, System.Classes, System.Generics.Collections,
FMX.Graphics, FMX.Types, FMX.Objects;
type
TBlinkState = (bsOff, bsBlinking, bsSteady);
我有火灾报警系统的背景,通常有三种状态;熄灭、闪烁并常亮。TBlinkState 代表这些。
然后是一个代表 UI 中指标的类。指标可以是任何 TShape 衍生物,如 TRectangle、TCircle、TPath 等。每个状态都可以有自己的颜色。
type
[...]
TBlinkingShape = class
private
FShape: TShape;
FState: TBlinkState;
FOffColor: TAlphaColor;
FBlinkColor: TAlphaColor;
FSteadyColor: TAlphaColor;
public
constructor Create(AShape: TShape);
procedure SetBlinkState(NewState: TBlinkState);
end;
FShape 字段包含对 TShape 导数的引用。通过这个引用,我们可以访问 UI 表单上的实际组件并可以更改其颜色。稍后我们将看到如何将 TShape 传递给构造函数。
然后是第二个类,它管理 TBlinkingShape、时间和表单上指标的实际颜色变化的集合。
type
[...]
TShapeBlinker = class
private
FBlinkingShapes: TObjectList<TBlinkingShape>;
FBlinkPhase: integer;
FTimer: TTimer;
public
constructor Create;
destructor Destroy; override;
procedure RegisterShape(Shape: TShape; OffColor, BlinkColor, SteadyColor: TAlphaColor);
procedure UnRegisterShape(Shape: TShape);
procedure BlinkTimer(Sender: TObject);
procedure SetBlinkState(Shape: TShape; NewState: TBlinkState);
function GetBlinkState(Shape: TShape): TBlinkState;
end;
FBlinkingShapes 是包含 TBlinkingShapes 实例的对象列表。FBlinkPhase 同步指示灯的闪烁,以便所有闪烁的指示灯同时更改为 BlinkColor。FTimer 适用于所有指标。当 UI 要向列表添加指示器时,过程 RegisterShape 会被调用。当要从列表中删除指标时调用 UnRegister。SetBlinkState 用于更改状态,GetBlinkState 用于检索指标的状态。
该单元被设计为可用于任何数量的形式,同步闪烁所有的形式。这要求 TShapeBlinker 是单例的。因此,它在单元的初始化部分创建,并在最终确定中释放。该实例由实现中的 var 持有,因此无法直接从任何其他单元访问。访问由在单元接口中声明为最后一项的函数提供:
function ShapeBlinker: TShapeBlinker;
这有效地防止了意外调用 ShapeBlinker.Create 的错误。
我没有评论每种方法,而是在这里复制实现:
implementation
var
SShapeBlinker: TShapeBlinker;
function ShapeBlinker: TShapeBlinker;
begin
result := SShapeBlinker;
end;
{ TBlinkingShape }
constructor TBlinkingShape.Create(AShape: TShape);
begin
FShape := AShape;
FState := bsOff;
end;
procedure TBlinkingShape.SetBlinkState(NewState: TBlinkState);
begin
FState := NewState;
case NewState of
bsOff: begin
FShape.Fill.Color := FOffColor;
end;
bsBlinking: begin
FShape.Fill.Color := FBlinkColor;
end;
bsSteady: begin
FShape.Fill.Color := FSteadyColor;
end;
end;
end;
{ TShapeBlinker }
constructor TShapeBlinker.Create;
begin
FBlinkingShapes := TObjectList<TBlinkingShape>.Create;
FTimer := TTimer.Create(nil);
FTimer.OnTimer := BlinkTimer;
FTimer.Interval := 500;
FTimer.Enabled := False;
end;
destructor TShapeBlinker.Destroy;
begin
FTimer.Enabled := False;
FTimer.Free;
FBlinkingShapes.Free;
inherited;
end;
function TShapeBlinker.GetBlinkState(Shape: TShape): TBlinkState;
var
RegShape: TBlinkingShape;
begin
result := bsOff;
for RegShape in FBlinkingShapes do
if Shape = RegShape.FShape then result := RegShape.FState;
end;
procedure TShapeBlinker.SetBlinkState(Shape: TShape; NewState: TBlinkState);
var
RegShape: TBlinkingShape;
begin
for RegShape in FBlinkingShapes do
if Shape = RegShape.FShape then RegShape.SetBlinkState(NewState);
self.FTimer.Enabled := True;
end;
procedure TShapeBlinker.BlinkTimer(Sender: TObject);
var
i: integer;
begin
FTimer.Enabled := False;
FBlinkPhase := (FBlinkPhase + 1) mod 2;
for i := 0 to FBlinkingShapes.Count-1 do
with FBlinkingShapes[i] do
begin
case FState of
bsOff: begin
FShape.Fill.Color := FOffColor;
end;
bsBlinking: begin
if FBlinkPhase = 1 then
FShape.Fill.Color := FOffColor // alt. FSteadyColor
else
FShape.Fill.Color := FBlinkColor;
FTimer.Enabled := True;
end;
bsSteady: begin
FShape.Fill.Color := FSteadyColor;
end;
end;
end;
end;
procedure TShapeBlinker.RegisterShape(Shape: TShape; OffColor, BlinkColor, SteadyColor: TAlphaColor);
begin
with FBlinkingShapes[FBlinkingShapes.Add(TBlinkingShape.Create(Shape))] do
begin
FOffColor := OffColor; //TAlphaColors.Silver;
FBlinkColor := BlinkColor; //TAlphaColors.Red;
FSteadyColor := SteadyColor; //TAlphaColors.Yellow;
end;
end;
procedure TShapeBlinker.UnRegisterShape(Shape: TShape);
var
i: integer;
begin
for i := FBlinkingShapes.Count-1 downto 0 do
if FBlinkingShapes[i].FShape = Shape then
FBlinkingShapes.Delete(i);
end;
initialization
SShapeBlinker := TShapeBlinker.Create;
finalization
SShapeBlinker.Free;
end.
最后说说用法。考虑一个表格,比如 TAlarmView,有 2 个 TRectangle 和 1 个 TCircle。在 FormCreate 你可以注册这些闪烁如下
procedure TAlarmView.FormCreate(Sender: TObject);
begin
ShapeBlinker.RegisterShape(Rect1, TAlphaColors.Silver, TAlphaColors.Red, TAlphaColors.Yellow);
ShapeBlinker.RegisterShape(Circle1, TAlphaColors.Silver, TAlphaColors.Red, TAlphaColors.Yellow);
ShapeBlinker.RegisterShape(Rect3, TAlphaColors.Silver, TAlphaColors.Red, TAlphaColors.Yellow);
end;
然后通过单击按钮来测试它们
procedure TAlarmView.Button1Click(Sender: TObject);
begin
case ShapeBlinker.GetBlinkState(Rect1) of
bsOff: ShapeBlinker.SetBlinkState(Rect1, bsBlinking);
bsBlinking: ShapeBlinker.SetBlinkState(Rect1, bsSteady);
else ShapeBlinker.SetBlinkState(Rect1, bsOff);
end;
end;
如您所见,我只是为每次点击浏览不同的状态。