如何在Delphi中以编程方式创建一个带有几个组件的表单

11

我正在使用 Delphi 7,尝试通过编程方式创建一个窗体。这是我的窗体类桩:

unit clsTStudentInfoForm;

interface

    uses Forms;

    type
        TStudentInfoForm = class (TForm)

        end;

implementation


end.

在我的主表单上我也有一个按钮(它只是一个普通的表单,用于在运行时创建和显示上面的表单),当点击它时,它会创建并显示学生表单作为模态窗口。它确实会显示表单,但表单上什么都没有。你唯一能做的就是点击窗口右上角的关闭按钮来关闭它。

procedure TLibraryForm.btnShowStudentIfoFormClick(Sender: TObject);
var
    f : TStudentInfoForm;
begin
    f := TStudentInfoForm.CreateNew(Self);
    f.ShowModal;
    f.Free;
    f := nil;
end;

enter image description here

我不知道如何在通过编程创建的表单中添加组件(不是在运行时,而是在源代码中)。你能帮我编写一些代码,将“确定”按钮添加到学生表单中,并设置标题以及表单的高度和宽度(代码必须写在学生表单文件中)吗?任何建议和示例将不胜感激。谢谢。


要在运行时完全创建带有控件的表单,请查看 Dialogs.CreateMessageDialog 以获取示例。 - NGLN
2个回答

23

默认情况下(也就是所有默认IDE配置设置),新设计的表单将自动创建。只有主表单会被显示,而次要表单可以通过以下方式显示:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Form2.Show;
  Form3.ShowModal;
end;

通常情况下最好禁用自动创建选项。前往:工具 > (环境) 选项 > (VCL) 设计师 > 模块创建选项,然后禁用/取消勾选自动创建表单和数据模块选项。

相反,在需要时只创建一个已经设计好的表单:

procedure TForm1.Button1Click(Sender: TObject);
var
  Form: TForm2;
begin
  Form := TForm2.Create(Self);
  Form.Show;
end;

这也说明了次要表单的全局变量是不必要的,良好的常规操作是尽快删除它们以防止错误使用:
type
  TForm2 = class(TForm)
  end;

//var
//  Form2: TForm2;  << Always delete these global variable

implementation

如果您不想通过表单设计器设置这样的次要表单,那么您需要在运行时通过代码创建所有控件。具体步骤如下:

unit Unit2;

interface

uses
  Classes, Forms, StdCtrls;

type
  TForm2 = class(TForm)
  private
    FButton: TButton;
  public
    constructor CreateNew(AOwner: TComponent; Dummy: Integer = 0); override;
  end;

implementation

{ TForm2 }

constructor TForm2.CreateNew(AOwner: TComponent; Dummy: Integer = 0);
begin
  inherited CreateNew(AOwner);
  FButton := TButton.Create(Self);
  FButton.SetBounds(10, 10, 60, 24);
  FButton.Caption := 'OK';
  FButton.Parent := Self;
end;

end.

正如你所看到的,我使用了 CreateNew 构造函数。这是 必要的,用于 T(Custom)Form 的派生类:

使用 CreateNew 来创建一个不使用相关联的 .DFM 文件初始化的表单。如果 TCustomForm 的后代不是 TForm 对象或 TForm 的后代,则始终使用 CreateNew

对于所有其他容器控件(例如 TPanelTFrame 等),可以重写默认的构造函数 Create

按以下方式调用此表单:

procedure TForm1.Button1Click(Sender: TObject);
var
  Form: TForm2;
begin
  Form := TForm2.Create(nil);
  try
    Form.ShowModal;
  finally
    Form.Free;
  end;
end;

或者:

procedure TForm1.Button1Click(Sender: TObject);
begin
  FForm := TForm2.CreateNew(Application);
  FForm.Show;
end;

在这种情况下,当表单关闭时,它不会被释放,而是隐藏起来,因此您需要将其引用存储在私有字段(FForm)中,并稍后释放它。或者您可以自动完成此操作:
unit Unit2;

interface

uses
  Classes, Forms, StdCtrls;

type
  TForm2 = class(TForm)
  private
    FButton: TButton;
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  public
    constructor CreateNew(AOwner: TComponent; Dummy: Integer = 0); override;
  end;

implementation

{ TForm2 }

constructor TForm2.CreateNew(AOwner: TComponent; Dummy: Integer = 0);
begin
  inherited CreateNew(AOwner);
  OnClose := FormClose;
  FButton := TButton.Create(Self);
  FButton.SetBounds(10, 10, 60, 24);
  FButton.Caption := 'OK';
  FButton.Parent := Self;
end;

procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;

end.

现在,您可以在不存储引用的情况下调用它:
procedure TForm1.Button1Click(Sender: TObject);
begin
  TForm2.CreateNew(Self).Show;
end;

无论是将SelfApplication还是nil作为新表单的所有者,取决于你希望何时在不手动释放或通过OnClose事件释放的情况下自动销毁它。使用:
  • Self:当调用窗体被销毁时,将销毁新的窗体。当调用窗体不是主窗体时,这非常有用。
  • Application:将在应用程序结束时销毁新窗体。这是我的首选。
  • nil:将不会销毁新窗体,导致在应用程序完成时出现内存泄漏。不过,当Windows杀死进程时,内存最终会被释放。

非常感谢。这正是医生所开的药方。但是像TForm2.CreateNew(Self).Show这样调用表单是否确保在关闭时被删除? - Mikhail
@NGLN,解释得非常好。非常感谢,你的回答解决了我的困扰。 - user5404910
哥们,你真是帮我省了不少功夫啊,太棒了,真的是非常好的教程。 - Wh1T3h4Ck5

8
创建带有动态控件的模态表单很容易:
procedure CreateGreetingForm;
var
  frm: TForm;
  lbl: TLabel;
  edt: TEdit;
  btn: TButton;
begin

  frm := TForm.Create(nil);
  try
    lbl := TLabel.Create(frm);
    edt := TEdit.Create(frm);
    btn := TButton.Create(frm);
    frm.BorderStyle := bsDialog;
    frm.Caption := 'Welcome';
    frm.Width := 300;
    frm.Position := poScreenCenter;

    lbl.Parent := frm;
    lbl.Top := 8;
    lbl.Left := 8;
    lbl.Caption := 'Please enter your name:';

    edt.Parent := frm;
    edt.Top := lbl.Top + lbl.Height + 8;
    edt.Left := 8;
    edt.Width := 200;

    btn.Parent := frm;
    btn.Caption := 'OK';
    btn.Default := true;
    btn.ModalResult := mrOk;
    btn.Top := edt.Top + edt.Height + 8;
    btn.Left := edt.Left + edt.Width - btn.Width;

    frm.ClientHeight := btn.Top + btn.Height + 8;
    frm.ClientWidth := edt.Left + edt.Width + 8;

    if frm.ShowModal = mrOk then
      ShowMessageFmt('Welcome, %s', [edt.Text]);

  finally
    frm.Free;
  end;

end;

很酷。但我想将这些控件打包到窗体类文件中。 - Mikhail
2
@Mikhail:那么你可以在TStudentInfoFormFormCreate中完成它。但是,如果你有一个“窗体类文件”,你可以通过可视化布局来完成,而不需要首先在代码中完成它。 - Ken White
1
@KenWhite:不要在类的OnCreate事件中执行此操作。应该让类重写其构造函数。 - Remy Lebeau
2
@Remy:那不是我的观点,显然你没有完整地阅读。 :-) 如果您想在声明中放置表单上的控件,则可以使用默认的 .dfm 并在其中创建可视化控件,以便您可以排列它们、连接事件处理程序并设置属性。但是您是正确的 - 在正常情况下,构造函数是正确的。如果您只是出于没有明显原因而进行一次性操作,则 FormCreate 就可以了,因为没有其他人会创建该表单。这不像您正在创建一个可重用的组件;没有必要为不同的开发人员保留事件。 - Ken White

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