1. I noticed that the Notification procedure in the main form will be called anyway, even if I do so
procedure TfrmChild.FormCreate(Sender: TObject);
const
offset = 50;
begin
<skiped>
//Self.FreeNotification(frmMain);
end;
Why it happens?
As @Martin_fr told you earlier, this statement:
tmpFrm:= TfrmChild.Create(Self);
Will call
FreeNotification() on the
frmMain object for you, since the specified
Owner is not
nil.
2. If I don't create child forms, then after closing the main form, I still receive a notification about closing the "phantom" child form frmChild_0.
Why it happens?
You can't be notified of a child Form that doesn't exist. So there must be a child Form being created that you don't know about. Do you have a child Form set to be auto-created at startup?
3. If I create multiple unowned child forms, I have to take care of destroying them myself if I try to close the main form first. Where and how can I do this?
Wherever makes sense. In the MainForm's
OnClose or
OnDestroy event would suffice.
Another option would be to simply not create them unowned. If you don't want to use the MainForm as the
Owner, simply pick another
Owner. For instance, the
TApplication object would suffice.
If I do this way
procedure TfrmMain.FormClose(Sender: TObject; var CloseAction: TCloseAction);
var
i: PtrInt;
childfrm: TfrmChild = nil;
begin
for i := 0 to Pred(Screen.FormCount) do
if Screen.Forms[i].InheritsFrom(TfrmChild) then
begin
childfrm:= TfrmChild(Screen.Forms[i]);
if Assigned(childfrm) then FreeAndNil(childfrm);
end;
then I get the error
Project project1 raised exception class 'EListError' with message:
List index (3) out of bounds
At address 10003F020
That is because you are modifying the
Screen.Forms list while you are iterating through it, changing the Count and order of the elements in it, so your loop index will not only go out of bounds eventually, but it will also skip elements. To account for this, you need to either:
1. iterate backwards, not forwards:
procedure TfrmMain.FormClose(Sender: TObject; var CloseAction: TCloseAction);
var
i: Integer;
begin
for i := Pred(Screen.FormCount) downto 0 do
if Screen.Forms[i] is TfrmChild then
Screen.Forms[i].Free;
end;
2. collect the
TfrmChild objects into a local list first, don't free them yet. Then iterate that list afterwards to free them:
procedure TfrmMain.FormClose(Sender: TObject; var CloseAction: TCloseAction);
var
i: Integer;
childs: TList;
begin
childs := TList.Create;
try
for i := 0 to Pred(Screen.FormCount) do
begin
if Screen.Forms[i] is TfrmChild then
childs.Add(Screen.Forms[i]);
end;
for i := 0 to Pred(childs.Count) do
TForm(childs[i]).Free;
finally
childs.Free;
end;
end;
Alternatively:
procedure TfrmMain.FormClose(Sender: TObject; var CloseAction: TCloseAction);
var
i: Integer;
childs: TObjectList;
begin
childs := TObjectList.Create(True);
try
for i := 0 to Pred(Screen.FormCount) do
begin
if Screen.Forms[i] is TfrmChild then
childs.Add(Screen.Forms[i]);
end;
finally
childs.Free; // will Free() each Form in the list for you...
end;
end;
I changed your code a bit to not see the notification from child forms if the main form is closed (I moved the notification to another event).
procedure TfrmChild.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
CloseAction:= caFree;
if Assigned(FOnNotifyEvent) then
FOnNotifyEvent(Self, Self.Name + '(' + Self.ClassName + ')');
end;
Why not just have the MainForm assign a handler to each child's
OnDestroy event instead? You don't need to create a new event for this.