norgepaul
norgepaul

Reputation: 6053

How can I create an internal timer that will work with VCL and FMX?

I have a class that requires a timer. The class must work with both VCL and FMX. Unfortunately, the FMX timer is declared in FMX.Types and the VCL timer in Vcl.ExtCtrls.

As there is no conditional define like {$IFDEF FMX}xxx{$ENDIF} how do I use a timer in a cross platform class?

Upvotes: 2

Views: 2011

Answers (3)

yonojoy
yonojoy

Reputation: 5566

TTimer is a component and thus supports interfaces. I use an interposer class to inject an ITimer interface to the original timer class and program only against this interface (which is common for both TTimer classes).

unit Mv.TimerIntf;

interface

uses
    System.Classes;

type
    ITimer = interface
        function PropGetEnabled: Boolean;
        function PropGetInterval: Cardinal;
        function PropGetOnTimer: TNotifyEvent;
        procedure PropSetEnabled(AValue: Boolean);
        procedure PropSetInterval(AValue: Cardinal);
        procedure PropSetOnTimer(AValue: TNotifyEvent);
        property Enabled: Boolean read PropGetEnabled write PropSetEnabled;
        property Interval: Cardinal read PropGetInterval write PropSetInterval;
        property OnTimer: TNotifyEvent read PropGetOnTimer write PropSetOnTimer;
    end;

implementation

end.

and

unit Mv.VCL.Interposer.Timer;

interface

uses
    Mv.TimerIntf,
    System.Classes,
    VCL.ExtCtrls;

type

    TTimer = class(VCL.ExtCtrls.TTimer, ITimer)
        function PropGetEnabled: Boolean;
        function PropGetInterval: Cardinal;
        function PropGetOnTimer: TNotifyEvent;
        procedure PropSetEnabled(AValue: Boolean);
        procedure PropSetInterval(AValue: Cardinal);
        procedure PropSetOnTimer(AValue: TNotifyEvent);
    end;

implementation

function TTimer.PropGetEnabled: Boolean;
begin
    Result := Enabled;
end;

function TTimer.PropGetInterval: Cardinal;
begin
    Result := Interval;
end;

function TTimer.PropGetOnTimer: TNotifyEvent;
begin
    Result := OnTimer;
end;

procedure TTimer.PropSetEnabled(AValue: Boolean);
begin
    Enabled := AValue;
end;

procedure TTimer.PropSetInterval(AValue: Cardinal);
begin
    Interval := AValue;
end;

procedure TTimer.PropSetOnTimer(AValue: TNotifyEvent);
begin
    OnTimer := AValue;
end;

end.

...

Usage: Code against the interface:

procedure InitTimer(ATimer: ITimer)
begin
    ATimer.Interval := 100;
    ATimer.OnTimer := DoWhatEver;
end;

...

uses 
    Vcl.ExtCtrls,
    Mv.VCL.Interposer.Timer;

type
    TForm1 = class(TForm)
        Timer1: TTimer;
    end;


//...

begin
    InitTimer(Timer1);
end;

Upvotes: 1

David Heffernan
David Heffernan

Reputation: 613422

If it was me I would write a dedicated cross platform timer class that was independent of the FMX and VCL frameworks. Conceptually it would sit at the same level as the Delphi RTL.

If you don't want to do that and want to re-use the existing timer classes then you are in a bind. For targets which don't have the VCL, what do you do? There's no way for you to know whether your code will be consumed by an FMX or VCL project. Think about it. You can compile your unit to a .dcu and include it in any project. At the time when the unit is compiled it cannot know the type of project that will ultimately consume it.

So what to do? You could use the FMX timer everywhere. But that forces FMX onto VCL projects. I know I wouldn't like that. You could use the FMX timer everywhere other than Windows and use the VCL timer there. But that forces the VCL onto Windows FMX projects.

So you might take this approach:

  1. Create your own cross-platform timer class.
  2. On platforms other than Windows implement it on top of the FMX timer.
  3. On Windows implement it using the raw Windows API functions SetTimer and KillTimer.

Upvotes: 4

J...
J...

Reputation: 31443

We can assume that your class is non-visual and that, further, it is not a design-time component. If this were not the case then it would not be otherwise already compatible with both FMX and VCL.

With that being the case, there is no reason you can't include FMX.Types in a VCL application and there is further no reason that you can't create an FMX.Types.TTimer in a VCL application - you simply can't do it at design time (ie: drop an FMX TTimer on a VCL form). If you only need the timer internally, then the answer is clear - just use an FMX timer since it will compile no matter the platform target or the framework being used.

unit FMXTimerInVCLApplication;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  FMX.Types;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    FTimer : TTimer;   // FMX.Types.TTimer !
    procedure foo(Sender : TObject);
  end;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  FTimer := TTimer.Create(nil);
  FTimer.Interval := 1000;
  FTimer.OnTimer := foo;
end;

procedure TForm1.foo(Sender : TObject);
begin
  ShowMessage('foo');
end;

end.

This does bring a fair bit of FMX baggage into your application, of course. It's a lot of bloat for a timer, if you care about that sort of thing. I present it as an alternative to the other natural answer (which is writing your own).

Upvotes: 1

Related Questions