Search code examples
delphi

How to Dynamically Add Controls to Delphi Form


I am trying to dynamically add button controls to an application. The application I'm working on has up to 6 different pre-defined button positions which can be any one of several different operations. For example, if the user presses the "New" button, I want to dynamically show a "Save", "Clear" and "Cancel" buttons and have each of the OnClick events point to the proper procedure/function. Each of the buttons are defined in an array of TButton and assigned as needed to the pre-defined buttons on the form.

This is a sample program I built to help understand what I'm trying to achieve:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    btnPos1: TButton;
    procedure btnNewClick(Sender: TObject);
    procedure Form1Close(Sender: TObject; var Action: TCloseAction);
    procedure Form1Create(Sender: TObject);
  private
    { Private declarations }
  public
    procedure LoadButtons;
  end;

var
    Form1: TForm1;
    FormBtns: array[1..20] of TButton;

implementation

{$R *.dfm}

procedure TForm1.LoadButtons;
begin
    FormBtns[1].Caption:='&New';
    FormBtns[1].OnClick:=btnNewClick;
    FormBtns[1].Left:=340;
    FormBtns[1].Top:=20;

    btnPos1.Caption:=FormBtns[1].Caption;
    btnPos1.OnClick:=FormBtns[1].OnClick;
    btnPos1.Left:=FormBtns[1].Left;
    btnPos1.Top:=FormBtns[1].Top;
end;

procedure TForm1.btnNewClick(Sender: TObject);
begin
    // do something
end;

procedure TForm1.Form1Close(Sender: TObject; var Action: TCloseAction);
begin
    Application.Terminate();
end;

procedure TForm1.Form1Create(Sender: TObject);
begin
    LoadButtons;
end;

end.    

When this test and actual application runs I receive the following error:

Project BtnTest.exe raised exception class $C0000005 with message 'c0000005 ACCESS_VIOLATION'.

It is occurring on the following code:

FormBtns[1].OnClick:=btnNewClick;

I am assuming that it can't reconcile the btnNewClick reference for some reason but I don't understand why. I'm not even sure if this is best way to get the job done. Any help would be greatly appreciated. Thanks.


Solution

  • You have declared an array of TButton object pointers, but you have not pointed them at anything yet. You have to actually create the TButton objects before you can access their properties, eg:

    procedure TForm1.LoadButtons;
    begin
      FormBtns[1] := TButton.Create(Self);
      FormBtns[1].Parent := Self;
      ...
    end;
    

    FormBtns should also be a member of your TForm1 class instead of a global variable.