Recent

Author Topic: Ugly callback code - works but........  (Read 1504 times)

iru

  • Full Member
  • ***
  • Posts: 224
Ugly callback code - works but........
« on: January 28, 2019, 10:07:58 am »
Gentlefolk,

Environment: Linux Mint 18, Laz 1.84....

I have an object with a dynamic array of Buttons. Object, array size and buttons are created in code, all works well.

The 'OnClick' event handler for each button is set to @AllButtonClick.
This 'AllButtonClick' common code uses the 'AllButtonClick(Sender: TObject) tButton(Sender).Tag to specify the 'clicked' button and change button color, etc.

The 'button' event handlers do not return anything so I cannot return what button was selected.

So I looked around for some sort 'callback' code that would be called by the the AllButtonClick event.


After some reading and fiddling I came up with the following pointer based code. It works but frightens me.....

Any help, directions, examples appreciated, Ian.

Process flow:
  Create MyButtonHolder.
  Execute MyButtonHolder.SetCall(@GetButtonNumber) << Saves A pointer to GetButtonNumber
  Click on a button.
  MyButtonHolder.AllButtonClick code is executed.
    begin
      ..
      ..
      FMBArray[Index].Color := clRed;
      WriteLN(tfun(CallPtr));
    end;
 
The code 'WriteLN(tfun(CallPtr))' is executed and imediatley after the code @GetButtonNumber is executed (from CallStack).
  Strange and ugly:
    The WriteLN is necessary, things do not compile otherwise.
    In the 'CallBack' code tForm1.GetButtonNumber the 'Sender' returned is odd, of type tBrush.
   
My Button module code:

type
  tFun = function() : String;
  ptr = Pointer;
 
  tMyButtonHolder = class
    ..
    ..
    CallPtr : Pointer;

    function SetCall(InPtr: Pointer): Boolean; // Stores the 'Callback pointer
    function GetButton : Integer; // Returns clicked button index
  end;

function tMButHolder.SetCall(InPtr: Pointer): Boolean;

begin
  // InPtr = @TForm1.GetButtonNumber
  CallPtr := InPtr;
end;   

procedure tMButtonHolder.AllButtonClick(Sender: TObject);
  // A click on all buttons winds up here, button is determined by Tag value

begin
   ..
   ..
  FMBArray[Index].Color := clRed;
  WriteLN(tfun(CallPtr));
end;     

Main form code:

procedure tForm1.GetButtonNumber(Sender: TObject);
var
  vButton : Integer;

Begin
  ..
  vTheButton:= ButtonHolder.GetButton; // get the clicked button index
end;         

Bart

  • Hero Member
  • *****
  • Posts: 3518
    • Bart en Mariska's Webstek
Re: Ugly callback code - works but........
« Reply #1 on: January 28, 2019, 11:45:46 am »
Code: [Select]
WriteLN(tfun(CallPtr));
Why do you cast a pointer to a function en write the result?
I assume you want to call a function (of type tfun) with CallPtr as argument, but you are not doing that AFAICS.

Your code is the equivalent of
Code: [Select]
writeln(byte(ACharVariable));This also would not compile if you leave out the writeln().

Bart
« Last Edit: January 28, 2019, 01:14:20 pm by Bart »

Thaddy

  • Hero Member
  • *****
  • Posts: 8967
Re: Ugly callback code - works but........
« Reply #2 on: January 28, 2019, 12:08:08 pm »
Also note the sender parameter will identify the button for you. You do not need a number for that.
Most people that want to use threading should learn to patch their jeans first: use a needle.

iru

  • Full Member
  • ***
  • Posts: 224
Re: Ugly callback code - works but........
« Reply #3 on: January 28, 2019, 08:06:07 pm »
In reply to Bart.
  Why cast a pointer to a function?
  I was trying to find some way to execute the code pointed to by the pointer.
  Any suggestions?

Ian

iru

  • Full Member
  • ***
  • Posts: 224
Re: Ugly callback code - works but........
« Reply #4 on: January 28, 2019, 08:12:16 pm »
In reply to Thaddy.
  Yes, in the AllButtonClick code I could determine the button number and save it somewhere persistent but how in the general code do I know the that a button has been clicked?
  Something like code in Application.Idle to regularly check a flag is also ugly to my mind....

Ian

FTurtle

  • Sr. Member
  • ****
  • Posts: 260
Re: Ugly callback code - works but........
« Reply #5 on: January 28, 2019, 09:33:51 pm »
Code: Pascal  [Select]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Button1: TButton;
  16.     Button2: TButton;
  17.     Button3: TButton;
  18.     Label1: TLabel;
  19.     procedure AnyButtonClick(Sender: TObject);
  20.   private
  21.     FClickedButton: TButton;
  22.     procedure DoSomething;
  23.   public
  24.  
  25.   end;
  26.  
  27. var
  28.   Form1: TForm1;
  29.  
  30. implementation
  31.  
  32. {$R *.lfm}
  33.  
  34. { TForm1 }
  35.  
  36. procedure TForm1.AnyButtonClick(Sender: TObject);
  37. begin
  38.   if Sender is TButton then
  39.     FClickedButton:=TButton(Sender)
  40.   else
  41.     FClickedButton:=nil;
  42.  
  43.   DoSomething;
  44. end;
  45.  
  46. procedure TForm1.DoSomething;
  47. begin
  48.   if FClickedButton = Button1 then
  49.     Label1.Color:=clRed
  50.   else if FClickedButton = Button2 then
  51.     Label1.Color:=clGreen
  52.   else if FClickedButton = Button3 then
  53.     Label1.Color:=clBlue;
  54. end;
  55.  
  56. end.

iru

  • Full Member
  • ***
  • Posts: 224
Re: Ugly callback code - works but........
« Reply #6 on: January 28, 2019, 10:51:18 pm »
In reply to FTurtle.
  Thanks for the example, however...
  In my program the 'MyButtonHolder' object will be used for multiple purposes on multiple forms.
  Therefore any 'DoSomething' code would be very messy and illogical in terms of the program/form structure.

The program is to input data for athletic meetings. Depending on the type of athletic discipline the use and number of 'MyButtonHolder' objects will vary. For example a 1500 metre race may have 6 heats, a 100 metre race 30+ heats. Each heat select-able by an individual button so results may be input or displayed.

Ian.

iru

  • Full Member
  • ***
  • Posts: 224
Re: Ugly callback code - works but........
« Reply #7 on: January 28, 2019, 11:18:17 pm »
A little more...

Why buttons rather than objects like radio buttons, grids, etc?
This program will/may be used on tablets with touch screens by volunteer unskilled users....

Keep it simple and easy to use....

Ian

jamie

  • Hero Member
  • *****
  • Posts: 1997
Re: Ugly callback code - works but........
« Reply #8 on: January 28, 2019, 11:31:17 pm »

You do not  need to use messy code like that.  :D

You can have a variable in like this

Var
  LastButtonPressed :Tbutton;

and in your all Button click..

  LastButtonPressed := Sender;

 The SENDER parameter will reflect which button currently was pressed.

At any time from there on..
you can test the LastButtonPressed this way

 If LastBUttonPressed <> Nil then
 
 MyIndex := LastBUttonPressed.Tag;

I think that is what you were looking for..

The LastSenderPressed Variable can be in the class or somewhere accessible.

iru

  • Full Member
  • ***
  • Posts: 224
Re: Ugly callback code - works but........
« Reply #9 on: February 05, 2019, 10:39:45 am »
Gentlefolk,

I am still thinking and pottering with 'callback' code/mechanisms/approaches/etc.

However I have been looking at 'buttons' to use in my 'multibutton' button array.
The 'ATButton' appeals, it has a theme mechanism to change 'global' parameters, nicely written, has some good examples, works well.....

          Alexey Torgashin, uvviewsoft.com

Ian

iru

  • Full Member
  • ***
  • Posts: 224
Re: Ugly callback code - works but........
« Reply #10 on: September 17, 2019, 12:32:09 pm »
Gentlefolk,

I have a requirement which forced me to revisit this issue. I have made a couple of changes:

Added: tCB = ...... and made some other changes.
Current code is

type
  tCB = procedure();

  tMBArray = array of tPanel;
  mbAlign  = (mbVert, mbHori);

  { tMBut }

  tMBut = class
    FMBArray : tMBArray;
    FMBPanel : tPanel;
    procedure AllButtonClick(Sender: TObject);
    ...
    ...
    CallPtr : Pointer;
  end
  ...
  ...
implementation

procedure tMBut.AllButtonClick(Sender: TObject);
  // A click on all buttons winds up here, button is determined by Tag value

var
  vTest : Integer;

begin
  ...
  ...

  tCB(CallPtr);
end;

The code compiles and loads:

On execution with  tMBut class on the MAIN form the code at CallPtr (AllButtonClick) is executed but "Sender" appears to point to odd places.

If I ignore sender and access objects on the MAIN form directly they are accessible. Everything good......

If in the "AllButtonClick" (on the main form) procedure I perform a ShowModal, the form is displayed.

If I have an instance of tMBut = class on the modal form it displays correctly but when I click on a button/panel and arrive at     
tMBut.AllButtonClick.

"sender" is rubbish and more importantly NO objects on the form are visible.

I think that the code is being executed is a different thread..... I think...

After a lot of chasing I am stuck at this point, cannot find anything that helps/explains/.....

I am especially concerned about the validity of "tCB = procedure();" and "tCB(CallPtr);".

It works (transfers control to "AllButtonClick" but is inherently ugly (sender is bad)) but any attempts find something better generally cause compile time errors about number of parameters, wrong types, etc!!!!!.

So, can anyone:
   Inform me how to debug the problem
   Inform me the correct way to use "tCB = procedure();" and "tCB(CallPtr);" correctly
   Help in any way?

Thank you, Ian