Forum > LCL

FreeNotification method: I need help

(1/2) > >>

zoltanleo:
Hi folks

I need to track an event in the parent form at the moment when the child form is destroyed. I am using code like this


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---{ TfrmMain } procedure TfrmMain.btnCreateChildFormClick(Sender: TObject);var  tmpFrm: TfrmChild;begin  Inc(FCnt);//this is counter, initialized to zero in the onCreateForm event   tmpFrm:= TfrmChild.Create(Self);  tmpFrm.Show;end;  procedure TfrmMain.Notification(AComponent: TComponent; Operation: TOperation);begin  inherited Notification(AComponent, Operation);   if (AComponent.InheritsFrom(TfrmChild) and (Operation = opRemove)) then  ShowMessage(Format('%s has been removed',[TfrmChild(AComponent).Name]));//this procedure is called twiceend;  { TfrmChild } procedure TfrmChild.FormClose(Sender: TObject; var CloseAction: TCloseAction);begin  CloseAction:= caFree;end; procedure TfrmChild.FormCreate(Sender: TObject);const  offset = 50;begin  Self.Name:= 'frmChild_' + IntToStr(frmMain.Cnt);  Self.Left:= frmMain.Left + frmMain.Cnt * 50 + offset;  Self.Top:= frmMain.Top + frmMain.Cnt * 50 + + offset;  Self.FreeNotification(frmMain);end;                                     

But I see some incomprehensible side effects:
1 showmessage procedure is called twice
2. if I close the parent form, I see a notification about closing the "phantom" form frmChild_0

What am I doing wrong?

gifs:
https://i.imgur.com/mW3iJAb.mp4
https://i.imgur.com/BNmQZz3.gif

Martin_fr:
TForm is a TComponent.
And TComponent has an owner.

If I create a component with an owner, then the Owner will set a FreeNotification on the component.


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---MyComp.Create(AOwner)AOwner will do MyComp.FreeNotification(self);

Thus the Owner will know, if one of its owned components is freed.

Your TfrmChild is owned by FrmMain.
So FreeNotification is set up already. You then add a 2nd FreeNotification in your code.

Either create the child as

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} --- tmpFrm:= TfrmChild.Create(NIL);
Or do not call child.FreeNotification(main), as you will receive the notification through the owning dependency.

GetMem:
I did a quick debug(breakpoint + the call stack window) and I came to the same conclusion as @Martin_fr. Please see attached images.
Using Notification is an overkill in my opinion. A simple TNotifyEvent in the child form will do the job, just subscribe to the event from main form. For more details please check attached application.

zoltanleo:
@Martin_fr
@GetMem

Thank a lot for your responces. It's helps me to better understand the LCL inner workings.

@Martin_fr

1. I noticed that the Notification procedure in the main form will be called anyway, even if I do so


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---procedure TfrmChild.FormCreate(Sender: TObject);const  offset = 50;begin  <skiped>  //Self.FreeNotification(frmMain);end;  
Why it happens?

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?

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?

If I do this way


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---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

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---Project project1 raised exception class 'EListError' with message:List index (3) out of bounds  At address 10003F020
@GetMem

A very interesting idea. 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).


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---procedure TfrmChild.FormClose(Sender: TObject; var CloseAction: TCloseAction);begin  CloseAction:= caFree;  if Assigned(FOnNotifyEvent) then    FOnNotifyEvent(Self, Self.Name + '(' + Self.ClassName + ')');end;
     

Remy Lebeau:

--- Quote from: zoltanleo on September 23, 2021, 02:39:59 pm ---1. I noticed that the Notification procedure in the main form will be called anyway, even if I do so


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---procedure TfrmChild.FormCreate(Sender: TObject);const  offset = 50;begin  <skiped>  //Self.FreeNotification(frmMain);end;  
Why it happens?

--- End quote ---

As @Martin_fr told you earlier, this statement:


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---tmpFrm:= TfrmChild.Create(Self);
Will call FreeNotification() on the frmMain object for you, since the specified Owner is not nil.


--- Quote from: zoltanleo on September 23, 2021, 02:39:59 pm ---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?

--- End quote ---

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?


--- Quote from: zoltanleo on September 23, 2021, 02:39:59 pm ---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?

--- End quote ---

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.


--- Quote from: zoltanleo on September 23, 2021, 02:39:59 pm ---If I do this way


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---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

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---Project project1 raised exception class 'EListError' with message:List index (3) out of bounds  At address 10003F020
--- End quote ---

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:


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---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:


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---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:


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---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; 

--- Quote from: zoltanleo on September 23, 2021, 02:39:59 pm ---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).


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---procedure TfrmChild.FormClose(Sender: TObject; var CloseAction: TCloseAction);begin  CloseAction:= caFree;  if Assigned(FOnNotifyEvent) then    FOnNotifyEvent(Self, Self.Name + '(' + Self.ClassName + ')');end;
--- End quote ---

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.

Navigation

[0] Message Index

[#] Next page

Go to full version