Tuesday, December 11, 2007

Programming in Delphi sins #2

In the first episode of this serie I talked about avoiding the default auto-create function for forms and datamodules in Delphi.
As Zarko Gajic (delphi.about.com) pointed out in the comments I made another sin ;-) in the example code. Always something to learn! Thanks for the tip Zarko!

Well here it goes #2:

Never use a owner if you create a component/form yourself and free it instantly
So don't do this: TForm3.Create(self);

procedure TForm5.Button1Click(Sender: TObject);
var
MyForm : TForm3;
begin
MyForm :
= TForm3.Create(self);
try
MyForm.ShowModal;
finally
MyForm.Free;
end;
end;

But do this: TForm3.Create(nil);


procedure TForm5.Button1Click(Sender: TObject);
var
MyForm : TForm3;
begin
MyForm :
= TForm3.Create(nil);
try
MyForm.ShowModal;
finally
MyForm.Free;
end;
end;


If you pass self (in this case Form5) as the owner you rely on Form5 to free it when form5 is destroyed. Beside maintenance confusion it also has a backdraft on performance.

Zarko has a great article on this:

A warning on dynamically instantiating components

Always great to learn something new!

9 comments:

Aleksander Oven said...

I disagree.

Not providing an owner for modal forms may very well be the real sin here.

Consider this:

When constructed, the underlying Win32 window requires a parent WndHandle. Without it, the modal window won't function correctly - it may display behind the calling window, if constructor has some work to do, and doesn't return immediately.

Even if this doesn't happen, the modal window still won't "feel" right. Properly coded modal windows will flash their caption and beep when user clicks the underlying window, while the modal one is displayed. Those, that don't, are not properly coded.

This can be seen quite often in programs written using Delphi, and is, in fact, one of the tell-tale signs that a program was indeed written using Delphi.

But you need to keep in mind, that simply passing an owner to a form's constructor won't solve this. You need to do something else, too. The reasons are historical...

Delphi's implementation of modality has been broken from beginning. The only way to code a proper modal window was to override form's CreateParams, and include the following line:

Params.WndParent := Owner.Handle;

For this to work, you obviously needed the owner.

Things changed in D2005, when a new property called PopupParent was introduced to TForm. This allowed you to explicitly set a parent form before calling ShowModal. Most of the time, this was the same as Owner, so the only gain was not having to explicitly override CreateParams any longer.

Also in D2005, the Application object gained a new property called PopupMode. This could be used to automatize the whole parent window assignment process. If set to pmAutomatic, every modal form would "intelligently" discover the proper parent window and set the Params.WndParent to its handle.

So, based on the above, you can omit passing the owner to modal windows, but only if you implement one of the counter-measures to ensure proper modality. Failing to do so is IMO a far bigger sin than the miniscule performance hit, when the form is inserted in its owner's internal list, and then removed on destruction. There are usually far bigger bottlenecks in form construction than this.

msohn said...

Another reason *not* to pass nil in the constructor is obviously when you have Position = poOwnerFormCenter. Which is what I always use to have proper multi-monitor support with several non-modal windows.

Anonymous said...

@aleksander: Superb! Thanks for this! (Rightclick - Save...) :)

Zarko Gajic said...

Since I strongly believe we should use the best from all the worlds, here's a skeleton for a modal form using a class function ("Execute") that will blink when the underlying form is clicked and still has "nil" for the owner. Position can also be calculated.

-----------------------------------
unit modalUnit;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;

type
TModalForm = class(TForm)
private
fWNDParent: THandle;
property WNDParent : THandle read fWNDParent;
protected
procedure CreateParams(var Params: TCreateParams); override;

public
class function Execute(const parent : THandle) : TModalResult;
constructor Create(AOwner: TComponent; const parent : THandle); reintroduce;
end;


implementation
{$R *.dfm}

constructor TModalForm.Create(AOwner: TComponent; const parent : THandle);
begin
fWNDParent := parent;
inherited Create(AOwner);

//GetWindowRect(parent), for example, can be used to "Position" the modal form
end;

procedure TModalForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.WndParent := WNDParent;
end;

class function TModalForm.Execute(const parent: THandle): TModalResult;
begin
with Self.Create(nil,parent) do
try
result := ShowModal;
finally
Release;
end;
end;

end.
-----------------------------------

Aleksander Oven said...

Zarko, that's all fine, and I'm sure it works great. But why bother? It seems to me you're trying to solve a problem, that's not even there.

Roland said...

--aleksander
I always thought that a component has an owner (the one that frees him) and a parent, which 'decides' what it does.
Anyway interesting thoughts on TForms and parents!

Aleksander Oven said...

Roland, the WndParent I'm talking about has nothing to do with Delphi's TControl.Parent. They might be called similarly, but they are very different.

Modal windows need a WndParent to help the underlying system determine the Z-order relationship and behaviour. You could say, WndParent governs *on top* of what the modal window will be displayed. However, such window is still a top-level window.

On the other hand, the Parent property governs *inside* of what the window will be displayed. A control (a.k.a. a window), that has its Parent property set, is no longer the top-level window.

You can best see WndParent in action with MessageBox() Windows API. Take a look at the first argument, and you'll see what I mean.

Anonymous said...

Now I'm confused. I tried both versions of Roland's code (the sinned and non-sinned) and both blink when I click on the parent.

I use D2007 sp3. Was this handle-passing fixed recently?

Aleksander Oven said...

I must admit I never checked this in D2007, and as it seems it was finally fixed in this version.