Custom TButton not showing on the Form

262 views Asked by At

I am trying to have a functionality where a user will be able create a new button by clicking on the existing one on the form. Now, I am trying to achieve that by creating my own class called MyButton that has a TButton as an ancestor class.

The problem that I am having is that the new button is not showing on the Form after the existing button is clicked.

I have tried using the properties to set the needed values, as well as using the procedures that are stated in my class, but I always get the same three errors.

Here is the class that I have created:

unit clsButton;

interface

uses
  vcl.StdCtrls, System.Classes;

type
  myButton = class(TButton)
    private
      fTop, fLeft, fWidth, fHeight: Integer;
      fCaption: String;
    published
      property top: Integer read fTop write fTop;
      property left: Integer read fLeft write fLeft;
      property width: Integer read fwidth write fWidth;
      property height: Integer read fheight write fHeight;
      property caption: String read fCaption write fCaption;
    public
      procedure setTop(value: Integer);
      procedure setLeft(value: Integer);
      procedure setWidth(value: Integer);
      procedure setHeight(value: Integer);
      procedure setCaption(value: String);
      constructor Create();
  end;

implementation

constructor myButton.Create;
begin
  inherited Create(Self);
end;

procedure myButton.setTop(value: Integer);
begin
  fTop := value;
end;

procedure myButton.setleft(value: Integer);
begin
  fLeft := value;
end;

procedure myButton.setWidth(value: Integer);
begin
  fWidth := value;
end;

procedure myButton.setHeight(value: Integer);
begin
  fHeight := value;
end;

procedure myButton.setCaption(value: String);
begin
  fCaption := value;
end;

end.

Here is the main unit where the OnClick procedure is called in order to create a new button:

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);

  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  btn: myButton;
begin
  btn := myButton.Create();
  with btn do
  begin
    //top := 25;
    //left := 25;
    //height := 45;
    //width := 50;
    //caption := 'MyButton';
    parent := self;
    settop(25);
    setLeft(25);
    setheight(45);
    setwidth(50);
    setcaption('MyButton');
    show;
  end;
end;

end.

And here are the errors that I get after running the program:

image

Now, if I go to the class that I have created clsButton and comment everything and then, in my main unit set the values of the properties by not using the setter methods (the block of code that is initially commented in the main unit), the new button shows on my Form having the caption value that I assigned... but, all other values are not the ones that I have given (top, left, width and height). Those are the values that Form1 has.

My guess is that I am mistaking somewhere in the constructor, but I can not figure it out.


UPDATE: I am adding the class I created based on what @Remy suggested in his coment:

unit clsButton;

interface
    
uses
  System.Types, Vcl.StdCtrls, VCl.Forms, Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Dialogs;
    
type
  myButton = class(Tbutton)
  private
    procedure setBounds(ALeft, ATop, AHeight, AWidth: Integer); override;
  public
    constructor Create(aOwner: TComponent); override;
end;
    
implementation
    
constructor myButton.Create;
begin
  inherited create(AOwner);
end;

procedure myButton.setBounds(ALeft, ATop, AHeight, AWidth: Integer);
begin
  inherited;
  left := aLeft;
  top := ATop;
  height := AHeight;
  width := AWidth;
end;
    
end.

This is also the OnButtonClick event that should create a new button:

procedure TForm1.Button1Click(Sender: TObject);
var
  p: myButton;
begin
  p := mybutton.Create(self);
  with p do
  begin
    parent := form1;
    caption := 'MyButton';
    setbounds(50, 85, 78, 92);
  end;
end;

I still get the same errors....

2

There are 2 answers

1
Remy Lebeau On

Why are you introducing your own left/top/width/height/caption functionality in myButton? You are not assigning any values to the existing properties of the same names that are inherited from TButton. You really should be using the existing properties, and if you want your button to react to changes to those properties then you can override the virtual SetBounds() method, and handle the CM_TEXTCHANGED message, as needed.

Also, why is myButton setting itself as its own Owner in its constructor? That is just plain wrong, and is likely why you are getting "stack overflow" errors at runtime, which will cause your button constructor to abort.

Try this instead:

type
  myButton = class(TButton)
  protected
    procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
    procedure CMTextChanged(var Message: TMessage); message CM_TEXTCHANGED;
  end;

procedure myButton.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
  inherited;
  // do something...
end;

procedure myButton.CMTextChanged(var Message: TMessage);
begin
  inherited;
  // do something...
end;

And then you need to fix TForm1.Button1Click() to use the correct constructor and properties, eg:

procedure TForm1.Button1Click(Sender: TObject);
var
  btn: myButton;
begin
  btn := myButton.Create(Self);
  with btn do
  begin
    Parent := Self;

    Top := 25;
    Left := 25;
    Height := 45;
    Width := 50;
    // or: SetBounds(25, 25, 50, 45);

    Caption := 'MyButton';
    Visible := True;
  end;
end;
1
Petar Sindjic On

So, with the help from @Remy, here is the code that I used and that solved my issue.

unit clsButton;

interface

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

type
  myButton = class(Tbutton)
    private
      ftop, fleft, fheight, fwidth: Integer;
      procedure setBounds(ALeft, ATop, AHeight, AWidth: Integer);override;
//      procedure CMTextChanged(var Message: TMessage); message CM_TEXTCHANGED;

    public
      constructor Create(aOwner: TComponent);override;
  end;

implementation
uses
  unit1;
constructor myButton.Create(aowner: TComponent);
begin
  inherited create(AOwner);
  parent := form1;
  top := ftop;
  left := fleft;
  height := fheight;
  width := fwidth;
  caption := 'MyButton';
end;
procedure myButton.setBounds(ALeft, ATop, AHeight, AWidth: Integer);
begin
  inherited;
   fleft := aLeft;
   ftop := ATop;
   fheight := AHeight;
   fwidth := AWidth;
end;
 end.

And here is the OnClick that creates a new button

procedure TForm1.Button1Click(Sender: TObject);
var
  p: myButton;
begin
  p := mybutton.Create(self);
  p.setbounds(50, 85, 78, 92);

end;