1. Dérivation à partir d’un composant
visuel
1.1 Ajout de méthodes à un composant d’arbre
1.2 Ajout de propriétés au composant d’arbre
2. Construire par association de composants visuels
2.1 Le composant de visualisation d'arbre TWinArbre
2.2 Evénement OnChange de TWinArbre
2.3 Evénement OnMouseDown de TWinArbre
2.4 Le composant final TWinArbre2.4.1 Le composant TWinArbre peut se redimensionner2.5 Le composant de saisie d'expression arithmétique
3. Construire un composant non visuel
Certains paragraphes de ce chapitre sont complétés en liaison avec le code source complet du paragraphe sous deux formes rpérable par les icônes suivantes
pages HTML contenant la unit source Delphi, avec explications.
accès au projet complet sur disque par l'explorateur (projet à exécuter ensuite avec Delphi).
Ce chapitre est consacré
à la production de composants logiciels réutilisables. Nous
allons construire en Delphi trois genres d’exemples de " kits de logiciels
" réutilisables et donc créer trois composants que vous pourrez
améliorer ou modifier. Une fois terminé, chaque composant sera
placé dans la palette des outils composants de Delphi.
Nous proposons encore une fois
une démarche méthodique afin de construire certains de ces "
kits ". Pour des composants visuels, nous nous limitons à la construction
de composants par dérivation de composants existants. Nous montrons
comment ajouter des propriétés ou des méthodes à
un composant existant. Nous montrons aussi comment associer plusieurs composants
visuels de Delphi pour construire un nouveau composant visuel. Nous construisons
à la fin un composant non visuel.
1. Dérivation à partir d’un composant visuel
Nous allons construire
un nouveau composant qui héritera d'une classe déjà existante
de la VCL (Visual Composant Library), nous prendrons un composant visuel
d'arbre. Notre action consistera essentiellemnt à étendre les
fonctionnalités d'un composant déjà existant.
1.1 Ajout de méthodes à un composant d’arbre
Démarche conseillée :
Ce composant est obtenu à partir de l’onglet Win 3.1 :
Nous avons choisi ce composant
pour deux raisons essentielles :
Nous désirons que notre
composant affiche toutes les branches d’un arbre quelconque jusqu’à
un niveau de profondeur donné. Appliquons la démarche précédente.
Projet méthode - 1 : le programme Delphi |
Nous écrivons 3 procédures pascal permettant d’effectuer cette action :
procedure affiche_racine(tree:TOutline)
{remonte à la racine d'où
que l'on soit}
niveau = 0 |
niveau = 1 |
niveau = 2 |
niveau = 3 |
niveau = 4 |
Projet méthode - 2 : la classe Delphi |
Delphi.Composants
\Methode.Tree\Outline1.avant
&
Delphi.Composants
\Methode.Tree\ Outline1.avant
On crée une classe Ttree2 dérivée du Toutline.
TTree2 = class(TOutline)
{la nouvelle classe TTree dérivée de Toutline} private { Déclarations private } public { Déclarations public } procedure affiche_un_niveau(le_niveau:integer); {méthode publique} end; |
On déclare un objet de classe Ttree2.
var new_compos:TTree2; {objet Ttree2 déclaré} |
On instancie l’objet de classe Ttree2.
procedure TForm1.FormCreate(Sender:
TObject); begin app_path:=ExtractFilePath(Application.ExeName); new_compos:=TTree2.create(self); {objet Ttree2 créé} new_compos.parent:=self; {objet Ttree2 affichable} new_compos.setbounds(8,8,233,233) end; |
new_compos.affiche_un_niveau(3);
{appel de méthode de l'objet pour affichage niveau = 3} |
Projet méthode - 3 : le composant Delphi |
Delphi.Composants \Methode.Tree\Outline2.après
&
Delphi.Composants
\Methode.Tree\ Outline2.après
On reprend la classe Ttree2 dérivée du Toutline
Il suffit de rajouter un constructeur
d’objet à la classe :
TTree2 = class(TOutline)
{la nouvelle classe TTree dérivée de Toutline} private { Déclarations private } public { Déclarations public } constructor Create(Aowner:Tcomponent);override; procedure affiche_un_niveau(le_niveau:integer); {méthode publique} end; |
Dans le constructeur nous reprenons
le code du gestionnaire Oncreate de la fiche.
constructor
TTree2.Create(Aowner:Tcomponent); {remplace le create dans l'étape précédente} begin inherited create(Aowner); self.setbounds(8,8,233,233); {position : left,top,width,height} end; |
procedure register;
|
1.2 Ajout de propriétés au composant d’arbre
Démarche conseillée :semblable
à la précédente
Nous désirons que notre composant affiche toutes les branches d’un arbre quelconque jusqu’à un niveau de profondeur donné. Nous allons implanter cette action à partir d’une propriété Delphi et non plus d’une méthode. Nous allons ainsi voir la puissance et la facilité fournies par le RAD.
Projet propriété - 1 : le programme Delphi |
Delphi.Composants
\Propriete.Tree \Outline0.sans
&
Delphi.Composants
\Propriete.Tree\Outline0.sans
Le programme est strictement le même que celui du paragraphe précédent. Nous partons donc sur les mêmes bases.
procedure affiche_racine(tree:TOutline);
{remonte à la racine d'où
que l'on soit}
procedure lire_un_niveau(rac:Toutline;indice:integer;niveau:integer);
{descente recursive en préordre
sur un outline}
Projet propriété - 2 : la classe Delphi |
On crée une classe Ttree1 dérivée du Toutline.
TTree1 = class(TOutline)
{la nouvelle classe Ttree1 dérivée de Toutline} private Fniveau:integer; function Getmaxniveau:integer; {méthode interne} procedure affiche_un_niveau(le_niveau:integer);{méthode interne} public property profondeur:integer read Getmaxniveau; property show_niveau:integer read Fniveau write affiche_un_niveau; end; |
Les deux propriétés
Afin de montrer au lecteur les possibilités
de lecture et d’écriture des propriétés, nous avons
rajouté à la classe une nouvelle fonctionnalité. Nous
souhaitons pouvoir consulter pour un arbre donné la valeur de sa profondeur.
Cette action est implantée à travers la propriété
profondeur qui est en lecture seulement (puisqu’elle est uniquement consultable).
property profondeur:integer read Getmaxniveau; |
La propriété profondeur lorsqu’elle sera lue lors de l’exécution fera appel à la méthode Getmaxniveau. Cette méthode doit être une fonction et doit renvoyer un résultat du même type que la propriété (ici un integer).
La méthode interne Getmaxniveau
est construite par nos soins.
En voici un exemple de code :
function TTree1.Getmaxniveau:integer;
{donne la profondeur maximum de l'arbre profondeur racine=0} var i,max:integer; begin if self.Itemcount<>0 then begin max:=1; for i:=1 to self.Itemcount do if max<self.Items[i].level then max:=self.Items[i].level; Getmaxniveau:=max-1; end else Getmaxniveau:=0 end; |
Cette particularité est intéressante puisque nous voyons dans l’exemple que c’est au moment où l’on appelle Getmaxniveau que le calcul de la profondeur est effectué. Il s’agit d’une action dynamique qui nous assure quelle que soit la modification apportée à l’arbre, nous aurons toujours sa profondeur réelle.
Les propriétés en Delphi sont donc comme des médias permettant d’accéder à des informations à travers elles.
Il est toujours possible de faire
fonctionner une propriété comme un champ statique lorsque l’on
ne veut pas lire à travers une fonction. Il faut alors utiliser une
variable du même type que la propriété. Pour des raisons
de sécurité, nous conseillons au lecteur de mettre cette variable
dans la partie privée.
Exemple fictif basé
sur la propriété profondeur :
private
ValeurProf : integer ; ..... property profondeur:integer read ValeurProf; |
Nous avons repris ce fonctionnement
dans la deuxième propriété " show_niveau " chargée
de provoquer l’affichage de l’arbre sur tout un niveau fixé.
property show_niveau:integer read Fniveau write Affiche _un_niveau; |
Elle possède la même
particularité d’être écrite à travers une méthode
qui doit être en Delphi une procédure avec un seul paramètre
transmis par adresse ou par valeur mais du même type que la propriété.
Exemple pour show_niveau :
property show_niveau:integer
read Fniveau write Affiche _un_niveau; ..... procedure TTree1.Affiche_un_niveau(le_niveau:integer);
|
Si nous ne souhaitons pas utiliser
cette fonctionnalité, comme nous l’avons vu dans le cas de la propriété
profondeur, il nous suffit d’utiliser en écriture le champ statique
privé Fniveau qui nous sert déjà pour la lecture.
property show_niveau:integer read Fniveau write Fniveau; |
var new_compos:TTree1; {objet Ttree1 déclaré} ...... //On programme le code du gestionnaire de création de la fiche. procedure TForm1.FormCreate(Sender:
TObject); |
On utilise les propriétés de l’objet instancié.
Var x :integer ;
.... X :=new_compos.profondeur
; { utilisée en mode lecture} |
Projet propriété - 3 : le composant Delphi |
Delphi.Composants \Propriete.Tree \Outline2.après
&
Delphi.Composants
\Propriete.Tree \ composant
On reprend la classe Ttree1 dérivée du Toutline
Il suffit d’ajouter un constructeur
d’objets à la classe :
TTree1 = class(TOutline)
{la classe Ttree1 dérivée de Toutline} private Fniveau:integer; function Getmaxniveau:integer; {méthode interne} procedure affiche_un_niveau(le_niveau:integer);{méthode interne} public constructor Create(Aowner:Tcomponent);override; property profondeur:integer read Getmaxniveau; property show_niveau:integer read Fniveau write affiche_un_niveau; end; |
Identiquement à l’exemple
précédent, nous remplaçons le code du gestionnaire de
création de la fiche par le code du constructeur d’objets de la classe(Create).
constructor TTree1.Create(Aowner:Tcomponent);
{remplace le create dans l'étape précédente} begin inherited create(Aowner); self.setbounds(8,8,233,233); {position : left,top,width,height} end; |
procedure register;
|
2. Construire par association de composants visuels
Nous nous proposons ici
de fournir trois exemples de composants visuels construits par association
(agrégation) d'autres composants visuels.
Information sur les 3 exemples
TWinArbre, TExpraritm, TEditListe
2.1 Le composant de visualisation d'arbre
Nous montrons au lecteur comment
associer trois composants visuels existants pour n’en former qu’un seul que
nous notons TwinArbre. Nous reprenons
comme base le composant standard d’arbre : le Toutline, auquel nous
ajoutons :
La démarche reste fondamentalement
la même que celle que nous avons utilisée pour les deux exemples
précédents. Ceci nous permet d’avoir une base simple, suffisante
en initiation, d’élaboration de nouveaux composants.
Lorsque nous voulons construire
un tel assemblage de composants il nous faut remonter dans la hiérarchie
des classes. Delphi nous conseille de faire dériver notre futur composant
TwinArbre systématiquement de la classe TcustomControl
:
Nous vous livrons ci-après
le composant TwinArbre en utilisant la version adjonction des
propriétés. Nous élargissons les fonctionnalités
par des nouvelles propriétés de l’ensemble des 3 composants
associés.
La property Potentiometre
est reliée au composant TTrackBar. La property Tree est reliée au composant TOutline. La property profondeur est reliée au composant TOutline. La property Enabled est reliée aux trois composants. La property Lignes est reliée au composant TOutline. La property Couleur est reliée au composant TOutline. La property Ascenceur est reliée au composant TOutline. |
Syntaxe Delphi : |
property Potentiometre:
TTrackBar read FPotentiometre; property Tree: Toutline read FTree write FTree; property Profondeur:integer read GetProfondeur; property Enabled:boolean read Getenabled write Setenabled; property Lignes: Tstrings read GetLignes write SetLignes; property Couleur: TColor read GetCouleur write SetCouleur; property Ascenceur: TScrollStyle read GetAscenceur write SetAscenceur ; |
Afin de donner un vue un peu
plus élargie de la construction de tels composants, nous avons programmé
deux événements dans notre composant TwinArbre :
un événement associé au Toutline et un événement
associé au TTrackBar. Le lecteur pourra s’inspirer de ce développement
pour écrire son code personnel sur d’autres événements.
2.2 Evénement OnChange de TWinArbre
Nous avons programmé la réaction de notre composant à la manipulation de la glissière sur la barre graduée. Nous avons choisi l’événement OnChange du TTrackBar.
Cet événement permet d’afficher tout un niveau de l’arbre du Toutline lorsque l’utilisateur actionne la glissière.
La valeur du niveau est affichée
dans le Tedit.
Comme pour l’événement
OnMouseDown, le gestionnaire d’événement associé doit
avoir l’en-tête obligatoire d’un gestionnaire d’événement
OnChange. Il a été nommé PotentiometreChange.Voici
son en-tête :
procedure PotentiometreChange(Sender: TObject); |
Nous avons programmé une réaction spécifique de notre composant sur un click du bouton droit de la souris. Nous avons choisi l’événement OnMouseDown du Toutline.
Cet événement
permet d’afficher seulement le chemin partant de la racine vers la feuille
ou le noeud sélectionné :
on sélectionne "deux011"
le chemin racine\deux\deux01\deux011
est affiché visuellement, toutes les autres branches inutiles visuellement
sont refermées.
Le gestionnaire d’événement
associé a été nommé ArbreMouseDown. Il
doit avoir l’en-tête obligatoire d’un gestionnaire d’événement
OnMouseDown. Voici son en-tête :
procedure ArbreMouseDown(Sender:
TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); |
2.4 Le composant final TWinArbre
Delphi.Composants \Compos.TwinArbre
&
Delphi.Composants
\Compos.TwinArbre
&
Delphi.Composants
\Compos.TwinArbre\Lecomposant
Voici la classe du composant TwinArbre :
TwinArbre = class(TCustomControl)
private FPotentiometre: TTrackBar; FEdit1: TEdit; FTree:Toutline; OldColor:TColor; procedure WMSize(var Message:TWMsize);message WM_SIZE; function Getmaxniveau:integer; function GetProfondeur:integer; function GetEnabled:boolean; procedure SetEnabled(x:boolean); function GetLignes:Tstrings; procedure SetLignes(x:Tstrings); function GetCouleur:TColor; procedure SetCouleur(x:TColor); function GetAscenceur:TScrollStyle; procedure SetAscenceur(x:TScrollStyle); procedure affiche_un_niveau(le_niveau:integer); procedure affiche_racine; procedure lire_un_niveau(indice:integer;niveau:integer); procedure CalCulChemin(Noeud:ToutLineNode;Liste:TStringList); procedure ArbreMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure PotentiometreChange(Sender: TObject); public Liste: TStringList; constructor Create(Aowner:Tcomponent);override; property Potentiometre: TTrackBar read FPotentiometre; property Tree: Toutline read FTree write FTree; property Profondeur:integer read GetProfondeur; published property Enabled:boolean read Getenabled write Setenabled; property Lignes: Tstrings read GetLignes write SetLignes; property Couleur: TColor read GetCouleur write SetCouleur; property Ascenceur: TScrollStyle read GetAscenceur write SetAscenceur; end; |
Voici son constructeur d’objet
:
constructor TwinArbre.Create(Aowner:Tcomponent);
begin inherited create(Aowner); OldColor:=clWindow; setbounds(10,10,200,200); Ftree:=Toutline.create(self); Ftree.parent:=self; Ftree.setbounds(0,0,width,height-25); FTree.OnMouseDown:=ArbreMouseDown; Ftree.color:=OldColor; FPotentiometre:=TTrackBar.create(self); FPotentiometre.parent:=self; FPotentiometre.setbounds(0,FTree.Top+FTree.Height,width-25,25); FEdit1:=TEdit.create(self); FEdit1.parent:=self; FEdit1.color:=OldColor; if Getmaxniveau>0 then FPotentiometre.Max:=Getmaxniveau-1 else FPotentiometre.Max:=0; FPotentiometre.Min:=0; FPotentiometre.Position:=0; FPotentiometre.LineSize:=1; FPotentiometre.PageSize:=1; FPotentiometre.TickMarks:=tmTopLeft; FPotentiometre.OnChange:=PotentiometreChange; FEdit1.setbounds(FPotentiometre.left+FPotentiometre.width,Ftree.Top+Ftree.Height,25,25); FEdit1.text:=inttostr(FPotentiometre.Min); FEdit1.ReadOnly:=true; Liste:=TStringList.create; end; |
2.4.1 Le composant TWinArbre peut se redimensionner
Nous avons rajouté, à l’intention du lecteur désireux de pouvoir modifier la taille de son composant lors de la conception, une méthode interne spécifique de redimensionnement:
Delphi.Composants \Retailler.Tree
&
Delphi.Composants
\Retailler.Tree(composant et test)
private
procedure WMSize(var Message:TWMsize);message WM_SIZE; ...... procedure TwinArbre.WMSize(var
Message:TWMsize); |
Sans vouloir trop entrer dans de la technicité inutile et spécifique à ce RAD, indiquons que nous avons intercepté le message de modification de taille. Nous avons conçu tous les composants en positionnement relatif les uns par rapport aux autres en prenant comme référence de départ le Toutline Ftree. L’écriture " Ftree.setbounds(0,0,width,height-25) " indique que le Toutline est positionné en top=0, left=0 du composant, qu’il a toute la largeur du composant (Width) et que sa hauteur est celle du composant moins 25 pixels (height-25). Il faut donc dessiner sur le papier soigneusement le composant avant de l’implanter.
En fait cette attitude permet
d’avoir une sorte d’homothétie sur les différents éléments
visuels de TwinArbre. Le lecteur pourra se servir de ce composant,
il possède alors, une base de travail à enrichir soit par de
nouvelles propriétés, soit par des réactions à
de nouveaux événements.
2.5 Le composant de saisie d'expression arithmétique
Delphi.Composants \Compos.exprarit
Le lecteur trouvera un dossier sur le CD-ROM contenant un autre composant de saisie des expressions arithmétiques, élaboré à partir de plusieurs composants visuels associés. Son développement met en œuvre la démarche du paragraphe précédent associée à une saisie par filtrage avec un analyseur descendant récursif qui est fourni dans la unit du composant.
3. Construire un composant non visuel
En utilisant notre démarche
de construction progressive et de tests successifs, nous pouvons sans effort
particulier encapsuler dans un composant non visuel beaucoup d’unités
réutilisables. Afin de ne pas ennuyer le lecteur avec un nouvel exemple,
nous reprenons la structure de donnée de liste déjà étudiée
au chapitre " approches abstraites pour les listes de chaînes ".
Nous avions déjà
fourni par anticipation dans ce chapitre précédent un composant
de liste visuelle dérivée du TListBox. Le lecteur pourra s’y
reporter s’il le souhaite. Nous avons aussi déjà fourni une
classe non visuelle dérivée du TStringList que le lecteur trouvera
sur le CD-ROM dans
Delphi.ListeChaines
\Liste2ObjBase.Dlfi. L’effort de construction que nous avons à apporter
est alors minimal puisqu’il concerne uniquement la transformation de la classe
en un composant.
Libellé de la classe
:
T0=string ; Tliste =class(TstringList) public procedure init_liste; function longueur : integer; procedure supprimer (k: integer); procedure inserer (k: integer; x: T0); function kieme (n: integer): T0; function Test (x: T0): boolean; procedure Rechercher (x: T0; var place: integer); procedure creationListe(Box:TListBox); procedure Ajouter(x:T0); procedure effacer; private function Est_vide:boolean; end; |
Le composant construit à partir de la classe de composant abstrait TComponent
La seule différence avec
les exemples précédents réside dans la classe de départ
dont nous devons faire hériter notre futur composant. En Delphi nous
devons dériver notre composant non visuel de la classe Tcomponent.
La classe TComponent est le point de départ abstrait de tous
les composants de Delphi et nous devons remonter à ce niveau lorsque
nous voulons construire un composant qui ne sera pas un contrôle.
TListe = class(TComponent) |
Comme nous voulons malgré
tout bénéficier des propriétés et des méthodes
de la classe TStringList, nous conseillons la démarche suivante :
TListe = class(TComponent)
private FListGeneric:TstringList; ..... public .... property ListGeneric:TstringList read FListGeneric; |
Si l’on veut lire et écrire
dans la liste à travers la propriété, voici son libellé
:
property ListGeneric:TstringList read FListGeneric write FListGeneric; |
Exemple complet de composant
pour Tliste :
TListe = class(TComponent)
private FListGeneric:TstringList; function Getcount:integer; function GetDuplic:boolean; procedure SetDuplic(x:boolean); function Getsorted:boolean; procedure Setsorted(x:boolean); public constructor create(Aowner:Tcomponent);override; destructor Liberer; {opérateurs de manipulation de la liste} procedure Effacer; function Element(rang:integer):string; function Position(element:string):integer; procedure Ajouter(ch:string); procedure Inserer(element:string;rang:integer); procedure Modifier(rang:integer;element:string); procedure Supprimer(rang:integer); {opérateurs de recopie d'une liste} procedure EstUneCopiede(T:TListe); procedure VersListBox(LBox:TListBox); {opérateurs d'entrée/sortie dans une liste} procedure Charger; procedure Sauver; {propriétés publiques, uniquement consultables} property Quantite:integer read Getcount ; property ListGeneric:TstringList read FlistGeneric write FListGeneric; published property Dupliquee:boolean read GetDuplic write SetDuplic; property Triee:boolean read Getsorted write Setsorted; end; |
Le source complet comprenant l'implementation de ce composant se trouve dans :
Delphi.Composants \ Compos.ListeNonVisu\Composant
&
Delphi.Composants
\Compos.ListeNonVisu\Composant
(l’icône
du composant est située dans UListecompos.dcr).
Les propriétés et
les méthodes sont fournies à titre d’exemple pédagogique.
Nous encourageons le lecteur à réécrire et à développer
lui-même à partir du composant Tliste un composant personnalisé
de liste de chaînes.
3.2 Utilisation du composant TListe
Delphi.Composants
\ Compos.ListeNonVisu\PoidsListe.Dlfi
Il s’agit d’une interface de stockage dans trois listes de type Tliste de la taille et du poids idéal d’un individu (à partir d’un exemple d’un ouvrage sur Visual basic) :
Au fur et à mesure de la demande de l’utilisateur, le programme range dans la liste " ListeIndiv " les informations sur la personne. Il est possible à chaque instant de construire à partir de cette liste deux sous listes selon le sexe des individus ( ListeHomme et ListeFemme ).
Le logiciel " PoidsListe.Dlfi
" assure un certain niveau de sécurité en utilisant les principes
de programmation défensive étudiés au chapitre correspondant.
3.3 Un débogueur pour le composant TListe
Pour terminer nous livrons au lecteur un logiciel à compléter.
Un composant d’interface de débogage
pour composant Tliste noté " UEditListeCompos " que vous pouvez insérer
et utiliser n’importe quand lorsque vous voulez explorer un Tliste en cours
d’exécution.
Ce composant de debugging
" UEditListeCompos " est à améliorer et à adapter à
vos besoins et se trouve entièrement développé en trois
étapes dans :