0

Two days ago I gave an accepted answer for this question (that is what bothering me the most):

  newtabsheet:=ttabsheet.Create(PageControl1);
  NewTabSheet.PageControl := PageControl1;
  newtabsheet.Caption:='tab1';
  i1:=tabs.Count;
  tabs.Add(newtabsheet);

I have analysed this code the best I can, and these are my results.

Line 1: I create a ttabsheet with pagecontrol1 as the parent/owner (based on the constructor below).

constructor TTabSheet.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Align := alClient;
  ControlStyle := ControlStyle + [csAcceptsControls, csNoDesignVisible,
    csParentBackground, csPannable];
  Visible := False;
  FTabVisible := True;
  FHighlighted := False;
end;

Then I stored a reference to it in the variable Newtabsheet (this remarque is based on an answer @David Heffernan gave to one of my questions).

Line 2: I used the reference to assign pagecontrol1 to the property .pagecontrol.

And this is the code of the setting method:

procedure TTabSheet.SetPageControl(APageControl: TPageControl);
begin
  if FPageControl <> APageControl then
  begin
    if FPageControl <> nil then
      FPageControl.RemovePage(Self);
    Parent := APageControl;
    if APageControl <> nil then
      APageControl.InsertPage(Self);
  end;
end;

Based on this I think (maybe wrong) it is comparing the existing parent to the new passed parameter, if there is a different then if the parent<>nil removes this newtabsheet from the previous pagecontrol (visual affects) and assign it to the new parent/owner, then if the new parent<>nil insert it in the new pagecontrol(visual affects).

So this line is unnecessary (maybe wrong), because I already did that in the first line.

Line 3: I use the reference to assign the caption of the tabsheet with 'tab1'.

Line 5: I use the add method to store a reference to the tabsheet in the tabs list tabs:TList<ttabsheet>; a generic.

Here is where things start to fly over my head.

I think that the parent/owner is tabs, but the pagecontrol1 is still showing the tab (based on the accepted answer to this question changing the parent visually removes the tabsheet from pagecontrol1), but it does not.

Now this could be wrong, but again if it is just a reference then why when I delete the tabsheet from pagecontrol1 by doing PageControl1.ActivePage.free the tabs.count remains constant.

And if I delete the tabsheet from tabs then the tabsheet on the pagecontrol1 is not deleted (removed visually).

From this question I understood that generics becomes the parent/owner and that you do not need to worry about freeing tabsheet from pagecontrol1, because tabs is the parent and you only need to free it from tabs.

My question: What is happening in this code and why I'm facing this behavior?

Clarification

What is driving the question is when I delete the ttabsheet in the pagecontrol why it is not raising an error when I try to use the reference to it in the list are they two different objects.

Community
  • 1
  • 1
Nasreddine Galfout
  • 2,550
  • 2
  • 18
  • 36
  • The generic list knows nothing about page controls. It doesn't interfere with parent or owner relationships. Not sure what the question really is. So many excerpts of code. – David Heffernan May 06 '17 at 12:59
  • @DavidHeffernan my question is why when I delete the tabsheet from the list it is not deleted from the pagecontrol1 and vice versa?. – Nasreddine Galfout May 06 '17 at 15:01
  • Why should it? The `tabs` list is just a parallel reference to the tabsheet. The owner (= responsible for lifetime management) is the `PageControl`. – Tom Brunberg May 06 '17 at 15:06
  • `Owner`is reponsible of freeing an object when not needed anymore. `Parent` provides a surface for visual controls to render themselves. `Owner` and `Parent` can be the same but doesn't have to be. In case of the `PageControl` (which is a Windows control) the `PageControl` property is overruling the `Parent`property, therefore changing the `Parent` of a `TTabSheet` doesn't affect its visibility. You refer to another question (moving listitems between LB1 and LB2). That is a completely different issue. The question is about Firemonkey and about listboxes, not Microsoft PageControls. – Tom Brunberg May 06 '17 at 15:13
  • @TomBrunberg could you explain the other way around, I mean when I delete the tab from the pagecontrol why it is not deleted from the list. or at least gives some error when using the reference. – Nasreddine Galfout May 06 '17 at 15:14
  • @TomBrunberg the other question is about firmonkey but still the idea about parent relationships is the same. – Nasreddine Galfout May 06 '17 at 15:16
  • As David already said, the list knows nothing about the page control and vv. If you have to variables, A and B holding references to one and same object, and you remove the reference from A, you can still use B to access the object. – Tom Brunberg May 06 '17 at 15:19
  • But, if you **destroy** the one and only object using reference A (or B), it is unusable from both A and B. – Tom Brunberg May 06 '17 at 15:22
  • @tom If I use free isn't that destroying the ttabsheet. – Nasreddine Galfout May 06 '17 at 15:24
  • Yes, that is covered by the "destroying". But I don't see any calls to Free in the codes above? – Tom Brunberg May 06 '17 at 15:26
  • @tom `PageControl1.ActivePage.free` read carefully it is written above, after the line 5 paragraph. – Nasreddine Galfout May 06 '17 at 15:29
  • Found it in the text. Well I said "it is unusable". Once you have destroyed an object nothing guarantees that you can use it further. In most cases you will get an error if you try, but under some circumstances you may be able to e.g. read some properties, because the objects data structure is still residing in memory. You need to read up on memory management, objects memory model and remember that there are differencies between Vcl and Fmx and the various platforms. – Tom Brunberg May 06 '17 at 15:45
  • @tom will do. Is just I thought that if I use it again I will get an error but that is not the case here. – Nasreddine Galfout May 06 '17 at 15:49
  • *...by doing PageControl1.ActivePage.free the tabs.count remains constant.* I once more want to emphasize, the list and the pagecontrol have **no knowledge** of each other, IOW freeing a tabsheet has no effect on the list, and especially not on the lists count. – Tom Brunberg May 06 '17 at 15:49

1 Answers1

4

The Owner is responsible for memory management. Assigning the TPageControl as the Owner of the TTabSheet means the TPageControl will destroy the TTabSheet when the TPageControl is destroyed. The Owner and ownee contain pointers to each other so they can notify each other of important events related to memory management.

The Parent is responsible for window management and visual presentation. Assigning the TPageControl as the Parent of the TTabSheet means the TTabSheet's window is a child of the TPageControl's window, and will be displayed inside the client area of the TPageControl's window. The Parent and child contain pointers to each other so they can notify each other of important events related to window management.

The Owner and Parent are two different things. They can be the same object, but they don't have to be (case in point - components created at design-time are always owned by the TForm, TFrame, or TDataModule that is being designed, but can be children of container components, like TPanel, etc).

The TList is responsible for nothing. It is just a dynamic array of arbitrary values, which in this case happen to be TTabSheet pointers. Nothing more. There is no Owner/Parent relationship between the TList and TTabSheet at all.

When the TTabSheet is destroyed, there are relational links between itself and its TPageControl. The TTabSheet removes itself from its TPageControl's management. It is no longer owned by the TPageControl, and is no longer a child of the TPageControl. The two objects clear their pointers to each other.

There is no relational link between the TTabSheet and the TList at all. The TTabSheet has no concept that the TList even exists. When the TTabSheet is added to and removed from the TList, the TList is simply adding/removing a pointer to the TTabSheet object. The TList does not notify the TTabSheet about the insertion/removal. And there is no pointer from the TTabSheet to the TList, so when the TTabSheet is destroyed, there is no mechanism in place that allows the pointer to that TTabSheet to be removed from the TList. The pointer remains in the list, and you can continue using the pointer (for comparisons, etc) as long as you do not dereference it to access the destroyed TTabSheet.

If you want to remove the TTabSheet pointer from the TList automatically when the TTabSheet is destroyed, you need to call the TTabSheet's FreeNotification() method. Your Notification() callback can then remove the TTabSheet pointer from the TList in response to opRemove notifications, eg:

TMyForm = class(TForm)
  ...
private
  tabs: TList<TTabSheet>;
  ...
protected
  procedure Notification(AComponent: TComponent; Operation: TOperation); override; // <-- ADD THIS
  ...
end;

...  

procedure TMyForm.DoSomething;
var
  NewTabSheet: TTabSheet;
  ...
begin
  ...
  NewTabSheet := TTabSheet.Create(PageControl1);
  NewTabSheet.PageControl := PageControl1;
  NewTabSheet.Caption := 'tab1';
  tabs.Add(NewTabSheet);
  NewTabSheet.FreeNotification(Self); // <-- ADD THIS
  ...
end;

procedure TMyForm.Notification(AComponent: TComponent; Operation: TOperation);
begin
  inherited;
  if (Operation = opRemove) and (AComponent is TTabSheet) then
    tabs.Remove(TTabSheet(AComponent)); // <-- HERE
end;

If you want the TTabSheet to be removed from its TPageControl automatically when its pointer is removed from the TList, switch to TObjectList instead. Its OwnsObjects property is true by default, so it will destroy the TTabSheet when the pointer is removed from the list using the Remove() or Delete() method. The Extract() method will not destroy the TTabSheet, so use that instead of Remove() in your Notification() callback, eg:

TMyForm = class(TForm)
  ...
private
  tabs: TObjectList<TTabSheet>;
  ...
end;

...  

procedure TMyForm.Notification(AComponent: TComponent; Operation: TOperation);
begin
  inherited;
  if (Operation = opRemove) and (AComponent is TTabSheet) then
    tabs.Extract(TTabSheet(AComponent)); // <-- HERE
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I have tried this solution, in this question and it gave me an error. can you tell me more about freenotification method https://stackoverflow.com/questions/44487294/deleting-an-fmxobject-inside-its-event-handler. – Nasreddine Galfout Jun 23 '17 at 20:46
  • That is way too much code to look through, and too little description. I'm not going to spend my time trying to figure it out. You need to debug it for yourself. – Remy Lebeau Jun 23 '17 at 20:59
  • I later found the problem about the use of the `FreeNotification` method. In the question I linked above. thank you – Nasreddine Galfout Nov 11 '17 at 00:30