Recent

Author Topic: How do you move a Procedure to a separate file  (Read 1573 times)

Aruna

  • Hero Member
  • *****
  • Posts: 513
Re: How do you move a Procedure to a separate file
« Reply #15 on: September 16, 2024, 04:33:39 am »
From what you described, I guess the code you have does not currently compile.  In spite of that, I suggest you attach it to your next post.  The attachment will be more precise than a description and, a solution, if there is one, that is proven to work can be attached back by me or someone else.
Yes, the code in the screenshot you're referring to doesn't compile. I reverted all the changes I was experimenting with, and now it compiles successfully. I've attached a zip file with the source code that compiles properly.

To help clarify things:
  • We have two forms: TForm1 and TForm2.
  • TForm1 contains the main menu, while TForm2 holds the option checkboxes.
  • When the "About" option is selected and 'Status Bar Options' selected from the menu in TForm1, TForm2 is displayed, showing the available checkboxes for configuration.
The window menu item option has two options
  • Split Window Vertically
  • Split Window Horizontally
These work as expected and splits the widow vertically and horizontally. My brick wall is trying to find out how to correctly identify which TSynEdit is active and then passing that to the File/Open/Save options.

In the Edit Menu item you get three options. I have no idea how to implement this. I am doing lot's of searching on Google.
  • Cut
  • Copy
  • Paste
In the Search Menu item you get three options. The Search and Replace I am trying to implement, no idea how yet. The Goto line option works.
  • Search
  • Replace
  • Goto Line

@440bx Thank you for your time, and please let me know if you come across a solution. I will keep on fiddling with the code.

EDIT: The main reason I'm eager to get this working is that, while Geany Editor is great, I believe we can create an even lighter, more efficient editor. I would really love to use an editor 'I' built from the ground up for my future projects.
« Last Edit: September 16, 2024, 04:50:51 am by Aruna »

440bx

  • Hero Member
  • *****
  • Posts: 4731
Re: How do you move a Procedure to a separate file
« Reply #16 on: September 16, 2024, 05:22:09 am »
@440bx Thank you for your time, and please let me know if you come across a solution.
You're most welcome but, just so you know, I know next to _nothing_ about the LCL and forms and all the things related to them but, I still try to help when I can.  All the programming I do is strictly procedural (no OOP whatsoever and directly to the O/S API and, I avoid using the FPC rtl as much as I possibly can.

As far as the circular unit references you mentioned earlier, I noticed that you have "unit2, unit3" in the "unit1" uses clause.  That will likely cause you problems because it doesn't look like there is anything in the interface of "unit1" that needs anything from unit2 and/or unit3.  Therefore the references to unit2 and unit3 should be in the "implementation" section _not_ in the "interface" section. 

As a general rule, you want the uses clause in the interface section to use as few units as possible (keeps the interface clean and uncluttered).  Generally, it is the "implementation''s "uses" clause that has a larger number of units because the implementation is usually where the facilities provided by the units are actually used.

The fact that you are placing units in the "interface"'s "uses" clause instead of the "implementation"'s may be the reason you are getting circular references errors from the compiler (you won't get those errors if you can relocate the units to the "implementation"'s "uses" clause.)

HTH.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

wp

  • Hero Member
  • *****
  • Posts: 12459
Re: How do you move a Procedure to a separate file
« Reply #17 on: September 16, 2024, 10:11:00 am »
My two cents:

For a long time, I've been trying to have only those units in the interface section which are abolutely needed, and to put all others into the implementation section. However, this may lead to careless design of the functionality in the units and their dependencies because circular unit references are hidden. And circular unit references are bad, they should be removed rather than hidden since when a project becomes more and more complex it will be very difficult to fix this.

Example from your editor:
You have the main form (unit1 - why don't you call it "uMainForm" or so?). And you have another unit (unit2 - I would call it "uSettingsForm") with checkboxes to control the appearance of the statusbar in the main form. This configuration is bad because unit2 needs to have unit1 in its uses clause. In my opinion, secondary forms should never use the main unit because this leads to a circular reference (because the main unit unavoidably needs to use the secondary forms, too, in order to be able to open them). Putting unit2 into the implementation section makes the project compile again, but it only hides this issue without solving the real problem.

For a correct solution ("correct" in my opinion) I would add another unit only containing settings values (and other declarations), no visual controls:
Code: Pascal  [Select][+][-]
  1. unit uSettings;
  2.  
  3. type
  4.   TSettings = record
  5.     ShowFullPathInCaption: Boolean;
  6.     ShowDate: Boolean;
  7.     ShowClock: Boolean;
  8.     EnableBackups: Boolean;
  9.   end;
  10.  
  11. var
  12.   Settings: TSettings = (
  13.     ShowFullPathInCaption: true;
  14.     ShowDate: true;
  15.     ShowClick: true;
  16.     EnableBackups: true
  17.   );
  18.  
  19. implementation
  20.  
  21. end.

Now both unit1 and unit2 will use this new unit: unit2 is the visual control to modify these settings, and unit1 will read the settings and update the statusbar. It is no longer needed that Form2 knows Form1.

There is one problem, though: How is Form1 notified that Form2 has changed the settings? For this, it is often helpful to introduce an event fired by Form2 and handled by Form1:
Code: Pascal  [Select][+][-]
  1. unit unit2;
  2. uses
  3.   uSettings;
  4. type
  5.   TForm2 = class(TForm)
  6.     procedure FormClose(Sender: TObject);
  7.   private
  8.     FOnSettingsChanged: TNotifyEvent;
  9.     ...
  10.   public
  11.     ...
  12.     property OnSettingsChanged: TNotifyEvent;
  13.   end;
  14.  
  15. procedure TForm2.FormClose(Sender: TObject);
  16. begin
  17.   // Copy the changed settings into the Settings variable.
  18.   Settings.ShowFullPathInCaption := Checkbox1.Checked;
  19.   // etc.
  20.  
  21.   // Now the event OnSettingsChanged is fired. The main form has a handler for this event (in its own unit) and this way
  22.   // it is notified of the changes without Form2 having to know Form1 explictely.
  23.   if Assigned(FOnSettingsChanged) then FOnSettingsChanged(self);
  24. end;
  25.  
Code: Pascal  [Select][+][-]
  1. unit unit1;
  2.  
  3. interface
  4.  
  5. uses
  6.   uSettings, unit2;
  7.  
  8. ...
  9.  
  10. // This event is fired when Form2 is closed
  11. procedure TForm1.SettingsChanged(Sender: TObject);
  12. begin
  13.   // Read the Settings elements and show/hide the statusbar panels correspondingly
  14. end;
  15.  
  16. // FORM2 SHOW
  17. procedure TForm1.MenuItem3Click(Sender: TObject);
  18. begin
  19.   Form2.OnSettingsChanged := @SettingsChanged;  //Prepares Form1 so that it is notified when the settings have changed in Form2
  20.   Form2.Show;
  21. end;
  22.  

 

440bx

  • Hero Member
  • *****
  • Posts: 4731
Re: How do you move a Procedure to a separate file
« Reply #18 on: September 16, 2024, 10:42:16 am »
I just want to add my support to the advice @wp gave you.

In particular, one thing that is rather easy to fix is the naming of your units.  I didn't say anything in my previous post because I wanted to focus on the circular dependencies problem but, as @wp hinted, you really should give better names to those units.

It is also a given that circular dependencies are very undesirable and placing most units in the implementation section's uses clause can hide them, which is not good.

Personally, I still think most units belong in the implementation's uses clause and not in the interface's.  Basically, good design will prevent circular dependencies.  Maybe a prophylactic step would be to temporarily place the units in the interface's uses section to have the compiler pinpoint the circular dependencies and once they've been removed, move them (back?) to the implementation's uses clause (keeps the interface clean and enables the compiler to flag a missing dependency if something is later added in the interface.)

HTH.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

MarkMLl

  • Hero Member
  • *****
  • Posts: 8014
Re: How do you move a Procedure to a separate file
« Reply #19 on: September 16, 2024, 10:54:01 am »
Personally, I still think most units belong in the implementation's uses clause and not in the interface's.

Specifically: if something is to be published in a unit then define it in the interface part, with other units referencing it in their implementation part.

That will mean that on occasion a (e.g.) class definition will have to contain a field representing a TThread rather than the derivative being used for a specific task. However you can tighten that up- make code more concise and reduce the risk of errors- by adding a class helper in the implementation which accesses the TThread field and converts it into a TDataReadingThread or whatever.

MarkMLl
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

Thaddy

  • Hero Member
  • *****
  • Posts: 16158
  • Censorship about opinions does not belong here.
Re: How do you move a Procedure to a separate file
« Reply #20 on: September 16, 2024, 11:19:37 am »
To add to what wp wrote, an alternative is to provide fpobserver/fpobserved to the forms. That is built-in functionality that can be used across units. TForm has that available by default, just needs a little simple implementation. Examples are on the forum. ( some of them wrong, since all components derive from Tpersistent and that has already IFPObserved )
https://www.freepascal.org/docs-html/rtl/classes/ifpobserver.html
The main form implements an observer for another form that implements observed.
This functionality is often overlooked and in this scenario may be an alternative option.
If I smell bad code it usually is bad code and that includes my own code.

alpine

  • Hero Member
  • *****
  • Posts: 1297
Re: How do you move a Procedure to a separate file
« Reply #21 on: September 16, 2024, 11:32:56 am »
@Aruna
What I notice in your unit3.pas is that you named the parameter of GotoLine as SynEdit, which is also a identifier of a unit in the uses clause. Despite that probably won't stop the compiler from successfully compiling it, the parameter will obscure the unit name. Avoid using already defined names for local variables and procedure parameters as it can lead to big confusions. 

Also, most of your components have meaningless names as they are auto named by the IDE. My advice is to rename them all using some convention, e.g. menu items: miCut, miCopy, miPaste, buttons: btnOpen, btnSave, dialogs: dlgOpenFile, dlgSaveFileAs, etc. It will make things a lot easier when you (or somebody else) is trying to read the source.

"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Aruna

  • Hero Member
  • *****
  • Posts: 513
Re: How do you move a Procedure to a separate file
« Reply #22 on: September 17, 2024, 01:34:38 am »
@Aruna
What I notice in your unit3.pas is that you named the parameter of GotoLine as SynEdit, which is also a identifier of a unit in the uses clause. Despite that probably won't stop the compiler from successfully compiling it, the parameter will obscure the unit name. Avoid using already defined names for local variables and procedure parameters as it can lead to big confusions. 
After trying mulitle times unsuccesfuuly I asked chatGPT and that is what I was advised to do so that is why I passed it in as a parameter. I will try to find a different way with your help.

Also, most of your components have meaningless names as they are auto named by the IDE. My advice is to rename them all using some convention, e.g. menu items: miCut, miCopy, miPaste, buttons: btnOpen, btnSave, dialogs: dlgOpenFile, dlgSaveFileAs, etc. It will make things a lot easier when you (or somebody else) is trying to read the source.
Hi @alpine is what I have done in the screenshot what you wanted? I can't figure out a way to rename the statusbar panels. There is no name property? Can we give it a name property? If so how?

Aruna

  • Hero Member
  • *****
  • Posts: 513
Re: How do you move a Procedure to a separate file
« Reply #23 on: September 17, 2024, 02:01:52 am »
My two cents:

For a long time, I've been trying to have only those units in the interface section which are abolutely needed, and to put all others into the implementation section. However, this may lead to careless design of the functionality in the units and their dependencies because circular unit references are hidden. And circular unit references are bad, they should be removed rather than hidden since when a project becomes more and more complex it will be very difficult to fix this.
I am still very new to all this like I said I did not know better ( I do now @wp)

Example from your editor:
You have the main form (unit1 - why don't you call it "uMainForm" or so?). And you have another unit (unit2 - I would call it "uSettingsForm") with checkboxes to control the appearance of the statusbar in the main form. This configuration is bad because unit2 needs to have unit1 in its uses clause. In my opinion, secondary forms should never use the main unit because this leads to a circular reference (because the main unit unavoidably needs to use the secondary forms, too, in order to be able to open them). Putting unit2 into the implementation section makes the project compile again, but it only hides this issue without solving the real problem.
I renamed everything the way suggested and now I have a huge problem ide is complaining saying:
Code: Pascal  [Select][+][-]
  1. stbOptions.pas(1,16) Error: Illegal unit name: EditorForm (expecting STBOPTIONS)
but that is ok I am learning. Will just take a while for me to go through and fix again :-)

For a correct solution ("correct" in my opinion) I would add another unit only containing settings values (and other declarations), no visual controls:
Code: Pascal  [Select][+][-]
  1. unit uSettings;
  2.  
  3. type
  4.   TSettings = record
  5.     ShowFullPathInCaption: Boolean;
  6.     ShowDate: Boolean;
  7.     ShowClock: Boolean;
  8.     EnableBackups: Boolean;
  9.   end;
  10.  
  11. var
  12.   Settings: TSettings = (
  13.     ShowFullPathInCaption: true;
  14.     ShowDate: true;
  15.     ShowClick: true;
  16.     EnableBackups: true
  17.   );
  18.  
  19. implementation
  20.  
  21. end.

Now both unit1 and unit2 will use this new unit: unit2 is the visual control to modify these settings, and unit1 will read the settings and update the statusbar. It is no longer needed that Form2 knows Form1.

There is one problem, though: How is Form1 notified that Form2 has changed the settings? For this, it is often helpful to introduce an event fired by Form2 and handled by Form1:
Code: Pascal  [Select][+][-]
  1. unit unit2;
  2. uses
  3.   uSettings;
  4. type
  5.   TForm2 = class(TForm)
  6.     procedure FormClose(Sender: TObject);
  7.   private
  8.     FOnSettingsChanged: TNotifyEvent;
  9.     ...
  10.   public
  11.     ...
  12.     property OnSettingsChanged: TNotifyEvent;
  13.   end;
  14.  
  15. procedure TForm2.FormClose(Sender: TObject);
  16. begin
  17.   // Copy the changed settings into the Settings variable.
  18.   Settings.ShowFullPathInCaption := Checkbox1.Checked;
  19.   // etc.
  20.  
  21.   // Now the event OnSettingsChanged is fired. The main form has a handler for this event (in its own unit) and this way
  22.   // it is notified of the changes without Form2 having to know Form1 explictely.
  23.   if Assigned(FOnSettingsChanged) then FOnSettingsChanged(self);
  24. end;
  25.  
Code: Pascal  [Select][+][-]
  1. unit unit1;
  2.  
  3. interface
  4.  
  5. uses
  6.   uSettings, unit2;
  7.  
  8. ...
  9.  
  10. // This event is fired when Form2 is closed
  11. procedure TForm1.SettingsChanged(Sender: TObject);
  12. begin
  13.   // Read the Settings elements and show/hide the statusbar panels correspondingly
  14. end;
  15.  
  16. // FORM2 SHOW
  17. procedure TForm1.MenuItem3Click(Sender: TObject);
  18. begin
  19.   Form2.OnSettingsChanged := @SettingsChanged;  //Prepares Form1 so that it is notified when the settings have changed in Form2
  20.   Form2.Show;
  21. end;
  22.  

 
Thank you very much @wp. I think I am starting to understand what you have done.

Aruna

  • Hero Member
  • *****
  • Posts: 513
Re: How do you move a Procedure to a separate file
« Reply #24 on: September 17, 2024, 04:50:10 am »
My two cents:

For a long time, I've been trying to have only those units in the interface section which are abolutely needed, and to put all others into the implementation section. However, this may lead to careless design of the functionality in the units and their dependencies because circular unit references are hidden. And circular unit references are bad, they should be removed rather than hidden since when a project becomes more and more complex it will be very difficult to fix this.
So @wp, if I have two forms. Each form has one button that when clicked changes the caption of the button in the other form when. I have this working. But after reading what you had said many times I am wondering if what I am doing here is 'hiding' the problem rather than removing it?  It works fine, but is this the correct way to do this? Please advice.

First Form:
Code: Pascal  [Select][+][-]
  1. implementation
  2. uses unit2;
  3.  
  4. {$R *.lfm}
  5.  
  6. { TForm1 }
  7.  
  8. procedure TForm1.Button1Click(Sender: TObject);
  9. begin
  10.   Form2.Button1.Caption:='Caption Set From Form1';
  11.   Form2.Show;
  12. end;                        

Second Form:
Code: Pascal  [Select][+][-]
  1. implementation
  2. uses unit1;
  3.  
  4. {$R *.lfm}
  5.  
  6. { TForm2 }
  7.  
  8. procedure TForm2.Button1Click(Sender: TObject);
  9. begin
  10.   Form1.Button1.Caption:='Caption Set From Form2';
  11. end;                

Aruna

  • Hero Member
  • *****
  • Posts: 513
Re: How do you move a Procedure to a separate file
« Reply #25 on: September 17, 2024, 04:59:45 am »
The fact that you are placing units in the "interface"'s "uses" clause instead of the "implementation"'s may be the reason you are getting circular references errors from the compiler (you won't get those errors if you can relocate the units to the "implementation"'s "uses" clause.)
You were right, thank you very much. I am beginning to understand how to go about passing stuff between forms :-)

MarkMLl

  • Hero Member
  • *****
  • Posts: 8014
Re: How do you move a Procedure to a separate file
« Reply #26 on: September 17, 2024, 12:40:14 pm »
If I could make one observation here:

You were right, thank you very much. I am beginning to understand how to go about passing stuff between forms :-)

From the language's POV, the important thing is that you are trying to access procedures/functions (and potentially types etc.) across units. A form is merely an instance of a specific type/class, which is accessed using a public variable in the unit in which it is implemented.

MarkMLl
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

 

TinyPortal © 2005-2018