[教程] 基于 RTTI 创建 VCL/FMX 皆可用的定时器

我们在程序中经常用到定时器,但是如果你的代码想同时在VCL和FMX中使用,那么由于 VCL 的 TTimer 位于 Vcl.ExtCtrls 单元,而 FMX 的 TTimer 位于 FMX.Types 中,造成我们没有办法简单的创建跨框架的 TTimer 类。

好在,我们还有 RTTI 这个东西,而 TTimer 无论 VCL 还是 FMX 都是基于 TComponent 来的,所以,我们可以使用下面的代码来创建公共的Timer。下面是参考代码,注意引用 System.Rtti 单元。

function CreateTimer(AOwner:TComponent;AInterval:Cardinal;AOnTimer:TNotifyEvent):TComponent;
var
  AType:TRttiType;
  AContext:TRttiContext;
  AValue:TValue;
begin
  AContext:=TRttiContext.Create;
  AType := AContext.FindType('Vcl.ExtCtrls.TTimer');
  if not Assigned(AType) then
  begin
    AType:=AContext.FindType('FMX.Types.TTimer');
    if not Assigned(AType) then
      Exit(nil);
  end;
  if Assigned(AType.Handle.TypeData.ClassType) then
  begin
    Result := TComponentClass(AType.Handle.TypeData.ClassType).Create(AOwner);
    if Assigned(AOnTimer) then
    begin
      TValue.Make<TNotifyEvent>(AOnTimer,AValue);
      AType.GetProperty('OnTimer').SetValue(Result,AValue);
    end;
    if AInterval<>1000 then
    begin
      AValue:=AInterval;
      AType.GetProperty('Interval').SetValue(Result,AValue);
    end;
  end;
end;

当然,为了更好用一些,我们做一个简易的封装,引用下 System.Rtti,System.TypInfo 两个单元。

type
  TZTimer = record
    class function Create(AOwner: TComponent): TComponent; overload; static;
    class function Create(AOwner: TComponent; AOnTimer: TNotifyEvent; AInterval: Cardinal = 1000;
      AEnabled: Boolean = true): TComponent; overload; static;
    class procedure SetEnabled(AOwner: TComponent; AEnabled: Boolean); static;
    class function GetEnabled(AOwner: TComponent): Boolean; static;
    class procedure SetInterval(AOwner: TComponent; AInterval: Cardinal); static;
    class function GetInterval(AOwner: TComponent): Cardinal; static;
    class procedure SetOnTimer(AOwner: TComponent; AOnTimer: TNotifyEvent); static;
    class function GetOnTimer(AOwner: TComponent): TNotifyEvent; static;
    class property Enabled[AOwner: TComponent]: Boolean read GetEnabled write SetEnabled;
    class property Interval[AOwner: TComponent]: Cardinal read GetInterval write SetInterval;
    class property OnTimer[AOwner: TComponent]: TNotifyEvent read GetOnTimer write SetOnTimer;
  end;

{ TZTimer }

class function TZTimer.Create(AOwner: TComponent): TComponent;
var
  AType: TRttiType;
  AContext: TRttiContext;
begin
  AContext := TRttiContext.Create;
  AType := AContext.FindType('Vcl.ExtCtrls.TTimer');
  if not Assigned(AType) then
  begin
    AType := AContext.FindType('FMX.Types.TTimer');
    if not Assigned(AType) then
      Exit(nil);
  end;
  if Assigned(AType.Handle.TypeData.ClassType) then
    Result := TComponentClass(AType.Handle.TypeData.ClassType).Create(AOwner)
  else
    Result := nil;
end;

class function TZTimer.Create(AOwner: TComponent; AOnTimer: TNotifyEvent; AInterval: Cardinal; AEnabled: Boolean)
  : TComponent;
begin
  Result := Create(AOwner);
  if Assigned(AOnTimer) then
    OnTimer[Result] := AOnTimer;
  if AInterval <> 1000 then
    Interval[Result] := AInterval;
  if not AEnabled then
    Enabled[Result] := false;
end;

class function TZTimer.GetEnabled(AOwner: TComponent): Boolean;
begin
  Result := GetOrdProp(AOwner, GetPropInfo(AOwner, 'Enabled')) <> 0;
end;

class function TZTimer.GetInterval(AOwner: TComponent): Cardinal;
begin
  Result := GetOrdProp(AOwner, GetPropInfo(AOwner, 'Interval'));
end;

class function TZTimer.GetOnTimer(AOwner: TComponent): TNotifyEvent;
var
  AMethod: TMethod absolute Result;
begin
  AMethod := GetMethodProp(AOwner, GetPropInfo(AOwner, 'OnTimer'));
end;

class procedure TZTimer.SetEnabled(AOwner: TComponent; AEnabled: Boolean);
begin
  SetOrdProp(AOwner, GetPropInfo(AOwner, 'Enabled'), Ord(AEnabled));
end;

class procedure TZTimer.SetInterval(AOwner: TComponent; AInterval: Cardinal);
begin
  SetOrdProp(AOwner, GetPropInfo(AOwner, 'Interval'), AInterval);
end;

class procedure TZTimer.SetOnTimer(AOwner: TComponent; AOnTimer: TNotifyEvent);
var
  AMethod: TMethod absolute AOnTimer;
begin
  SetMethodProp(AOwner, GetPropInfo(AOwner, 'OnTimer'), AMethod);
end;

现在我们就可直接在VCL/FMX 用同一套代码创建 TTimer 实例了。

var
  ATimer:TComponent;
begin
  ATimer := TZTimer.Create(Self);
  TZTimer.Interval[ATimer] := 100;
  TZTimer.OnTimer[ATimer] := DoTimer;
  TZTimer.Enabled[ATimer] := true;
end;

当然,我们 TZTimer 还额外封装了一个 Create 的重载,可以直接将上面的代码简化为:

TZTimer.Create(Self,DoTimer,100,true);

滚动至顶部