How to call FormClose event handler? - delphi

I try to call the method FormClose, but I have a problem with its parameters when I try:
FormName.FormClose(nil, CaFree);
Normally I can call a component's event handler with a parameter using nil or sender as TOBject. But now I get the error:
Constant object cannot be passed as var parameter
I have tried many combinations for these two TObject and TAction values. For TObject I tried `sender as TObject', and for TAction all parameters like CaFree etc.

The second parameter is a var parameter which is what the compiler error message is telling you. So you need to pass a variable. You cannot pass a literal.
var
Action: TCloseAction;
....
Action := caFree;
Name.FormClose(nil, Action);
Note that you almost certainly should not be doing this. You are not meant to call event handlers directly. The framework will call them at the appropriate time. I think it exceedingly likely that you are mistaken in thinking that you need to fire this event handler directly, or even execute the code outside the normal scenario of the form closing.
As a general rule, if you need to invoke code in an event handler directly then the normal approach is to first extract it to a separate method which can readily be called directly. Then refactor the event handler to call that separate method.

Related

Calling a class method from a custom method

I'm relatively new to Delphi, so please pardon my ignorance.
As an exercise, I am writing a little MasterMind game. The idea is that, when the user derives the correct number, a custom method (Congrats) executes.
One of the options in this method is to play again. The obvious step following this is that the form must be reset to its default ("start up") state. I have created a type method for that (Resetform, declared as a method in public, as it needs to access the controls). I can't call that from Congrats, but I noticed that I can call it from other event handlers.
Is it possible to do this, and if so, how do I go about it?
Works from event handler, but not from custom procedure.
The method Resetform is a method of your form, which is why you can call it from event handler of your form, they reside in the same object.
To call your Resetform from outside the context of your form, you need a reference to your form, otherwise the code won't know which instance of your form it needs to call Resetform on.
Now, assuming you call Congrats from your form, you could add a parameter and call it like this:
Congrats(Self)
And your procedure could be implemented as
procedure Congrats(AForm : TMyForm); // "TMyForm" being the class of your form
begin
...
AForm.Resetform;
end;
But then, you could as well make Congrats a method of your form instead.
If Congrats is not called from your form, then you will need to find another way to pass your form's reference to the function, or use another approach.

Delphi Component Assign Event on the fly

I have an ADOStoredProc on my form. It's not visual but in code.Normally it's pretty easy to handle an event if a component is visual.It's just a matter of double clicking the desired event. But how do I do it with code.I've declared a procedure:
procedure SP_SearchAfterScroll(DataSet:TDataSet)
Now how do I assign SP_Search(this is the ADOStoredProc) AfterScroll event handler property to the procedure I wrote above. I'm sure you're going to answer it. So thanks in advance.
When SP_Search is the TAdoStoredProc and has an OnAfterScroll property, all you need to do is:
SP_Search.OnAfterScroll := SP_SearchAfterScroll;
I am assuming that you used the correct signature for SP_SearchAfterScroll. That is to say that the OnAfterScroll property has a type looks like:
TScrollEvent = procedure(DataSet: TDataSet) of object;
If the OnAfterScroll property has a type that differs from this, you will need to make sure that your SP_SearchAfterScroll procedure matches the parameters in that type.
Edit
In the comments Mikayil asked
SP_Search.AfterScroll :=
SP_SearchAfterScroll(SPSearch)' the
compiler complains saying incompatible
types TNotifyEvent and procedure. But
when I write SP_Search.AfterScroll :=
SP_SearchAfterScroll it works. What's
the difference?
I hadn't gotten round to answering that and in the mean time Mikey explained it very well, so for (easier) future reference I am including his explanation up here:
SP_Search.AfterScroll := that code
assigns a function to handle the event
when it fires - you are not making a
call to SP_SearchAfterScroll at
'assign time' just assigning a value
to a variable, so you don't pass
parameter. Parameter is needed when
call is made - when event fires then
caller will assign parameter with the
right value. When you pass the
parameter,compiler assumes you are
calling the function, not assigning
it, so you get incompatible types
error. When you simply assign the
function without the parameter,
compiler understands you're assigning,
not calling the function.
Declare as:
TDataSetNotifyEvent
then it works

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.

How do events in Delphi work?

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.

Passing methods as parameters on a deserialized form with no ClassType

I'm effectively trying to deserialize a form.
One of the objects on the serialized form has a method which takes a series of events as parameters.
Now since I don't have the class type of the object when I'm deserializing, I have a method on the object doing the deserialization called AddMethod which is declared like this:
procedure TMyDeserializer.AddMethod(ControlName, EventName: String;
MethodAddr: Pointer);
var
TargetControl : TControl;
Method : TMethod;
begin
if Not Assigned(TempForm) then
Exit;
if TempForm.Name = ControlName then
TargetControl := TempForm
else
TargetControl := TempForm.FindChildControl(ControlName);
if Assigned(TargetControl) then
begin
Method.Code := MethodAddr;
Method.Data := TargetControl;
SetMethodProp(TargetControl, EventName, Method);
end;
end;
So that I can poke subroutines into the various controls as I deserialize them, The problem is I need to add events as a list of parameters (not to a control). e.g.
SetUpEvents(EventHandler1:TNotifyEvent;EventHandler2:TNotifyEvent);
Where EventHandler1 and EventHandler2 are defined somewhere in code as
Procedure EventHandler1(Sender:TNotifyEvent);
begin
// Do something
end;
These are not methods but stand alone subroutines.
When I'm assigning these to objects the subroutine doesn't need to be part of an object as the AddMethod procedure handles it with a call like
MyDeserializerInstance.AddMethod('Button1','OnClick',#EventHandler1);
This works for standard event handlers, such as Button1.OnClick but not if I want to do
Procedure SetUpButton1Click(Method: TNotifyEvent)
begin
TButton(MyDeserializerInstance.TempForm.FindChildControl('Button1')).OnClick = Method;
end;
The problem is I can't pass the subroutine as a method to the example Set Up Procedure.
The form being created isn't declared in an interface and is entirely defined by the file it is read from as well as a few stand alone routines in code.
So I suppose the question is how do turn a subroutine into a method at run time (after creating the object it is supposed to be part of), and if I can't do that how do I pass the subroutines in code as parameters in another method?
So far I've tried casting a TMethod as the correct event type and filling in the .Data as the TempForm. It called the correct method but it scrambled the parameters.
Delphi version is 2007
Non-static class methods have a hidden Self input parameter that is filled in when the method is called. That is what the TMethod.Data field corresponds to. In order to use a standalone procedure as a handler for an event that expects a class method, the procedure must have an extra parameter defined to represent the Self parameter so the value of TMethod.Data has somewhere to go, ie:
procedure Button1ClickHandler(Self: Pointer; Sender: TObject);
begin
// Do something
end;
MyDeserializerInstance.AddMethod('Button1', 'OnClick', #Button1ClickHandler);
Your AddMethod() implementation is assigning the TargetControl as the TMethod.Data value, so the Self and Sender parameters above will end up pointing at the same object at runtime, but that is OK.
Without the explicit Self parameter defined, that explains why your parameters are getting "scrambled" when the procedure called at runtime. The hidden Self value is being assigned to the Sender parameter, and the real Sender value is being ignored.
I'm sure someone will correct me if I'm wrong but I don't believe there is a way to create a type definition at runtime in native Delphi. Delphi's RTTI just doesn't handle this yet.
The two scenarios that come to mind for object serialization are persistence and IPC. (There may be more that I haven't thought of).
Delphi's DFM serialization would be an example of persistence. If you look at a dfm you'll notice it isn't defining methods at all. It's simply assigning event handlers to properties expecting an event handler. Both the handlers and the properties are defined at design time using normal type definitions.
If your intent is IPC(whether on the same machine or a remote one) there are already existing frameworks for accomplishing this. (RemObjects comes to mind)
You don't "make a method" at run time. That would amount to compiling new code. The methods that you assign to various event properties need to already exist.
Furthermore, you can't "add events." The object you're deserializing already has events. You defined them when you wrote the class declaration in your Delphi code. You can't add new event properties to a class after it's been compiled.
It appears that what you're really saying is that you have a standalone procedure that you happen to have named Method1, and you want to pass it as a TNotifyEvent parameter when you call SetUpMethods.
That's the wrong way to go. That Method1 procedure isn't a method, despite its name, so you mustn't use it where a method is required. Change that declaration so it belongs to a class, and then it will be a method.
If you don't want to have to instantiate the class that the method belongs to, that's fine — you can declare it as a class method instead:
class procedure TSomeClass.Method1(Sender: TNotifyEvent);
I encourage you to change the declaration of AddMethod so that the last parameter is of type TMethod. Then you're sure to have both the code and data portions of the method pointer. Right now, you're assigning the data portion based on the object whose event property you're assigning, but as I mentioned in my comment, it's rare for that relationship to exist, especially now that the method belongs to an entirely unrelated class (TSomeClass in my example). The value of the TMethod.Data field becomes the Self value when the method gets called. It's your responsibility to ensure that the value you store in that field is of a compatible type for the class the code belongs to.

Resources