Monday, December 10, 2007

Programming in Delphi sins #1

In this serie I will tell you, what we find a sin, in Delphi programming.

Using auto create forms and datamodules
Standard Delphi will create automatically all forms and datamodules for you. This gives you a headstart when you are first learning Delphi, but in the end it will give you a headache!

Sooner or later you begin to wonder where that 'Form1' object is coming from. (It is in fact a variable in the interface section of his own unit)
Other issues:

  1. Your program becomes slower, consuming more memory then needed with many forms created.
  2. Bugs will be harder to track, because you just can not know the state of all your forms and datamodules. Code could run, without you knowing about it.

Solution:
Turn off auto creation of forms
in the options Dialog-VCL Designer tab, and create your forms/datamodules when you need them, and free it when you are done with it. In existing projects remove those forms from the auto create forms list in the project options.

Create a form when you need it, using your own variable:

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

Remove the 'global' var in unit of the newly created forms and datamodules, you just don't need them:


unit Unit3;

interface

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

type
TForm3
= class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;

//Remove this global var, which is
//the instance that is created with
//the auto create forms option turned on
var
Form3: TForm3;
//--

implementation

{$R
*.dfm}

end.

12 comments:

dummzeuch said...

If a form is a dialog, I usually add an Execute class method to it:

type
TForm1 = class(TForm)
public
class function Execute(_Owner: TComponent): boolean;
end;

class function TForm1.Execute(_Owner: TComponent): boolean;
var
frm: TForm1;
begin
frm := TForm1.Create(_Owner);
try
Result := mrOK = frm.ShowModal;
finally
frm.Free;
end;

and call it like this:

OkPressed := TForm1.Execute(Self);

UltraCrepidarian said...

Forms which are not dialogs should be created on first use, and then left allocated from that point forward.

I have a routine I use called DynamicFormCreate that I pass in the global TForm variable, and the class name, and if it's not assigned, it creates it. This quicky one-liner can be inserted instead of an "Assert(Assigned(myForm))".

I also have found that gui code where FORM a references FORM b needs to be either (a) refactored to avoid the linkage between forms, or (b) if absolutely necessary, you should check if Assigned(forma) then forma.DoSomething.

Warren

Boro said...

How to avoid linkage between a form and a datamodule inside IDE using the global variable (Datamodule1) from datamodule's unit ?
For example a datasource on a form needs a dataset from the datamodule. The property editor offers the dataset using this global variable Datamodule1. How to do this without the global datamodule's variable ?

Hmmurm said...

DataModules !? May be you don`t close Connections before build apps...

Boro said...

sorry, I cannot see what you mean with 'Connections...'.
This article recommends to avoid the usage of a global variable generated by the IDE in the units of forms and datamodules. Even better to remove them.
Again, I'm asking how to set in OE a datasource's component property Dataset when the required dataset component is in a datamodule. Datasource component is on the form. IDE offers for this property for example Datamodule1.Dataset1 if the datamodule is in uses clause of the form.
How to fill this property inside IDE without the global object variable Datamodule1 ?

Roland Beenhakker said...

--Boro
Create a private variable for the datamodule in you form, which you can create in the oncreate event.
You can still set the properties with inspector if you like.

DataModule1 : TDatamodule1;

DataModule1 := TDataModule1.Create(Self);

Marcovaldo said...

Not a sin at all to code in Delphi :-)

What the heck should the idea of creating application forms on demand lead to? Isn't the fact that Delphi handles most things smoothly in the background a feature of Delphi, not a bug. I think there are more sophisticated ways to waste memory than having automatically generated forms(I prefer initialized variables and structures over not yet created and already released ones!) The only situation when I normally tamper on the delphi way of automatic form creation, is to startup splash screens before the main application starts up.

Nevertheless I apprechiate that there is a place like this where
guys like you and me may discuss about outdated languages like Delphi :-O

Boro said...

--Roland
Thank you. Now I see how to encapsulate the instance of the datamodule into the form. It functions ok.
Unfortunately, in the OE the form's datamodule field (and it's member dataset) doesn't show up in the dropdown list of the datasource's property.
I tried to move the datamodule field declaration into various parts of the form declaration (private, public, published) but nothing helped.
To assign the property by code functions ok, e.g.

procedure TSubForm.FormCreate(Sender: TObject);
begin
Self.dmoData := TDataModule3.Create(Self);
Self.DataSource1.DataSet := Self.dmoData.Query1;
end;

Why the dmoData.Query1 doesn't show up in OE for the Datasource1.Dataset property ?

Anonymous said...

Even shorter

procedure TForm5.Button1Click(Sender: TObject);
begin
with TForm3.Create(self) do
try
ShowModal;
finally
Release;
end;
end;

Roland said...

--Boro
You can select the datamodule properties, like a dataset in the inspector. Suppose your Datamodules name is DataModule1, you could select in a datasource DataModule1.SomeTable as its dataset. However if your form has multiple instances, you will have the same data in both forms. Setting this property in code solves this.

boro said...

--roland
Thanks, now I see advantage of assignment in code. But nevertheless, when I have:

TSubForm = class(TForm)
Datasource1: TDatasource;
.....
private
MyDatamodule: TDatamodule1;
end;

and the unit of TDatamodule1 type is in uses clause.
The MyDatamodule.Dataset1 neither shows up in object inspector for Datasource1.Dataset nor cannot be entered(!) manualy in OI. Only in code can it be assigned into Datasource1.Dataset.

But the global var Datamodule1.Dataset1 from datamodule's unit does show up in OI. Why not the form's private MyDatamodule.Dataset1 ?

~Zarko Gajic said...

Roland,

Just a note that you should use "NIL" as the owner for a form that you will free "right away".

More info:

TForm.Create([nil, self, Application]) ?