15

I have a TComponent which controls some of the UI. This component is designed to support both VCL and Firemonkey by using conditionals. Such conditionals instruct my component whether to accept a VCL control or an FMX control. It's currently expected that this conditional is defined on the application level to instruct the component in run-time whether it's to manage a VCL or FMX control.

I'd like to publish my component into the IDE with support for both VCL and FMX, sharing the same unit with conditionals. However, depending on whether VCL or FMX is currently in use, the property names/types differ.

For example...

type
  TMyComponent = class(TComponent)
  published
    {$IFDEF USE_FMX}
    property TabControl: TTabControl read FTabControl write SetTabControl;
    {$ENDIF}
    {$IFDEF USE_VCL}
    property PageControl: TPageControl read FPageControl write SetPageControl;
    {$ENDIF}
  end;

My goal is to be able to drop this non-visual component onto either a VCL or FMX form, and automatically show the appropriate framework-specific properties in the object inspector.

How do I go about registering this component which shares both VCL and FMX code via conditionals?

Jerry Dodge
  • 26,858
  • 31
  • 155
  • 327
  • 6
    I would use two components both inherited from a base class containing all the code that can be shared. – Sir Rufo Jul 24 '16 at 21:44
  • @SirRufo That's an excellent design, thanks. – Jerry Dodge Jul 24 '16 at 22:00
  • You can share even more code if you internally wrap the controls – Sir Rufo Jul 24 '16 at 22:08
  • 3
    Actually you would have problems to install both flavors inside the IDE. If the conditional designs are mutual exclusive, you need to compile two different versions of the component. This produces two different dcu files for the same pas file and those units have to go into different packages (because of the two compilations). I am not aware of any way to load two similar named units into the IDE at once, even if they reside in separate packages. – Uwe Raabe Jul 24 '16 at 22:44
  • @Uwe: two **equally** named dcus. And the IDE won't allow it. – Rudy Velthuis Jul 24 '16 at 23:21
  • @RudyVelthuis, that's what I wanted to say. – Uwe Raabe Jul 24 '16 at 23:24
  • 1
    not sure why this was downvoted +1 – John Easley Jul 24 '16 at 23:26
  • 1
    Not sure why it was downvoted, either. It's a well written question. – Ken White Jul 25 '16 at 00:30
  • 5
    Indy supports both VCL and FMX. If you look at how Embarcadero's copy of Indy is pre-installed in the IDE, there is only one set of design-time packages installed in the IDE, but a separate set of DCUs/BPLs has been compiled for each supported platform and framework. – Remy Lebeau Jul 25 '16 at 02:45
  • Have you tried your code? Does it work for both frameworks? I would expect the IDE to be able to figure it out automatically depending on the type of application your are creating – John Kouraklis Jul 25 '16 at 17:18
  • @JohnKouraklis That is an incorrect assumption. The IDE doesn't know which framework you intend to target. The unit names are also wrapped in the same conditionals, either the VCL or FMX units are used depending. A non-visual component may be independent from any framework, such as mine - although it has somewhat adapters with the UI - and that relies on whether it's VCL or FMX. My conditionals are not standard, and in fact a prior question of mine shows that Delphi doesn't even have a conditional for whether VCL or FMX is in use. – Jerry Dodge Jul 25 '16 at 17:26
  • @Jerry Dodge You are right when it comes to the dcus. But when you create an app and drop a component the IDE knows what framework you are using. So, I would expect your declares to choose the right parts if your code;but you need to manually declare them. Another more sophisticated way would be your component to inspect the XML project file where there is reference of the framework. I can't remember the tag right now but it is in the first couple of lines of the project file – John Kouraklis Jul 25 '16 at 17:33
  • 1
    @JohnKouraklis: are you thinking of the `` element? Because that is not always present, and can even be set to `None`. – Remy Lebeau Jul 28 '16 at 04:03
  • @RemyLebeau: is that so? I only work on FMX and it is always (I think) present. Isn't it present in VCL projects? Or, is there a kind of pattern? – John Kouraklis Jul 31 '16 at 15:07
  • @JohnKouraklis Not necessarily. For example, you can write a console application, which is neither VCL or FMX. In which case my component is not to be supported. – Jerry Dodge Jul 31 '16 at 16:30
  • @JerryDodge: I see. Thanks for the tip – John Kouraklis Jul 31 '16 at 17:03

1 Answers1

8

I would strongly advise against creating framework-specific properties like you are trying to do. I would suggest instead creating separate framework-specific adapter components, and then you can assign one of those adapters to your main component as needed, eg:

unit MyComponentUI;

interface

uses
  Classes;

type
  TMyComponentUIControl = class(TComponent)
  public
    procedure DoSomethingWithControl; virtual; abstract;
    ...
  end;

implementation

...

end.
unit MyComponentFmxUI;

uses
  MyComponentUI,
  FMX.TabControl;

type
  TMyComponentUIControl_FMXTabControl = class(TMyComponentUIControl)
  private
    FTabControl: TTabControl;
    procedure SetTabControl(Value: TTabControl);
  protected
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  public
    procedure DoSomethingWithControl; override;
  published
    property TabControl: TTabControl read FTabControl write SetTabControl;
  end;

procedure Register;

implementation

uses
  FMX.Controls;

procedure TMyComponentUIControl_FMXTabControl.DoSomethingWithControl; 
begin
  if FTabControl <> nil then
  begin
    ...
  end;
end;

procedure TMyComponentUIControl_FMXTabControl.SetTabControl(Value: TTabControl);
begin
  if FTabControl <> Value then
  begin
    if FTabControl <> nil then FTabControl.RemoveFreeNotification(Self);
    FTabControl := Value;
    if FTabControl <> nil then FTabControl.FreeNotification(Self);
  end;
end;

procedure TMyComponentUIControl_FMXTabControl.Notification(AComponent: TComponent; Operation: TOperation);
begin
  inherited;
  if (Operation = opRemove) and (AComponent = FTabControl) then
    FTabControl := nil;
end;

procedure Register;
begin
  GroupDescendentsWith(TMyComponentUIControl_FMXTabControl, TControl);
  RegisterComponents('My Component', [TMyComponentUIControl_FMXTabControl]);
end;

end.
unit MyComponentVclUI;

interface

uses
  MyComponentUI,
  Vcl.ComCtrls;

type
  TMyComponentUIControl_VCLPageControl = class(TMyComponentUIControl)
  private
    FPageControl: TPageControl;
    procedue SetPageControl(Value: TPageControl);
  protected
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  public
    procedure DoSomethingWithControl; override;
  published
    property PageControl: TPageControl read FPageControl write SetPageControl;
  end;

procedure Register;

implementation

uses
  Vcl.Controls;

procedure TMyComponentUIControl_VCLPageControl.DoSomethingWithControl; 
begin
  if FPageControl <> nil then
  begin
    ...
  end;
end;

procedure TMyComponentUIControl_VCLPageControl.SetPageControl(Value: TPageControl);
begin
  if FPageControl <> Value then
  begin
    if FPageControl <> nil then FPageControl.RemoveFreeNotification(Self);
    FPageControl := Value;
    if FPageControl <> nil then FPageControl.FreeNotification(Self);
  end;
end;

procedure TMyComponentUIControl_VCLPageControl.Notification(AComponent: TComponent; Operation: TOperation);
begin
  inherited;
  if (Operation = opRemove) and (AComponent = FPageControl) then
    FPageControl := nil;
end;

procedure Register;
begin
  GroupDescendentsWith(TMyComponentUIControl_VCLPageControl, TControl);
  RegisterComponents('My Component', [TMyComponentUIControl_VCLPageControl]);
end;

end.
unit MyComponent;

interface

uses
  Classes,
  MyComponentUI;

type
  TMyComponent = class(TComponent)
  private
    FUIControl: TMyComponentUIControl;
    procedure SetUIControl(Value: TMyComponentUIControl);
  protected
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  public
    procedure DoSomething;
  published
    property UIControl: TMyComponentUIControl read FUIControl write SetUIControl;
  end;

procedure Register;

implementation

procedure TMyComponent.DoSomething;
begin
  ...
  if FUIControl <> nil then
    FUIControl.DoSomethingWithControl;
  ...
end;

procedure TMyComponent.SetUIControl(Value: TMyComponentUIControl);
begin
  if FUIControl <> Value then
  begin
    if FUIControl <> nil then FUIControl.RemoveFreeNotification(Self);
    FUIControl := Value;
    if FUIControl <> nil then FUIControl.FreeNotification(Self);
  end;
end;

procedure TMyComponent.Notification(AComponent: TComponent; Operation: TOperation);
begin
  inherited;
  if (Operation = opRemove) and (AComponent = FUIControl) then
    FUIControl := nil;
end;

procedure Register;
begin
  RegisterComponents('My Component', [TMyComponent]);
end;

end.

By using GroupDescendentsWith() to group each adapter with either FMX.Controls.TControl or Vcl.Controls.TControl, this allows the IDE to filter the components at design-time based on framework used in the parent project:

On a VCL Form Designer, you will see only TMyComponentUIControl_VCLPageControl available in the Tool Palette.

On a FMX Form Designer, you will see only TMyComponentUIControl_FMXTabControl available in the Tool Palette.

On a DataModule Designer, you will not see either adapter, unless you set the TDataModule.ClassGroup property to a VCL or FMX group. Then you will see the appropriate adapter available in the Tool Palette.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 3 years later and I'm noting "I would strongly advise against creating **platform**-specific properties" ... I assume you mean **framework**? Sure, platform-specific applies too. But in the particular context, I think for terminology sake, it should be "framework". – Jerry Dodge Oct 22 '19 at 00:56
  • @JerryDodge Yes, I meant framework-specific instead of platform-specific. I have corrected it. – Remy Lebeau Oct 22 '19 at 02:09
  • It seems to be GroupDescendentsWith, with "e", not "a" in Delphi 11 (looking at System.Classes). Did they rename it somewhere in the way or is it a typo above? – George Birbilis Oct 06 '21 at 09:50
  • @GeorgeBirbilis it is a typo above. I have corrected it now – Remy Lebeau Oct 06 '21 at 14:40