如何创建一个类似对话框的组件,允许在其中放置其他控件?

43

这是一个Firemonkey组件,但我发现大部分组件基础都相同于VCL和FMX,所以如果您知道如何在VCL中实现,请分享您的知识,这可能是我的解决方案。

我使用TPopup作为祖先。这对我很方便,因为它保留在窗体/框架上,我可以使用相同的上下文/结构将其与LiveBindings连接起来,这对我非常方便。

我需要它像TPopup一样完全表现出容器的行为。但我需要它看起来更好,并且有我的特定按钮(我已经创建了一些属性和自动化程序)。

问题在于,我创建了一些内部控件,例如TLayouts、Tpanels和Tbuttons,使其看起来像这样:(empty)

My empty Popup

里面的黑色区域是我想要放置TEdit等控件的地方。

我将所有内部创建的控件设置为Store = false,因此它不会存储在流系统中。这样做时,当我拖放例如TEdit时,我得到的是这样的(我需要顶部对齐的Tedit):

My Popup with TEdit

然而,我期望的是这样的:

My popup with TEdit in the right position

如果我将Store = true更改为真,我可以获得正确的效果,但所有内部控件都会暴露在结构面板上,每次保存表单并重新打开时,所有内容都会被复制。内部组件的暴露对我来说不是问题,但复制是个问题,如果我关闭并打开该组件10次,我将获得整个内部结构复制了10次。

我将尝试展示一些与组件设计相关的代码:

类声明:

  [ComponentPlatformsAttribute(pidWin32 or pidWin64 or pidOSX32 or pidiOSSimulator or pidiOSDevice or pidAndroid)]
  TNaharFMXPopup = class(TPopup, INaharControlAdapter, INaharControl)
  private
  protected
    FpnlMain       : TPanel;
    FlytToolBar    : TLayout;
    FbtnClose      : TButton;
    FbtnSave       : TButton;
    FbtnEdit       : TButton;
    FpnlClientArea : TPanel;
    FlblTitle      : TLabel;
    procedure   Loaded; override;
    procedure   Notification(AComponent: TComponent; Operation: TOperation); override;

constructor Create:

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

      FpnlMain         := TPanel.Create(Self);
      FlblTitle        := TLabel.Create(Self);
      FlytToolBar      := TLayout.Create(Self);
      FbtnEdit         := TButton.Create(Self);
      FpnlClientArea   := TPanel.Create(Self);
      FbtnClose         := TButton.Create(FlytToolBar);
      FbtnSave          := TButton.Create(FlytToolBar);

      Height         := 382;
      Placement      := TPlacement.Center;
      StyleLookup    := 'combopopupstyle';
      Width          := 300;

      ApplyControlsProp;

    end;

设置内部控件的属性:

procedure TNaharFMXPopup.ApplyControlsProp;
begin
  with FpnlMain do
  begin
    Parent         := Self;
    Align          := TAlignLayout.Client;
    StyleLookup    := 'grouppanel';
    TabOrder       := 0;
    Margins.Bottom := 10;
    Margins.Left   := 10;
    Margins.Right  := 10;
    Margins.Top    := 10;
    Stored         := false;
  end;
  with FlblTitle do
  begin
    Parent         := FpnlMain;
    Text           := 'Título';
    Align          := TAlignLayout.Top;
    Height         := 36;
    StyleLookup    := 'flyouttitlelabel';
    Stored         := false;
  end;
  with FpnlClientArea do
  begin
    Parent         := FpnlMain;
    Align          := TAlignLayout.Client;
    StyleLookup    := 'gridpanel';
    TabOrder       := 0;
    Margins.Bottom := 5;
    Margins.Left   := 5;
    Margins.Right  := 5;
    Margins.Top    := 5;
    Stored         := false;
  end;
  with FlytToolBar do
  begin
    Parent         := FpnlMain;
    Align          := TAlignLayout.Bottom;
    Height         := 50;
    Stored         := false;
  end;
  with FbtnClose do
  begin
    Parent         := FlytToolBar;
    Text           := 'Fecha';
    Align          := TAlignLayout.Left;
    Height         := 50;
    StyleLookup    := 'tilebutton';
    TabOrder       := 0;
    Width          := 70;
    ModalResult    := mrClose;
    Stored         := false;
  end;
  with FbtnEdit do
  begin
    Parent         := FlytToolBar;
    Text           := '';//'Edita';
    Align          := TAlignLayout.Left;
    Height         := 50;
    StyleLookup    := 'tilebutton';
    TabOrder       := 1;
    Width          := 70;
    ModalResult    := mrContinue;
    Stored         := false;
    Enabled        := false;
  end;
  with FbtnSave do
  begin
    Parent         := FlytToolBar;
    Text           := 'Salva';
    Align          := TAlignLayout.Left;
    Height         := 50;
    StyleLookup    := 'tilebutton';
    TabOrder       := 2;
    Width          := 70;
    ModalResult    := mrOk;
    Stored         := false;
  end;
end;

已加载:

procedure TNaharFMXPopup.Loaded;
begin
  inherited;

  ApplyControlsProp;
  SetEvents;
end;

我尝试了以下通知方法,尝试使插入的控件成为我的内部“客户区”的父级。
procedure TNaharFMXPopup.Notification(AComponent: TComponent; Operation: TOperation);
begin
  inherited;
  if (Operation = opInsert) and (csDesigning in ComponentState) then
  begin
    if AComponent.Owner = self then
      if AComponent is TFmxObject then
      begin
        (AComponent as TFmxObject).Parent := FpnlClientArea;
      end;
  end;

end;

但这并没有改变任何事情。

我以前问过类似的问题,但我对创建此类组件所需的许多东西不太了解,而且回答并没有给出太多帮助,我缺少每个内部组件的父级。

现在我正在尝试真正展示我的需求:我需要将控件放置在我的TPopup对话框上,这些控件将成为其内部ClientArea的父级。


7
给那位踩负评的人:为什么呢?我花了很大的力气来创建这个组件,进行了研究,但不知道该怎么修复它。我已经尽我所能在这个问题上进行了解释。请问我还能改进什么吗? - Eduardo Elias
5
就我而言,我认为减一分有点奇怪,因为你显然付出了很大的努力来组织你的问题。也许他们会提供解释。 - MartynA
是的,我记得在FireMonkey的第一个版本中,任何组件都可以作为容器并包含任何其他组件。但我提到的问题出现在Delphi XE3上。到目前为止,即使我拥有它,我也没有在Delphi XE6上尝试过这个功能。主要原因是当前项目限制我使用Delphi XE3,因为我使用的库与Delphi XE6不完全兼容。 - SilverWarior
你是否在组件的样式文件中创建控件? - r_j
@caputo 是的。实际上我是用一种完全不同的方式来做这件事情,因为我无法解决它。不是作为对话框/组件,而是直接在表单上使用 TPopup,所以我将所有内容都复制并粘贴到其中。 - Eduardo Elias
显示剩余10条评论
1个回答

8

请仔细查看FMX.TabControl单元中的TTabControl/TTabItem。这是您的完美示例,因为它基本上需要解决同样的问题。

以下函数是您需要重写的:

procedure DoAddObject(const AObject: TFmxObject); override;

当控件添加到您的控件时,将调用此函数。重写此函数,以便您的控件添加到FpnlClientArea控件中。您将得到类似于以下内容:

procedure TNaharFMXPopup.DoAddObject(const AObject: TFmxObject);
// ...
begin
  if (FpnlClientArea <> nil) and not AObject.Equals(FpnlClientArea) and not AObject.Equals(ResourceLink) then
  begin
    FpnlClientArea.AddObject(AObject);
  end
  else
    inherited;
end;

请确保AObject.Equals也排除您的其他“未存储”控件。

没有DoAddObject覆盖,FMX TabControl将显示与您的组件当前存在的同样问题。


TPopup不打算接受控件。因此需要更多技巧。 这是一个经过修改的单元版本,适合我使用。我添加了一些注释:

unit NaharFMXPopup;

interface

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

type
  [ComponentPlatformsAttribute(pidWin32 or pidWin64 or pidOSX32 or pidiOSSimulator or pidiOSDevice or pidAndroid)]
  TNaharFMXPopup = class(TPopup)
  private
    procedure   ApplyControlsProp;
  protected
    FpnlMain       : TPanel;
    FlytToolBar    : TLayout;
    FbtnClose      : TButton;
    FbtnSave       : TButton;
    FbtnEdit       : TButton;
    FpnlClientArea : TContent; // change to TContent. 
    // For TPanel we'd have to call SetAcceptControls(False), 
    // but that is not easily possible because that is protected
    FlblTitle      : TLabel;
    procedure   Loaded; override;
    procedure   Notification(AComponent: TComponent; Operation: TOperation); override;
    procedure   DoAddObject(const AObject: TFmxObject); override;
  public
    procedure   InternalOnClose(Sender: TObject);
    procedure   InternalOnSave(Sender: TObject);
    procedure   InternalOnEdit(Sender: TObject);
    constructor Create(AOwner: TComponent); override;
    destructor  Destroy; override;
    procedure   SetEvents;
  published
  end;

implementation


{ TNaharFMXPopup }

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

  FpnlMain         := TPanel.Create(Self);
  FlblTitle        := TLabel.Create(Self);
  FlytToolBar      := TLayout.Create(Self);
  FbtnEdit         := TButton.Create(Self);
  FpnlClientArea   := TContent.Create(Self); // change to TContent
  FbtnClose         := TButton.Create(FlytToolBar);
  FbtnSave          := TButton.Create(FlytToolBar);

  Height         := 382;
  Placement      := TPlacement.Center;
  StyleLookup    := 'combopopupstyle';
  Width          := 300;

  // A TPopup is not intended to accept controls
  // so we have to undo those restrictions:
  Visible := True;
  SetAcceptsControls(True);

  ApplyControlsProp;
end;

destructor TNaharFMXPopup.Destroy;
begin

  inherited;
end;

procedure TNaharFMXPopup.ApplyControlsProp;
begin
  with FpnlMain do
  begin
    Parent         := Self;
    Align          := TAlignLayout.Bottom;
    StyleLookup    := 'grouppanel';
    TabOrder       := 0;
    Height         := 50;
    Margins.Bottom := 10;
    Margins.Left   := 10;
    Margins.Right  := 10;
    Margins.Top    := 10;
    Stored         := false;
  end;
  with FpnlClientArea do
  begin
    Parent         := Self; // we have to change this to Self (it refuses working if the parent is FPnlMain)
    Align          := TAlignLayout.Client;
    Margins.Left   := 3;
    Margins.Right  := 3;
    Margins.Top    := 3;
    Margins.Bottom := 3;
    Stored         := false;
  end;
  with FlytToolBar do
  begin
    Parent         := FpnlMain;
    Align          := TAlignLayout.Bottom;
    Height         := 50;
    Stored         := false;
  end;
  with FbtnClose do
  begin
    Parent         := FlytToolBar;
    Text           := 'Close';
    Align          := TAlignLayout.Left;
    Height         := 50;
    StyleLookup    := 'tilebutton';
    TabOrder       := 0;
    Width          := 70;
    ModalResult    := mrClose;
    Stored         := false;
  end;
  with FbtnEdit do
  begin
    Parent         := FlytToolBar;
    Text           := '';//'Edita';
    Align          := TAlignLayout.Left;
    Height         := 50;
    StyleLookup    := 'tilebutton';
    TabOrder       := 1;
    Width          := 70;
    ModalResult    := mrContinue;
    Stored         := false;
    Enabled        := false;
  end;
  with FbtnSave do
  begin
    Parent         := FlytToolBar;
    Text           := 'Save';
    Align          := TAlignLayout.Left;
    Height         := 50;
    StyleLookup    := 'tilebutton';
    TabOrder       := 2;
    Width          := 70;
    ModalResult    := mrOk;
    Stored         := false;
  end;
end;

procedure TNaharFMXPopup.Loaded;
begin
  inherited;

  ApplyControlsProp;
//  SetEvents;

end;

procedure TNaharFMXPopup.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;

end;

procedure TNaharFMXPopup.InternalOnClose(Sender: TObject);
begin
end;

procedure TNaharFMXPopup.InternalOnEdit(Sender: TObject);
begin
end;

procedure TNaharFMXPopup.InternalOnSave(Sender: TObject);
begin
end;

procedure TNaharFMXPopup.SetEvents;
begin
  FbtnClose.OnClick := InternalOnClose;
  FbtnSave.OnClick := InternalOnSave;
  FbtnEdit.OnClick := InternalOnEdit;
end;


procedure TNaharFMXPopup.DoAddObject(const AObject: TFmxObject);
begin
//inherited; try commenting the block bellow and uncommenting this one
//Exit;

  if (FpnlClientArea <> nil)
    and not AObject.Equals(FpnlClientArea)
    and not AObject.Equals(ResourceLink)
    and not AObject.Equals(FpnlMain)
    and not AObject.Equals(FlblTitle)
    and not AObject.Equals(FlytToolBar)
    and not AObject.Equals(FbtnEdit)
    and not AObject.Equals(FpnlClientArea)
    and not AObject.Equals(FbtnClose)
    and not AObject.Equals(FbtnSave) then

  begin
    FpnlClientArea.AddObject(AObject);
  end
  else
    inherited;
end;

end.

你能否提供一个完全可编译的源代码链接,以便我可以在这里尝试吗? - Sebastian Z
这里是链接(https://www.dropbox.com/s/ukhv10kw66itpji/Popup.zip?dl=0)。这是一个简化版本,我已经删除了与我的框架相关的部分,只保留我们在这里要解决的控件部分!谢谢! - Eduardo Elias
@SebastianZ 如果你有更大的源代码示例(例如来自Eduardo的?),能否在答案中包含它?无论答案有多长,只要包含好的信息 - 完整的示例将是一个很好的答案! - David
@DavidM 我已经添加了修改过的演示单元的版本。 - Sebastian Z
@SebastianZ 很好,它奏效了!我建议在 ApplyControlsProp 过程中添加 FpnlClientArea.ClipChildren := true; ,它将强制子控件保持在客户区内。谢谢! - Eduardo Elias
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接