프로그램/바로쓰는델파이

[펌]동적 델파이 VCL components 생성

mulderu 2008. 1. 21. 01:17

Creating Components Dynamically (at Run-Time)

From Zarko Gajic,
Your Guide to Delphi Programming.
FREE Newsletter. Sign Up Now!

Most often when programming in Delphi you don't need to dynamically create a component. If you drop a component on a form, Delphi handles the component creation automatically when the form is created. This article will cover the correct way to programmatically create components at run-time.

Dynamic Component Creation

There are two ways to dynamically create components. One way is to make a form (or some other TComponent) the owner of the new component. This is a common practice when building composite components where a visual container creates and owns the subcomponents. Doing so will ensure that the newly-created component is destroyed when the owning component is destroyed.

To create an instance (object) of a class, you call its "Create" method. The Create constructor is a class method, as opposed to virtually all other methods you’ll encounter in Delphi programming, which are object methods.

For example, the TComponent declares the Create constructor as follows:

constructor Create(AOwner: TComponent) ; virtual;

Dynamic Creation with Owners
Here's an example of dynamic creation, where Self is a TComponent or TComponent descendant (e.g., an instance of a TForm):

with TTimer.Create(Self) do
begin
  Interval := 1000;
  Enabled := False;
  OnTimer := MyTimerEventHandler;
end;

Dynamic Creation with an Explicit Call to Free
The second way to create a component is to use nil as the owner.

Note that if you do this, you must also explicitly free the object you create as soon as you no longer need it (or you'll produce a memory leak). Here's an example of using nil as the owner:

with TTable.Create(nil) do
try
  DataBaseName := 'MyAlias';
  TableName := 'MyTable';
  Open;
  Edit;
  FieldByName('Busy').AsBoolean := True;
  Post;
finally
  Free;
end;

Dynamic Creation and Object References
It is possible to enhance the two previous examples by assigning the result of the Create call to a variable local to the method or belonging to the class. This is often desirable when references to the component need to be used later, or when scoping problems potentially caused by "With" blocks need to be avoided. Here's the TTimer creation code from above, using a field variable as a reference to the instantiated TTimer object:

FTimer := TTimer.Create(Self) ;
with FTimer do
begin
  Interval := 1000;
  Enabled := False;
  OnTimer := MyInternalTimerEventHandler;
end;

In this example "FTimer" is a private field variable of the form or visual container (or whatever "Self" is). When accessing the FTimer variable from methods in this class, it is a very good idea to check to see if the reference is valid before using it. This is done using Delphi's Assigned function:

if Assigned(FTimer) then FTimer.Enabled := True;

Dynamic Creation and Object References without Owners
A variation on this is to create the component with no owner, but maintain the reference for later destruction. The construction code for the TTimer would look like this:

FTimer := TTimer.Create(nil) ;
with FTimer do
begin
  ...
end;

And the destruction code (presumably in the form's destructor) would look something like this:

FTimer.Free;
FTimer := nil;
(*
Or use FreeAndNil (FTimer) procedure, which frees an object reference and replaces the reference with nil.
*)

Setting the object reference to nil is critical when freeing objects. The call to Free first checks to see if the object reference is nil or not, and if it isn't, it calls the object's destructor Destroy.

Dynamic Creation and Local Object References without Owners
Here's the TTable creation code from above, using a local variable as a reference to the instantiated TTable object:

localTable := TTable.Create(nil) ;
try
  with localTable do
    begin
      DataBaseName := 'MyAlias';
      TableName := 'MyTable';
    end;
  ...
  // Later, if we want to explicitly specify scope:
  localTable.Open;
  localTable.Edit;
  localTable.FieldByName('Busy').AsBoolean := True;
  localTable.Post;
finally
  localTable.Free;
  localTable := nil;
end;

In the example above, "localTable" is a local variable declared in the same method containing this code. Note that after freeing any object, in general it is a very good idea to set the reference to nil.

A Word of Warning

IMPORTANT: Do not mix a call to Free with passing a valid owner to the constructor. All of the previous techniques will work and are valid, but the following should never occur in your code:

with TTable.Create(self) do
try
...
finally
  Free;
end;

The code example above introduces unnecessary performance hits, impacts memory slightly, and has the potential to introduce hard to find bugs. Find out why.

Note: If a dynamically created component has an owner (specified by the AOwner parameter of the Create constructor), then that owner is responsible for destroying the component. Otherwise, you must explicitly call Free when you no longer need the component.


Dynamic Creation Notification Performance

A test program was created in Delphi to time the dynamic creation of 1000 components with varying initial component counts. The test program appears at the bottom of this page. The chart shows a set of results from the test program, comparing the time it takes to create components both with owners and without. Note that this is only a portion of the hit. A similar performance delay can be expected when destroying components. The time to dynamically create components with owners is 1200% to 107960% slower than that to create components without owners, depending on the number of components on the form and the component being created.

Analyzing the Results

Creating 1000 owned components requires less than a second if the form initially owns no components. However, the same operation takes roughly 10 seconds if the form initially owns 9000 components. In other words, creation time is dependant on the number of components on the form. It is equally interesting to note that creating 1000 components that are not owned takes only a few milliseconds, regardless of the number of components owned by the form. The chart serves to illustrate the impact of the iterative Notification method as the number of owned components increase. The absolute time required to create an instance of a single component whether owned or not, is negligible. Further analysis of the results is left to the reader.

The Test Program

You can perform the test on one of four components: TButton, TLabel, TSession, or TStringGrid (you can of course modify the source to test with other components). Times should vary for each. The chart above was from the TSession component, which showed the widest variance between creation times with owners and without.

Warning: This test program does not track and free components that are created without owners. By not tracking and freeing these components, times measured for the dynamic creation code more accurately reflect the real time to dynamically create a component.

Download Source Code

Warning!

If you want to dynamically instantiate a Delphi component and explicitly free it sometime later, always pass nil as the owner. Failure to do so can introduce unnecessary risk, as well as performance and code maintenance problems. Read the "A warning on dynamically instantiating Delphi components" article to learn more...