2
votes

I've got a problem with simply thing in Delphi 10.1 with Firemonkey. When creating a new component (based on TLayout, where are some another components like TDateEdits) I wanted to create a property

property EditDate_Position:TPosition read FDateEdits_Position write FDateEdits_PositionSet stored True;

where I declared a FDateEdits_Position as TPosition, and FDateEdits_PositionSet is a function FDateEdits_PositionSet(Value:TPosition).

The main constructor of component consist a code:

PointF.X:=10;
PointF.Y:=30;
FDateEdits_Position:=TPosition.Create(PointF);

So I have this property EditDate_Position in Object Inspector, and I can modify this value. But why - after compiling and running, this value is reseted to values as in Constructor? I tried to use a

If (csDesigning in ComponentState) then
begin
  PointF.X:=10;
  PointF.Y:=30;
  FDateEdits_Position:=TPosition.Create(PointF);
end;

to exclude those lines when running, but the program crashes (FDateEdits not created). I looked to Object Inspector - values are properly, and more - in .fmx file I see proper values.

So what I should do? I've noticed that this value is in begin point when constructor is executing, but a bit moment after it (checked with a TTimer with an Interval=1) - it takes proper values.

Overriding the AfterConstruction procedure doesn't fix this problem, and I need a something with a startup (creation moment) with proper values. And more: not everything have this behavior - I see that properties type of Boolean are similar like TPosition, but a TBitmap property is working properly...

I think that it is an result of the TPosition.Create(PointF), but how to create this without setting those default values during runtime?

procedure TTest.FDateEdits_PositionSet(Value:TPosition);
begin
  FDateEdits_Position:=Value;
  FDateEdits_Resize;
end;

FDateEdits_Resize moves some components in (Self).

There is an sample code (but not the same, it's simplified):

unit Layout1;

interface

uses
  System.SysUtils, System.Classes, FMX.Types, FMX.Controls, FMX.Layouts,
  FMX.StdCtrls, System.Types;

type
  TLayout1 = class(TLayout)
  private
    { Private declarations }
    FBtn:TButton;
    FPosition:TPosition;

    procedure FPositionSet(Value:TPosition);
  protected
    { Protected declarations }
  public
    { Public declarations }
    constructor Create(AOwner:TComponent); override;
    destructor Destroy; override;
  published
    { Published declarations }
    property BtnPosition:TPosition read FPosition write FPositionSet;
  end;

procedure Register;

implementation

constructor TLayout1.Create(AOwner:TComponent);
var
  PointF:TPointF;
begin
  inherited Create(AOwner);
  FBtn:=TButton.Create(Self);
  FBtn.Parent:=Self;
  FBtn.Stored:=False;
  FBtn.Text:='Text';

  PointF.X:=10;
  PointF.Y:=10;
  FPosition:=TPosition.Create(PointF);

  FBtn.Position.Assign(FPosition);
end;

destructor TLayout1.Destroy;
begin
  If FPosition<>nil then FPosition.Free;
  If FBtn<>nil then FBtn.Free;

  inherited;
end;

procedure TLayout1.FPositionSet(Value:TPosition);
begin
  FPosition.Assign(Value);
  FBtn.Position.Assign(Value);
end;

procedure Register;
begin
  RegisterComponents('Samples', [TLayout1]);
end;

end.

But I've noticed that simply calling

Layout11.BtnPosition.X:=50;

doesn't have any result, the breakline in code doesn't work (but in constructor section works...)

1
Added at the end of postWojtek
Still don't work. Work only when I'll add a TTimer with Interval. Then this value is properly. It seems like after creating in Designtime the RunTime is creating again - with factory settings, but after the construction the values are mapped from fmx file (or something similar)...Wojtek

1 Answers

2
votes

What you describe is normal behavior. TPosition's subproperties are defined as nodefault, so they are always stored in the FMX file regardless of value. Your constructor is called at design-time and run-time, so you have to set default values first. When opening an existing Form/Frame at design-time, or running the project at run-time, the FMX is loaded to overwrite the defaults. Perfectly normal behavior. If you don't want your component to act on the default values when loading the FMX file, you need to check the ComponentState property and override the virtual Loaded() method.

In order for assignments to BtnPosition.X (or Y) to have an effect, you need to assign an event handler to the TPosition.OnChange event.

Try this:

unit Layout1;

interface

uses
  System.SysUtils, System.Classes, FMX.Types, FMX.Controls, FMX.Layouts,
  FMX.StdCtrls, System.Types;

type
  TLayout1 = class(TLayout)
  private
    { Private declarations }
    FBtn: TButton;
    FPosition: TPosition;

    procedure FPositionChanged(Sender: TObject);
    procedure FPositionSet(Value: TPosition);
  protected
    { Protected declarations }
    procedure Loaded; override;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    { Published declarations }
    property BtnPosition: TPosition read FPosition write FPositionSet;
  end;

procedure Register;

implementation

constructor TLayout1.Create(AOwner: TComponent);
var
  PointF: TPointF;
begin
  inherited Create(AOwner);
  FBtn := TButton.Create(Self);
  FBtn.Parent := Self;
  FBtn.Stored := False;
  FBtn.Text := 'Text';

  PointF.X := 10;
  PointF.Y := 10;
  FPosition := TPosition.Create(PointF);
  FPosition.OnChange := FPositionChanged;

  If not (csLoading in ComponentState) then
    FBtn.Position.Assign(FPosition);
end;

destructor TLayout1.Destroy;
begin
  FPosition.Free;
  FBtn.Free;    
  inherited;
end;

procedure TLayout1.FPositionChanged(Sender: TObject);
begin
  if (FBtn <> nil) and not (csLoading in ComponentState) then
    FBtn.Position.Assign(FPosition);
end;

procedure TLayout1.FPositionSet(Value: TPosition);
begin
  if Value <> FPosition then
    FPosition.Assign(Value);
end;

procedure TLayout1.Loaded;
begin
  inherited;
  FBtn.Position.Assign(FPosition);
end;

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

procedure Register;
begin
  RegisterComponents('Samples', [TLayout1]);
end;

end.