views:

385

answers:

4

I stumbled on this page Why shouldn’t I call Application​.CreateForm. Now I have some code like this:

SplashForm := TSplashForm.Create(Application);
SplashForm.Show;
SplashForm.Update; // force update
Application.Initialize;
Application.CreateForm(TClientData, ClientData);
SplashForm.Update; // force update
Application.CreateForm(TClientMainForm, ClientMainForm);
Application.ShowHint := True;

Application.Run;
ClientMainForm.ServerConnected := false;
FreeAndNil(ClientMainForm);
FreeAndNil(ClientData);

First a splashform is created, then a datamodule and last the main form. The page says that Application.CreateForm should not be called twice. Should the code above be changed ?

Regards

+3  A: 

There is nothing wrong with using Application.CreateForm multiple times. But this introduces global variables for each form which can be a code smell. Unfortunately the IDE creates one for each form. Although you can remove them if you like.

A better way is to create a form when you need it and release it when you are ready with it. So you only use Application.CreateForm for the main form.

A main datamodule can be created by the main form. But it can be global too, just a matter of taste.

So to answer the question, you can avoid Application.CreateForm by creating and releasing the forms locally.

The article mentions the side effect of Application.CreateForm (the first completed form is the main form). So there can be unexpected side effects if the main form creates other forms using Application.CreateForm.

So just to avoid any nastyness, you should limit yoursef to a single call. Which is done using only one global form.

Gamecat
I don't consider it better style to remove the IDE created globals. Those are a part of how Delphi applications work. Such "optimizations" are in fact more of a "code smell" than non-optimized IDE generated code.
Warren P
@Warren: That comment doesn't make any sense at all. With the sole exception of the main form variable a Delphi application need not make use of *any* of those global variables, and that one for the main form could easily be replaced by a variable in the project file, which would not be known to any other unit, so it wouldn't be really a global variable either.
mghie
I was simply commenting on the phrase "Although you can remove them if you like", I do not consider that removal a good idea.
Warren P
+1  A: 

If TClientData is a Data Module and TClientMainForm is a form, then no (except perhaps the two FreeAndNil calls at the end - not really needed). But take care. Because as it says Rob Kennedy in his post, the Application.CreateForm does other things behind (it sets the MainForm variable), so I would advise to set up your project file according to the following rules:

  1. Create all the forms which you want to create at startup using a single call to Application.CreateForm - usually this is done by the IDE.

  2. Remove from the project file the forms which you want to create dynamically (on-demand) in your program. (In Project | Options | Forms...) - move them from 'Auto-Create Forms' to 'Available Forms'

  3. Create your forms in your code using TmyForm.Create(Owner) (etc.) and not with Application.CreateForm(...). As an aside, if you are sure that you will free the form, then it is better (in order to speed the things up) to call TmyForm.Create(nil) - iow without any owner.

  4. If you want to do some kind of initialization at startup you can have a procedure / method in the project file tied to a form / data module already created and run it before application run.

For example:

begin 
  Application.Initialize; 
  Application.MainFormOnTaskbar := True; 
  Application.CreateForm(TdmoMain, dmoMain); //<--this is a data module
  Application.CreateForm(TfrmMain, frmMain); //<--this will became the main form
  Application.CreateForm(TfrmAbout, frmAbout);
  //... other forms created here...
  frmMain.InitEngine; //<--initialization code. You can put somewhere else, according with your app architecture
  Application.Run;
end.

In this way you will have the project file clean and you will know exactly which is which.

HTH

+1 PLAINTH's answer explains and follows the established conventions used by most Delphi developers. Anything other than that violates what I call the "principle of least amazement".
Warren P
+1  A: 

When I wrote that article, I was thinking primarily of code outside the DPR file. People see the form-creation code generated by the IDE in the DPR file and think that's the best way to create forms generally, so they use that elsewhere in their programs. They sometimes use it in the main form's OnCreate event handler to create other forms their program needs, and then they hit problems because the program's main form isn't what they think it is.

In the code you provided, it's easy to call CreateForm just once. Use it for the main form, and for nothing else. The data module isn't a main form, so you don't need CreateForm's magic there.

SplashForm := TSplashForm.Create(Application);
SplashForm.Show;
SplashForm.Update; // force update
Application.Initialize;

// Change to this.
ClientData := TClientData.Create(Application);

SplashForm.Update; // force update
Application.CreateForm(TClientMainForm, ClientMainForm);
Application.ShowHint := True;

Application.Run;
ClientMainForm.ServerConnected := false;

// Remove these.
FreeAndNil(ClientMainForm);
FreeAndNil(ClientData);

You really shouldn't free the objects you've created here because you don't own them. They're owned by the global Application object, so let it take care of freeing them: Remove the two calls to FreeAndNil.

Rob Kennedy
If you know the main form has been created, then another form cannot be magically turned into the main form. If that caveat is understood, then calling Application.CreateForm from somewhere other than your .dpr source file is a good idea, if and only if you intend for the Application object to manage the lifetime of your forms from that point onwards. However, as many of us want forms that live as long as the Application lives, and are then freed, this is exactly the desired behaviour of calling Application.CreateForm. I call it "late form creation", and I use it all the time.
Warren P
The main form can run code of its own before it gets *designated as* the main form. That's where problems occur. If you wish for the Application object to manage the lifetime, then simply specify it as the Owner parameter when you call a form's constructor, just like you do when creating any other object, and as I demonstrated in the code in my answer. Why ascribe extra importance to the creation of a form when in fact it's the same as creating an instance of any other class? The *only* reason to call CreateForm is when you need Application.MainForm to get set.
Rob Kennedy
A: 

The article you refer to is incorrect. There are a number of valid reasons why you would want multiple calls to Application.CreateForm

1) Datamodules: You probably want these available all of the time. Best way to do this is Application.CreateForm. I know of applications with several themed Datamodules e.g. Customer, Invoice, Address to handle different areas of the database & encapsulate the functionality neatly. All of these are created in the .dpr

2) Big, slow loading stuff (which is a bad idea in & of itself but these things happen and are often inherited by support programmers...). Move the load time into the application startup exactly like your example code along with the splash screen updating. The users expect applications to take a while to get going thanks to the stirling efforts of our collegues working on Microsoft Office to lower the bar of expectations for the rest of us :)

So, in summary, don't worry your code is fine - but you can lose the "FreeAndNil" stuff. However small quick hitting Dialog type stuff is best invoked by:

with TMyform.Create(nil) do
try
  //Setup
  case ShowModal of
    // Whatever return values you care about (if any)
  end;
finally
  Free;
end;

Short, sweet, to the point & minimises memory usage...

mcottle
Your assertion that the linked article is incorrect is, in fact, very incorrect. None of the *number of reasons* you give is a reason to call `Application.CreateForm()`, at all. Re 1) you can simply `Create()` them, passing any `Owner` including `Application`. Re 2) you can do that first thing in the `OnCreate` event of the main form, and nobody will see any bit of difference.
mghie
You can create forms in different ways but the ways you describe involve you having to write extra code for no apparent gain. Why would you want to deliberately do things the "non-Delphi" way? If you create a form or Datamodule in the IDE Delphi helpfully allows you to create it at runtime using Application.Createform statements that it puts into the .dpr. In 16 years of working with Delphi I have yet to see any evidence that Application.Createform is inherently inefficient or undesirable for forms that are needed for the lifetime of the application which seems to be the authors assertion.
mcottle