【问题标题】:Delphi 10.1 Firemonkey - Property values during component constructionDelphi 10.1 Firemonkey - 组件构建期间的属性值
【发布时间】:2024-01-02 21:46:01
【问题描述】:

我在 Delphi 10.1 中使用 Firemonkey 遇到了简单的问题。 创建新组件时(基于 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 文件中我看到了正确的值。

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

重写 AfterConstruction 过程并不能解决这个问题,我需要一个具有适当值的启动(创建时刻)的东西。还有更多:不是所有的东西都有这种行为——我看到 Boolean 的属性类型类似于 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;

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

【问题讨论】:

  • 在文章末尾添加
  • 还是不行。仅当我将添加带间隔的 TTimer 时才工作。那么这个值是正确的。似乎在设计时创建后运行时再次创建 - 使用出厂设置,但在构建之后,值是从 fmx 文件(或类似文件)映射的......

标签: delphi components firemonkey delphi-10.1-berlin


【解决方案1】:

您所描述的是正常行为。 TPosition 的子属性定义为nodefault,因此无论值如何,它们始终存储在 FMX 文件中。您的构造函数在设计时和运行时调用,因此您必须首先设置默认值。在设计时打开现有窗体/框架或在运行时运行项目时,会加载 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.

【讨论】:

  • 嗨 Remy,感谢您的解决。它仅使用 Layout11.BtnPosition.X:=50 问题解决,但仅此而已。我仍然在顶部描述了一个问题 - 启动程序时的属性值处于“构造函数”模式,然后属性值采用我的值(取自 Object Inspector)。
  • @Wojtek:这是完全正常的行为。我已经更新了我的答案。
  • 很好——很有帮助!还有一个问题:如何在设计时强制更新 Btn 位置?修改属性 i Object Inspector 时我看到仍然在同一个地方
  • @Wojtek:显示的代码中没有任何内容会阻止按钮在设计时移动。
  • 真的吗?它在我的 10.1 Berlin 中不起作用,可能是 IDE 的错误?