Recent

Author Topic: Many Similar Components And Messy Case Statements  (Read 7786 times)

Bazzao

  • Full Member
  • ***
  • Posts: 178
  • Pies are squared.
Many Similar Components And Messy Case Statements
« on: July 03, 2017, 01:16:34 am »
Hi,

I am writing a small application that just has multiple pages ( 8 ) under a Page Control. Each page has its own memo. The idea is to automatically load and save each memo respectively at start or end. The program works fine, but the coding is getting a bit messy, as I have to address each component individually, via a case statement.

Is there a way I can put the various components in a for loop and do away with these lengthy case statements.

The Istr() is my own function.

eg, what I want to achieve is coding similar to the following:

  for I:=1 to 8 do
    WriteString('TabLabel'+Istr(I,0),TabSheet.Caption);
   
  for I:=1 to 8 do
    Memo.Lines.SaveToFile('Memo'+IStr(I,0)+'.txt');

  for I:=1 to 8 do
    TabSheet.Caption:=ReadString('TabLabel'+Istr(I,0),'Memo'+IStr(I,0)+'.txt');

  for I:=1 to 8 do
    begin
      Memo.Lines.Clear;
      LoadFile:='Memo'+IStr(I,0)+'.txt');
      if FileExists(LoadFile) then Memo.Lines.LoadFromFile(LoadFile);
    end;
   
   
Here is my existing code, with the messy case statements:

procedure TForm1.StoreFormState;
var I:integer;
begin
  with XMLConfig1 do
    begin
    WriteInteger('NormalLeft', Left);
    WriteInteger('NormalTop', Top);
    WriteInteger('NormalWidth', Width);
    WriteInteger('NormalHeight', Height);

    WriteInteger('RestoredLeft', RestoredLeft);
    WriteInteger('RestoredTop', RestoredTop);
    WriteInteger('RestoredWidth', RestoredWidth);
    WriteInteger('RestoredHeight', RestoredHeight);

    WriteInteger('WindowState', Integer(WindowState));
    for I:=1 to 8 do
      case I of
        1: WriteString('TabLabel'+Istr(I,0),TabSheet1.Caption);
        2: WriteString('TabLabel'+Istr(I,0),TabSheet2.Caption);
        3: WriteString('TabLabel'+Istr(I,0),TabSheet3.Caption);
        4: WriteString('TabLabel'+Istr(I,0),TabSheet4.Caption);
        5: WriteString('TabLabel'+Istr(I,0),TabSheet5.Caption);
        6: WriteString('TabLabel'+Istr(I,0),TabSheet6.Caption);
        7: WriteString('TabLabel'+Istr(I,0),TabSheet7.Caption);
        8: WriteString('TabLabel'+Istr(I,0),TabSheet8.Caption);
      end;
  end;
end;

procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
var I:integer;
begin
  StoreFormState;
  for I:=1 to 8 do
    begin
      case I of
        1: Memo1.Lines.SaveToFile('Memo1.txt');
        2: Memo2.Lines.SaveToFile('Memo2.txt');
        3: Memo3.Lines.SaveToFile('Memo3.txt');
        4: Memo4.Lines.SaveToFile('Memo4.txt');
        5: Memo5.Lines.SaveToFile('Memo5.txt');
        6: Memo6.Lines.SaveToFile('Memo6.txt');
        7: Memo7.Lines.SaveToFile('Memo7.txt');
        8: Memo8.Lines.SaveToFile('Memo8.txt');
      end;
    end;
  CloseAction:=caFree;
end;

procedure TForm1.RestoreFormState;
var I:integer; LastWindowState:TWindowState;
begin
  with XMLConfig1 do begin
    LastWindowState := TWindowState(ReadInteger('WindowState', Integer(WindowState)));

    if LastWindowState = wsMaximized then begin
      WindowState := wsNormal;
      BoundsRect := Bounds(
        ReadInteger('RestoredLeft', RestoredLeft),
        ReadInteger('RestoredTop', RestoredTop),
        ReadInteger('RestoredWidth', RestoredWidth),
        ReadInteger('RestoredHeight', RestoredHeight));
      WindowState := wsMaximized;
    end else begin
      WindowState := wsNormal;
      BoundsRect := Bounds(
        ReadInteger('NormalLeft', Left),
        ReadInteger('NormalTop', Top),
        ReadInteger('NormalWidth', Width),
        ReadInteger('NormalHeight', Height));
    end;
    for I:=1 to 8 do
      case I of
        1: TabSheet1.Caption:=ReadString('TabLabel'+Istr(I,0),'Memo1');
        2: TabSheet2.Caption:=ReadString('TabLabel'+Istr(I,0),'Memo2');
        3: TabSheet3.Caption:=ReadString('TabLabel'+Istr(I,0),'Memo3');
        4: TabSheet4.Caption:=ReadString('TabLabel'+Istr(I,0),'Memo4');
        5: TabSheet5.Caption:=ReadString('TabLabel'+Istr(I,0),'Memo5');
        6: TabSheet6.Caption:=ReadString('TabLabel'+Istr(I,0),'Memo6');
        7: TabSheet7.Caption:=ReadString('TabLabel'+Istr(I,0),'Memo7');
        8: TabSheet8.Caption:=ReadString('TabLabel'+Istr(I,0),'Memo8');
      end;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
var I:integer;
begin
  RestoreFormState;
  Memo1.Lines.Clear;
  Memo2.Lines.Clear;
  Memo3.Lines.Clear;
  Memo4.Lines.Clear;
  Memo5.Lines.Clear;
  Memo6.Lines.Clear;
  Memo7.Lines.Clear;
  Memo8.Lines.Clear;
  for I:=1 to 8 do
    begin
      case I of
        1: if FileExists('Memo1.txt') then Memo1.Lines.LoadFromFile('Memo1.txt');
        2: if FileExists('Memo2.txt') then Memo2.Lines.LoadFromFile('Memo2.txt');
        3: if FileExists('Memo3.txt') then Memo3.Lines.LoadFromFile('Memo3.txt');
        4: if FileExists('Memo4.txt') then Memo4.Lines.LoadFromFile('Memo4.txt');
        5: if FileExists('Memo5.txt') then Memo5.Lines.LoadFromFile('Memo5.txt');
        6: if FileExists('Memo6.txt') then Memo6.Lines.LoadFromFile('Memo6.txt');
        7: if FileExists('Memo7.txt') then Memo7.Lines.LoadFromFile('Memo7.txt');
        8: if FileExists('Memo8.txt') then Memo8.Lines.LoadFromFile('Memo8.txt');
      end;
    end;
  PageControl1.ActivePage:=TabSheet1;
  PageControl1.TabIndex:=0;
end;

Thanks

Bazza

Lazarus 2.0.10; FPC 3.2.0; SVN Revision 63526; x86_64-win64-win32/win64
Windows 10.

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: Many Similar Components And Messy Case Statements
« Reply #1 on: July 03, 2017, 02:32:04 am »
sure there are 2 main paths you can take.
1) iterate through the page control's pages, find the memo and use the index (or tag property) of the tabsheet as the unique portion of the filename.
Code: Pascal  [Select][+][-]
  1. procedure SaveEverything(const aControl:TPageControl);
  2. var
  3.   vCntr , vMemoCntr: Integer;
  4. begin
  5.   For vCntr := 0 to aControl.PageCount -1 do begin
  6.     For vMemoCntr := 0 to aControl.pages[vCntr].ControlCount -1 do
  7.       if aControl.Pages[vCntr].Controls[vMemoCntr] is TMemo then begin
  8.         //save the memo to 'memo'+inttostr(vCntr+1)+'.Txt' filename.
  9.         // save the tablabel of aControl.Pages[vCntr]
  10.       end;
  11.   end;
  12. end;
  13.  
2) write your own custom ttabsheet control that autocreates all the necessary controls for you and add there the code to save and load the data to/from the settings file.

The second one does not have the second loop to find the memo inside the tabsheet's list of controls, making things a bit faster which might or might not have any weight.
If you want an example of the second option post an .lfm file with a single tabsheet and all its components inside it.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

wp

  • Hero Member
  • *****
  • Posts: 13513
Re: Many Similar Components And Messy Case Statements
« Reply #2 on: July 03, 2017, 02:45:41 am »
I would declare an array of 8 TMemos and assign the memo instances to the array elements in the FormCreate event. Then you easily can iterate through all memos. And you can access the inidividual sheets of a PageControl by using its Sheets[index] propery (Note that the index starts at 0).

Code: Pascal  [Select][+][-]
  1. type
  2.   TForm1 = class(TForm)
  3.   ..
  4.   private
  5.     FMemos: array[1..8] of TMemo;
  6.   ...
  7.  
  8. procedure TForm1.FormCreate(Sender: TObject);
  9. begin
  10.   FMemos[1] := Memo1;
  11.   FMemos[2] := Memo2;
  12.   FMemos[3] := Memo3;
  13.   FMemos[4] := Memo4;
  14.   FMemos[5] := Memo5;
  15.   FMemos[6] := Memo6;
  16.   FMemos[7] := Memo7;
  17.   FMemos[8] := Memo8;
  18.  
  19.   for I:=1 to 8 do
  20.     if FileExists('Memo' + IntToStr(i) + '.txt' then
  21.       FMemos[i].Lines.LoadFromFile('Memo' + IntToStr(i) + '.txt');
  22.  
  23.   PageControl1.ActivePage:=TabSheet1;
  24.   PageControl1.TabIndex:=0;
  25. end;
  26.  
  27. procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
  28. var I:integer;
  29. begin
  30.   StoreFormState;
  31.   for I:=1 to 8 do
  32.     FMemos[i].Lines.SaveToFile('Memo' + IntToStr(i) + '.txt');
  33.   CloseAction:=caFree;
  34. end;
  35.  
  36. procedure TForm1.StoreFormState;
  37. var I:integer;
  38. begin
  39.   with XMLConfig1 do
  40.   begin
  41.     WriteInteger('NormalLeft', Left);
  42.     WriteInteger('NormalTop', Top);
  43.     WriteInteger('NormalWidth', Width);
  44.     WriteInteger('NormalHeight', Height);
  45.  
  46.     WriteInteger('RestoredLeft', RestoredLeft);
  47.     WriteInteger('RestoredTop', RestoredTop);
  48.     WriteInteger('RestoredWidth', RestoredWidth);
  49.     WriteInteger('RestoredHeight', RestoredHeight);
  50.  
  51.     WriteInteger('WindowState', Integer(WindowState));
  52.     for I:=1 to 8 do
  53.       WriteString('TabLabel' + Istr(I, 0), PageControl1.Sheets[I-1].Caption);
  54.   end;
  55. end;
  56. ... etc.
  57.  

Please note that the index in the Sheets of the PageControl starts at 0. I declared the Memo array with start index 1 to be inline with the Memos' Names. But maybe it would be a better idea to begin this index also at 0. This way you would never have to think: At which index did this damn array start again?
 
« Last Edit: July 03, 2017, 02:48:39 am by wp »

Bazzao

  • Full Member
  • ***
  • Posts: 178
  • Pies are squared.
Re: Many Similar Components And Messy Case Statements
« Reply #3 on: July 03, 2017, 04:43:11 am »
Hi,

Thank you both for your replies.

taaz's method is a bit daunting, as there are many components. But I have that solution on file now.


The second method (from wp)
Quote
  FMemos[1] := Memo1;
  FMemos[2] := Memo2;

I grasped straight away, and modified a small section of code to see the result. And it worked perfectly.

So I'm now off to modify the rest of the code.

But I will try taaz's answer somewhere else. Between the posting and the replies I added even more components, and 2 more pages ( 8 memos were not enough ).

Thanks taaz& wp.



Bazza

Lazarus 2.0.10; FPC 3.2.0; SVN Revision 63526; x86_64-win64-win32/win64
Windows 10.

Thaddy

  • Hero Member
  • *****
  • Posts: 19150
  • Glad to be alive.
Re: Many Similar Components And Messy Case Statements
« Reply #4 on: July 03, 2017, 09:39:57 am »
Code: Pascal  [Select][+][-]
  1. var
  2.   FMemos:Array[1..8] of TMemo =  (Memo1,Memo2,Memo3, Memo4, Memo5, Memo6,Memo7,Memo8);
objects are fine constructs. You can even initialize them with constructors.

wp

  • Hero Member
  • *****
  • Posts: 13513
Re: Many Similar Components And Messy Case Statements
« Reply #5 on: July 03, 2017, 09:41:37 am »
I added even more components, and 2 more pages ( 8 memos were not enough ).
To avoid having to change too many places you should declare a constant (NUM_MEMOS, or similar) in my code:

Code: Pascal  [Select][+][-]
  1. const
  2.   NUM_MEMOS = 8;
  3.  
  4. type
  5.   TForm1 = class(TForm)
  6.     FMemos: array[1..NUM_MEMOS] of TMemo;
  7. ...
  8.  
  9. procedure TForm1.FormCreate(Sender: TObject);
  10. begin
  11.   ...
  12.   for i := 1 to NUM_MEMOS do ...

Thaddy

  • Hero Member
  • *****
  • Posts: 19150
  • Glad to be alive.
Re: Many Similar Components And Messy Case Statements
« Reply #6 on: July 03, 2017, 11:51:13 am »
If you use trunk, consider this:
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, generics.collections;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Memo1: TMemo;
  16.     Memo2: TMemo;
  17.     Memo3: TMemo;
  18.     Memo4: TMemo;
  19.     procedure FormCreate(Sender: TObject);
  20.     procedure FormDestroy(Sender: TObject);
  21.   private
  22.     FMemos:specialize TObjectList<TMemo>;
  23.   public
  24.   end;
  25.  
  26. var
  27.   Form1: TForm1;
  28.  
  29. implementation
  30.  
  31. {$R *.lfm}
  32.  
  33. { TForm1 }
  34.  
  35. procedure TForm1.FormCreate(Sender: TObject);
  36. var
  37.   Memo:Tmemo;
  38. begin
  39.   FMemos := specialize TObjectList<TMemo>.Create;
  40.   FMemos.AddRange([memo1,memo2,memo3,memo4]);
  41.   for Memo in Fmemos do
  42.     if FileExists(memo.name+'.txt') then
  43.       Memo.Lines.LoadFromFile(Memo.Name+'.txt');
  44. end;
  45.  
  46. procedure TForm1.FormDestroy(Sender: TObject);
  47. var memo:TMemo;
  48. begin
  49.   for Memo in FMemos do
  50.     memo.lines.Savetofile(memo.name+'.txt');
  51.   FMemos.free;
  52. end;
  53.  
  54. end.

Basically you can loop, add memo's, delete them, do anything, by just using for..in. All operations for TMemo are valid. All operations become one-liners.
« Last Edit: July 03, 2017, 11:59:19 am by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

wp

  • Hero Member
  • *****
  • Posts: 13513
Re: Many Similar Components And Messy Case Statements
« Reply #7 on: July 03, 2017, 12:02:08 pm »
Code: Pascal  [Select][+][-]
  1. var
  2.   FMemos:Array[1..8] of TMemo =  (Memo1,Memo2,Memo3, Memo4, Memo5, Memo6,Memo7,Memo8);
Nice!

Bazzao

  • Full Member
  • ***
  • Posts: 178
  • Pies are squared.
Re: Many Similar Components And Messy Case Statements
« Reply #8 on: July 05, 2017, 08:37:28 am »
Code: Pascal  [Select][+][-]
  1. var
  2.   FMemos:Array[1..8] of TMemo =  (Memo1,Memo2,Memo3, Memo4, Memo5, Memo6,Memo7,Memo8);

I get an error message stating
Syntax error, ";" expected but "=" found.

So it is expecting the statement end after TMemo.

B
Bazza

Lazarus 2.0.10; FPC 3.2.0; SVN Revision 63526; x86_64-win64-win32/win64
Windows 10.

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: Many Similar Components And Messy Case Statements
« Reply #9 on: July 05, 2017, 09:01:29 am »
Code: Pascal  [Select][+][-]
  1. var
  2.   FMemos:Array[1..8] of TMemo =  (Memo1,Memo2,Memo3, Memo4, Memo5, Memo6,Memo7,Memo8);

I get an error message stating
Syntax error, ";" expected but "=" found.

So it is expecting the statement end after TMemo.

B
global variables can not be initialized this way make sure the declaration is inside a method of the form that hosts the page control and memos.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

Thaddy

  • Hero Member
  • *****
  • Posts: 19150
  • Glad to be alive.
Re: Many Similar Components And Messy Case Statements
« Reply #10 on: July 05, 2017, 09:02:15 am »
You *should* get an error Error: (3039) typed constants of classes or interfaces are not allowed, but I was mistaken and indeed it does not work (yet).
objects are fine constructs. You can even initialize them with constructors.

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12851
  • FPC developer.
Re: Many Similar Components And Messy Case Statements
« Reply #11 on: July 05, 2017, 10:14:14 am »
The values of memo (and the instance of the form class they are in) are only determined runtime, so can never be in an array determined at compiletime.

That is not to say that something to ease this can't be done, and it is actually quite simple. This has come up multiple times (partially because the VB system has some way to make arrays of controls automatically)

Code: Pascal  [Select][+][-]
  1. type       TControlDynArray      = Array of TControl;
  2.  
  3. procedure loadarray (root:tcomponent;var x : TControlDynArray;basename:string;i1,i2:integer);
  4. var l,i : integer;
  5. begin
  6.  l:=i2-i1+1;
  7.  setlength(x,l);
  8.  for i := i1 to i2 do
  9.    begin
  10.      x[i-i1]:=tcontrol(root.FindComponent(basename+inttostr(i)));
  11.    end;
  12. end;
  13.  

Then have the following as field in your form class,

Code: Pascal  [Select][+][-]
  1. memoarray : array of Tmemo;

and in the oncreate put something like

Code: Pascal  [Select][+][-]
  1. loadarray(self,tcontrolarray(memo1),1,10);

to load  memo1..memo10 into memoarray[0] to memoarray[9]. The loadcontrol procedure can be recycled and put in some utility unit for any sequence to load arrays of other controls in any form.

« Last Edit: July 05, 2017, 10:17:51 am by marcov »

J-G

  • Hero Member
  • *****
  • Posts: 992
Re: Many Similar Components And Messy Case Statements
« Reply #12 on: July 05, 2017, 12:14:10 pm »
You *should* get an error Error: (3039) typed constants of classes or interfaces are not allowed, but I was mistaken and indeed it does not work (yet).

Would it not work if the array was declared as a 'const'
ie.
Code: Pascal  [Select][+][-]
  1. const
  2.  FMemos:Array[1..8] of TMemo =(Memo1,Memo2,Memo3,Memo4,Memo5,Memo6,Memo7,Memo8);

... rather than a var ?

Aren't constants declared like this at compile time are still modifiable at run-time?
FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

Thaddy

  • Hero Member
  • *****
  • Posts: 19150
  • Glad to be alive.
Re: Many Similar Components And Messy Case Statements
« Reply #13 on: July 05, 2017, 12:24:10 pm »
The values of memo (and the instance of the form class they are in) are only determined runtime, so can never be in an array determined at compiletime.
Marco, I don't agree with that. The pointers are known, the type is known and the space is known to the compiler at that point. So it *should* be possible.
But it doesn't work (yet) as I expected.
objects are fine constructs. You can even initialize them with constructors.

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12851
  • FPC developer.
Re: Many Similar Components And Messy Case Statements
« Reply #14 on: July 05, 2017, 04:00:36 pm »
The values of memo (and the instance of the form class they are in) are only determined runtime, so can never be in an array determined at compiletime.
Marco, I don't agree with that. The pointers are known

No they are not. Form1 is not allocated.

 

TinyPortal © 2005-2018