带定时器的单独单元的调用过程

Alv*_*Lin 0 delphi timer timertask firemonkey delphi-xe7

我正在尝试编写一个单独的单元供我的主窗体调用,除了使用TTimer.

基本上,该函数应该做的就是主窗体uDataReceived调用BlinkRect(Gateway)rRectControl单元形式处理,并且相应的矩形将在主窗体中闪烁。

以下是代码:

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.
Run Code Online (Sandbox Code Playgroud)

我知道上面显示的单位一定有问题,我的问题是:

如何在单独的单元中使用TTimer以及如何BlinkRect(Gateway)在主窗体上调用过程。

多谢!!

Tom*_*erg 5

如果在尝试调用 BlinkRect 之前调用了AssignRectangles,则 uRectControl 中的代码可以正常工作。然而,还有许多问题需要解决。

1)单位之间的相互依赖

表单(uDataReceived)显然使用了uRectControl,这很好。uRectControl 的编写方式需要使用(在实现中使用 uDataReceived)表单,这不好。这个错误很容易纠正,因为AssignRectangles 过程是唯一引用该表单的地方。AssignRectangles 也可以在表单中,因为 Blinks[] 数组是全局的(在 uRectControl 的接口中),因此可以由表单访问。

2)全局变量

应尽可能避免全局变量。您已将 Blinks[] 数组和 Timer 定义为全局的,因此您可能会错误地从程序中的任何位置访问和修改它们,只需将 uRectControl 添加到 use 子句即可。在未来的开发中,您可能会添加包含您想要闪烁的指示器的新表单,并将 TRectangle 添加到 Blinks[] 数组,这可能会覆盖已经存在的值,最终会陷入混乱。我将在下面的建议中解决这个问题。

3)硬编码实体

在概念验证代码中,可以接受(或不可以)硬编码常量、数组大小等,但在生产代码中则不然。想想您需要做的所有更改,只是为了向表单添加一个闪烁的矩形。动态数组或更好的 TList 及其衍生产品等可以解决这个问题。您还将自己限制为只能使用 TRectangle。如果您想在表单中使用圆形指示器怎么办?

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);
Run Code Online (Sandbox Code Playgroud)

我有火灾报警系统的背景,通常有三种状态;关闭、闪烁并常亮。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;
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

FBlinkingShapes 是保存 TBlinkingShapes 实例的对象列表。FBlinkPhase 同步指示器的闪烁,以便所有闪烁的指示器同时更改为 BlinkColor。FTimer 对于所有指标都是通用的。当 UI 想要将指示器添加到列表中时,将调用过程 RegisterShape。当要从列表中删除指标时调用 UnRegister。SetBlinkState 用于更改状态,GetBlinkState 用于检索指示器的状态。

该装置被设计为可用于任意数量的形式,并同步所有形式的闪烁。这要求 TShapeBlinker 是单例。因此,它是在单元的初始化部分中创建的,并在终结时释放的。该实例由实现中的 var 保存,因此无法从任何其他单元直接访问。访问由声明为单元接口中最后一项的函数提供:

  function ShapeBlinker: TShapeBlinker;
Run Code Online (Sandbox Code Playgroud)

这有效地防止了意外调用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.
Run Code Online (Sandbox Code Playgroud)

最后简单说一下用法。考虑一个表单,比如 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;
Run Code Online (Sandbox Code Playgroud)

然后通过单击按钮来测试它们,例如

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;
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,我只是为每次点击经历不同的状态。