Jump to content
Mike Torrettinni

Common callback functions, or not?

Recommended Posts

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

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

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.

  • Like 2

Share this post


Link to post

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
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
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

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 by FPiette
  • Like 1
  • Thanks 1

Share this post


Link to post
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
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 by FPiette
  • Like 1
  • Thanks 1

Share this post


Link to post
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
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

@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 by Kryvich
  • Thanks 1

Share this post


Link to post
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.

ScreenDump001.jpg

  • Like 1

Share this post


Link to post
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.

ScreenDump001.jpg

Oh, Ok, I wasn't sure what you are referring to. But you want the purple icon, right?

 

image.png.65dbce74f8de1d1beff35b64edb143d2.png

 

OK, I just 'thanked you'.

  • Thanks 1
  • Haha 1

Share this post


Link to post
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 by Mike Torrettinni

Share this post


Link to post
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
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

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;

 

  • Thanks 1

Share this post


Link to post

Nowadays, callbacks (not events) are often replaced by anonymous functions.

Share this post


Link to post
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
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
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
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
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 by Rollo62
  • Thanks 1

Share this post


Link to post
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

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×