Reputation: 2591
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?
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.
Upvotes: 0
Views: 489
Reputation: 597215
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;
Upvotes: 4