Delphi 10.1 Firemonkey - 属性 组件构造期间的值

Delphi 10.1 Firemonkey - Property values during component construction

我在使用 Firemonkey 的 Delphi 10.1 中遇到了简单的问题。 创建新组件时(基于 TLayout,其中有一些其他组件,如 TDateEdits)我想创建一个 属性

property EditDate_Position:TPosition read FDateEdits_Position write FDateEdits_PositionSet stored True;

其中我将 FDateEdits_Position 声明为 TPosition,并且 FDateEdits_PositionSet 是一个 函数 FDateEdits_PositionSet(Value:TPosition).

组件的主要构造函数有一段代码:

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

所以我在Object Inspector中有这个属性 EditDate_Position,我可以修改这个值。但是为什么 - 在编译和 运行 之后,这个值被重置为构造函数中的值? 我尝试使用

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

在 运行 时排除那些行,但程序崩溃(FDateEdits 未创建)。我查看了 Object Inspector - 值是正确的,而且更多 - 在 .fmx 文件中我看到了正确的值。

那我该怎么办?我注意到这个值在构造函数执行时处于开始点,但在它之后的片刻(使用 Interval=1 的 TTimer 检查)- 它采用正确的值。

覆盖 AfterConstruction 过程并不能解决这个问题,我需要一个具有适当值的启动(创建时刻)的东西。还有更多:不是所有的东西都有这种行为——我看到布尔值的属性类型与 TPosition 相似,但是 TBitmap 属性 工作正常...

我认为这是 TPosition.Create(PointF) 的结果,但是如何在运行时不设置这些默认值的情况下创建它?

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

FDateEdits_Resize 移动一些组件在 (Self).

有示例代码(但不一样,是简化版):

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.

但我注意到只要调用

Layout11.BtnPosition.X:=50;

没有任何结果,代码中的断行不起作用(但在构造函数部分起作用...)

您描述的是正常行为。 TPosition 的子属性定义为 nodefault,因此无论值如何,它们始终存储在 FMX 文件中。您的构造函数在设计时和 运行 时被调用,因此您必须首先设置默认值。在设计时打开现有的 Form/Frame,或在 运行 时 运行 打开项目时,加载 FMX 以覆盖默认值。完全正常的行为。如果您不希望您的组件在加载 FMX 文件时根据默认值进行操作,则需要检查 ComponentState 属性 并覆盖虚拟 Loaded() 方法。

为了使 BtnPosition.X(或 Y)的赋值生效,您需要为 TPosition.OnChange 事件分配一个事件处理程序。

试试这个:

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.