Lazarus

Free Pascal => Beginners => Topic started by: Gald on May 09, 2021, 06:06:38 pm

Title: Setting events stored in other unit to controls
Post by: Gald on May 09, 2021, 06:06:38 pm
Hello hello!

I trying to do something like this:

Code: Pascal  [Select][+][-]
  1. Label1.OnClick:=@LabelClick;

But the event LabelClick is on another unit.
None of this below is working.

Code: Pascal  [Select][+][-]
  1. Label1.OnClick:=@Unit2.LabelClick;
  2. Label1.OnClick:=Unit2.@LabelClick;
  3. Label1.OnClick:=Unit2.SomeClass.LabelClick;
  4. Label1.OnClick:=Unit2.SomeClass.@LabelClick;
  5. Label1.OnClick:=@Unit2.SomeClass.LabelClick;

Then, how can I do it?

Title: Re: Setting events stored in other unit to controls
Post by: Bart on May 09, 2021, 07:25:15 pm
Define "not working".
Compilation error?
Runtime error?
Something else?

Bart
Title: Re: Setting events stored in other unit to controls
Post by: Gald on May 09, 2021, 07:47:29 pm
Define "not working".

C:\Test\unit1.pas(37,20) Error: (4001) Incompatible types: got "<address of procedure(TObject);Register>" expected "<procedure variable type of procedure(TObject) of object;Register>"

There's also this hit info on Attachment.
Title: Re: Setting events stored in other unit to controls
Post by: Gald on May 09, 2021, 09:23:33 pm
Could you please look at this sample project?
I still having problems.
Title: Re: Setting events stored in other unit to controls
Post by: Handoko on May 09, 2021, 09:52:14 pm
This works:

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Forms, StdCtrls;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Button1: TButton;
  16.     Label1: TLabel;
  17.     procedure Button1Click(Sender: TObject);
  18.   private
  19.     procedure TheReplacement(Sender: TObject);
  20.   public
  21.  
  22.   end;
  23.  
  24. var
  25.   Form1: TForm1;
  26.  
  27. implementation
  28.  
  29. uses Unit2;
  30.  
  31. {$R *.lfm}
  32.  
  33. { TForm1 }
  34.  
  35. procedure TForm1.Button1Click(Sender: TObject);
  36. begin
  37.   Label1.OnClick := @TheReplacement;
  38. end;
  39.  
  40. procedure TForm1.TheReplacement(Sender: TObject);
  41. begin
  42.   OnClickReplacement(Sender);
  43. end;
  44.  
  45. end.

Code: Pascal  [Select][+][-]
  1. unit Unit2;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   StdCtrls, Graphics;
  9.  
  10. procedure OnClickReplacement(Sender: TObject);
  11.  
  12. implementation
  13.  
  14. procedure OnClickReplacement(Sender: TObject);
  15. begin
  16.   if not(Sender is TLabel) then Exit;
  17.   (Sender as TLabel).Font.Style := [fsBold];
  18. end;
  19.  
  20. end.

Just to warn you, don't use such trick too much. Or your code will be become spaghetti code:
https://en.wikipedia.org/wiki/Spaghetti_code
Title: Re: Setting events stored in other unit to controls
Post by: Gald on May 09, 2021, 10:33:55 pm
Just to warn you, don't use such trick too much.

 :o :o LOooolll

My goal was to do it a lot.
I have to find another way.
Title: Re: Setting events stored in other unit to controls
Post by: Gustavo 'Gus' Carreno on May 09, 2021, 11:48:03 pm
Hey Gald,

But the event LabelClick is on another unit.

I understand separation of concerns and the need to put things in little manageable boxes, but what I can't understand is why you want events from one form to be handled in some other unit.

In my point of view, a Label's event should reside on the same unit that declares it and that, at least for me, checks both these boxes: separation of concern and the put-things-in-little-boxes.

Can I, very politely, ask for an explanation on why would you want to bridge across a unit for an event?
I think there's a design flaw on your side that needs to be addressed, not shamed, but addressed :)

Cheers,
Gus
Title: Re: Setting events stored in other unit to controls
Post by: Handoko on May 10, 2021, 05:39:22 am
My goal was to do it a lot.
I have to find another way.

You will better understand what spaghetti code is if you really make one.

If you're working on a commercial project or a company, you have to be careful. Any wrong decision can be costly. But if it is you personal project then it is okay. We can learn from other's experience and we can learn from our own mistakes.

Programmers dislike spaghetti code because it is hard to maintain. Finding and fixing bug is hard, adding new features is hard, even reading it is hard. Just as what Gus said, I believe there is a design flaw. But let's put that aside, here are the things you can do to minimize that bad effects.

Use proper names for variables, functions and procedures
It really will improve the code readability, a lot.

Put them into groups
Normally we should put the codes that related near together but you're going to separate it and put them into other unit. I heard you said 'a lot'. So those things should be grouped and properly arranged in a good order.

Don't mix with others
The unit that stores events, should not contain other kind of codes. If it has, you should move them out to another unit.

Give proper comments
Often, we need to revisit the code we wrote some years ago. It can be helpful if you document the code by using comments. Just don't overuse comment, that is bad too.

If someday that code becomes 'too hard' for you to handle then you will know it is a spaghetti code. Lesson learned. But if that never happens, you learn the new trick.
Title: Re: Setting events stored in other unit to controls
Post by: Gustavo 'Gus' Carreno on May 10, 2021, 07:05:47 am
Hey Gald,

After reading what Handoko wrote, and from a recent experience with code from a person that didn't went the OOP route and instead opted for the Procedural way and having units to group those procedure/function by his arbitrary rules, I now have another question:

How well versed are you on OOP?

I ask this because it kinda sounds like your decision to put Event handlers in other units is what that other fellah did with the procedural approach.

I need to stress this out: No shame what so ever.
But you have to pick a lane and stick to it.

And by that I mean, you either go OOP and every object has his own unit, of sorts.
Or you decide to go all in with the procedural approach and you spread your code in a different way.

If you embrace the OOP methodology, I think your code will be a lot kinder to other programmers.
If, on the other side, you are going for the procedural approach, then your code will be considered spaghetti by the majority of the programmers that only have patience for OOP.
But at least it's your spaghetti and you'll know how to navigate it. But, alas, you'll be a solo programmer on this one for a long time :(

So yeah, pick your lane, stick to it and PLEASE pay attention to the VERY GOOD advice that Handoko gave you, it's rather good and very valuable!!

Cheers,
Gus
Title: Re: Setting events stored in other unit to controls
Post by: Gald on May 10, 2021, 08:32:21 am
Thank you so so so much for your tips!

Handoko, I'll do some tests on it, and learn more about that.

Gus, I like your curiosity!
I'm doing an application with a kind of "web page" (scrollbox) that loads a file with the interface and his methods.
It's like what a Browser does with HTML, but with native controls. So, the users will have tools to create their own "pages", very limited, with a list of premade code in some units. If the user puts a Buttom he must choose a small list of premade actions, which will be stored in some units. Then he will generate his personal form (page) and it will be stored in a file and shared with others users to load it in runtime and navigate.
And was not a fan of OOP, but it starts to get interesting. I saw some things in recent years, but I didn't use my hands to write and learn for real. I gonna find some direct/concentrated articles/lessons about it right now.



You may find this video interest: https://www.youtube.com/watch?v=cvDyQUpaFf4
Title: Re: Setting events stored in other unit to controls
Post by: Handoko on May 10, 2021, 08:40:41 pm
What you're going to develop is not simple. Not only creating a set of predefined actions, you may also need to create a GUI editor and/or maybe a text parser/interpreter, which has some similarities with the game builder I'm developing. But I like your creativity and eagerness for doing something challenging.

Here I wrote a simplified demo. Not exactly what you want but it should give the idea.

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, Forms, ExtCtrls, StdCtrls, ActionsLib;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     btnUp: TButton;
  16.     btnDown: TButton;
  17.     btnLeft: TButton;
  18.     btnRight: TButton;
  19.     Shape1: TShape;
  20.     procedure FormCreate(Sender: TObject);
  21.   end;
  22.  
  23. var
  24.   Form1: TForm1;
  25.  
  26. implementation
  27.  
  28. {$R *.lfm}
  29.  
  30. { TForm1 }
  31.  
  32. procedure TForm1.FormCreate(Sender: TObject);
  33. begin
  34.   SetTarget(Shape1);
  35.   SetButtonUp(btnUp);
  36.   SetButtonDown(btnDown);
  37.   SetButtonLeft(btnLeft);
  38.   SetButtonRight(btnRight);
  39. end;
  40.  
  41. end.

Code: Pascal  [Select][+][-]
  1. unit ActionsLib;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, Forms, StdCtrls, ExtCtrls;
  9.  
  10. procedure SetTarget(Shape: TShape);
  11. procedure SetButtonUp(Button: TButton);
  12. procedure SetButtonDown(Button: TButton);
  13. procedure SetButtonLeft(Button: TButton);
  14. procedure SetButtonRight(Button: TButton);
  15.  
  16. implementation
  17.  
  18. type
  19.  
  20.   { TTmpButton }
  21.  
  22.   TTmpButton = class(TButton)
  23.   public
  24.     procedure ShapeMoveUp(Sender: TObject);
  25.     procedure ShapeMoveDown(Sender: TObject);
  26.     procedure ShapeMoveLeft(Sender: TObject);
  27.     procedure ShapeMoveRight(Sender: TObject);
  28.   end;
  29.  
  30. const
  31.   TargetObject: TShape = nil;
  32.  
  33. var
  34.   TmpButton: TTmpButton;
  35.  
  36. procedure SetTarget(Shape: TShape);
  37. begin
  38.   TargetObject := Shape;
  39. end;
  40.  
  41. procedure SetButtonUp(Button: TButton);
  42. begin
  43.   Button.OnClick := @TmpButton.ShapeMoveUp;
  44. end;
  45.  
  46. procedure SetButtonDown(Button: TButton);
  47. begin
  48.   Button.OnClick := @TmpButton.ShapeMoveDown;
  49. end;
  50.  
  51. procedure SetButtonLeft(Button: TButton);
  52. begin
  53.   Button.OnClick := @TmpButton.ShapeMoveLeft;
  54. end;
  55.  
  56. procedure SetButtonRight(Button: TButton);
  57. begin
  58.   Button.OnClick := @TmpButton.ShapeMoveRight;
  59. end;
  60.  
  61. procedure TTmpButton.ShapeMoveUp(Sender: TObject);
  62. begin
  63.   if not(Assigned(TargetObject)) then Exit;
  64.   TargetObject.Top := TargetObject.Top - 10;
  65.   if TargetObject.Top < -TargetObject.Height then
  66.     TargetObject.Top := TargetObject.Parent.Height;
  67. end;
  68.  
  69. procedure TTmpButton.ShapeMoveDown(Sender: TObject);
  70. begin
  71.   if not(Assigned(TargetObject)) then Exit;
  72.   TargetObject.Top := TargetObject.Top + 10;
  73.   if TargetObject.Top > TargetObject.Parent.Height then
  74.     TargetObject.Top := -TargetObject.Height;
  75. end;
  76.  
  77. procedure TTmpButton.ShapeMoveLeft(Sender: TObject);
  78. begin
  79.   if not(Assigned(TargetObject)) then Exit;
  80.   TargetObject.Left := TargetObject.Left - 10;
  81.   if TargetObject.Left < -TargetObject.Width then
  82.     TargetObject.Left := TargetObject.Parent.Width;
  83. end;
  84.  
  85. procedure TTmpButton.ShapeMoveRight(Sender: TObject);
  86. begin
  87.   if not(Assigned(TargetObject)) then Exit;
  88.   TargetObject.Left := TargetObject.Left + 10;
  89.   if TargetObject.Left > TargetObject.Parent.Width then
  90.     TargetObject.Left := -TargetObject.Width;
  91. end;
  92.  
  93. end.
Title: Re: Setting events stored in other unit to controls
Post by: egsuh on May 11, 2021, 04:05:59 am
Handoko's effort is remarkable. This would not be so difficult if actions are done to "Sender" itself, but  would be difficult if events have to do something on other controls.
Title: Re: Setting events stored in other unit to controls
Post by: Gald on May 11, 2021, 04:25:41 am
Handoko, your demo is impressive!
This was exactly what I need.

There are no words to express my gratitude for you.
Keep being the nice guy you are  :-*
Title: Re: Setting events stored in other unit to controls
Post by: Gustavo 'Gus' Carreno on May 11, 2021, 08:18:58 am
Hey Gald,

Gus, I like your curiosity!

Thank you :)

I'm doing an application with a kind of "web page" (scrollbox) that loads a file with the interface and his methods.

Hummmm, so in a way, just what Lazarus is doing with the *.lfm. Rather interesting !!!

It's like what a Browser does with HTML, but with native controls. So, the users will have tools to create their own "pages", very limited, with a list of premade code in some units. If the user puts a Buttom he must choose a small list of premade actions, which will be stored in some units. Then he will generate his personal form (page) and it will be stored in a file and shared with others users to load it in runtime and navigate.
And was not a fan of OOP, but it starts to get interesting. I saw some things in recent years, but I didn't use my hands to write and learn for real. I gonna find some direct/concentrated articles/lessons about it right now.

Getting more interesting by the minute !!!
Please report back with your progress!!!

Can I ask if you're willing to share your code in one of the many code sharing platforms? I'm partial to GitHub, but anyone will do :)


You may find this video interest: https://www.youtube.com/watch?v=cvDyQUpaFf4

I'll have a watch :)

Cheers,
Gus
Title: Re: Setting events stored in other unit to controls
Post by: Gustavo 'Gus' Carreno on May 11, 2021, 08:20:24 am
Hey Handoko,

As per usual a brilliant effort!!!

Thanks Handoko!!

Cheers,
Gus
Title: Re: Setting events stored in other unit to controls
Post by: Handoko on May 18, 2021, 08:54:08 pm
This would not be so difficult if actions are done to "Sender" itself, but  would be difficult if events have to do something on other controls.

Yes, you're right. It is much more challenging. Here I wrote a demo for setting button's OnClick event to control the behavior of 2 shapes.


Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, Forms, Graphics, StdCtrls, ExtCtrls, ActionsLib;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     btnStart: TButton;
  16.     procedure btnStartClick(Sender: TObject);
  17.     procedure FormDestroy(Sender: TObject);
  18.   private
  19.     FShapes: array of TShape;
  20.   end;
  21.  
  22. var
  23.   Form1: TForm1;
  24.  
  25. implementation
  26.  
  27. {$R *.lfm}
  28.  
  29. { TForm1 }
  30.  
  31. procedure TForm1.btnStartClick(Sender: TObject);
  32. var
  33.   aShape:  TShape;
  34.   aButton: TButton;
  35. begin
  36.  
  37.   btnStart.Free;
  38.  
  39.   // Create the shapes
  40.   SetLength(FShapes, 2);
  41.   aShape              := TShape.Create(Self);
  42.   aShape.Parent       := Self;
  43.   aShape.Left         := 40;
  44.   aShape.Top          := 40;
  45.   aShape.Brush.Color  := clMoneyGreen;
  46.   aShape.Shape        := stCircle;
  47.   FShapes[0]          := aShape;
  48.   aShape              := TShape.Create(Self);
  49.   aShape.Parent       := Self;
  50.   aShape.Left         := 130;
  51.   aShape.Top          := 40;
  52.   aShape.Brush.Color  := clYellow;
  53.   aShape.Shape        := stTriangle;
  54.   FShapes[1]          := aShape;
  55.   aShape              := TShape.Create(Self);
  56.   aShape.Parent       := Self;
  57.   aShape.Left         := 220;
  58.   aShape.Top          := 40;
  59.   aShape.Brush.Color  := clSkyBlue;
  60.   aShape.Shape        := stDiamond;
  61.   FShapes[2]          := aShape;
  62.  
  63.   // Create the labels
  64.   with TLabel.Create(Self) do
  65.   begin
  66.     Parent := Self;
  67.     Left   := 70;
  68.     Top    := 20;
  69.     Caption := 'A';
  70.   end;
  71.   with TLabel.Create(Self) do
  72.   begin
  73.     Parent := Self;
  74.     Left   := 160;
  75.     Top    := 20;
  76.     Caption := 'B';
  77.   end;
  78.   with TLabel.Create(Self) do
  79.   begin
  80.     Parent := Self;
  81.     Left   := 250;
  82.     Top    := 20;
  83.     Caption := 'C';
  84.   end;
  85.  
  86.   // Create buttons
  87.   aButton := TButton.Create(Self);
  88.   aButton.Parent  := Form1;
  89.   aButton.Left    := 40;
  90.   aButton.Top     := 130;
  91.   aButton.Width   := 240;
  92.   aButton.Caption := 'AB swap color';
  93.   ButtonClickSwapColor(aButton, FShapes[0], FShapes[1]);
  94.   aButton         := TButton.Create(Self);
  95.   aButton.Parent  := Form1;
  96.   aButton.Left    := 40;
  97.   aButton.Top     := 160;
  98.   aButton.Width   := 240;
  99.   aButton.Caption := 'BC swap color';
  100.   ButtonClickSwapColor(aButton, FShapes[1], FShapes[2]);
  101.   aButton         := TButton.Create(Self);
  102.   aButton.Parent  := Form1;
  103.   aButton.Left    := 40;
  104.   aButton.Top     := 190;
  105.   aButton.Width   := 240;
  106.   aButton.Caption := 'AB swap shape';
  107.   ButtonClickSwapShape(aButton, FShapes[0], FShapes[1]);
  108.  
  109. end;
  110.  
  111. procedure TForm1.FormDestroy(Sender: TObject);
  112. var
  113.   i: Integer;
  114. begin
  115.   for i := 0 to Length(FShapes)-1 do
  116.     FShapes[i].Free;
  117.   SetLength(FShapes, 0);
  118. end;
  119.  
  120. end.

In the code above, all the visual components on the form are created at run-time except btnStart, line #15.
Setting the OnClick events can be found on the lines #93, #100, #107.


Code: Pascal  [Select][+][-]
  1. unit ActionsLib;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Graphics, StdCtrls, ExtCtrls;
  9.  
  10. procedure ButtonClickSwapColor(Button: TButton; Shape1, Shape2: TShape);
  11. procedure ButtonClickSwapShape(Button: TButton; Shape1, Shape2: TShape);
  12.  
  13. implementation
  14.  
  15. const
  16.   ID_Length = 30; // not sure but 30 should be more than enough
  17.  
  18. type
  19.  
  20.   IDstring = string[ID_Length];
  21.   TLink = record
  22.     ID:    IDstring;
  23.     Item1: TShape;
  24.     Item2: TShape;
  25.   end;
  26.  
  27.   { TTmpShape }
  28.  
  29.   TTmpShape = class(TShape)
  30.   public
  31.     procedure SwapColor(Sender: TObject);
  32.     procedure SwapShape(Sender: TObject);
  33.   end;
  34.  
  35. var
  36.   TmpShape: TTmpShape;
  37.   Links:    array of TLink;
  38.  
  39. procedure SaveLink(const ID: IDstring; Shape1, Shape2: TShape);
  40. var
  41.   i: Integer;
  42. begin
  43.   i := Length(Links);
  44.   SetLength(Links, i+1);
  45.   Links[i].ID    := ID;
  46.   Links[i].Item1 := Shape1;
  47.   Links[i].Item2 := Shape2;
  48. end;
  49.  
  50. procedure GetLink(const ID: IDstring; out Shape1, Shape2: TShape);
  51. var
  52.   i: Integer;
  53. begin
  54.   Shape1 := nil;
  55.   Shape2 := nil;
  56.   for i := 0 to Length(Links)-1 do
  57.     if Links[i].ID = ID then
  58.     begin
  59.       Shape1 := Links[i].Item1;
  60.       Shape2 := Links[i].Item2;
  61.       Exit;
  62.     end;
  63. end;
  64.  
  65. procedure ButtonClickSwapColor(Button: TButton; Shape1, Shape2: TShape);
  66. var
  67.   S: string;
  68. begin
  69.   S := PtrInt(Button).ToString + 'Click';
  70.   SaveLink(S, Shape1, Shape2);
  71.   Button.OnClick := @TmpShape.SwapColor;
  72. end;
  73.  
  74. procedure ButtonClickSwapShape(Button: TButton; Shape1, Shape2: TShape);
  75. var
  76.   S: string;
  77. begin
  78.   S := PtrInt(Button).ToString + 'Click';
  79.   SaveLink(S, Shape1, Shape2);
  80.   Button.OnClick := @TmpShape.SwapShape;
  81. end;
  82.  
  83. { TTmpShape }
  84.  
  85. procedure TTmpShape.SwapColor(Sender: TObject);
  86. var
  87.   Shape1, Shape2: TShape;
  88.   Temp:           TColor;
  89.   S:              string;
  90. begin
  91.   if not(Sender is TButton) then Exit;
  92.   S := PtrInt(Sender).ToString + 'Click';
  93.   GetLink(S, Shape1, Shape2);
  94.   if (Shape1 = nil) or (Shape2 = nil) then Exit;
  95.   Temp               := Shape1.Brush.Color;
  96.   Shape1.Brush.Color := Shape2.Brush.Color;
  97.   Shape2.Brush.Color := Temp;
  98. end;
  99.  
  100. procedure TTmpShape.SwapShape(Sender: TObject);
  101. var
  102.   Shape1, Shape2: TShape;
  103.   Temp:           TShapeType;
  104.   S:              string;
  105. begin
  106.   if not(Sender is TButton) then Exit;
  107.   S := PtrInt(Sender).ToString + 'Click';
  108.   GetLink(S, Shape1, Shape2);
  109.   if (Shape1 = nil) or (Shape2 = nil) then Exit;
  110.   Temp         := Shape1.Shape;
  111.   Shape1.Shape := Shape2.Shape;
  112.   Shape2.Shape := Temp;
  113. end;
  114.  
  115. initialization
  116.   SetLength(Links, 0);
  117.  
  118. finalization
  119.   SetLength(Links, 0);
  120.  
  121. end.
TinyPortal © 2005-2018