tags:

views:

483

answers:

4

This has been discussed here, but not in this detail.

I'm having trouble when trying to have a non-modal subform close itself. I have it notify the parent, but I'm getting abstract errors and other exceptions. What am I doing wrong? Does the parent have to free the non-modal form, or just never try again to access it through that variable?

Main form:

NonModal := NonModalTForm.Create(Self);
NonModal.Callback := Callback;
NonModal.Show;

Procedure TForm.Callback; // called by non-modal form when closing 
begin
   FreeAndNil(NonModal);  // or should this just be NonModal := nil so I don't try to access a dangling pointer?
end;

In NonModal.pas

procedure NonModalTForm.FormClose;
begin
  Callback; // calls parent
end;
+4  A: 

Use Hide if you want to show the window later on.

Use Close if you want to close it. (Closing the main window, closes the application). The exact action of Close depends on the form parameters.

See the source of Close:

procedure TCustomForm.Close;
var
  CloseAction: TCloseAction;
begin
  if fsModal in FFormState then
    ModalResult := mrCancel
  else
    if CloseQuery then begin
      if FormStyle = fsMDIChild then
        if biMinimize in BorderIcons then
          CloseAction := caMinimize 
        else
          CloseAction := caNone
      else
        CloseAction := caHide;
      DoClose(CloseAction);
      if CloseAction <> caNone then
        if Application.MainForm = Self then 
          Application.Terminate
        else if CloseAction = caHide then 
          Hide
        else if CloseAction = caMinimize then 
          WindowState := wsMinimized
        else 
          Release;
    end;
end;

But be careful with free. There can be some messages in the windows queue left which can lead to crashes. Better use Release to cleanup the window. Because this waits for the messages before freeing it.

Gamecat
+3  A: 

You are exactly doing what you should not do.
In the event onClose of the NonModalForm, you call some code that is bluntly freeing it, while it is still inside the event handlers execution, so you end up with a self object that is not valid anymore.
That is the poster case for why to use Release instead of Free on a Form.

As Gamecat pointed it out, just simply call Close...
The beauty of the VCL is often that it is as simple as that.

François
>>As Gamecat pointed it out, just simply call Close...<<You're saying to call Close in the Callback procedure which is itself called from the non-Modal's FormClose itself? Doesn't that trigger a second OnClose event to the non-Modal form? Or are you saying that VCL is smart enough not to call the non-Modal's FormClose a second time.
Tom
Your `Callback` function is irrelevant to this discussion, Tom. As you note, you're calling it in the form's `OnClose` event handler. That means that by the time the callback function gets called, *your form is **already** closing*, and so your whole question is moot: you don't need to learn how to close the form because you've already accomplished that, even if you don't know how you managed it.
Rob Kennedy
Tom, the whole point is that you don't need a callback. To close the NonModal form, the user can just close it like any window, or if you want to provide a button to close it programmatically, you just need that code: `procedure TNonModal.Button1Click(Sender: TObject);` `begin` `Close;` `end;` or from the Mainform: `procedure TFormMain.Button1Click(Sender: TObject);` `begin` `NonModal.Close;` `end;`
François
Without a callback procedure, how do I avoid a memory leak and/or dangling pointer on the part of the parents pointer to the non-modal form? I have to free the memory pointed to by NonModal ... and set NonModal to nil so I know it's not in use.The only solution I can see is yet another layer of logic: non-modal form, when closing, calls a callback to parent, which sets a flag "Non-modal form closed" to True. Later (when itself is closing or wants to reuse non-modal form, it checks this flag and frees the memory.Sorry for lingering on this, but I'm really trying to figure this out without
Tom
You don't need to Free NonModal as it has been created by passing Self (the MainForm) as the Owner. The Owner will free it upon its own destruction. And before that, until it is explicitly Freed and Nilled by some code, it is still available to be reused if needed. And if you need to Free it yourself, you can always check if it has been closed.
François
Thank you, Francois and others. My question is now answered. My confusion stemmed from the fact that I didn't understand that NonModal.Close left the for available for re-use. Thank you all again for your help!
Tom
@Tom: It doesn't necessarily, it depends on the close action which *you* have control over. See skamradt's answer (+1). Also, if your question is answered you should accept the one which helped you the most.
mghie
+2  A: 

You call close to close your form from someplace other than the FormClose event. In the FormClose event, just set Action equal to one of the following:

  • caFree - dispose of the form completely
  • caMinimize - Minimize the form
  • caHide - Hide the form
  • caNone - Ignore the close

for example:

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;
skamradt
+1  A: 

The VCL already has a mechanism to notify components when other components are freed. You can use it this way;

type
  TfrmParent = class(TForm)
    btnShowChild: TButton;
    procedure btnShowChildClick(Sender: TObject);
  private
    FChild: TfrmChild;
  public
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  end;


procedure TfrmParent.btnShowChildClick(Sender: TObject);
begin
  // Check status of child
  if FChild = nil then
  begin
    // Child does not exist, create it
    FChild:= TfrmChild.Create(Application);
    FChild.Show;

    // Ask Child to notify us when it is destroyed
    FChild.FreeNotification(Self);
  end
  else
  begin
    // Child already exists
    FChild.Show;
  end;
end;

procedure TfrmParent.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;

  if (AComponent = FChild) and (Operation = opRemove) then
  begin
    // FChild is about to be freed, so set reference to Child to nil
    FChild:= nil;
  end;
end;

After creating the child form, use the created form's FreeNotification method to register yourself to receive a notification when the child form is destroyed.

To react to the notification, overwrite the Notification method. In there, you can find out which component is destroyed and compare it to the remembered reference to the child form. When you receive the notification, just set the reference to the child form to nil.

In the child TfrmChild itself you don't have to do anything else but what skamradt has written: Just set the parameter Actionb to caFree in the OnClose event.

NineBerry
+1. Calling `FreeNotification()` isn't even needed when `Self` is passed to `TfrmChild.Create()` instead of `Application` - the ownership management in the VCL takes care of notification calls.
mghie