Search code examples
delphionclickdelphi-xe7

Custom Button OnClick event


I create buttons at runtime and would like to be able to set custom OnClick event that each button would pass it's own custom values when clicked:

I get error when trying to assiign custom OnClick procedure:

E2010 Incompatible types: 'TNotifyEvent' and 'procedure, untyped pointer or untyped parameter'

here is how i use it:

procedure myOnClick(Sender:TObject; Info1:string; info2:integer);
begin
  // process info1, info2 based on which button is Sender
end;

procedure TForm1.Button1Click(Sender: TObject);
var vBtn:Tbutton;
  Idx:integer;
  vStr:string;
begin
   // ...

   // create btn
   vBtn := Tbutton.Create(nil);
   vBtn.Parent := Form1.ButtonsPanel;
   vBtn.Tag := Idx;
   vBtn.OnClick:=myOnCLick(self,vStr,vBtn.Tag);

   // assign btn
   //...

end;

How can I use custom click event, procedure, so I can pass values specific to the button, when button is clicked?


EDIT:

right now I use the CustomOnClick:

procedure TForm1.CustomOnClick(Sender: TObject);
begin
  myOnClick(Sender,TControl(Sender).Tag);
end;

vBtn.OnClick:=Form1.CustomOnClick; // works, but only accepts Sender parameter

As David suggested, I already use Tag property. Now I need more info and would like to get rid of this 'middle man' CustomOnClick and call myOnClick directly.


Solution

  • Method 1

    You can store "button info" separatelly, like there:

    type
      TButtonInfo = record
        info1: string;
        info2: Integer;
        info3: Double;
    
        constructor Create(const AInfo1: string; AInfo2: integer; AInfo3: Double);
      end;
    
      TButtonInfoDictionary = class(TDictionary<TObject,TButtonInfo>)
      end;
    
      TForm6 = class(TForm)
      .....
      private
        { Private declarations }
        FButtonInfoDict: TButtonInfoDictionary;
        procedure OnButtonClick(Sender: TObject);
      public
        { Public declarations }
        destructor Destroy; override;
      end;
    
    implementation
    
    constructor TButtonInfo.Create(const AInfo1: string; AInfo2: integer;
      AInfo3: Double);
    begin
      Info1:=AInfo1;
      Info2:=AInfo2;
      Info3:=AInfo3;
    end;
    
    procedure TForm6.btnAddNewButtonClick(Sender: TObject);
    var
      btn: TButton;
    begin
      if not Assigned(FButtonInfoDict) then
        FButtonInfoDict:=TButtonInfoDictionary.Create;
    
      btn:=TButton.Create(nil);
      btn.Parent:=Self;
      btn.Align:=alTop;
      btn.Caption:='btn'+FButtonInfoDict.Count.ToString;
      btn.OnClick:=OnButtonClick;
    
      FButtonInfoDict.Add(btn, TButtonInfo.Create(FButtonInfoDict.Count.ToString, FButtonInfoDict.Count, 0));
    end;
    
    destructor TForm6.Destroy;
    begin
      FreeAndNil(FButtonInfoDict);
      inherited;
    end;
    
    procedure TForm6.OnButtonClick(Sender: TObject);
    var
      ButtonInfo: TButtonInfo;
    begin
      if Assigned(FButtonInfoDict) then
        if FButtonInfoDict.TryGetValue(Sender, ButtonInfo) then
          Caption:=ButtonInfo.Info1+'  ' + ButtonInfo.info2.ToString;
    end;
    

    Method 2 As David said, more preferred.

    "Expand" TButton class and add needed properties to new class:

    type
      TButton = class(Vcl.StdCtrls.TButton)
      private
        FInfo2: integer;
        FInfo1: string;
      public
        property Info1: string read FInfo1 write FInfo1;
        property Info2: integer read FInfo2 write FInfo2;
      end;
    
      TForm6 = class(TForm)
      .....
      private
        { Private declarations }
        procedure OnButtonClick(Sender: TObject);
      public
        { Public declarations }
      end;
    
    procedure TForm6.btnAddNewButtonClick(Sender: TObject);
    var
      btn: TButton;
    begin
      btn:=TButton.Create(nil);
      btn.Parent:=Self;
      btn.Align:=alTop;
      btn.Caption:='btn'+Self.Tag.ToString;
      btn.Info1:=Self.Tag.ToString;
      btn.Info2:=Self.Tag;
      btn.OnClick:=OnButtonClick;
    
      Self.Tag:=Self.Tag + 1;
    end;
    
    procedure TForm6.OnButtonClick(Sender: TObject);
    begin
      if Sender is TButton then
        Caption:=TButton(Sender).Info1+'  ' + TButton(Sender).info2.ToString;
    end;
    

    Method 3, unwanted

    Use dynamic allocated memory for store TButtonInfo from Method 1:

    PButtonInfo = ^TButtonInfo;
    
    procedure TForm6.btnAddNewButtonClick(Sender: TObject);
    var
      btn: TButton;
      pInfo: PButtonInfo;
    begin
      btn:=TButton.Create(nil);
      btn.Parent:=Self;
      btn.Align:=alTop;
      btn.Caption:='btn'+Self.Tag.ToString;
      btn.OnClick:=OnButtonClick;
    
      New(pInfo); // once we have allocated memory, we need to release it after all.
      // but we dont have event, where we can call Dispose(PButtonInfo(btn.tag));
      // so, we have memory leak...
      // You can change TButtonInfo declaration from record to class,
      // and use .Create against New, but memory leak still there
      pInfo.info1:=Self.Tag.ToString;
      pInfo.info2:=Self.Tag;
      btn.Tag:=NativeInt(pInfo);
    
      Self.Tag:=Self.Tag + 1;
    end;
    
    procedure TForm6.OnButtonClick(Sender: TObject);
    begin
      if Sender is TButton then
        Caption:=PButtonInfo(TButton(Sender).Tag).Info1+'  ' + PButtonInfo(TButton(Sender).Tag).info2.ToString;
    end;