0

Is it real? For example, I have form1 and button1. I can assign to button1.onClick in design time onlyButton26Click.

  TForm1 = class(TForm)    
    procedure Button26Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

I also have unit 2 like :

unit ProcAndFunc;
interface
uses sysutils,forms;

procedure createArrTable(Sender: TObject);

Can I assign createArrTable to button1.onClick in design time? Thanks.

Ok. Unit1

TForm1 = class(TForm)  
        myButton1 : TButton; 
        procedure Button26Click(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;

Unit2

TForm2 = class(TForm) 
            myButton2 : TButton; 
            procedure ButtonClick(Sender: TObject);
          private
            { Private declarations }
          public
            { Public declarations }
          end;

Just to know how it works. What should I change in unit2 that I can assign ButtonClick to myButton1.OnClick How to make a separate unit for event methods, which IDE allows me to assign to component events at design time? It seems that I duplicate question. But that answer does not suit me. Is there the other way?

EDIT3 (08.08.2016): I created this TDataModule;

unit Unit3;

interface

uses
  System.SysUtils, System.Classes,dialogs;

type
  TDataModule3 = class(TDataModule)
  procedure Click2(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  DataModule3: TDataModule3;

implementation

{%CLASSGROUP 'Vcl.Controls.TControl'}

{$R *.dfm}

procedure TDataModule3.Click2(Sender: TObject);
begin
ShowMessage('hello world');
end;   

end.

unit Unit1;

interface

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

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);
begin
ShowMessage('hello world');
end;

end.

And I still cant assign DataModule3.Click2 to button1.onclick in design-time. (screenshot). What do I also need to do?

Community
  • 1
  • 1
  • 1
    No, for two reasons. 1) It is not a TNotifyEvent, which is a procedure of object (it's a member of a class). 2) The Object Inspector has no idea it exists, because it's not a properly defined (and published) procedure. – Ken White Aug 05 '16 at 12:26

2 Answers2

1

You cannot ever assign that procedure as an event handler since it is not a method of a class. So you could make the procedure be a method of another class and make sure that there was a global instance of that class visible to the designer.

However that would be a very poor solution in my view. It is best practice to decouple UI logic from application logic where possible. The clean solution is to simply call the procedure from an event handler. That means that your procedure is still available to other parties in its current form and does not need to be shoe-horned into looking like an event handler. You could also likely remove the Sender parameter which I suspect you are not using in any case.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Yeah... createArrTable was in main unit and was assigned to onClick. Today I started to isolate this and other procedures to separate unit (it`s general procedure for many controls) and stuck with this. Just wanted to decrease lines in main unit. And I thought abount "make the procedure be a method of another class" but didn`t try it. However, thanks for quick reply! – Игорь Радченко Aug 05 '16 at 13:56
  • While @davidheffernan is right, as usual, personally I'd leave the sender there which allows for multiple buttons to call the same function/procedure and you can determine which it was. Future flexibility. – Admiral Noisey Bottom Aug 06 '16 at 04:41
  • @admiral Once the code is decoupled from the UI then it should not know that information. That information should be processed at the UI level. – David Heffernan Aug 06 '16 at 04:44
  • @admiral Further you are in control of the code. If you need to add arguments later do it later. Adding unused arguments now on the off chance you might use them later is bad practise. – David Heffernan Aug 06 '16 at 08:20
  • @davidHeffernan I yield to your experience and knowledge and agree totally. However, my own experiences have taught me that there is a 95% chance I've forgotten something and will later optimize by reusing code and often wish I'd left a simple sender in there. You are correct though, it is sloppy coding. – Admiral Noisey Bottom Aug 07 '16 at 04:41
  • @admiral Trivial to add if needed, although would still be a violation of separating UI from app logic – David Heffernan Aug 07 '16 at 06:01
1

Can I assign createArrTable to button1.onClick in design time?

Not at design-time, no. createArrTable() is not a member of a class, and thus is not accessible via an object pointer at run-time. So it does not satisfy the signature that is normally required for an OnClick event handler. The IDE will not allow you to assign it at design-time.

However, you can assign createArrTable() at run-time, with some minor tweaking.

The OnClick event (and most RTL/VCL events in general) is implemented using a closure, which consists of 2 pointers - a pointer to the procedure code itself, and a pointer to an object.

When the event is fired at runtime, the compiled code extracts the object pointer from the closure and calls the procedure with that object passed to the procedure's Self parameter. It is the lack of a Self parameter that prevents your current createArrTable() implementation from being used as an OnClick event handler. If you were to somehow assign it as-is, the parameter list will get corrupted at runtime.

However, if you add an explicit Self parameter to createArrTable(), you can avoid that issue, and skip the class object requirement:

procedure createArrTable(Self: Pointer; Sender: TObject);

The button will still try to pass an object pointer from the closure to Self when calling the procedue, but now you have made space for an object pointer to be passed correctly and not corrupt other parameters. The object pointer itself becomes irrelevant and can be set to anything you want it to be, even nil.

However, since createArrTable() is still not a member of a class, you cannot assign it directly to the OnClick event, but you can use a TMethod variable to facilitate the assignment, eg:

var
  M: TMethod;
begin
  M.Code := @createArrTable;
  M.Data := WhateverYouWantSelfToBe; 
  Button1.OnClick := TNotifyEvent(M);
end;

Now createArrTable() will work as expected when OnClick is fired at run-time.

What should I change in unit2 that I can assign ButtonClick to myButton1.OnClick

Nothing. It is already a suitable event handler as-is. All you would have to do is add unit2 to the uses clause of the interface section in unit1 so the IDE can see ButtonClick() at design-time, then you would be able to assign Form2.ButtonClick to myButton1.OnClick. Just know that you will have to setup your project to create Form2 before Form1 at runtime, otherwise you will likely crash the code when the DFM streaming system tries to access ButtonClick via an invalid Form2 object when assigning the value of Form1.myButton.OnClick.

To avoid having to change the creation order of the Forms, you can place the event handlers into a separate TDataModule instead, and then link its methods to events on your Form. Just make sure the TDataModule is created at runtime before the Form is created:

Access DataModule's event from another Form (delphi design-time)

Community
  • 1
  • 1
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770