Mike Torrettinni 198 Posted October 24, 2020 Since I started using Frames, I'm setting up a lot of callback functions. I noticed a lot of them are getting duplicated, especially the simple ones, with simple type parameters. Of course all callbacks are defined and used within the needed scope, not global. Should I create a few common callback functions and have them defined in global scope, like: TIntFunctionWithIntParam = function(aValue: integer): integer of object; TStringFunctionWithIntParam = function(aValue: integer): string of object; TIntFunctionWithStringParam = function(const aStr: string): integer of object; TStringFunctionWithStringParam = function(const aStr: string): string of object; TIntFunction = function: integer of object; TStringFunction = function: string of object; these function can then be used in any Frame. Of course functions that are specific to each Frame, would still be defined in each Frame. Or is this the wrong approach and all callbacks should be defined exactly for the defined purpose, and within defined scope? Any advice is appreciated! Share this post Link to post
FPiette 385 Posted October 24, 2020 Usually, with Delphi we use events which are a form of callbacks which are 1st category citizen in Delphi language. Callbacks as you are doing will of course work in Delphi but are more C/C++ style. I usually define all event types where they are used and duplicate it at will. Sometimes, I use a unit having "Types" as suffix in his name to put those definitions along with other related data types. Share this post Link to post
Guest Posted October 24, 2020 You stripped the most valuable feature of the usage "of object" ! Add a parameter "Sender: Object" as first to all of them, you might see the benefit right away or not, but it will not cost you anything right now, after that you will see what are you missing, these are event calls and don't need to be used or handled as anonymous callback, right ? Now name them accordingly, as events, and reuse them everywhere just like TNotifyEvent, but if the name callback is suitable for you then that is OK. Share this post Link to post
Mike Torrettinni 198 Posted October 24, 2020 Perhaps I'm using the callback term incorrectly... and these are events? I use these to assign local Frame method to a general Search form, like this: // definition callback/event TGetSearchTextLine = function (aDataIdx: integer) string of object; // method in Frame that returns data line to be searched within function TFrameA.PrepareDataLineForSearch(aDataIdx: integer): string; begin Result := fData[aDataIdx].ProjectName + fData[aDataIdx].ProjectDescription; end; // create Search form and assign/connect methods (callbacks/events) procedure TFrameA.StartSearch; begin ...// create fSearchForm fSearchForm.GetSearchTextLine := PrepareDataLineForSearch; end; // in Search Form I have public variable of type of callback/event public var GetSearchTextLine: TGetSearchTextLine; // and it gets called when Search is executed in Search form procedure TSearchForm.Search; begin fDataLineToSearch := GetSearchTextLine; SearchForText(fDataLineToSearch); end; When used like this, am I using a callback or event? 4 hours ago, Kas Ob. said: Add a parameter "Sender: Object" as first to all of them In this case I'm not using Form/Controls methods (like OnClick and others). Do I still need Sender: Object? Is the reason for this so that I know who called the method? Share this post Link to post
FPiette 385 Posted October 24, 2020 5 minutes ago, Mike Torrettinni said: Perhaps I'm using the callback term incorrectly... and these are events? No, they are not events. An event would looks like: TGetSearchTextLineEvent = procedure function (Sender : TObject; aDataIdx: integer; var TextLine : String) of object; Your code is not very clear to me. I understand you have a TFrame which has to do a search and for that, it create and display a form. Right? Why not simply have a function Search in the form, taking what to search in arguments and return search result? No event nor callback required at all. Share this post Link to post
Mike Torrettinni 198 Posted October 24, 2020 17 minutes ago, FPiette said: Why not simply have a function Search in the form, taking what to search in arguments and return search result? No event nor callback required at all. Maybe I over-designed it, but this is what I have: MainForm -FrameA -FrameB -FrameC -Frame..N CommonSearchForm each Frame has VirtualTreeview control to display data. CommonSearchForm has Search, SearchNext, SearchPrev, keyboard actions (Ctrl+F/N)... and these actions also control (move next/prev) searched lines in Frame's VirtualTreeView. So, each Frame creates it's own instance of CommonSearchForm and assigns local methods to events so that GetSearchTextLine gets different text from each Frame's local method that returns text line to be searched. Are you suggesting I create all Search features in each Frame? Share this post Link to post
FPiette 385 Posted October 24, 2020 (edited) OK, I understand better. Your design is correct. You need an event in the search form to get text line at given index. Each frame initialize the search form event to a handler. TGetSearchTextLineEvent = procedure function (Sender : TObject; aDataIdx: integer; var TextLine : String) of object; TSearchForm = class(TForm) private FDataIdx : Integer; FOnGetSearchTextLine : TGetSearchTextLineEvent; function GetSearchTextLine(ADataIdx : Integer) : String; public function Search(ADataIdx : Integer) : String; property OnGetSearchTextLine : TGetSearchTextLineEvent read FOnGetSearchTextLine write FOnGetSearchTextLine; end; function TSearchFrom.GetSearchTextLine(ADataIdx : Integer) : String; begin Result := ''; // This will be the search line if no event handler assigned if Assigned(FOnGetSearchTextLine) then FOnGetSearchTextLine(Self, ADataIdx, Result); end; Edited October 24, 2020 by FPiette 1 Share this post Link to post
Mike Torrettinni 198 Posted October 24, 2020 14 minutes ago, FPiette said: OK, I understand better. Your design is correct. You need an event in the search form to get text line at given index. Each frame initialize the search form event to a handler. TGetSearchTextLineEvent = procedure function (Sender : TObject; aDataIdx: integer; var TextLine : String) of object; TSearchForm = class(TForm) private FDataIdx : Integer; FOnGetSearchTextLine : TGetSearchTextLineEvent; function GetSearchTextLine(ADataIdx : Integer) : String; public function Search(ADataIdx : Integer) : String; property OnGetSearchTextLine : TGetSearchTextLineEvent read FOnGetSearchTextLine write FOnGetSearchTextLine; end; function TSearchFrom.GetSearchTextLine(ADataIdx : Integer) : String; begin Result := ''; // This will be the search line if no event handler assigned if Assigned(FOnGetSearchTextLine) then FOnGetSearchTextLine(Self, ADataIdx, Result); end; Aha, thanks. Looking through some examples, most of them use TEvent = procedure() of object, I assume TEvent = function(): resulttype of object; is not the normal way, right? Am I just lucky it works for me? Share this post Link to post
FPiette 385 Posted October 24, 2020 (edited) 15 minutes ago, Mike Torrettinni said: Looking through some examples, most of them use TEvent = procedure() of object The idea is that when no event handler is assigned, the component/class/object works correctly. That's why they are procedure and returned values are passed as var argument, just like I have done in my example: function TSearchFrom.GetSearchTextLine(ADataIdx : Integer) : String; begin Result := ''; // This will be the search line if no event handler assigned if Assigned(FOnGetSearchTextLine) then FOnGetSearchTextLine(Self, ADataIdx, Result); end; Another design tips is that if an event is triggered several times, a procedure is created for that: procedure TSearchForm.TriggerGetSearchTextLine(ADataIdex : Integer; var SearchText : String); begin if Assigned(FOnGetSearchTextLine) then FOnGetSearchTextLine(Self, ADataIdx, Result); end; and then you'll write: function TSearchFrom.GetSearchTextLine(ADataIdx : Integer) : String; begin Result := ''; // This will be the search line if no event handler assigned TriggerGetSearchTextLine(ADataIdx, Result); end; Edited October 24, 2020 by FPiette 1 Share this post Link to post
FPiette 385 Posted October 24, 2020 18 minutes ago, Mike Torrettinni said: Aha, thanks. Instead of writing "thanks" in the message, you should click the "thanks" button (Hover the "like" button to see it). Share this post Link to post
Mike Torrettinni 198 Posted October 24, 2020 43 minutes ago, FPiette said: Instead of writing "thanks" in the message, you should click the "thanks" button (Hover the "like" button to see it). Where is the "thanks" button? Share this post Link to post
Kryvich 165 Posted October 24, 2020 (edited) @Mike Torrettinni I think procedures are more preferable than functions for events, because you can easily recognize where an event is called and where it is assigned. I mean FOnGetSearchTextLine := GetSearchTextLine; // Assigning s := GetSearchTextLine(123); // Calling in your initial design Edited October 24, 2020 by Kryvich 1 Share this post Link to post
FPiette 385 Posted October 24, 2020 35 minutes ago, Mike Torrettinni said: 1 hour ago, FPiette said: Instead of writing "thanks" in the message, you should click the "thanks" button (Hover the "like" button to see it). Where is the "thanks" button? As I said: hover the "like" button and you'll see it. The like button is the heart icon on the bottom right corner of a message. 1 Share this post Link to post
Mike Torrettinni 198 Posted October 24, 2020 1 minute ago, FPiette said: As I said: hover the "like" button and you'll see it. The like button is the heart icon on the bottom right corner of a message. Oh, Ok, I wasn't sure what you are referring to. But you want the purple icon, right? OK, I just 'thanked you'. 1 1 Share this post Link to post
Mike Torrettinni 198 Posted October 24, 2020 (edited) 26 minutes ago, Kryvich said: @Mike Torrettinni I think procedures are more preferable than functions for events, because you can easily recognize where an event is called and where it is assigned. I mean FOnGetSearchTextLine := GetSearchTextLine; // Assigning s := GetSearchTextLine(123); // Calling in your initial design Oh, of course! I didn't really think about it in that way. It makes sense: assigned vs called, thanks! EDIT: Hm, well thinking about it... function is also assigned and you can use if Assigned(FOnGetSearchTextLine) then FOnGetSearchTextLine; Right? Maybe I'm proving your point, but I'm still little unsure about procedure vs function event. Edited October 24, 2020 by Mike Torrettinni Share this post Link to post
FPiette 385 Posted October 24, 2020 42 minutes ago, Mike Torrettinni said: if Assigned(FOnGetSearchTextLine) then FOnGetSearchTextLine; No exactly. You need to pass the arguments for the call: if Assigned(FOnGetSearchTextLine) then FOnGetSearchTextLine(Self, ADataIdx, Result); Share this post Link to post
Mike Torrettinni 198 Posted October 24, 2020 1 minute ago, FPiette said: No exactly. You need to pass the arguments for the call: if Assigned(FOnGetSearchTextLine) then FOnGetSearchTextLine(Self, ADataIdx, Result); Yes, arguments need so to be passed in procedure and function call. That is the same. Share this post Link to post
Fr0sT.Brutal 900 Posted October 26, 2020 Event handlers and callbacks are more or less the same, I mean event handlers are callbacks (which is more general term). Usually event handlers in follow some additional traditions: - they're procedures not functions and if they return a value, they do it as 'out' parameter - they're optional, that is, an app won't crash if no handler is assigned (though it might do nothing useful then) - most of the time they're called as a reaction to an input or event; often the call chain provides mechanism of cancelling of further process (either by setting a flag of raising EAbort) I use explicit naming as callback when I have an obligatory method that must be assigned and return a value. TGetNameCb = function : string of object; TGreeter = class GetNameCb: TGetNameCb; procedure SayHi; end; procedure TGreeter.SayHi; begin WriteLn('Hi, ', GetNameCb); end; Of course it's just a matter of taste TGetNameEvent = procedure(out aName: string) of object; TGreeter = class GetNameEvent: TGetNameEvent; procedure SayHi; end; procedure TGreeter.SayHi; var Name: string; begin if not Assigned(GetNameEvent) then raise ..; GetNameEvent(Name); WriteLn('Hi, ', Name); end; 1 Share this post Link to post
Kryvich 165 Posted October 26, 2020 Nowadays, callbacks (not events) are often replaced by anonymous functions. Share this post Link to post
Mike Torrettinni 198 Posted October 26, 2020 5 hours ago, Fr0sT.Brutal said: Of course it's just a matter of taste Thanks! I was thinking about this why I made such a big deal about this, the event vs callback, function vs procedure... It's simple, I can use what I need in that situation, as they both have valid use cases. Share this post Link to post
Mike Torrettinni 198 Posted October 26, 2020 5 hours ago, Kryvich said: Nowadays, callbacks (not events) are often replaced by anonymous functions. I very rarely use anonymous functions, so I assume the limitation (compared to callback) is that it can only be used within already executed code. You can't just assign it to something, because it's anonymous (not defined anywhere). Right? Share this post Link to post
FPiette 385 Posted October 26, 2020 5 minutes ago, Mike Torrettinni said: It's simple, I can use what I need in that situation, as they both have valid use cases. I really like event very much. I don't see any use case which requires a callback. Do you? Share this post Link to post
Mike Torrettinni 198 Posted October 26, 2020 3 minutes ago, FPiette said: I really like event very much. I don't see any use case which requires a callback. Do you? Well, not sure how to answer this, because my understanding is event=callback. Share this post Link to post
Rollo62 539 Posted October 26, 2020 (edited) 1 hour ago, Mike Torrettinni said: I very rarely use anonymous functions, so I assume the limitation (compared to callback) is that it can only be used within already executed code. You can't just assign it to something, because it's anonymous (not defined anywhere). Right? You can change the anon-proc at runtime, if you mean that ... type TAnon = class FParam : Integer; FProc : TProc< Integer >; procedure Setup( AParam : Integer; AProc : TProc< Integer > ); procedure Call; end; ... procedure TAnon.Setup( AParam : Integer; AProc : TProc< Integer > ); begin FParam : AParam; FProc : AProc; end; procedure TAnon.Call; var LInt : Integer; begin LInt := Random( FParam ); if Assigned( FProc ) begin FProc( LInt ); end; end; procedure Test; begin TAnon.Setup( // This are params for the caller 100, // This is called back procedure ( AResult : Integer ) begin Label1.Text := Result: ' + AResult.ToString; end ); TAnon.Call; TAnon.Call; TAnon.Setup( // This are params for the caller 75, // This is called back procedure ( AResult : Integer ) begin Label2.Text := Result: ' + AResult.ToString; end ); TAnon.Call; TAnon.Call; end; Anon procs are especially useful when dealing with async callbacks. Edited October 26, 2020 by Rollo62 1 Share this post Link to post
Mike Torrettinni 198 Posted October 26, 2020 35 minutes ago, Rollo62 said: You can change the anon-proc at runtime, if you mean that ... type TAnon = class FParam : Integer; FProc : TProc< Integer >; procedure Setup( AParam : Integer; AProc : TProc< Integer > ); procedure Call; end; ... procedure TAnon.Setup( AParam : Integer; AProc : TProc< Integer > ); begin FParam : AParam; FProc : AProc; end; procedure TAnon.Call; var LInt : Integer; begin LInt := Random( FParam ); if Assigned( FProc ) begin FProc( LInt ); end; end; procedure Test; begin TAnon.Setup( // This are params for the caller 100, // This is called back procedure ( AResult : Integer ) begin Label1.Text := Result: ' + AResult.ToString; end ); TAnon.Call; TAnon.Call; TAnon.Setup( // This are params for the caller 75, // This is called back procedure ( AResult : Integer ) begin Label2.Text := Result: ' + AResult.ToString; end ); TAnon.Call; TAnon.Call; end; Anon procs are especially useful when dealing with async callbacks. Thanks, good to know. Share this post Link to post