2

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?

norgepaul
  • 6,013
  • 4
  • 43
  • 76
  • Related, [Delphi XE2: Is there a predefined conditional to identify VCL and FireMonkey?](http://stackoverflow.com/q/7720508/576719). – LU RD Sep 15 '15 at 09:22
  • @DavidHeffernan - That would be a possibility, but I would prefer to be able to create the class in any project without having to worry about setting conditional defines. – norgepaul Sep 15 '15 at 09:30
  • @DavidHeffernan - That will work to a certain extent, but if I test for Windows, I still don't know whether it's FMX Windows or VCL Windows. If I assume it's VCL but it's an FMX application I will include the VCL code and vice versa. This is what I want to avoid. – norgepaul Sep 15 '15 at 09:50
  • Ah, I see. Of course I can write my own, but I was hoping there was a simpler way that could leverage the existing code. – norgepaul Sep 15 '15 at 10:13
  • @norgepaul I could not see a neat way of distinguishing between VCL Windows or FMX Windows, so I created my own conditional define. I then test that at lower levels to decide whether to call the FMX or VCL routines. In fact, I have one app which is a VCL app but uses FMX forms, so you can mix the two, hence the reason for there not being a built-in define. – Brian Frost Sep 15 '15 at 10:48
  • @BrianFrost No such way is possible. Compile a source file to .dcu. At that point, how can the compiler know where this .dcu file will get used? Sometime down the line the user might elect to include it in an FMX project or a VCL project. But that is not known when the file is compiled. A good example might be `SysUtils` or indeed any RTL unit. You typically link against the .dcu file supplied by Embarcadero. When that .dcu file was compiled, nobody knew whether you were going to use it in an FMX or a VCL project. – David Heffernan Sep 15 '15 at 16:50
  • @David, perhaps I was not clear. I create a top level conditional (e.g. APP_VCL, or APP_FMX} and use this to control the uses clauses where needed (FMX.Dialogs, VCL.Dialogs etc). Yes it's messy, but it does solve a problem. I am not using packages, and everything is compiled together. – Brian Frost Sep 17 '15 at 07:20
  • @Brian You said, "I could not see a neat way of distinguishing between VCL Windows or FMX Windows." I was explaining why there can be no such neat way. – David Heffernan Sep 17 '15 at 07:49

3 Answers3

4

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.
norgepaul
  • 6,013
  • 4
  • 43
  • 76
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 1
    Emba put some of the platform services in a wrong place. `IFMXTimerService` and `IFMXLoggingService` should not belong to FMX. This is more RTL or just raw cross platform. But you are right in building an own platform framework which has no dependency to VCL or FMX. – Sir Rufo Sep 15 '15 at 13:15
1

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).

J...
  • 30,968
  • 6
  • 66
  • 143
1

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;
yonojoy
  • 5,486
  • 1
  • 31
  • 60