【问题标题】:What do I override to manage the layout of components in a Delphi composite component?我应该重写什么来管理 Delphi 复合组件中的组件布局?
【发布时间】:2023-03-16 15:18:01
【问题描述】:

我正在开发一个 Delphi 组件,该组件由一个带有一些标签和按钮的面板组成。它可能看起来像这样:

或者像这样:

取决于属性的设置。此外,标签的布局会根据第一个标签的长度而变化。

我一直在用 TFrame 制作原型,并在框架的 OnPaint 方法中进行布局计算。在基于 TPanel 的组件中执行此操作的正确位置是什么?或者,更准确地说,在 TCustomAdvPanel 中,这就是我的派生。它是否像这样在 Paint 方法的覆盖中起作用?

procedure TDateRangePicker.Paint;
const
  hSpacing = 5;
begin
  if FShowRefresh then
  begin
    btnRefresh.Visible := true;
    btnRefresh.Left := Width - hSpacing - btnRefresh.Width;
    btnClearDates.Left := btnRefresh.Left - hSpacing - btnClearDates.Width;
    btnChooseDates.Left := btnClearDates.Left - hSpacing - btnChooseDates.Width;
  end
  else begin
    btnRefresh.Visible := false;
    btnClearDates.Left := Width - hSpacing - btnClearDates.Width;
    btnChooseDates.Left := btnClearDates.Left - hSpacing - btnChooseDates.Width;
  end;
  lblRangeCaption.Left := hSpacing;
  lblDateRange.Left := lblRangeCaption.Left + lblRangeCaption.Width + hSpacing;
  inherited Paint;
end;

【问题讨论】:

  • Paint 绝对是这样做的错误位置 - 它实际上是用于 绘图 控件,而不是用于定位它们。 (它们应该定位在 Windows 本身调用 Paint 之前。)但是,很难说你应该把它放在哪里,因为我没有 TCustomAdvPanel,因此不知道它来自什么或它有什么可用的事件。
  • TCustomAdvPanel 基本上是一个非常花哨的TCustomPanel,用于本次讨论。或者至少我对此非常确定。
  • 为什么不使用btnRefresh.Anchors := [akTop,akBottom,akRight];等?

标签: delphi custom-component


【解决方案1】:

绝对不要使用Paint 方法重新定位控件。在最坏的情况下,这会继续触发Paint 方法,一次又一次……因为,好吧:由于替换控件,面板需要重新绘制。 Paint 和所有等价物仅用于绘制自己。

何时实现您的代码:这应该在 ShowRefresh 属性的设置器中完成。

关于如何实现ShowRefresh 属性:当然,您可以像现在一样移动控件。您还可以考虑使用Margins (Delphi XE) 并对齐按钮和标签。那么属性设置器会变得相当简单:

type
  TDateRangePicker = class(TCustomPanel)
  private
    FChooseButton: TButton;
    FClearButton: TButton;
    FRefreshButton: TButton;
    FLabel1: TLabel;
    FLabel2: TLabel;
    function GetLabel1Caption: String;
    function GetRefreshButtonVisible: Boolean;
    procedure SetLabel1Caption(const Value: String);
    procedure SetRefreshButtonVisible(Value: Boolean);
  public
    constructor Create(AOwner: TComponent); override;
  published
    property RefreshButtonVisible: Boolean read GetRefreshButtonVisible
      write SetRefreshButtonVisible default True;
    property Label1Caption: String read GetLabel1Caption
      write SetLabel1Caption;
  end;

...

constructor TDateRangePicker.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FChooseButton := TButton.Create(Self);
  FChooseButton.Caption := 'Choose';
  FChooseButton.Align := alRight;
  FChooseButton.AlignWithMargins := True;
  FChooseButton.Margins.Left := 10;
  FChooseButton.Parent := Self;
  FClearButton := TButton.Create(Self);
  FClearButton.Caption := 'Clear';
  FClearButton.Align := alRight;
  FClearButton.AlignWithMargins := True;
  FClearButton.Margins.Left := 10;
  FClearButton.Parent := Self;
  FRefreshButton := TButton.Create(Self);
  FRefreshButton.Caption := 'Refresh';
  FRefreshButton.Align := alRight;
  FRefreshButton.AlignWithMargins := True;
  FRefreshButton.Margins.Left := 10;
  FRefreshButton.Parent := Self;
  FLabel1 := TLabel.Create(Self);
  FLabel1.Caption := 'Foo caption: ';
  FLabel1.Align := alLeft;
  FLabel1.Layout := tlCenter;
  FLabel1.Parent := Self;
  FLabel2 := TLabel.Create(Self);
  FLabel2.Caption := 'From 03/08/2012 to 06/06/2012';
  FLabel2.Align := alLeft;
  FLabel2.Layout := tlCenter;
  FLabel2.Parent := Self;
end;

function TDateRangePicker.GetLabel1Caption: String;
begin
  Result := FLabel1.Caption;
end;

function TDateRangePicker.GetRefreshButtonVisible: Boolean;
begin
  Result := FRefreshButton.Visible;
end;

procedure TDateRangePicker.SetLabel1Caption(const Value: String);
begin
  FLabel1.Caption := Value;
end;

procedure TDateRangePicker.SetRefreshButtonVisible(Value: Boolean);
begin
  FRefreshButton.Visible := Value;
  FRefreshButton.Left := Width;
end;

以及测试程序:

procedure TMainForm.TestButtonClick(Sender: TObject);
begin
  DateRangePicker1.Label1Caption := 'Test: ';
  DateRangePicker1.RefreshButtonVisible := not DateRangePicker1.RefreshButtonVisible;
end;

【讨论】:

  • 这基本上是我采取的方法。由于我暂时放弃了该组件以支持基于TFrame 的模板,因此我现在必须在初始化小部件时内联显式设置 ShowRefresh 属性(因为模板没有 OnCreate 事件我可以在默认情况下做到这一点),但我想你只需要在使用TFrame时接受这一点。
  • @wades 实际上,TFrame 没有 OnCreate 事件。您应该改写 Create 构造函数。
【解决方案2】:

您可以为 TDateRangePicker创建一个属性,例如:

property ShowRefresh:boolean read GetShowRefresh write SetShowRefresh

procedure TDateRangePicker.SetShowRefresh( Value : boolean);
begin
  btnRefresh.Visible := Value;
  // Force autosize after hidding Refresh button
  Autosize := True;
end;

所以,在绘图期间你无事可做。

【讨论】:

  • 去掉按钮后在框架上设置autosize的目的是什么?面板没有改变大小。除此之外,这是要走的路。
  • @LeonardoHerrera 如果您不想有空白空间,可以调整面板大小。我从你的例子中理解了这一点,但似乎我误解了;)
  • 我不想调整面板大小,我想移动面板上的控件。
  • 这并不能真正回答问题。它显示了如何更改 btnRefresh 的可见性,但没有显示在哪里显示它已更改。 (AutoSize := True; 没有反映所提供代码中的任何内容 - 您是否知道帖子中没有的 TCustomAdvPanel?)
  • @wades - 通过使用属性,您可以移动组件并在设置属性值时重新计算(您只需要添加代码而不是在此处设置 Autosize)。这样,控件就不必在每个Paint 循环中都执行此操作。
【解决方案3】:

您在创建子控件时设置初始位置,然后在需要更新它们时更新位置(更改属性时,调整父组件大小时等)。您不得更改 Paint() 方法或 OnPaint 事件中的位置。

如果您使用的是现代版本的 Delphi,则应改用子控件的 AlignMarginsAlignWithMargins 属性。这样,您只需在创建控件时定位一次,然后让 VCL 完成所有艰苦的工作,在需要时自动重新定位它们。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-11
    • 2019-05-06
    • 2023-03-21
    • 2014-08-21
    • 2020-10-30
    • 1970-01-01
    • 1970-01-01
    • 2022-10-13
    相关资源
    最近更新 更多