tags:

views:

88

answers:

3

Hi. I have a MDI main (parent) form and a MDI child form. I create the child at runtime like this:

VAR
   FrmDereplic: TFrmDereplic;

procedure TMainFrm.Button2Click(Sender: TObject);
begin
 FrmDereplic:= TFrmDereplic.Create(MainFrm);
 FrmDereplic.Show;
end;

Steps to reproduce the error:
I start the app, I press the button to create the child, I press the 'x' button on main (parent) form to close the application and I get an "Cannot create form. No MDI forms are currently active" error.

The line on which the error appears is in the child form:

procedure TFrmDereplic.FormClose(Sender: TObject; var Action: TCloseAction);
begin
 Action:= caFree;
end;

procedure TFrmDereplic.FormDestroy(Sender: TObject);
VAR MyIniFile: TCubicIniFile;
begin
 MyIniFile:= TCubicIniFile.Create(AppINIFile);
 TRY
  with MyIniFile DO
  begin
   if WindowState<> wsMaximized then
    begin
     // save form's screen pos
     ...
    end;
   WriteInteger  ('Dereplicator', 'fltExtensions', fltExtensions.ItemIndex);  <----- HERE
 FINALLY
  FreeAndNil(MyIniFile);
 END;
end;

I save lots of form's properties (and other's controls properties) to the INI file. But it only fails when I try to save fltExtensions.ItemIndex (which is a TFilterComboBox). If I comment that line it works perfectly.

Any idea why it tries to create a form when I actually closed the application?????????

+2  A: 

I look on some web sites and just found the problem. It looks like is better if Owner is the Application, not the main form. Remy Lebeau suggests that the real problem is in the OnDestroy of the the child form. There is no valid handle to the window that holds the filter then the OnDestroy is called. So, changing the destruction order gives a chance to TFrmDereplic.OnDestroy to execute properly.

So, here is the solution:

SOLUTION(S)

FrmDereplic:= TFrmDereplic.Create(Application);

or

Do not save form's properties in OnDestroy

The second one requires few extra lines of code as the OnClose even is not always called. This was extracted from Delphi HELP:

Note: When the application shuts down, the main form receives an OnClose event, but any child forms do not receive the OnClose event.

If you use Application.Terminate, then onCloseQuery and onClose will not be called. Same for Halt (but... this is way too extreme, right?).

Altar
A: 

If the code you provided in your question is the real one then I guess the error is in this line:

 FrmDereplic:= TFrmDereplic.Create(TMainFrm);

I never tried this and I am not sure if the compiler really buys it (can't test it now), but you are trying to set a class as owner of the MDI child form. Instead of that you should do either

FrmDereplic:= TFrmDereplic.Create(Application);

or

FrmDereplic:= TFrmDereplic.Create(self);

The first option sets the application as owner of the MDI child form, while the second one sets the instance of the MDI main form as owner.

Hope that helps. :-)

Guillem Vicens
The OP came to this conclusion three hours before you made your post.
Andreas Rejbrand
@Andreas - I wanted to mark my post as solved but StackOverflow makes me wait 2 days. Anyway, its nice that other people confirm my solution. It means it is good.
Altar
@ Vicens - Sorry. It is indeed MainForm instead of TMainForm. I entered the error when I typed the code. In my code the form has a different name. I changed its name to MainForm to make the code easier to understand (main form = the parent of the child form). Sorry again. Please note that Self will not work!! It is actually equivalent with my original (buggy) code. Why? Because Self = MainForm.
Altar
@Andreas - yes, you are right. I merely wanted to clarify that if the code provided was the real then the error probably was in that code line, plus giving an alternative to setting the Application as the owner of the MDI child form. Granted, maybe a comment would have sufficed. :-)
Guillem Vicens
@Altar, I have no access to my Delphi copy at home but I could swear you should be able to set the main form as the owner of the MDI child form as long as it is declared as MDI itself. I will try it tomorrow at work.
Guillem Vicens
@Vicens - You are right. Setting main form as owner is ok BUT as just explained above is not working always. Using Application as owner is better.
Altar
+1  A: 

The error occurs when reading the fltExtensions.ItemIndex property because it requires fltExtensions to have an HWND, which requires its parent TFrmDereplic form to have a HWND, which requires the project's MainForm to have an HWND. But the app is in a state of shutdown, and the MainForm cannot allocate its HWND anymore, so TFrmDereplic raises an exception when it cannot obtain an HWND for itself.

Saving your INI data in the form's OnDestroy event is too late. You need to the OnClose event instead.

Remy Lebeau - TeamB
But OnClosed is skipped in some situations. Which means that data will not be saved to disk! My current implementation works. Is it wrong? Can I use it as it is? I guess that using Application as owner of that form change the destruction sequence. This gives a chance to TFrmDereplic to execute OnDestroy properly.
Altar
OnClose is called when closing the app by most means - X button on the window, TForm.Close(), Application.Terminate(), etc. But yes, there are some situations where OnClose is not always called, but those conditions can be handled separately. You can refactor your saving code into its own function that you can call from multiple places when needed. As for why the code itself is wrong, I already explained that - OnDestroy is usually too late to access HWND-based property values, like ItemIndex. Setting the Applicaton as Owner simply allowed the MDI child to be destroyed before the MainForm.
Remy Lebeau - TeamB
"As for why the code itself is wrong, I already explained that" - Sorry, I was referring to the new code where I have fixed it by setting the Application (instead of MainForm) as Owner. If this assignment is valid I would prefer to use it instead of giving up on OnClose.
Altar
Delphi 7 HELP says: "When the application shuts down, the main form receives an OnClose event, but any child forms do not receive the OnClose event"
Altar
That is true. MDI child forms do not fire thier `OnClose` event when the MainForm's `OnClose` event is fired. They do, however, fire their `OnCloseQuery` event when the MainForm's `OnCloseQuery` event is fired.
Remy Lebeau - TeamB
Assigning the Application as the Owner allows the child form to be destroyed before the MainForm rather than afterwards, because TComponent destroys its owned objects in the opposite order than they were created.
Remy Lebeau - TeamB