Composite component

Support forum for collection of native FMX shape controls.

Composite component

Postby Gary » Mon Mar 30, 2015 2:53 am

I'm trying to build a composite component, a TRadiantCircle that contains a TRadiantX. This doesn't seem like a component problem, but I'm a little perplexed. When I install this component, at design time if I toggle between the form and its code (view as text) and back to the form, the inner component is recreated so that there are two TRadiantX components inside the TRadiantCircle .

I would appreciate it if anyone could point out what I'm doing wrong.

Code: Select all
unit GGXButton;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.UIConsts, FMX.Types, FMX.Graphics, FMX.Controls,
  Radiant.Shapes, FMX.Objects, Math, FMX.Dialogs;

const
  GG_FILL_COLOR = claGreen;
  GG_STROKE_THICKNESS = 3;
  GG_OUTER_STROKE_COLOR = claLimeGreen;
  GG_INNER_STROKE_COLOR = claOliveDrab;
  GG_SCALE_STROKE_THICKNESS = False;

type
  TGGXButton = class(TRadiantCircle)
  protected
    FInnerX: TRadiantX;
  private
    procedure GGXButtonResize(Sender: TObject);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    {}
  end;

  procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('GG', [TGGXButton]);
end;

constructor TGGXButton.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  with Self do begin
    Fill.Color := GG_FILL_COLOR;
    {  GRADIANT
    Fill.Kind := TBrushKind.Gradient;

    Fill.Gradient.Points.Add;
    Fill.Gradient.Points[0].Color := claDarkgreen;
    Fill.Gradient.Points[0].Offset := 0;

    Fill.Gradient.Points.Add;
    Fill.Gradient.Points[1].Color := claOliveDrab;
    Fill.Gradient.Points[1].Offset := 1;
    }

    Height := 64;
    Width := 64;

    Padding.Top := 8;
    Padding.Left := 8;
    Padding.Bottom := 8;
    Padding.Right := 8;

    Stroke.Color := GG_OUTER_STROKE_COLOR;
    Stroke.Thickness := GG_STROKE_THICKNESS;
    ScaleStrokeThickness := GG_SCALE_STROKE_THICKNESS;

    OnResize := GGXButtonResize;
  end;

  //if (csDesigning in Self.ComponentState) and not
    //(csReading in Owner.ComponentState) then begin  // This doesn't seem right
    FInnerX := TRadiantX.Create(Self);
    //FInnerX.Name := 'InnerX';
    FInnerX.SetSubComponent(True);
    FInnerX.Parent := Self;
    FInnerX.Align := FMX.Types.TAlignLayout.Client;
    FInnerX.HitTest := False;

    FInnerX.ScaleStrokeThickness := GG_SCALE_STROKE_THICKNESS;
    FInnerX.Stroke.Thickness := GG_STROKE_THICKNESS;
    FInnerX.Stroke.Color := GG_INNER_STROKE_COLOR;
  //end;

end;

destructor TGGXButton.Destroy;
begin
  inherited;
end;

procedure TGGXButton.GGXButtonResize(Sender: TObject);
var
  APad: Double;
  ASize: Double;
begin
  ASize := Min(Width, Height);
  APad := (ASize * 0.25) / 2;
  Padding.Top := APad;
  Padding.Left := APad;
  Padding.Bottom := APad;
  Padding.Right := APad;
end;

initialization
  RegisterFMXClasses([TGGXButton]);

end.
Gary
 
Posts: 3
Joined:
Sun Mar 29, 2015 12:19 pm

Re: Composite component

Postby Raize Support » Tue Mar 31, 2015 2:15 am

Hi Gary,

Interesting. From your code, it does look like it should work. You are correctly passing Self as the owner of the embedded TRadiantX control. And your comment is correct, you should not need the ComponentState of Self nor the Owner to create the embedded control.

However, one thing that I did find odd in searching the Delphi source code is that in the couple of examples in FMX that uses SetSubComponent, the embedded control is created with nil as the Owner and not Self. Of course, this is valid as long as you also write a destructor to free the embedded control when the composite is destroyed.

I would try
FInnerX := TRadiantX.Create( nil );

Then add a destructor to

destructor TGGXButton.Destroy;
begin
FInnerX.Free;
inherited;
end;


As an aside, you should really move the Register procedure out of the component unit and put it into a Registration Unit. The Registration Unit would be contained in a Design Package. The Design Package would require the Runtime Package that contains the component unit. The Design Package gets loaded into the IDE, and the runtime package could be deployed if you wanted to build the app using the component with packages.

Ray
Raize Software Support
Raize Software
http://www.raize.com
Raize Support
 
Posts: 604
Joined:
Fri Mar 25, 2011 9:04 pm

Re: Composite component

Postby Gary » Tue Mar 31, 2015 7:03 am

Thanks very much for the input Ray. I tried creating the nested component with a nil owner and freeing it in the destructor, but the result is exactly the same. Every time I toggle between the form and the code (one of the manifestations of this problem), it creates a new object:

Code: Select all
object ButtonTestForm: TButtonTestForm
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 480
  ClientWidth = 692
  FormFactor.Width = 320
  FormFactor.Height = 480
  FormFactor.Devices = [Desktop, iPhone, iPad]
  DesignerMobile = False
  DesignerWidth = 0
  DesignerHeight = 0
  DesignerDeviceName = ''
  DesignerOrientation = 0
  DesignerOSVersion = ''
  object GGXButton1: TGGXButton
    Fill.Color = claGreen
    Height = 64.000000000000000000
    Padding.Left = 8.000000000000000000
    Padding.Top = 8.000000000000000000
    Padding.Right = 8.000000000000000000
    Padding.Bottom = 8.000000000000000000
    Position.X = 304.000000000000000000
    Position.Y = 192.000000000000000000
    Stroke.Color = claLimegreen
    Stroke.Thickness = 3.000000000000000000
    Width = 64.000000000000000000
    object TRadiantX
      Align = Client
      Height = 48.000000000000000000
      HitTest = False
      Stroke.Color = claOlivedrab
      Stroke.Thickness = 3.000000000000000000
      Width = 48.000000000000000000
    end
  end
end


becomes this after toggling:

Code: Select all
object ButtonTestForm: TButtonTestForm
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 480
  ClientWidth = 692
  FormFactor.Width = 320
  FormFactor.Height = 480
  FormFactor.Devices = [Desktop, iPhone, iPad]
  DesignerMobile = False
  DesignerWidth = 0
  DesignerHeight = 0
  DesignerDeviceName = ''
  DesignerOrientation = 0
  DesignerOSVersion = ''
  object GGXButton1: TGGXButton
    Fill.Color = claGreen
    Height = 64.000000000000000000
    Padding.Left = 8.000000000000000000
    Padding.Top = 8.000000000000000000
    Padding.Right = 8.000000000000000000
    Padding.Bottom = 8.000000000000000000
    Position.X = 304.000000000000000000
    Position.Y = 192.000000000000000000
    Stroke.Color = claLimegreen
    Stroke.Thickness = 3.000000000000000000
    Width = 64.000000000000000000
    object TRadiantX
      Align = Client
      Height = 48.000000000000000000
      HitTest = False
      Stroke.Color = claOlivedrab
      Stroke.Thickness = 3.000000000000000000
      Width = 48.000000000000000000
    end
    object TRadiantX
      Align = Client
      Height = 48.000000000000000000
      HitTest = False
      Stroke.Color = claOlivedrab
      Stroke.Thickness = 3.000000000000000000
      Width = 48.000000000000000000
    end
  end
end
Gary
 
Posts: 3
Joined:
Sun Mar 29, 2015 12:19 pm

Re: Composite component

Postby Raize Support » Wed Apr 01, 2015 1:16 am

Hi Gary,

Thanks for posting the follow up. I should have remembered this in my previous response. Because of the way FMX handles compositing and styles, the streaming code in FMX is very aggressive. Fortunately, there is a way to workaround the issue. That is, for your embedded control, you need to set the Stored property to False. For example,

Code: Select all
    FInnerX := TRadiantX.Create(Self);
    //FInnerX.Name := 'InnerX';
    FInnerX.SetSubComponent(True);
    FInnerX.Parent := Self;
    FInnerX.Align := FMX.Types.TAlignLayout.Client;
    FInnerX.HitTest := False;

    FInnerX.ScaleStrokeThickness := GG_SCALE_STROKE_THICKNESS;
    FInnerX.Stroke.Thickness := GG_STROKE_THICKNESS;
    FInnerX.Stroke.Color := GG_INNER_STROKE_COLOR;
    FInnerX.Stored := False;


This instructs the streaming code to ignore the FInnerX control. Note that since you are calling SetSubComponent( True ) you will see the published properties of the embedded control in the XFM file. However, instead of being nested in an object block, they will be referenced using the name of the property referencing the component.

Ray
Raize Software Support
Raize Software
http://www.raize.com
Raize Support
 
Posts: 604
Joined:
Fri Mar 25, 2011 9:04 pm

Re: Composite component

Postby Gary » Wed Apr 01, 2015 10:43 pm

Hi Ray,

Yes, that did it.

As I originally thought, it wasn't a component problem. I really appreciate your wonderful support in spite of the fact that it wasn't really your problem.

You're a good man Ray!

Thanks.

Gary
Gary
 
Posts: 3
Joined:
Sun Mar 29, 2015 12:19 pm

Re: Composite component

Postby Raize Support » Thu Apr 02, 2015 5:05 pm

You're welcome. Glad to help out.

Ray
Raize Software Support
Raize Software
http://www.raize.com
Raize Support
 
Posts: 604
Joined:
Fri Mar 25, 2011 9:04 pm


Return to Radiant Shapes

Who is online

Users browsing this forum: No registered users and 2 guests

cron