[delphi] développer des composants

[delphi] développer des composants

On va passer à des choses très intéressantes en Delphi. Mais pour cela, il vous faut IMPERATIVEMENT relire le tutoriel 191 sur le développement de classes, car on va réutiliser les résultats.

Dérivation des classes

Un composant est une ressource qui a pour tâche d'effectuer des traitements et des calculs précis. C'est donc un encapsulement thématique. Certains sont non visuels (ils sont simples et indépendants des messages de Windows) et d'autres sont visuels (vous les placez visuellement comme vous aviez l'habitude de faire).

Or, pour développer un composant, il faut un socle déjà fait. Imaginez qu'on parte de rien (class...private...public...end) : alors il faudrait re-gérer la souris, l'affichage, les fonctions de base... Bref, il y aurait de la redondance absolument pénible, farfelue et superflue.

Merci Delphi de nous proposer 4 classes fondamentales pour entamer le développement de composants.
Il vous faut déjà cibler ce que vous voulez : visuel ou pas visuel ?
Pour les visuels, ils seront référencés dans la palette de composants et vous pourrez voir leur apparence en fonction des modifications apportées au niveau des propriétés.
Les non visuels auraient pu se cantonner à un simple fichier PAS qu'on aurait réutilisé via la clause USES (cf. classroom.pas du tuto 191), mais le fait de le mettre en tant que composant, permet d'avoir accès facilement à ses propriétés (les plus importantes généralement) dans l'inspecteur d'objets de Delphi.

Voici les 4 classes :

  • TComponent : Sert pour les composants non visuels (TOpenDialog, TSaveDialog, TTimer...). Delphi les détectent automatiquement et les affiche dans un petit carré placé au-dessus de tous les autres composants.
  • TGraphicControl : Sert pour les composants visuels en non interaction avec les messages de Windows. Ces composants ne sont pas fenêtrés (ils n'ont pas de Handle) et Windows ne les considère donc pas comme une fenêtre. Ces composants ne peuvent pas recevoir de focalisation et l'utilisateur n'ayant pas de souris (imaginons !) ne pourra jamais cliquer dessus. Ils prennent peu de mémoire et sont utiles pour faire des affichages au sein de composants simples.
  • TWinControl : Sert à l'interception des messages de Windows. Cette classe est le composant fenêtré le plus simple qu'il soit, mais ne peut pas servir proprement au développement de composant, car il ne propose pas de quoi dessiner dedans. Tout composant fenêtré reçoit des messages de la part de Windows. En conséquence, cette classe sert à les récupérer. Bien que ce composant soit visuel, il est souvent caché.
  • TCustomControl : C'est le TGraphicControl fenêtré. Il occupe plus de mémoire, peut recevoir des focalisations... Il dérive de TWinControl et propose une zone pour dessiner. N'abusons pas de ce type.

Pour choisir entre TGraphicControl et TCustomControl, il faut planifier nos projets. Pour une barre de progression simple, il existe : TGauge et TProgressBar qui sont respectivement un TGraphicControl et un TWinControl. En fait, TGauge est optimisée et est une alternative simple pour sa tâche. TProgressBar est un composant géré par les DLL de Windows. Windows étant du C++, tous les composants sont nécessairement des fenêtres. Si vous remarquez bien, les composants de l'onglet Standard sont gérés entièrement par Windows. C'est justifié : avec XPman vous pouvez changer leur apparence mais vous n'avez pas pour autant modifié leur code source !!! Dans leur ordre de présentation, vous avez les équivalents C++ suivant : [TMainMenu], [TPopupMenu], Static, Edit, Memo, Button, Button [TCheckBox], Button [TRadioButton], ListBox, Combo, ScrollBar, [TGroupBox], [TRadioGroup], [TPanel]. L'utilisation des fenêtres est indispensable pour cette gestion.

On résumera ainsi : par défaut, choisissez TGraphicControl. Si vous devez gérer des focus, alors vous mettez TCustomControl. De toute façon, passer de l'un à l'autre ne crée pas de problème, car ces classes proposent les mêmes fonctionnalités, au détail près, que TCustomControl est fenêtré et propose des choses en plus.

Note importante : les composants fenêtrés peuvent être espionnés et manipulés par des logiciels parasiteurs. Ainsi leur comportement peut être modifié. Donnons l'exemple d'un bouton désactivé (Enabled:=false) sur lequel l'utilisateur a quand même réussi à cliquer dessus. On verra ça un peu plus tard quand vous aurez bien compris certaines choses. Un TGrapicControl n'est pas sujet à ces problèmes (non fenêtrés, je le rappelle).

Création de propriétés

!!! AVERTISSEMENT !!!
Contrairement au tutorial précédent (N°191), les classes sont ici dérivées, donc ça va changer...

Créons un composant avec une propriété initialisée à TRUE :

unit MonCompo;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
  TMonCompo = class(TGraphicControl) // Nouveau
  private
    FProp : boolean;
  public
    constructor Create(AOwner:TComponent); override; // Nouveau
  published
    property Propriete:boolean read FProp write FProp;
  end;
  procedure Register; // Nouveau
implementation
constructor TMonCompo.Create(AOwner:TComponent);
begin
  inherited Create(AOwner); // Nouveau
  Width:=150; // Nouveau
  Height:=150; // Nouveau
  FProp:=true;
end;
procedure Register; // Nouveau
begin // Nouveau
  RegisterComponents('Exemples', [TMonCompo]); // Nouveau
end; // Nouveau
end.

Il faut toujours initialiser ses variables, sinon ça devient du n'importe quoi.

J'ai mis des commentaires pour signaler les nouveautés :

  • class(TGraphicControl) parce qu'on fait un composant graphique
  • override apparaît seulement ici dans le code. Il indique à Delphi qu'on va remplacer le CREATE de TGraphicControl tout en gardant ce qu'il y avait originellement. L'appel à ce passé se fait via INHERITED suivi de CREATE avec tout son paramétrage. Le paramètre AOwner indique la parenté du composant (de quel composant il dépend afin de pouvoir faire la transmission des messages). Ces liens se schématisent facilement : Form1.Refresh doit permettre aux sous-composants d'être également redessinés.
  • Register est bien un nom de procédure (confus mais habituel) et nécessite la lecture du tutoriel N°86.
  • inherited est déjà expliqué
  • Width et Height (hérités de TGraphicControl) permettent de spécifier une taille minimale du composant quand on le crée. Ainsi, l'utilisateur qui clique une fois sur la palette puis un fois sur la fiche fait comme s'il avait tracé un rectangle de 150x150.

Le destructeur demande aussi un override. Il faut toujours garder à l'esprit que la dérivation nous offre un certain nombre de propriétés qu'il faut préserver avec soins. La preuve est qu'on utilise WIDTH et HEIGHT alors que ça n'apparaît pas directement dans la classe.

Note importante : par habitude, toutes les variables de PRIVATE doivent commencer par la lettre "F".

N'oubliez pas non plus qu'on peut utiliser GetQqch et SetQqch dans les READ et WRITE.

Dessiner le composant

Ça ne marche que pour les composants visuels : TGraphicControl et TCustomControl.

Ça se fait via la procédure Paint écrite dans le PROTECTED.

unit MonCompo;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
  TMonCompo = class(TGraphicControl)
  private
    FProp : boolean;
  protected
    procedure Paint; override;
  public
    constructor Create(AOwner:TComponent); override;
  published
    property Propriete:boolean read FProp write FProp;
  end;
  procedure Register;
implementation
constructor TMonCompo.Create(AOwner:TComponent);
begin
  inherited Create(AOwner);
  Width:=150;
  Height:=150;
  FProp:=true;
end;
procedure TMonCompo.Paint;
begin
  inherited Paint;
  //on dessine
end;
procedure Register;
begin
  RegisterComponents('Exemples', [TMonCompo]);
end;
end.

L'accès à la procédure Paint efface l'arrière-plan automatiquement. Là où est marqué "on dessine", l'arrière-plan est de la couleur du Brush du canevas du composant. Après c'est du dessin stratégique avec les propriétés de base des canevas et les propriétés du composant (passer par les variables F* et non par les publications !!!)

with Canvas do
  begin
    Pen.Width:=1;
    Pen.Color:=clBlack;
    Pen.Style:=psSolid;
    Brush.Color:=clBlack;
    Brush.Style:=bsSolid;
  end;

PEN détermine le contour des formes géométriques. BRUSH détermine leur contenu.

Pour un exemple de dessin, voir (par exemple) le lien suivant. C'est assez transparent.
http://www.delphifr.com/code.aspx?ID=20705

!!! AVERTISSEMENT !!!
Dans la procédure PAINT, n'utilisez jamais les fonctions suivantes. Vous risqueriez de planter Delphi et vos applications (boucle sans fin). Une fois sorti du PAINT, le composant est automatiquement rafraîchi.
Refresh, Invalidate, Update, Repaint

Ailleurs que dans PAINT, l'appel à ces 4 fonctions entraîne l'appel à PAINT.

Propriétés héritées

L'alignement, la visibilité, les PopupMenu... sont des propriétés protégées (PROTECTED) mais qui ne sont disponibles que si on les publie. C'était la petite remarque bizarre du tutorial 191. Il suffit simplement de noter leur nom pour les activer :

unit MonCompo;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
  TMonCompo = class(TGraphicControl)
  private
    FProp : boolean;
  protected
    procedure Paint; override;
  public
    constructor Create(AOwner:TComponent); override;
  published
    property Propriete:boolean read FProp write FProp;
    property Align;
    property Caption;
    property Color;
    property Cursor;
    property DragCursor;
    property DragMode;
    property Enabled;
    property Font;
    property Hint;
    property ParentFont;
    property ParentShowHint;
    property PopupMenu;
    property Visible;
  end;
  procedure Register;
implementation
constructor TMonCompo.Create(AOwner:TComponent);
begin
  inherited Create(AOwner);
  Width:=150;
  Height:=150;
  FProp:=true;
end;
procedure TMonCompo.Paint;
begin
  inherited Paint;
  //on dessine
end;
procedure Register;
begin
  RegisterComponents('Exemples', [TMonCompo]);
end;
end.

Certaines propriétés héritées de TCustomControl ne sont pas disponibles dans TGraphicControl : TabIndex, TabStop...

D'autres ne sont pas disponibles dans certaines versions de Delphi. Il faut alors faire de la compilation conditionnelle via les directives de compilation, qui, comme leur nom l'indique, orchestre l'interprétation du code. Voir pour cela le tutoriel N°78

Événements hérités

On parle de propriétés héritées, mais les évènements s'héritent également. Voici une liste non exhaustive :

property OnClick;
property OnContextPopup;
property OnCreate;
property OnDblClick;
property OnDestroy;
property OnDockDrop;
property OnDockOver;
property OnDragDrop;
property OnDragOver;
property OnEndDock;
property OnEndDrag;
property OnEnter;
property OnHelp;
property OnHide;
property OnHint;
property OnIdle;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnMoved;
property OnPaint;
property OnResize;
property OnScroll;
property OnShow;
property OnShowHint;
property OnStartDock;
property OnStartDrag;
property OnUnDock;

La compatibilité de ces évènements dépendra de quelle classe la dérivation a eu lieu.

Créer ses propres évènements

C'est possible et le code suivant a pu le montrer :
http://codes-sources.commentcamarche.net/source/20705-une-barre-de-compression-et-progression-de-type-winrar-trarbar

Avant de déclarer votre classe, vous déclarez une chose qu'on n'a pas encore fait :

type
  TEvenementPerso = procedure (Sender:TObject ; Param1:integer ; Param2:string) of object;
  TMonCompo = class [...]

C'est forcément un procedure of object. Le fait de noter of object annonce à Delphi que ce sera un évènement personnalisé. Pour déclarer l'évènement, il suffit de compléter la classe avec les éléments nouveaux positionnés de cette manière :

type
  TEvenementPerso = procedure (Sender:TObject ; Param1:integer ; Param2:string) of object;
  TMonCompo = class(TGraphicControl)
  private
    FOnEvent : TEvenementPerso;
  published
    property OnEvenementPersonnel:TEvenementPerso read FOnEvent write FOnEvent;
  end;
[...]
constructor [...] ;
begin
  [...]
  FEvent:=nil;
end;

Tout évènement est initialisé à NIL. En anglais, ça signifie "RIEN". Eh bien, FOnEvent n'est rien, tout en étant quelque chose. Disons plutôt que c'est une valeur spéciale qui dit qu'il n'y a rien au bout du fil.

Pour appeler l'évènement, il faut tester (avec ASSIGNED obligatoirement) avant de lancer. Au moment désiré, faites :

if Assigned(FOnEvent) then FOnEvent(Self,0,'chaîne idiote');

Il est d'usage de toujours mettre un SENDER dans les évènements personnels (tous même), car ça permet souvent de grouper des procédures en une seule (c'est souvent plus lisible). La variable spéciale SELF renvoie l'objet dans lequel on développe. Attention: SELF sert uniquement pour les composants.

Remarquez : TNotifyEvent = procedure(Sender:TObject) of object; (c'est ce qui a été utilisé dans http://www.delphifr.com/code.aspx?ID=20705)

Intercepter les messages

Ce paragraphe concerne uniquement les composants fenêtrés TWinControl et TCustomControl.

Important : Déclarez l'unité MESSAGES dans la clause USES.

Pour intercepter un message, structurez les éléments comme cela :

uses [...], Messages;
type
  TMonCompo = class(TCustomControl)
  private
    procedure WMSize(var Message: TWMSize); message WM_SIZE;
  end;
implementation
procedure TMonCompo.WMSize(var Message: TWMSize);
begin
  inherited;
end;
end.

Le nom de la procedure et le type de la variable Message est directement fonction du nom du message à intercepter. Si le type n'est pas reconnu, utilisez TMessage. TMessage marche dans tous les cas, mais le type dédié permet d'accéder à des propriétés avancées.

Logiciel de création de composants

Je vous propose d'utiliser un logiciel pour éviter de saisir inutilement des dizaines de lignes de code. Vous pouvez le trouver à l'adresse suivante :
http://altert.family.free.fr/fils/prgms/progd11.html
http://altert.family.free.fr/fils/prgms/exes/dlphcmps.zip

Il fonctionne sous 98 et XP, donc sur le reste des versions (normalement). Malgré son titre, il ne fait que créer une structure de base. Vous n'aurez rien de miraculeux avec. Vous gagnerez seulement beaucoup de temps.

C'est moi qui l'ai développé pour me faciliter la vie, et la votre dans le même temps. Vous dézippez tout dans le dossier de votre choix et lancez l'application (complètement propre, c'est à dire que vous pourrez la supprimer et ne laissera aucune trace REG ni INI). Choisissez la version de Delphi qui compilera le composant (une fenêtre s'affiche uniquement si plusieurs versions ont été détectées).

ONGLET GENERAL

  • Donnez le nom de l'unité qui sera créée
  • Donnez la classe de dérivation (parmi les 4 que je vous ai présenté)
  • Choisir la méthode de rafraîchissement (Refresh est correct)
  • Le nom du composant TBidule
  • Donnez la palette de référencement
  • Cliquez sur la flèche à côté de générer.

ONGLET RESSOURCES

  • Sélectionnez les unités à utiliser.
  • Suivant

ONGLET CONSTANTES

  • Elles servent pour qualifier des données générales au projet. Le type sera choisi automatiquement par Delphi.
  • Pour ajouter plus de 4 constantes, remplissez TOUT le tableau, mettez vous sur la dernière case et appuyez sur la flèche du bas de votre clavier.
  • Suivant

ONGLET VARIABLES UNIVERSELLES

  • Elles devront être initialisées.
  • Suivant

ONGLET EVENEMENT

  • Il suffit de cliquer, et vous verrez bien s'ils sont supportés par la classe de dérivation.
  • Suivant

ONGLET PROPRIETES HERITEES

  • Idem
  • Suivant

ONGLET PROPRIETES PERSONNELLES

  • Les colonnes correspondent à la syntaxe suivante. Il est bien sûr conseillé d'avoir une idée poussée des propriétés dont on aura besoin.
  • property [NOM] : [TYPE] read [F*] write Set[NOM];
    [...]
    procedure [CLASSE].Set[NOM] ( Value : [TYPE] );
    begin
      if Value<>[F*] then
        begin
          [F*]:=Value;
          [METHODE DE RAFRAICHISSEMENT];
        end;
    end;
  • Suivant

ONGLET STYLE

  • On n'en a pas parlé, mais il faut laisser les coches par défaut et activer csOPAQUE !!
  • Suivant

ONGLET OPTIONS COMPLEMENTAIRES

  • C'est d'un niveau bien plus fort. Certaines coches sont dédiées au composants fenêtres.
  • Il n'y a pas de suivant.

FINAL

  • Cliquez sur "Générer"

Le composant est presque prêt. Il suffit de la compiler et de corriger les erreurs (à force de cliquer, il y en a forcément).

Pour plus de renseignements sur INHERITED, voir par exemple : http://codes-sources.commentcamarche.net/source/25416-proteger-les-tedit-contre-les-revelateurs-de-mots-de-passe

Conclusion

Comme pour toute chose, il faut avoir de la pratique. Théoriquement, vous avez tout pour créer des composants. Reste ensuite le délicat problème de comprendre le Windows : les messages (important !), les fonctions de Kernel, de User et de GDI. La rubrique Composants de DelphiFr.com propose de bon compos et vous saurez vite en créer, car ça simplifie beaucoup les choses.

Tanguy ALTERT, http://altert.family.free.fr/

Ce document intitulé « [delphi] développer des composants » issu de CodeS SourceS (codes-sources.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.
Rejoignez-nous