How do events in Delphi work? - delphi

I'm trying to get console output from a program using this library uZpRunConsoleApp.
It's well documented but I've not used Delphi for very long and I don't understand how events work.
From what I can tell I need to call ExecuteConsoleApp with my application, which I have working with no output. It looks like that method wants me to specify a function it can fire when an event happens but I don't understand how to do that.
I hope somebody can spread some light here.
I didn't post any code since this isn't really a code specific problem, but if somebody wants what I have so far I'll edit for them.

Yeah, an event handler is basically a reference to a function. If you've ever used callbacks, it's basically the same idea. If not, here's a quick overview:
The event type is defined like this:
TZpOnNewTextEvent = procedure(const Sender: TObject;
const aText: string) of object;
What that means is that it's a reference to an object method (of object) with a signature that looks like this:
type
TMyObject = class (TMyObjectAncestor)
//stuff here
procedure MyEventHandler(const Sender: TObject; const aText: string);
//more stuff here
end;
The of object bit is important. This is specifically a method reference and not a reference to a standalone function.
What the event handler is for is to allow you to customize the way ExecuteConsoleApp works. It's almost exactly like adding code to a button in the Form Designer. You place the button on the form, then you assign an event handler to its OnClick event that customizes the button by adding in code that executes when the button is clicked. The difference is that here, you don't have a Form Designer to wire it together for you.
Fortunately, the syntax is pretty simple. For a procedure (whatever) of object, you pass the event handler by just giving the name. Throw Self.MyEventHandler in the appropriate place in the parameter list, and it will work.

Related

A better way of creating from a class parameter?

In TApplication.CreateForm (not sure if it is allowed to paste the code here. I will do so if somemone confirms) it seems a way of creating an instance of a TForm descendant by using the class of the derived form and a variable pointing to the form. Both are parameters of CreateForm;
procedure TApplication.CreateForm(InstanceClass: TComponentClass; var Reference);
Is there a better or even simpler way (maybe with less code) of doing what is done in CreateForm if I wanted to have a method which creates a derived control with only some parameters as indicators of what class it is and the variable it will be using.
EDIT: I would like to have a method that creates a control which I use in my project. The method will also do some additional code related to the control so that is the reason for the method. I do not want to duplicate that additional work and the method will be called numerous times. I can implement the code in the same way as CreateForm but was wondering if there is a way of doing the same in less or simpler code.
I want to have a method which creates a derived control with only some parameters as indicators of what class it is and the variable it will be using.
You don't need a method for that. You can write it like this:
MyForm := TMyForm.Create(Owner);
Don't be put off by all the code in Application.CreateForm. That code performs many tasks, the principle of which is to assign the Application.MainForm variable. The IDE likes to encourage you to use Application.CreateForm but in reality you only need to call it once, and that is to create the main form.
If you are dead set on making this into a method then it would look like this:
function CreateForm(FormClass: TFormClass; Owner: TComponent): TForm;
begin
Result := FormClass.Create(Owner);
end;
When calling this function you would need to cast the value returned:
MyForm := TMyForm(CreateForm(TMyForm, Owner));
or
MyForm := CreateForm(TMyForm, Owner) as TMyForm;
As an alternative you could use a generic method:
type
TFormCreator = class
public
class function CreateForm<T: TForm>(Owner: TComponent): T; static;
end;
Implement it like this:
class function TFormCreator.CreateForm<T>(Owner: TComponent): T;
begin
Result := T(TFormClass(T).Create(Owner));
end;
Call it like this:
MyForm := TFormCreator.CreateForm<TMyForm>(Owner);
Pretty ridiculous isn't it? All you want to do is instantiate a form! So, I have a strong suspicion that you have been confused by the IDE's use of Application.CreateForm and believe that there is more to instantiating forms than there really is. My instincts tell me that you are actually looking for this:
MyForm := TMyForm.Create(Owner);
AS. You can post YOUR OWN code, but with regards to code which copyrights holds someone else - that gets a bit complicated. I believe it falls under USA "Fair Use" doctrine. For example you can post a snippet of VCL source to criticize or demonstrate something, but not to copy-paste it into another project and only as little of the VCL code - as required for that "fair use" intention.
A VCL form is a component, thus it needs an owner, who would be responsible for memory management. So you can create the form in a typical TComponent creation pattern.
MyFormVariable := TMyFormClass.Create( Application );
This also adds for type safety that untyped var Reference in CreateForm denies.
Whether that way is better or worse than using Application.CreateForm is up to your tastes. Personally I prefer uniform way so when I need to create a form or a datamodule explicitly I usually go the "component" way not the "application" way.
I guess (just guess) that back in old days TApplication.CreateForm added some extra required setup that "component way" of creating forms could not do, at least not before VCL would get started by Application.Run call. Or maybe there were types of TApplication - and AFAIR there are 5 ones for different projects - that were not derived from TComponent? But anyway I think today that limitations - if ever there where any - not apply any more.

Undeclared 'FormCreate' error Delphi

I'm trying to name columns (in this grey row) in a string grid. I know that I should use something like this:
procedure TForm1.FormCreate(Sender: TObject);
begin
StringGrid1.Cells[0,0] := 'Text 1';
StringGrid1.Cells[1,0] := 'Text 2';
end;
The problem is that there is error:
'TForm1' does not contain a member named 'FormCreate'at line 81".
I'm a beginner. What is wrong with my program?
You need to declare the method in the type.
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
...
end;
And that line of code to the declaration of the type that you will find near the top of your unit. Then your program will compile. You also need to make sure that the event handler attaches the handler to the form's OnCreate event. Use the Object Inspector to check that.
But the easiest way to make this all happen is to get the IDE to write it all. So, you would:
Delete the code that you have shown in the question.
Click on the form in the designer.
Select the Events view in the Object Inspector.
Find the OnCreate event in the Object Inspector.
Double click in the handler column of the OnCreate event in the Object Inspector.
Now the code editor opens showing an empty event handler body for you to add code to, and all the other parts are joined up. Specifically the method is declared in the type, and the handler is connected to the event.
Now, that's how you do it normally, but it does pay to know the three things that need to be in place for an event to fire:
The event handler is declared in the type of the class.
The event handler is defined in the implementation of the class.
The event handler is attached to the event in the Object Inspector. In fact, although you set it in the Object Inspector, the information actually lives in the .dfm file.
If you don't know all this already, then asking questions on Stack Overflow is really not the most effective way to get up to speed. A good book would certainly help. Even if it's for an older version of Delphi, the primary concepts have not changed for years. But if you don't have a book, then you should at least follow the tutorial.

Using another forms event procedure

Is there any way to allow one form to use the event procedures from another form?
E.g. I have a form called PongForm and another called ObstPongForm. There is a ticker on PongForm and another one on ObstPongForm. Is it possible to get ObstPongForm to use the code from PongForm's 'tick' event in it's own 'tick' event? Maybe by letting ObstPongForm inherit from PongForm?
You can simply assign it by code (as long as you have access to both instances):
ObstPongForm.Ticker.OnTick := PongForm.TickerTick;
Yes, forms are just classes like any other, and Delphi supports visual inheritance, so you can call inherited methods normally.
If ObstPongForm is a specialized version of PongForm then inheritance makes sense, but be careful as ObstPongForm will inherit all visual controls from the PongForm, including whatever you may add in the future.
Also since I assume you already have both forms, making one inherit from another is doable but requires some manual DFM editing, mainly changing the
Object ObstPongForm: TObstPongForm
to
Inherited ObstPongForm: TObstPongForm
If the code you want to reuse may be needed in several unrelated forms, then moving the code to a common unit used by these forms may be the best solution
It would be better style to have both of the forms call another class that implements the logic used by both. If you're writing all your program logic in your OnTimer event handler, you're heading down a bad road that many delphi programmers take years to realize was a bad idea
So one form needs to call your method, it does it like this:
procedure TForm1.DoSomething;
begin
DataModule1.LogicMethod;
end;
Elsewhere there is a timer...
procedure TForm2.Timer1Timer(Sender:TObject);
begin
DataModule1.LogicMethod;
end;
And then the method itself:
procedure TDataModule1.LogicMethod;
begin
// Everything that you used to have in Timer1Timer goes here, except the setting of
// UI properties in Form1 which is kept in Form1:
Inc(FCounter);// stupid example.
//
if Assigned(FOnResults) then
FOnResults(Self, FCounter, FDataObject1);
// Form2 is connected to FOnResults event, and stores the
// result in the UI somewhere.
end;
Event handlers are just normal procedures. If your ObstPongForm tick handler has additional code that it needs to run in addition to the PongForm's code, then you can call the PongForm's tick handler manually when needed, eg:
uses
..., PongForm;
procedure ObstPongForm.TickHandler(Sender: TObject);
begin
...
PongForm.TickHandler(Self);
...
end;

Delphi: How to assign dynamically an event handler without overwriting the existing event handler?

I need to loop through Components and assign an event handler (for example Dynamically assigning OnClick event for all TButton to
ShowMessage('You clicked on ' + (Sender as TButton).Name);
The problem is that in some cases I already assigned the TButton OnClick event.
Is there a way to solve the problem?
Let's imagine I have Button1 for which the harcoded onclick event handler is:
ShowMessage('This is Button1');
After my "parsing" I would like that the full event handler for Button1 becomes:
ShowMessage('This is Button1'); // design time event handler code
ShowMessage('You clicked on ' + (Sender as TButton).Name); // runtime added
Note: I am looking for a soliution that allows me to use TButton as it is without inheriting from it.
you could look for an assignment of OnClick before overwriting it, persist this and use it in your new handler - basically chaining the events.
Something like this:
var original : TNotifyEvent;
original := Component.OnClick;
Component.OnClick := NewMethod;
and then in your NewMethod:
if assigned(original) then original(Sender);
you'll likely not want a single original variable but instead hold a collection keyed on the component.
If you are writing the replacement handlers yourself you could do the following:
Before assignment of the new handler, store a reference to the object and a reference to the original handler in a list or other structure.
In your replacement handler, included code to check the list to see if the current object has an entry. If it does, call the stored method before doing your own work.
This is fairly lightweight (a list where each entry is two pointers). With a little extra work (to support ordering) you could probably even chain more than one replaced handler.
You can't do that directly as Delphi does not support chained events (I'm assuming VCL Delphi).
So anything that you want to do that involves directly assigning the OnClick event will not work. What you might do is create an interceptor component, that is, something with this signature:
type
TButton = class(stdctrls.TButton)
You'll have to override the OnClick property so that the write event stores the event handler into an internal list, available to class.
property OnClick: TNotifyEvent read GetOnClick write SetOnClick;
then on the SetOnClick handler you'll be able to just store your event handler in the internal list.
Now, you will have to override the Click procedure so that it invokes every delegate (every event handler) in turn.
procedure Click; override;
And that will do it.
Note that after all you'll be kind of subclassing the TButton class, even if you're using the same name... Anyway that's the best solution I can think of. The cool thing about this is that you can still use assignments without having to know about chained events, you can just do:
MyButton.OnClick := MyHandler
and it will get automatically chained with the rest of event handlers.

Why doesn't TApplicationEvents.OnIdle get called?

in my app I have a main form with a button.
Clicking this button, a form (not auto-created in dpr) is created and displayed; on this form, I placed a TApplicationEvents component and I defined its OnIdle event handler.
This event handler doesn't get called! May this depend because I derived this second form not from
TForm but from an other class, TChartBasicForm (by means of VFI)?
Thank you very much for replies.
Massimo.
Hooking an application's idle event can lead to a lot of debugging woes and other maintenance headaches, especially on a form other than the main form. I realize this may not answer your specific question (which is hard to do at this point given the vagueness), but are you sure you can't accomplish what you're trying to do with a TTimer or TThread instead?
Thanks for interest to all people.
"It doesn't work" means that it's not called at all.
Instead, the event OnShowHint works!
Ooops!
Perhaps I have understood the bad behaviour!
In the main form I defined a procedure like this one:
procedure IdleHandler(Sender: TObject; var Done: Boolean);
and in the FormCreate:
Application.OnIdle := IdleHandler;
This probably inhibits TApplicationEvents.OnIdle, even if
in IdleHandler, at the end of the procedure, I put:
Application.OnIdle := nil;
because the code is usefull to try a connection only at the begin
of the application.
I beg your pardon: my face is red......

Resources