Recent

Author Topic: Best way to exchange data between a form and a unit  (Read 48393 times)

cdbc

  • Hero Member
  • *****
  • Posts: 1497
    • http://www.cdbc.dk
Re: Best way to exchange data between a form and a unit
« Reply #15 on: January 13, 2024, 09:31:54 pm »
Hi
...Ok, we'll start with the basics:
- We keep this within one _1_ form. We need an edit-component, that's it for
  now.
- Then in your form-code, you enter this code:
Code: Pascal  [Select][+][-]
  1. // we'll implement this   v    observer interface(from the rtl)
  2. TForm1 = class(TForm,IFPObserver)
  3.   ...
  4.   Edit1: TEdit;
  5.   ...
  6.   // just click in the OI to connect edit1 to this OnChange event-handler
  7.   procedure Edit1Change(Sender: TObject);
  8.   ...
  9. private
  10.   ...
  11. public
  12.   // here we define the _only_ method in IFPObserver interface
  13.   procedure FPOObservedChanged(ASender: TObject; Operation: TFPObservedOperation;Data: Pointer);
  14.   ...
  15. end;
  16.  
  17. implementation
  18. ...
  19.  
  20. procedure TForm1.Edit1Change(Sender: TObject);
  21. begin
  22.   // here we utilize the built-in observer-pattern, namely IFPObserved
  23.   // to notify everybody, who might be attached to observe Edit1
  24.   Edit1.FPONotifyObservers(Edit1,ooChange,pchar(Edit1.Text));
  25.   // we send Edit1 in the ASender param, ooChange because edit1 is
  26.   // changing and typecast Edit1.Text string to a pchar, so we can send it
  27.   // in a pointer variable
  28. end;
  29. // here we implement the only method in IFPObserver (the listener)
  30. // it can observe mutiple "subjects", by attaching iself to them
  31. procedure TForm1.FPOObservedChanged(ASender: TObject;
  32.                                     Operation: TFPObservedOperation;
  33.                                     Data: Pointer);
  34. begin
  35.   // TFPObservedOperation is an enumerated type, thus we can use 'case'
  36.   case Operation of
  37.     ooCustom: ; // a 'TButton' might send us custom operations...
  38.     // because we can handle multiple subjects, we check what's been sent
  39.     ooChange: begin  // to us and from whom -> 'ASender'
  40.                 if ASender is TEdit then
  41.                   if TEdit(ASender) = Edit1 then Caption:= pchar(Data);
  42.               end;
  43. // we know that, in this case, Edit1 sends us text disguised as a pchar
  44.     ooAddItem: ; // a list might notify us, of an added item etc...
  45.   end;
  46. end;
  47.  
  48. procedure TForm1.FormCreate(Sender: TObject);
  49. begin
  50.   // here we attach ourselves to the Edit1's IFPObserved subject, meaning
  51.   Edit1.FPOAttachObserver(Self); // "please notify us"
  52. end;
  53.  
  54. procedure TForm1.FormDestroy(Sender: TObject);
  55. begin
  56.   // it's pretty _important_ to detach ourselves again, when we're done
  57.   // listening, to avoid dangling references AV's
  58.   Edit1.FPODetachObserver(Self);
  59. end;
  60.  
Try this, I think you should be able to step through it with debugger...
You can also ctrl-click on the objects and methods to follow the code-flow.
Have fun and let me know, if you have questions, before we move on  :D
Regards Benny
« Last Edit: January 13, 2024, 09:33:50 pm by cdbc »
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 2.2.6 up until Jan 2024 from then on it's: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 3.0

cdbc

  • Hero Member
  • *****
  • Posts: 1497
    • http://www.cdbc.dk
Re: Best way to exchange data between a form and a unit
« Reply #16 on: January 14, 2024, 12:48:30 am »
Hi
@Joanna: Sorry, but what is it about "Basics", that eludes you?!?
I'm trying to shed some light, on the workings of the observer pattern...
When he knows the mechanics "by heart" we can build on that.
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 2.2.6 up until Jan 2024 from then on it's: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 3.0

Hansvb

  • Hero Member
  • *****
  • Posts: 687
Re: Best way to exchange data between a form and a unit
« Reply #17 on: January 14, 2024, 10:09:52 am »
At first glance, your example seems clear to me. You add
Code: Pascal  [Select][+][-]
  1. Procedure FPOAttachObserver(AObserver : TObject);
to the edit. In the onchange of the edit you notify the observer with the type of action and where this action comes from. As a result, the observer sees a change and depending on the type of change you do something. In this case change the forms caption.

Now I add another Tedit to the form and i use only one Onchange event for both Tedit's. I have expanded FPOObservedChanged a bit to check whether the change comes from edit1 or edit2. And I think that's where I'm going wrong. The change is noticed with the correct edit (line 67 / 69), but the form caption becomes empty instead of editx.text.

Code: Pascal  [Select][+][-]
  1. ...
  2.   TForm1 = class(TForm,IFPObserver)  // <==
  3.     Edit1: TEdit;
  4.     Edit2: TEdit;
  5.     procedure EditChange(Sender: TObject);
  6.     procedure FormCreate(Sender: TObject);
  7.     procedure FormDestroy(Sender: TObject);
  8.   public
  9.     procedure FPOObservedChanged(ASender: TObject; Operation: TFPObservedOperation;Data: Pointer);  
  10.   end;
  11. ...
  12. implementation
  13. ...
  14. procedure TForm1.FormCreate(Sender: TObject);
  15. begin
  16.   // here we attach ourselves to the Edit1's IFPObserved subject, meaning
  17.   Edit1.FPOAttachObserver(Self); // "please notify us"
  18.   Edit2.FPOAttachObserver(Self);
  19. end;
  20.  
  21. procedure TForm1.FormDestroy(Sender: TObject);
  22. begin
  23.   // it's pretty _important_ to detach ourselves again, when we're done
  24.   // listening, to avoid dangling references AV's
  25.   // todo: search if there a way to check if an observer is attached?
  26.   Edit1.FPODetachObserver(Self);
  27.   Edit2.FPODetachObserver(Self);
  28. end;
  29.  
  30. procedure TForm1.EditChange(Sender: TObject);
  31. var
  32.   aEdit : TEdit;
  33. begin
  34.    if Sender is TEdit then
  35.    begin
  36.      aEdit := (Sender as TEdit);
  37.      // here we utilize the built-in observer-pattern, namely IFPObserved
  38.      // to notify everybody, who might be attached to observe aEdit
  39.      // TFPObservedOperation = (ooChange,ooFree,ooAddItem,ooDeleteItem,ooCusto
  40.      aEdit.FPONotifyObservers(aEdit,ooChange,pchar(aEdit));
  41.     // we send aEdit in the ASender param, ooChange because aEdit is
  42.     // changing and typecast aEdit.Text string to a pchar, so we can send it
  43.     // in a pointer variable
  44.   end;
  45. end;
  46.  
  47.  
  48. // here we implement the only method in IFPObserver (the listener)
  49. // it can observe mutiple "subjects", by attaching iself to them
  50. procedure TForm1.FPOObservedChanged(ASender: TObject;
  51.   Operation: TFPObservedOperation; Data: Pointer);
  52. var
  53.   aEdit : TEdit;
  54.   s : String;
  55. begin
  56.   // TFPObservedOperation is an enumerated type, thus we can use 'case'
  57.   case Operation of
  58.     ooCustom: ; // a 'TButton' might send us custom operations...
  59.     // because we can handle multiple subjects, we check what's been sent
  60.     ooChange: begin  // to us and from whom -> 'ASender'
  61.                 if ASender is TEdit then
  62.                 begin
  63.                   aEdit := (ASender as TEdit);
  64.                   s := aEdit.Name; // just to see in the watchlist the edit.name
  65.  
  66.                   if aEdit = Edit1 then
  67.                     Caption:= pchar(Data) // <== Gets here but Form.caption is changes to empty and not to the edit text.
  68.                   else if aEdit = Edit2 then
  69.                     Caption:= pchar(Data) // <==  Gets here but Form.caption is changes to empty and not to the edit text.
  70.                 end
  71.               end;
  72.     // we know that, in this case, Edit1 sends us text disguised as a pchar
  73.     ooAddItem: ; // a list might notify us, of an added item etc...
  74.   end;
  75. end;
  76.  
  77. end.
  78.  

cdbc

  • Hero Member
  • *****
  • Posts: 1497
    • http://www.cdbc.dk
Re: Best way to exchange data between a form and a unit
« Reply #18 on: January 14, 2024, 01:40:48 pm »
Hi
Hahaha... I sometimes forget that too  :D
Namely: ".Text"  :-X
So, you change your method to this:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.EditChange(Sender: TObject);
  2. var
  3.   aEdit : TEdit;
  4. begin
  5.    if Sender is TEdit then
  6.    begin
  7.      aEdit := (Sender as TEdit);
  8.      // here we utilize the built-in observer-pattern, namely IFPObserved
  9.      // to notify everybody, who might be attached to observe aEdit
  10.      // TFPObservedOperation = (ooChange,ooFree,ooAddItem,ooDeleteItem,ooCusto
  11.      aEdit.FPONotifyObservers(aEdit,ooChange,pchar(aEdit.Text));//<-HERE
  12.     // we send aEdit in the ASender param, ooChange because aEdit is
  13.     // changing and typecast aEdit.Text string to a pchar, so we can send it
  14.     // in a pointer variable
  15.   end;
  16. end;
Before you sent the whole edit-control... No biggie, ...if you know it  ;D
then you can do:
Code: Pascal  [Select][+][-]
  1. ledit:= TEdit(Data);
"ledit" is just a local TEdit variable, that can act as a temporary placeholder.
The /catch/ is that usually, you cant rely on the receiver/observer to know about LCL-classes.
Now you're on the right track, let me know, if you want more  :)
HTH  8-)
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 2.2.6 up until Jan 2024 from then on it's: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 3.0

cdbc

  • Hero Member
  • *****
  • Posts: 1497
    • http://www.cdbc.dk
Re: Best way to exchange data between a form and a unit
« Reply #19 on: January 14, 2024, 01:50:51 pm »
Hi again...
@Hansvb: Mind you, that at the moment we're just playing with the built-in observer framework in the "TPersistent" and descendants, there's a whole lot more to the story  :D
But it's a good start... Quick question: how do you put a trackbar and a progressbar on a form and connect them via the observer-pattern, so that the progress of the trackbar is shown in real time?!?  %) ...and let the edit show the percentage in text....
Have fun  8)
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 2.2.6 up until Jan 2024 from then on it's: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 3.0

Hansvb

  • Hero Member
  • *****
  • Posts: 687
Re: Best way to exchange data between a form and a unit
« Reply #20 on: January 14, 2024, 06:18:46 pm »
Give me some time for the question. The weekend is almost over and during the week i can only play with Lazarus in the evenings.

Thaddy

  • Hero Member
  • *****
  • Posts: 15516
  • Censorship about opinions does not belong here.
Re: Best way to exchange data between a form and a unit
« Reply #21 on: January 14, 2024, 06:47:18 pm »
Simpler example consisting of four parts: A GUI app, a console app, a unit for the  console, the gui and a unit with the logic:
Code: Pascal  [Select][+][-]
  1. unit logic;
  2. // this unit contains all logic completely decoupled from any user interface
  3. {$mode objfpc}{$H+}
  4. interface
  5.  
  6. function reply(const s:string):string;
  7.  
  8. implementation
  9.  
  10. function reply(const s:string):string;
  11. begin
  12.   result := s;
  13. end;
  14.  
  15. end.
Code: Pascal  [Select][+][-]
  1. program consolelogic;
  2. {$apptype console}{$H+}
  3. uses logic;
  4.  
  5. begin
  6.   writeln(reply('replied'));// called logic unit.
  7. end.

GUI application:
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}{$H+}
  2.  
  3. interface
  4.  
  5. uses
  6.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, logic;
  7.  
  8. type
  9.  
  10.   { TForm1 }
  11.  
  12.   TForm1 = class(TForm)
  13.     Button1: TButton;
  14.     procedure Button1Click(Sender: TObject);
  15.   private
  16.  
  17.   public
  18.  
  19.   end;
  20.  
  21. var
  22.   Form1: TForm1;
  23.  
  24. implementation
  25.  
  26. {$R *.lfm}
  27.  
  28. { TForm1 }
  29.  
  30. procedure TForm1.Button1Click(Sender: TObject);
  31. begin
  32.   Button1.Caption := reply('Test me');//called logic unit
  33. end;
  34.  
  35. end.
This assumes a form with a button with autosize = true.
There is really nothing more to it.
In principle the logic unit does not rely on the LCL at all, although the LCL comes with some auxilliary units that also are not reliant on the GUI stuff.
The logic unit should never contain references to GUI components.

It does not come any simpler than this. More complex examples should simply adhere to this.

Adagio is still that writing simple examples is complex. Requires thought.
But this is rudimentary Mvm/MvvM. If you understand this, you understand the theory.
« Last Edit: January 14, 2024, 07:07:39 pm by Thaddy »
My great hero has found the key to the highway. Rest in peace John Mayall.
Playing: "Broken Wings" in your honour. As well as taking out some mouth organs.

cdbc

  • Hero Member
  • *****
  • Posts: 1497
    • http://www.cdbc.dk
Re: Best way to exchange data between a form and a unit
« Reply #22 on: January 14, 2024, 07:22:50 pm »
Hi
No worries mate :) ...not in a hurry....
Here's a hint for you:
Code: Pascal  [Select][+][-]
  1. type
  2.   PProgressRec = ^TProgressRec;
  3.   TProgressRec = record
  4.     prMin,
  5.     prPos,
  6.     prMax: longint;
  7.     prText: shortstring;
  8.   end
  9.  
One should build on @Thaddy's excellent example and place it in the "logic" unit. Then in your form, put "logic" in the uses.
Remember:
Code: Pascal  [Select][+][-]
  1. var
  2.   lRec: TProgressRec;
  3. begin
  4.   lrec.prMin:= 0;
  5.   ...
  6.   x.FPONotifyObservers(x,ooChange,@lRec);
  7. ...
  8. end;
  9.  
edit: /could/ to /should/
Just a little "Food for Thought"
Regards Benny
« Last Edit: January 14, 2024, 07:31:50 pm by cdbc »
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 2.2.6 up until Jan 2024 from then on it's: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 3.0

jamie

  • Hero Member
  • *****
  • Posts: 6523
Re: Best way to exchange data between a form and a unit
« Reply #23 on: January 14, 2024, 07:35:41 pm »
Quote

One should build on @Thaddy's excellent example and place it in the "logic" unit. Then in your form, put "logic" in the uses.
Remember:

Oh Please, we don't need him gloating.
 :o
The only true wisdom is knowing you know nothing

cdbc

  • Hero Member
  • *****
  • Posts: 1497
    • http://www.cdbc.dk
Re: Best way to exchange data between a form and a unit
« Reply #24 on: January 14, 2024, 08:01:21 pm »
 :D :D :D
 8-)
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 2.2.6 up until Jan 2024 from then on it's: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 3.0

Joanna

  • Hero Member
  • *****
  • Posts: 994
Re: Best way to exchange data between a form and a unit
« Reply #25 on: January 15, 2024, 02:40:58 am »
@hansvp

Code: Pascal  [Select][+][-]
  1.  case Operation of
  2.     ooCustom: ; // a 'TButton' might send us custom operations...
  3.     // because we can handle multiple subjects, we check what's been sent
  4.     ooChange: begin  // to us and from whom -> 'ASender'
  5.                 if ASender is TEdit
  6.                    then with sender as tedit do
  7.                                   begin
  8.                                   s := Name; // just to see in the watchlist the edit.name
  9.                if aEdit = Edit1 then
  10.                     Caption:= pchar(Data) // <== Gets here but Form.caption is changes to empty and not to the edit text.
  11.                   else if aEdit = Edit2 then
  12.                     Caption:= pchar(Data) // <==  Gets here but Form.caption is changes to empty and not to the edit text.
  13.                 end
  14.               end;
  15.     // we know that, in this case, Edit1 sends us text disguised as a pchar
  16.     ooAddItem: ; // a list might notify us, of an added item etc...

It’s possible to simply your code a bit. It can be done without the extra aedit variable using
Code: Pascal  [Select][+][-]
  1. If sender is tedit
  2. Then With sender as tedit do
  3.          Begin
  4.          s := Name; // just to see in the watchlist the edit.name
  5.          Case name of
  6.                  Edit1.name,edit2.name: form1.Caption:= text; //pchar(Data);
  7.                  End; //case
  8.          End;// With sender as tedit do
  9.  
  10.  
« Last Edit: January 15, 2024, 03:39:08 am by Joanna »
✨ 🙋🏻‍♀️ More Pascal enthusiasts are needed on IRC .. https://libera.chat/guides/ IRC.LIBERA.CHAT  Ports [6667 plaintext ] or [6697 secure] channel #fpc  #pascal Please private Message me if you have any questions or need assistance. 💁🏻‍♀️

Hansvb

  • Hero Member
  • *****
  • Posts: 687
Re: Best way to exchange data between a form and a unit
« Reply #26 on: January 15, 2024, 05:55:19 pm »
See the attachment, when you move the trackbar the progress is shown. The calculation of the percentage takes place outside the form.

cdbc

  • Hero Member
  • *****
  • Posts: 1497
    • http://www.cdbc.dk
Re: Best way to exchange data between a form and a unit
« Reply #27 on: January 16, 2024, 02:11:31 am »
Hi
Very nice, indeed  8)
However, we need to shift your focus from mainly visual to more
data/logic -oriented. Broaden your horizon, so to speak  :)
I've made a few quick changes to your form-code, for you to try out...
I'll be back tomorrow with some more.

Have you noticed, that the implementation of IFPOxxx is rather geared towards lists etc... Quite a narrow set of oo-options, don't you think?

Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 2.2.6 up until Jan 2024 from then on it's: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 3.0

egsuh

  • Hero Member
  • *****
  • Posts: 1436
Re: Best way to exchange data between a form and a unit
« Reply #28 on: January 16, 2024, 04:58:22 am »
I believe I'll need Observer model in the future when my project gets larger. For now, I tried to understand observer/mediator model but couldn't pour myself enough to that subject. So for now, I use RTTI controls. I'm not saying observer/mediator model is not necessary. But it seems to make things complex, for now for me.
But current RTTI controls have some limitations. Hope they develop further.

Code: Pascal  [Select][+][-]
  1. unit Model;
  2. interface
  3.  
  4. uses
  5.   Classes, SysUtils;
  6.  
  7. type
  8.   TModel = class(TComponent)
  9.   private
  10.     FText: string;
  11.   public
  12.     function GetText: string;
  13.     procedure SetText(NewText: string);
  14.  
  15.   published
  16.      property Text: string read GetText write SetText;
  17.   end;
  18. ///////////
  19.  
  20. unit FormMain;
  21.  
  22. {$mode objfpc}{$H+}
  23.  
  24. interface
  25.  
  26. uses
  27.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
  28.   Model  ;
  29.  
  30. type
  31.  
  32.   { TForm1 }
  33.  
  34.   TForm1 = class(TForm)
  35.     Button1: TButton;
  36.     TIEdit1: TTIEdit;
  37.     Label1: TLabel;
  38.     procedure FormCreate(Sender: TObject);
  39.   private
  40.     FModel: TModel;
  41.   end;
  42.  
  43. implementation
  44.  
  45. procedure TForm1.FormCreate(Sender: TObject);
  46. begin
  47.     FModel := TModel.Create (nil);
  48.     TIEdit1.LInk.TiObject := FModel;
  49.     TIEdit1.TiPropertyName := 'Text';
  50. end;
  51.  
  52.  

egsuh

  • Hero Member
  • *****
  • Posts: 1436
Re: Best way to exchange data between a form and a unit
« Reply #29 on: January 16, 2024, 06:37:48 am »
I made a simple example project using RTTI controls and MultiPropertyLink.
When I first ran this, there appear error message of 'Undefined property "Link"'. Possibly this error was caused by TPanel control, which does not have property Link.
At first, I was horrified, and aborted the execution of my application. But later I pressed "Ignore this type of error" and continued, then my application works fine.
All the RTTI controls on the TPanel are assigned TIObject by assigning MultiPropertyLink.TIObject.

Mediator/observer model is good but for simple applications RTTI components do the work very good. The reason I write this is to share and listen to more experiences of Lazarus users.

 

TinyPortal © 2005-2018