Recent

Author Topic: Scope anomolies  (Read 1615 times)

roger T

  • New Member
  • *
  • Posts: 23
Scope anomolies
« on: October 16, 2025, 04:57:14 am »
I have the following code and would like to know

1) Why CAN'T I access variables coded in the outer procedure from the inner procedure (They must be made global) and
2) Why CAN I access the inner procedure from outside the enclosing procedure

Code: Pascal  [Select][+][-]
  1.  
  2. unit unit1;
  3. {$mode objfpc}{$H+}
  4. interface
  5. uses
  6. type
  7. var
  8.  
  9. //Used for SaveFileAs - when building the form
  10.         wnd                             :       TForm;
  11.         lbl                             :       TLabel;
  12.         edt                             :       TEdit;
  13.         rad                             :       TRadioGroup;
  14.         btn                             :       TButton;
  15.         EventPtr        : TNotifyEvent;
  16.  
  17. implementation
  18.  
  19. procedure SaveFileAs();
  20. {
  21. var
  22.         wnd                             :       TForm;
  23.         lbl                             :       TLabel;
  24.         edt                             :       TEdit;
  25.         rad                             :       TRadioGroup;
  26.         btn                             :       TButton;
  27.         EventPtr        : TNotifyEvent;
  28. }
  29.  
  30. //Process button Click
  31.         procedure buttonClick(Sender : TObject);
  32.         begin
  33.  
  34.                 assignFile(outFile, edt.Text);
  35.                 rewrite(outFile);
  36.                 Y := 0;
  37.                 repeat
  38.                         begin
  39.                                         case rad.ItemIndex of
  40.                                                 0 : Write(outFile, FocusedSynEdit.Lines[Y] + char($0A));
  41.                                                 1 : Write(outFile, FocusedSynEdit.Lines[Y] + Char($0D) + char($0A));
  42.                                                 2 : Write(outFile, FocusedSynEdit.Lines[Y] + char($0A));
  43.                                         end; // END CASE
  44.                                         inc(Y);
  45.                                 end
  46.                         until Y > FocusedSynEdit.Lines.Count;
  47.                 close(outfile);
  48.  
  49.                 Sender := (Sender as TButton).Parent;                           // Don't know why this works as SENDER is crap
  50.                 (Sender as TForm).close;
  51.  
  52.         end;
  53.  
  54. begin
  55.   wnd                                                   := TForm.Create(Form1);                                 // Owner - Memory Management
  56.   wnd.SetBounds         (150, 150, 500, 150);
  57.   wnd.Caption                   := 'Save As';
  58.  
  59.         lbl                                                     := TLabel.Create(wnd);                                  // Owner - Memory Management
  60.         lbl.parent                      := wnd;                                                                                                 // Visualisation - or wont appear on te form
  61.         lbl.SetBounds           (25,27,50,15);
  62.         lbl.Caption                     := 'Save As';
  63.  
  64.         edt                                                     := TEdit.Create(wnd);                                           // Owner - Memory Management
  65.         edt.parent                      := wnd;                                                                                                 // Visualisation - or wont appear on te form
  66.         edt.SetBounds           (75,25,400,15);
  67.         edt.Text                                := files[Form1.Edit_PageControl.ActivePageIndex].Name;
  68.  
  69.         rad                                                     := TRadioGroup.Create(wnd);                     // Owner - Memory Management
  70.         rad.parent                      := wnd;                                                                                                 // Visualisation - or wont appear on te form
  71.         rad.SetBounds           (75,60,100,75);
  72.                 rad.Items.Add('Linux');
  73.                 rad.Items.Add('Windows');
  74.                 rad.Items.Add('MacOS');
  75.         {$IFDEF LINUX}
  76.                 rad.ItemIndex   := 0;
  77.         {$ENDIF}
  78.         {$IFDEF WINDOWS}
  79.                 rad.ItemIndex   := 1;
  80.         {$ENDIF}
  81.         {$IFDEF DARWIN}
  82.                 rad.ItemIndex   := 2;
  83.         {$ENDIF}
  84.  
  85.   btn                                                   := TButton.Create(wnd);                                 // Owner - Memory Management
  86.         btn.parent                      := wnd;                                                                                                 // Visualisation - or wont appear on te form
  87.   btn.Caption                   := 'Okay';
  88.   btn.SetBounds         (415, 75, 60, 24);
  89.                 TMethod(EventPtr).Code := @buttonClick;                         // Assign the procedure's code pointer
  90.     TMethod(EventPtr).Data := nil;                                                      // No object instance (standalone procedure)
  91.     btn.OnClick := EventPtr;
  92.  
  93.         X := 0;
  94.         Str1 := files[Form1.Edit_PageControl.ActivePageIndex].Name;
  95.  
  96.   wnd.ShowModal;
  97.  
  98. end;
  99.  
  100.  

creaothceann

  • Full Member
  • ***
  • Posts: 196
Re: Scope anomolies
« Reply #1 on: October 16, 2025, 07:34:13 am »
Don't see why it should fail...

Code: Pascal  [Select][+][-]
  1. AssignFile(OutFile, Edt.Text);
  2. ReWrite(OutFile);
  3. ...

Are you sure the FocusedSynEdit.Lines doesn't have a method "SaveToFile" that handles line endings automatically?

Also, https://en.wikipedia.org/wiki/Newline#Representation
(Also, most text editors should handle all line endings automatically)


Code: Pascal  [Select][+][-]
  1. Sender := (Sender as TButton).Parent;  // don't know why this works as SENDER is crap
  2. (Sender as TForm).Close;

Pretty sure this would fail at runtime. An object can't be a button and a form at the same time.


Also, formatting...

Code: Pascal  [Select][+][-]
  1. unit unit1; {$mode objfpc} {$H+}
  2.  
  3.  
  4. interface
  5.  
  6.  
  7. var
  8.         // used for SaveFileAs - when building the form
  9.         Wnd      : TForm;
  10.         Lbl      : TLabel;
  11.         Edt      : TEdit;
  12.         Rad      : TRadioGroup;
  13.         Btn      : TButton;
  14.         EventPtr : TNotifyEvent;
  15.         OutFile  : TextFile;
  16.  
  17.  
  18. implementation
  19.  
  20.  
  21. procedure SaveFileAs;
  22. var
  23.         {Wnd      : TForm;
  24.         Lbl      : TLabel;
  25.         Edt      : TEdit;
  26.         Rad      : TRadioGroup;
  27.         Btn      : TButton;
  28.         EventPtr : TNotifyEvent;
  29.         OutFile  : TextFile;      }
  30.         y        : integer;
  31.  
  32.  
  33.         procedure ButtonClick(Sender : TObject);  // process button Click
  34.         begin
  35.                 AssignFile(OutFile, Edt.Text);
  36.                 ReWrite(OutFile);
  37.                 for y := 0 to (FocusedSynEdit.Lines.Count - 1) do begin
  38.                         case Rad.ItemIndex of
  39.                                 0:  Write(OutFile, FocusedSynEdit.Lines[Y] + char($0A)            );
  40.                                 1:  Write(OutFile, FocusedSynEdit.Lines[Y] + Char($0D) + char($0A));
  41.                                 2:  Write(OutFile, FocusedSynEdit.Lines[Y] + char($0A)            );
  42.                         end;
  43.                 end;
  44.                 CloseFile(OutFile);
  45.                 Sender := (Sender as TButton).Parent;  // don't know why this works as SENDER is crap
  46.                 (Sender as TForm).Close;
  47.         end;
  48.  
  49. begin
  50.         Wnd := TForm.Create(Form1);         // ownership
  51.         Wnd.SetBounds(150, 150, 500, 150);
  52.         Wnd.Caption := 'Save As';
  53.  
  54.         Lbl        := TLabel.Create(Wnd);   // ownership
  55.         Lbl.Parent := Wnd;                  // visualisation
  56.         Lbl.SetBounds(25,27,50,15);
  57.         Lbl.Caption := 'Save As';
  58.  
  59.         Edt := TEdit.Create(Wnd);           // ownership
  60.         Edt.Parent := Wnd;                  // visualisation
  61.         Edt.SetBounds(75,25,400,15);
  62.         Edt.Text := files[Form1.Edit_PageControl.ActivePageIndex].Name;
  63.  
  64.         Rad := TRadioGroup.Create(Wnd);     // ownership
  65.         Rad.Parent := Wnd;                  // visualisation
  66.         Rad.SetBounds (75,60,100,75);
  67.         Rad.Items.Add('Linux'  );
  68.         Rad.Items.Add('Windows');
  69.         Rad.Items.Add('MacOS'  );
  70.         {$ifdef Linux}    Rad.ItemIndex := 0;  {$endif}
  71.         {$ifdef Windows}  Rad.ItemIndex := 1;  {$endif}
  72.         {$ifdef Darwin}   Rad.ItemIndex := 2;  {$endif}
  73.  
  74.         Btn         := TButton.Create(Wnd);  // ownership
  75.         Btn.parent  := Wnd;                  // visualisation
  76.         Btn.Caption := 'Okay';
  77.         Btn.SetBounds(415, 75, 60, 24);
  78.         TMethod(EventPtr).Code := @ButtonClick;  // assign the procedure's code pointer
  79.         TMethod(EventPtr).Data := NIL;           // no object instance (standalone procedure)
  80.         Btn.OnClick := EventPtr;
  81.  
  82.         X    := 0;
  83.         Str1 := Files[Form1.Edit_PageControl.ActivePageIndex].Name;
  84.  
  85.         Wnd.ShowModal;
  86. end;
« Last Edit: October 16, 2025, 07:41:51 am by creaothceann »
And don't start an argument, I am right.

Paolo

  • Hero Member
  • *****
  • Posts: 639
Re: Scope anomolies
« Reply #2 on: October 16, 2025, 08:12:55 am »
I don't know what are you doing, but this row seems strange to me
Code: Pascal  [Select][+][-]
  1. Sender:=(sender as tbutton).parent;
  2.  
Here you are changing the sender, you should do this
Code: Pascal  [Select][+][-]
  1. Tform(tbutton(sender).parent).close;
  2.  

roger T

  • New Member
  • *
  • Posts: 23
Re: Scope anomolies
« Reply #3 on: October 16, 2025, 08:45:43 am »
Don't see why it should fail...
and yet it does..

I don't know what are you doing, but this row seems strange to me
and yet it works fine

Are you sure the FocusedSynEdit.Lines doesn't have a method "SaveToFile" that handles line endings automatically?
I am sure it does have that method. But you cant change eol characters with that can you ?

Pretty sure this would fail at runtime. An object can't be a button and a form at the same time.
Nope, it works fine

Any comments on my actual problems ?



Thausand

  • Sr. Member
  • ****
  • Posts: 389
Re: Scope anomolies
« Reply #4 on: October 16, 2025, 08:56:13 am »
and yet it works fine
That code work is not make right.

Quote
I am sure it does have that method. But you cant change eol characters with that can you ?
https://www.freepascal.org/docs-html/rtl/classes/tstrings.linebreak.html

bytebites

  • Hero Member
  • *****
  • Posts: 751
Re: Scope anomolies
« Reply #5 on: October 16, 2025, 09:19:31 am »
This works fine:

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, ExtCtrls,StdCtrls,SysUtils, Forms, Controls, Graphics, Dialogs;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Button1:TButton;
  16.     Label1:TLabel;
  17.     focusynedit:TMemo;
  18.     RadioGroup1:TRadioGroup;
  19.     procedure Button1Click(Sender:TObject);
  20.   private
  21.  
  22.   public
  23.  
  24.   end;
  25.  
  26. var
  27.   Form1: TForm1;
  28.         // used for SaveFileAs - when building the form
  29.         //Wnd      : TForm;
  30.         //Lbl      : TLabel;
  31.         //Edt      : TEdit;
  32.         //Rad      : TRadioGroup;
  33.         //Btn      : TButton;
  34.         //EventPtr : TNotifyEvent;
  35.         //OutFile  : TextFile;
  36.  
  37.  
  38. implementation
  39.  
  40. {$R *.lfm}
  41.  
  42.  
  43. procedure SaveFileAs;
  44. var
  45.         Wnd      : TForm;
  46.         Lbl      : TLabel;
  47.         Edt      : TEdit;
  48.         Rad      : TRadioGroup;
  49.         Btn      : TButton;
  50.         EventPtr : TNotifyEvent;
  51.         OutFile  : TextFile;
  52.  
  53.  
  54.         procedure ButtonClick(Sender : TObject);  // process button Click
  55.         var y        : integer;
  56.         begin
  57.                 //AssignFile(OutFile, Edt.Text);
  58.                 //ReWrite(OutFile);
  59.                 //for y := 0 to (FocusedSynEdit.Lines.Count - 1) do begin
  60.                 //        case Rad.ItemIndex of
  61.                 //                0:  Write(OutFile, FocusedSynEdit.Lines[Y] + char($0A)            );
  62.                 //                1:  Write(OutFile, FocusedSynEdit.Lines[Y] + Char($0D) + char($0A));
  63.                 //                2:  Write(OutFile, FocusedSynEdit.Lines[Y] + char($0A)            );
  64.                 //        end;
  65.                 //end;
  66.                 //CloseFile(OutFile);
  67.                 Sender := (Sender as TButton).Parent;  // don't know why this works as SENDER is crap
  68.                 (Sender as TForm).Close;
  69.         end;
  70.  
  71. begin
  72.         Wnd := TForm.Create(Form1);         // ownership
  73.         Wnd.SetBounds(150, 150, 500, 150);
  74.         Wnd.Caption := 'Save As';
  75.  
  76.         Lbl        := TLabel.Create(Wnd);   // ownership
  77.         Lbl.Parent := Wnd;                  // visualisation
  78.         Lbl.SetBounds(25,27,50,15);
  79.         Lbl.Caption := 'Save As';
  80.  
  81.         Edt := TEdit.Create(Wnd);           // ownership
  82.         Edt.Parent := Wnd;                  // visualisation
  83.         Edt.SetBounds(75,25,400,15);
  84.         //Edt.Text := files[Form1.Edit_PageControl.ActivePageIndex].Name;
  85.  
  86.         Rad := TRadioGroup.Create(Wnd);     // ownership
  87.         Rad.Parent := Wnd;                  // visualisation
  88.         Rad.SetBounds (75,60,100,75);
  89.         Rad.Items.Add('Linux'  );
  90.         Rad.Items.Add('Windows');
  91.         Rad.Items.Add('MacOS'  );
  92.         {$ifdef Linux}    Rad.ItemIndex := 0;  {$endif}
  93.         {$ifdef Windows}  Rad.ItemIndex := 1;  {$endif}
  94.         {$ifdef Darwin}   Rad.ItemIndex := 2;  {$endif}
  95.  
  96.         Btn         := TButton.Create(Wnd);  // ownership
  97.         Btn.parent  := Wnd;                  // visualisation
  98.         Btn.Caption := 'Okay';
  99.         btn.tag:=11;
  100.         Btn.SetBounds(415, 75, 60, 24);
  101.         TMethod(EventPtr).Code := @ButtonClick;  // assign the procedure's code pointer
  102.         TMethod(EventPtr).Data := NIL;           // no object instance (standalone procedure)
  103.         Btn.OnClick := EventPtr;
  104.  
  105.         //X    := 0;
  106.         //Str1 := Files[Form1.Edit_PageControl.ActivePageIndex].Name;
  107.  
  108.         Wnd.ShowModal;
  109. end;
  110.  
  111. { TForm1 }
  112.  
  113. procedure TForm1.Button1Click(Sender:TObject);
  114. begin
  115.    SaveFileAs
  116. end;
  117.  
  118. end.
  119.  

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11792
  • Debugger - SynEdit - and more
    • wiki
Re: Scope anomolies
« Reply #6 on: October 16, 2025, 09:40:45 am »
Quote
Code: Pascal  [Select][+][-]
  1.                TMethod(EventPtr).Code := @buttonClick;                         // Assign the procedure's code pointer
  2.     TMethod(EventPtr).Data := nil;
  3.     Btn.OnClick := EventPtr;              

So you probably noticed that
Code: Pascal  [Select][+][-]
  1.  Btn.OnClick := @buttonClick;
does not work?

"buttonClick" is a nested procedure. That means it has a secretly different calling "signature". (It takes different parameters that those listed in the declaration).


"buttonClick" expects
Code: Pascal  [Select][+][-]
  1.   procedure TSomeObject.ButtonClick(Sender : TObject);

Which is different from
Code: Pascal  [Select][+][-]
  1.   procedure ButtonClick(Sender : TObject);  // plain procedure, not a method in a class

The method actually gets called with a hidden "self" param.
Code: Pascal  [Select][+][-]
  1.   procedure TSomeObject.ButtonClick((* Self: TSomeObject; *) Sender : TObject);
The plain procedure does not get this. So the argument "Sender" is not the first argument if it is a method. If TButton calls it via OnClick, then it passes "self".

This is why the type has
Code: Pascal  [Select][+][-]
  1.  TNotifyEvent = procedure (Sender : TObject) of object;  // "of object = needs a hidden self"
And NO,NO,NO => you cannot just declare "self" as a normal parameter. Because those events are stored in a "TMethod". But a variable for normal plain procedures generates a different variable, that can never store a TMethod.
(Well, if you use the assign to TMethod.Code, then you can declare self. But depending on target, you may have to change some details, so you may get issues when compiling for different targets).

Anyway, your ButtonClick is a nested procedure
And that is different again, from both of the above.

You noticed you can't access the local variables of "SaveFileAs".
That is because nested procedures in reality look like this:
Code: Pascal  [Select][+][-]
  1.   procedure ButtonClick((* parentfp: pointer; *) Sender : TObject); // maybe diff order, parentfp may be last, not sure
Where "parentfp" is set to point to the memory containing the local variables of the outer proc.

If you would call ButtonClick directly from SaveAsFile, then that parentfp would get the correct pointer.
But when TButton calls it (via your hack) then that pointer isn't there. And TButton would not have the correct value to set it for the call. (Besides that there is no way to declare that in a way that it would be possible to set it like that).





In short.

TNotifyEvent (TButton.OnClick) must be a method of a class. It can't be a plain procedure, and it can't be a nested procedure.

roger T

  • New Member
  • *
  • Posts: 23
Re: Scope anomolies
« Reply #7 on: October 16, 2025, 10:29:37 am »
Thanks for the replies guys but it does confirm my opinion. Declare everything globally and don't try and do anything out of the ordinary.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11792
  • Debugger - SynEdit - and more
    • wiki
Re: Scope anomolies
« Reply #8 on: October 16, 2025, 10:41:12 am »
Thanks for the replies guys but it does confirm my opinion. Declare everything globally and don't try and do anything out of the ordinary.

Well, you can do scoping but different.

In your case, you would likely somewhere have

Code: Pascal  [Select][+][-]
  1. TForm1 = class(TForm)
  2.   Button1: TButton;
  3.   // ...
  4.   // Lazarus want to create Button1OnClick here....
  5. end;

You can however in this unit, or in a different unit declare another class

Code: Pascal  [Select][+][-]
  1. interface
  2. type
  3. TMyClass = class // no base needed
  4. private
  5.         Wnd      : TForm;
  6.         Lbl      : TLabel;
  7.         Edt      : TEdit;
  8.         Rad      : TRadioGroup;
  9.         Btn      : TButton;
  10.         EventPtr : TNotifyEvent;
  11.         OutFile  : TextFile;
  12.  public
  13.    procedure SaveToFile;
  14.    procedure DoButtonClicked(Sender: TObject);  // this can be used for any Button.OnClick
  15. end;
  16. implementation
  17.    procedure TMyClass.SaveToFile;
  18.    begin
  19.    end;
  20.  
  21.    procedure TMyClass.DoButtonClicked(Sender: TObject);
  22.    begin
  23.    end;
  24.  

Of course you must then create that class
Code: Pascal  [Select][+][-]
  1. procedure ExecuteSaveToFile;
  2. var MySaver: TMyClass ;
  3. begin
  4.   MySaver := TMyClass.create;
  5.   MySaver.SaveToFile;
  6.   MySaver.Destoy;
  7. end;


Also, you can declare the class in the implementation, hiding it further.
Then you just have  a normal "procedure ExecuteSaveToFile;" (which needs to be declared in the interface, if you want to use it from other units). And that procedure then creates the class and makes the call.


roger T

  • New Member
  • *
  • Posts: 23
Re: Scope anomolies
« Reply #9 on: October 16, 2025, 11:16:39 am »
Lots of ways to skin a cat but not what I was asking.
Aren't I supposed to be able access variables defined in an enclosing procedure

MarkMLl

  • Hero Member
  • *****
  • Posts: 8504
Re: Scope anomolies
« Reply #10 on: October 16, 2025, 11:39:52 am »
Leaving aside everything else that has been written, what is /this/ supposed to mean?

I have the following code (...)

Code: Pascal  [Select][+][-]
  1.  
  2. unit unit1;
  3. {$mode objfpc}{$H+}
  4. interface
  5. uses
  6. type
  7. var
  8.  
  9. //Used for SaveFileAs - when building the form
  10.  
  11. (...)
  12.  
  13.  
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11792
  • Debugger - SynEdit - and more
    • wiki
Re: Scope anomolies
« Reply #11 on: October 16, 2025, 12:08:29 pm »
Lots of ways to skin a cat but not what I was asking.
Aren't I supposed to be able access variables defined in an enclosing procedure

Yes, if you call the nested proc correctly, then yes.

1)
call it from the enclosing procedure (without function pointer, or reference).

2)
use the correct function pointer
Code: Pascal  [Select][+][-]
  1. type TMyNestedProc = procedure (Sender: TObject) is nested;
wich will not require you to do any TMethod type cast

Any TMethod type cast will break the procedure




TButton.OnClick can not be given a nested procedure.

So if your question is: Can you make TButton.OnClick call an enclosed procedure => then the answer is NO

roger T

  • New Member
  • *
  • Posts: 23
Re: Scope anomolies
« Reply #12 on: October 16, 2025, 12:09:43 pm »
It's an attempt to abstract insignificance rather than upload 3,000 lines of code and a dozen forms

roger T

  • New Member
  • *
  • Posts: 23
Re: Scope anomolies
« Reply #13 on: October 16, 2025, 12:13:33 pm »
So if your question is: Can you make TButton.OnClick call an enclosed procedure => then the answer is NO

an yet, when I click the button the procedure is performed???

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11792
  • Debugger - SynEdit - and more
    • wiki
Re: Scope anomolies
« Reply #14 on: October 16, 2025, 12:17:11 pm »
The LCL (including TButton) is written so in requires (must have) a class (and call a method of a class). That part can not be abstracted away.

You can write a plain procedure (none method) that has the self pointer (and as long as you match the signature correctly / which then is your risk to take) you can do the TMethod hack.
But, a nested (enclosed) procedure will never work that way. It must have the correct value for "ParentFp" => otherwise it looses access to the enclosing proc's locals.


There are now (may be in fpc trunk) anonymous functions. I don't know what signature they have. So I don't know if you can make it and pass one of them to TButton. No idea.

 

TinyPortal © 2005-2018