Recent

Author Topic: more tabsheets woes  (Read 919 times)

Centauri

  • New Member
  • *
  • Posts: 25
more tabsheets woes
« on: March 28, 2020, 09:23:07 am »
Another small demo program to test some ideas, but it doesn't work - not sure why. Demo starts with a page control & one tabsheet which contains a frame with 3 memo boxes. Add button dynamically adds a new tabsheet with frame, and names them in order. To find the next name to use, I itterate through the tabsheet components until it returns nil.  To troubleshoot, I added a message box to display all component names, and found that when the second tabsheet is added, its name (or the new frame name, as well as the memo box names) does not appear on the components list, so when a third tab is to be added it tries to set the name to that of the second tab. Can anyone explain why the added components don't appear on the list?
Code: Pascal  [Select][+][-]
  1. unit CustForm;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ComCtrls, custframe;
  9.  
  10. type
  11.  
  12.   { TFormCust }
  13.  
  14.   TFormCust = class(TForm)
  15.     ButtonTogg: TButton;
  16.     ButtonDelTab: TButton;
  17.     ButtonAddTab: TButton;
  18.     FrameCust1: TFrameCust;
  19.     PageControl1: TPageControl;
  20.     TabCust1: TTabSheet;
  21.     procedure ButtonAddTabClick(Sender: TObject);
  22.   private
  23.  
  24.   public
  25.  
  26.   end;
  27.  
  28. var
  29.   FormCust: TFormCust;
  30.  
  31. implementation
  32.  
  33. {$R *.lfm}
  34.  
  35. { TFormCust }
  36.  
  37. procedure TFormCust.ButtonAddTabClick(Sender: TObject);
  38. var
  39.   sheetNo:Integer;
  40.   tmpFrame:TFrameCust;
  41.   tmpTab:TTabSheet;
  42.   testTab:TTabSheet;
  43.   i:Integer;
  44.   List:String;
  45. begin
  46.   If PageControl1.PageCount = 6 then
  47.   begin
  48.     MessageDlg('Maximum 6 pages allowed',mtError,[mbOk],0);
  49.     Exit
  50.   end;
  51.   For sheetNo:=1 to 6 do
  52.   begin
  53.     testTab:= FindComponent('TabCust'+IntToStr(sheetNo)) as TTabSheet;
  54.     if testTab=nil then break;
  55.   end;
  56.   ShowMessage(IntToStr(sheetNo));
  57.   tmpTab:=TTabSheet.Create(PageControl1);
  58.   tmpTab.PageControl:=PageControl1;
  59.   tmpFrame:=TFramecust.Create(tmpTab);
  60.   tmpFrame.Align:=alClient;
  61.   tmpFrame.BorderStyle:=bsNone;
  62.   tmpFrame.Parent:=tmpTab;
  63.   tmpFrame.Show;
  64.   tmpTab.Name:='TabCust'+IntToStr(sheetNo);
  65.   tmpFrame.Name:='FrameCust'+IntToStr(sheetNo);
  66.   for i := 0 to ComponentCount-1 do
  67.     list:=list+#10#13+Components[i].Name;
  68.   ShowMessage(list);
  69. end;
  70.  
  71. end.

howardpc

  • Hero Member
  • *****
  • Posts: 4144
Re: more tabsheets woes
« Reply #1 on: March 28, 2020, 11:09:16 am »
It's easier to give you a working example than explain the errors in your example.
The names of the frame memos are not shown because they have no names.

Bart

  • Hero Member
  • *****
  • Posts: 5290
    • Bart en Mariska's Webstek
Re: more tabsheets woes
« Reply #2 on: March 28, 2020, 11:28:54 am »
I used such an apporach in the past as well, but when you allow the user to delete pages as well, the names won't be simple 1, 2, 3 anymore.
So, you will have to do housekeeping to keep track of all names in use...

First question: why do you need the name of the component?
If you do need it: must it be a "pretty" name?

I decided that I wanted to have name, but only for debugging purposes, so they need not be pretty.
So I simply added a counter (type UInt64, which is probably a bit over the top) and stick that to the name.

As long as my users don't create Max(UInt64) tabsheets at runtime, I will be safe.

Bart

wp

  • Hero Member
  • *****
  • Posts: 11923
Re: more tabsheets woes
« Reply #3 on: March 28, 2020, 11:47:44 am »
You create the TabSheets with owner PageControl1 (tmpTab:=TTabSheet.Create(PageControl1)). This means that the new tabsheet is added to the Components of the PageControl, not to the form. So, when you call "FindComponent()" you query the Components of the form, not those of the PageControl. Therefore, your code does not find the tabsheet created.

Things get compilicated because you already have "TabCust1" already created at designtime. This one has the form as owner, and thus exists in the Components of the form, not in the pagecontrol.

Overall solution: Create the Tabsheets with "self" as parameter, or use "PageControl1.FindComponents" and "PageControl1.Components[]" and "PageControl1.ComponentCount" in your code, instead of the same identifiers without the PageControl1 prefix.:

Code: Pascal  [Select][+][-]
  1. tmpTab := TTabSheet.Create(self);
  2.  
  3. //or
  4.  
  5.   For sheetNo:=1 to 6 do
  6.   begin
  7.     testTab:= PageControl1.FindComponent('TabCust'+IntToStr(sheetNo)) as TTabSheet;
  8.     if testTab=nil then break;
  9.   end;
  10.   ...
  11.   list := '';
  12.   for i := 0 to PageControl1.ComponentCount-1 do
  13.     list:=list+#10#13+PageControl1.Components[i].Name;    
  14.  

Even better solution: Don't use the "Name" property at all. The "Name" is needed only by the Object Inspector, and you by-pass it when you create components at runtime. Taking care of duplicate component names is quite difficult, and there will always be cases which you forgot. Leave the "Name" of the run-time created components empty. To identify the new tabsheets you should use of the array property "Pages[]" provided by the PageControl.

P.S.
I am not sure about the "for" loop. Generally it is recommended to use the loop variable (sheetNo, in your case) only inside the loop. It seems to work here, but probably your code would be more safe if you use a loop counter different from the sheet index found:

Code: Pascal  [Select][+][-]
  1. var
  2.   i, sheetNo: Integer;
  3. ...
  4.   sheetNo := -1;
  5.   For i:=1 to 6 do
  6.   begin
  7.     testTab:= PageControl1.FindComponent('TabCust'+IntToStr(i)) as TTabSheet;
  8.     if testTab=nil then begin
  9.       sheetNo := i;
  10.       break;
  11.     end;
  12.   end;
  13.  
« Last Edit: March 28, 2020, 11:50:29 am by wp »

Centauri

  • New Member
  • *
  • Posts: 25
Re: more tabsheets woes
« Reply #4 on: March 28, 2020, 12:29:37 pm »
Thankyou for the example code howardpc. Whilst playing with & comparing it to mine, wp replied & confirmed some things I was finding. I had assumed that "components" would give me all components, including components of components, but is only restricted to immediate components of the referenced object. It seems strange that the design time tabsheet is owned by the form and not the page control as wp pointed out, which explains why I could get two tabs named the same.
Following on from that, does that mean the frames are components of the tabsheets, and the memos are components of the frames, and can be found by referencing the owners? (haven't tried that yet)
Bart, only reason for the names was to make it easier to find & reference the correct memo boxes - the final application will have around 45 memos which will be sequentially  read from & to a dbf table.
By the time I finish this project, I will either be a proficient programmer or insane - not sure which at this stage :)

lucamar

  • Hero Member
  • *****
  • Posts: 4219
Re: more tabsheets woes
« Reply #5 on: March 28, 2020, 12:52:43 pm »
Following on from that, does that mean the frames are components of the tabsheets, and the memos are components of the frames, and can be found by referencing the owners?

I think you're falling in the (rather common) error of mistaking the Owner, which is the component responsible for the "life" and eventual "death" of a control, with the Parent, which is the control inside which another control is shown.

For almost all controls created at design-time the Owner will be the form itself, so if you mix design- and run-time created controls, you should do the the same (for the run-time created) that the IDE does: make of the form their Owner and set the Parent as needed.

Of course, if all the tabsheets and their contents are created at run-time this doesn't matter so much and you can make whatever control you want the Owner. The problem then is that to look for a control in the list of all components you have to start from the form's Components and for each descend to its Components until you find the one you're looking for.

Anyway, a simple way of ascertaining how the tree of controls/components is build by the IDE (including frames, etc) is to build a small project with the needed components, add a function that traverses the full tree and show the result (component class + name, owner class + name, paret class + name) in a memo, message dialog, or whatever, or save it to a text file.

At leasst that's what I do whenever a doubt arises :)

HTH
« Last Edit: March 28, 2020, 12:57:48 pm by lucamar »
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

 

TinyPortal © 2005-2018