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.
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 :
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).
!!! 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 :
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.
Ç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.
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
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.
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)
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.
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).
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;
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
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/