Création de composants pendant l'exécution

-J'ai eu quelques messages dans ma BAL concernant la création de composants pendant l'exécution. On m'a aussi parfois posé la même question au cours des formations et initiation à Delphi que je donne de temps en temps.
- C'est vrai que face à certains problèmes la seule solution, du moins la seule solution élégante, consiste à créer un composant à " la volée ". Cela n'a rien de difficile si on pense à initialiser correctement quelques propriétés dont deux dont on ne s'occupe jamais en mode conception. Il s'agit des propriétés Owner et Parent.

- Explications :
- Owner désigne le contrôle propriétaire du composant. Tous les composants visuels de Delphi possèdent cette propriété.
- Qu'est ce que le propriétaire d'un composant ? A la conception le propriétaire d'un composant est la fiche sur laquelle le composant est posé.Ce composant apparait ensuite dans la déclaration de classe de la fiche. Voir à la fin de cette page dans le listing d'exemple le cas de Button1. Lorsque le bouton a été posé sa propriété Owner a été initialisée avec Form1. Lors de la création d'un composant pendant l'exécution c'est au programmeur de s'assurer que la propriété Owner sera bien initialisée. L'ennui c'est que cette propriété est en lecture seule. Mais pas de panique car c'est le constructeur du composant qui est chargé d'initialiser Owner avec la valeur qu'il aura reçu en paramètre.Ainsi si vous avez declaré une variable BTN de type Tbutton le code suivant BTN:=TButton.Create(Form1); va créer une instance de TButton et placer Form1 dans la propriété Owner.
Cette manière d'initialiser le propriétaire d'un composant est interressante car si vous oubliez le paramètre de Create Delphi vous signalera une erreur et votre projet ne se compilera pas.
- Quel propriétaire choisir ? Le propriétaire sera toujours une fiche. Dans 99.9% des cas on utilisera comme propriétaire la fiche où est declaré l'instance du composant. Mais le mieux est de passer SELF comme propriétaire. Voici un extrait de l'aide Delphi sur Self : Self peut être utilisé comme pointeur à l'instance par laquelle il est fait appel à la méthode. En clair cela veut dire qu'à l'intérieur d'une fonction ou d'une procedure du style Procédure TFORM1. MaProcedure.
Self désignera FORM1. Voir listing.

- Parent désigne le contrôle qui possède l'affichage du composant. Parent est une propriété Publique donc elle n'est accessible qu'à l'exécution. Ca tombe bien. Pour qu'un composant visuel s'affiche il faut que sa propriété Parent soit correctement initialisée. Après l'appel de Create, Parent vaut NIL et donc le composant ne s'affiche pas.
- Quel Parent choisir ? Ici le choix est plus vaste que pour Owner, et le Parent peut être une fiche ou un composant conteneur tel qu'un TPanel par exemple.Voir listing.
Pour plus d'explication sur la propriété Parent voir la page qui lui est consacrée.

- Le reste des propriétés est à initialiser selon vos besoins. Par défaut elles contiennent les mêmes valeurs que celles qui apparaissent dans l'inspecteur d'objet lorsque vous venez de poser un composant.
- Un point important : Il ne faut pas oublier les propriétés Left et Top.En effet si Left et Top désignent un point en dehors du contrôle Parent votre composant aura du mal à s'afficher.
- Note : Les valeurs de Left et Top se rapportent au contrôle Parent et non au contrôle Propriétaire.

- Les événements : Créer un composant pendant l'exécution c'est bien mais souvent il faudra lui associer des événements.
Ce n'est pas difficile car Delphi considère les événements comme un type de propriété pointant sur une procédure. En clair pour associer une procédure à un événement il faut écrire une procédure du type de celle de l'événément choisi et l'affecter à l'événément.
Exemple : L'événement OnClick de TButton est défini comme suit :
- property OnClick: TNotifyEvent;
Avec
- TNotifyEvent= procedure (Sender: TObject) of object;
Donc on écrit une procédure de ce style :
- Procedure UnBoutonClick(Sender:TObject);
et on l'affecte à l'événement OnClick par le code : MonComposant.OnClick:=UnBoutonClick; Voir listing.

- Déstruction : Lorsque vous n'avez plus besoin du composant il faut le détruire. Ceci se fait tout simplement en appelant sa méthode Free. Dans le code ci après il etait possible d'ajouter un bouton dont l'événement OnClick aurait détruit "UnBouton" avec le code suivant UnBouton.Free. Il est préférable de toujours appeler Free et non pas Destroy pour détruire une instance d'un composant. En effet Free fonctionne même si l'objet vaut Nil c'est à dire s'il n'a pas été initialisé. De même il faut détruire les objets que votre application a crée avant la fin de celle ci. Par exemple on peut placer le code de déstruction dans l'événement OnDestroy de la fiche contenant les composants à détruire. Voir listing.

- Ci après figure un listing d'exemple. La fiche Form1 comporte un bouton 'BUTTON1'. Lors d'un click sur ce bouton un autre bouton est crée. Une procédure est associée à l'événement OnClick de ce nouveau bouton. Ainsi un click sur le nouveau bouton déclanche un son.

**************************************************************************
* Listing d'exemple de création de composant pendant l'exécution *
**************************************************************************

unit Unit1;

interface

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

type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormDestroy(Sender: TObject);

private
{ Déclarations privées }
UnBouton:TButton;
Procedure UnBoutonClick(Sender:TObject);
public
{ Déclarations publiques }
end;
var
Form1: TForm1;

implementation
{$R *.DFM}

Procedure TForm1.Button1Click(Sender: TObject);
begin
UnBouton:=TButton.Create(Self); // Ici Self designe Form1. On aurait put mettre Form1 à la place de Self
With UnBouton do
Begin
Left:=10; // On initialise la propriété Left
Top:=20; // et Top
Width:=220; // Ainsi que la largeur
Caption:='Je suis un nouveau bouton. Cliquez sur moi.'; // On donne un titre au bouton
Parent:=Self; // Ici c'est Form1 qui sera chargé d'afficher le composant. Maintenant le bouton est visible
OnClick:=UnBoutonClick; // On affecte la procédure à l'événement OnClick du nouveau bouton
end;
end;

Procedure TForm1.UnBoutonClick(Sender:TObject); // Procédure pour l'événement OnClick du nouveau bouton
Begin
MessageBeep(Mb_IconExclamation); // Ici on fait juste un beep mais on peut faire ce qu'on veut
end;
Procedure TForm1.FormDestroy(Sender:TObject); // Fin de l'application
Begin
UnBouton.Free; // On détruit le bouton
end;


end. // Fin de l'unité
****************************************************************************


Ce code n'est pas très utile. Mais imaginez que vous vouliez associer un bouton à chaque fichier d'un dossier. Comme on ne peut pas prévoir le nombre de fichier contenu dans le dossier on peut placer sur une fiche un grand nombre de bouton et rendre visible le nombre nécessaire ( Ce n'est pas très élégant ) ou bien on peut créer pendant l'exécution juste le nombre voulu de bouton. Le premier éditeur de propriété Bitmap de la page téléchargement est un exemple de création de bouton pendant l'exécution. Un cas où la création à la volée est obligatoire C'est l'écriture d'un nouveau composant dans lequel sera utilisé un composant deja éxistant. Ici pas d'autre possibilité.

Debut de la page.
La propriété Parent.