You are on page 1of 194

Guide du concepteur

de composants

Borland®

Delphi 7
pour Windows™
Reportez-vous au fichier DEPLOY situé dans le répertoire racine de votre produit Delphi 7 pour obtenir la liste
complète des fichiers que vous pouvez distribuer conformément aux termes du contrat de licence de Delphi.
Les applications mentionnées dans ce manuel sont brevetées ou en attente de brevet. Ce document ne donne
aucun droit sur ces brevets. Reportez-vous au CD du produit ou à la boîte de dialogue A propos.
COPYRIGHT © 1983–2002 Borland Software Corporation. Tous droits réservés. Tous les produits Borland sont
des marques commerciales ou des marques déposées de Borland Software Corporation aux Etats-Unis ou dans
les autres pays. Toutes les autres marques sont la propriété de leurs fabricants respectifs.

D7–CWG–0802
Table des matières
Chapitre 1 Ancêtres, descendants et hiérarchies
des classes . . . . . . . . . . . . . . . . . . . . . 2-3
Présentation générale de la création Contrôle des accès . . . . . . . . . . . . . . . . . 2-4
d’un composant 1-1 Masquer les détails d’implémentation . . . . 2-5
Bibliothèque de classes. . . . . . . . . . . . . . 1-1 Définition de l’interface avec le concepteur
Composants et classes. . . . . . . . . . . . . . . 1-2 des composants . . . . . . . . . . . . . . . . 2-6
Création de composants . . . . . . . . . . . . . 1-3 Définition de l’interface d’exécution . . . . . 2-6
Modification de contrôles existants . . . . . 1-3 Définition de l’interface de conception. . . . 2-7
Création de contrôles fenêtrés . . . . . . . . 1-4 Répartition des méthodes . . . . . . . . . . . . . 2-8
Création de contrôles graphiques . . . . . . 1-4 Méthodes statiques . . . . . . . . . . . . . . . 2-8
Sous-classement de contrôles Windows. . . 1-5 Exemple de méthodes statiques . . . . . . 2-8
Création de composants non visuels . . . . 1-5 Méthodes virtuelles . . . . . . . . . . . . . . . 2-9
Contenu d’un composant ?. . . . . . . . . . . . 1-5 Redéfinition des méthodes . . . . . . . . . 2-9
Suppression des dépendances . . . . . . . . 1-6 Méthodes dynamiques . . . . . . . . . . . . 2-10
Définition des propriétés, méthodes Membres abstraits d’une classe . . . . . . . . . 2-10
et événements . . . . . . . . . . . . . . . . . 1-6 Classes et pointeurs . . . . . . . . . . . . . . . 2-10
Propriétés . . . . . . . . . . . . . . . . . . 1-6
Méthodes . . . . . . . . . . . . . . . . . . 1-7 Chapitre 3
Evénements . . . . . . . . . . . . . . . . . 1-7 Création de propriétés 3-1
Encapsulation des graphiques . . . . . . . . 1-8
Pourquoi créer des propriétés ?. . . . . . . . . . 3-1
Recensement des composants . . . . . . . . 1-9
Types de propriétés. . . . . . . . . . . . . . . . . 3-2
Création d’un nouveau composant . . . . . . . 1-9
Publication des propriétés héritées . . . . . . . . 3-3
Création d’un composant avec
Définition des propriétés . . . . . . . . . . . . . 3-4
l’expert composant . . . . . . . . . . . . . . 1-10
Déclarations des propriétés . . . . . . . . . . 3-4
Création manuelle d’un composant . . . . . 1-12
Stockage interne des données . . . . . . . . . 3-4
Création d’un fichier unité . . . . . . . . 1-12
Accès direct . . . . . . . . . . . . . . . . . . . 3-5
Dérivation du composant . . . . . . . . . 1-13
Méthodes d’accès . . . . . . . . . . . . . . . . 3-5
Recensement du composant. . . . . . . . 1-13
Méthode read . . . . . . . . . . . . . . . . 3-7
Création de bitmaps pour les composants . 1-14
Méthode write . . . . . . . . . . . . . . . . 3-7
Installation d’un composant
Valeurs par défaut d’une propriété . . . . . . 3-8
dans la palette de composants . . . . . . . . . 1-16
Spécification d’aucune valeur par défaut 3-8
Emplacement des fichiers du composant . . 1-17
Création de propriétés tableau . . . . . . . . . . 3-9
Test des composants non installés. . . . . . . . 1-17
Création de propriétés
Test des composants installés . . . . . . . . . . 1-19
pour sous-composants . . . . . . . . . . . . . 3-10
Création des propriétés pour interfaces . . . . 3-11
Chapitre 2 Stockage et chargement des propriétés . . . . 3-12
Programmation orientée objet Utilisation du mécanisme de stockage
et écriture des composants 2-1 et de chargement . . . . . . . . . . . . . . 3-12
Définition de nouvelles classes . . . . . . . . . 2-1 Spécification des valeurs par défaut . . . . 3-13
Dérivation de nouvelles classes . . . . . . . 2-2 Détermination du stockage . . . . . . . . . 3-14
Modification des valeurs par défaut Initialisation après chargement . . . . . . . 3-14
d’une classe pour éviter les répétitions 2-2 Stockage et chargement
Ajout de nouvelles capacités des propriétés
à une classe . . . . . . . . . . . . . . . . 2-3 non publiées . . . . . . . . . . . . . . . . . 3-15
Déclaration d’une nouvelle classe Création de méthodes pour le stockage et le
de composant . . . . . . . . . . . . . . . . . 2-3 chargement de valeurs de propriétés . 3-15

i
Redéfinition de la méthode Chapitre 6
DefineProperties . . . . . . . . . . . . . 3-16
Graphiques et composants 6-1
Présentation des graphiques . . . . . . . . . . . 6-1
Chapitre 4 Utilisation du canevas . . . . . . . . . . . . . . . 6-3
Création d’événements 4-1 Travail sur les images . . . . . . . . . . . . . . . 6-3
Qu’est-ce qu’un événement ? . . . . . . . . . . 4-1 Utilisation d’une image, d’un graphique
Les événements sont des pointeurs ou d’un canevas . . . . . . . . . . . . . . . . 6-4
de méthodes. . . . . . . . . . . . . . . . . . 4-2 Chargement et stockage des graphiques . . . 6-4
Les événements sont des propriétés . . . . . 4-2 Gestion des palettes. . . . . . . . . . . . . . . 6-5
Les types d’événements sont des types Spécification d’une palette
de pointeurs de méthodes. . . . . . . . . . 4-3 pour un contrôle . . . . . . . . . . . . . . 6-6
Les types gestionnaire d’événement Bitmaps hors écran . . . . . . . . . . . . . . . . . 6-6
sont des procédures . . . . . . . . . . . 4-3 Création et gestion des bitmaps hors écran . 6-6
Les gestionnaires d’événements Copie des images bitmap . . . . . . . . . . . 6-7
sont facultatifs . . . . . . . . . . . . . . . . 4-4 Réponse aux changements . . . . . . . . . . . . 6-7
Implémentation des événements standard . . . 4-5
Identification des événements standard. . . 4-5 Chapitre 7
Evénements standard Gestion des messages
pour tous les contrôles . . . . . . . . . . 4-5
Evénements standard pour les contrôles
et des notifications système 7-1
Compréhension du système de gestion
standard . . . . . . . . . . . . . . . . . . 4-5
des messages. . . . . . . . . . . . . . . . . . . . 7-1
Rendre visibles des événements . . . . . . . 4-6
Que contient un message Windows ? . . . . 7-2
Changement de la gestion
Répartition des messages . . . . . . . . . . . 7-3
des événements standard . . . . . . . . . . 4-6
Suivi du flux des messages . . . . . . . . 7-3
Définition de vos propres événements . . . . . 4-7
Modification de la gestion des messages . . . . 7-4
Déclenchement de l’événement . . . . . . . 4-7
Surcharge de la méthode du gestionnaire . . 7-4
Deux sortes d’événements. . . . . . . . . 4-8
Utilisation des paramètres d’un message . . 7-4
Définition du type de gestionnaire . . . . . 4-8
Interception des messages . . . . . . . . . . . 7-5
Notifications simples . . . . . . . . . . . . 4-8
Création de nouveaux gestionnaires
Gestionnaires d’événements spécifiques. 4-8
de messages . . . . . . . . . . . . . . . . . . . . 7-6
Renvoi d’informations
Définition de vos propres messages . . . . . 7-6
à partir du gestionnaire . . . . . . . . . 4-9
Déclaration d’un identificateur
Déclaration de l’événement . . . . . . . . . . 4-9
de message . . . . . . . . . . . . . . . . . 7-6
Les noms d’événement débutent
Déclaration d’un type enregistrement
par “On” . . . . . . . . . . . . . . . . . . 4-9
de message . . . . . . . . . . . . . . . . . 7-6
Appel de l’événement . . . . . . . . . . . . . 4-9
Déclaration d’une nouvelle méthode
de gestion d’un message . . . . . . . . . . . 7-7
Chapitre 5 Envoi des messages . . . . . . . . . . . . . . . 7-8
Création de méthodes 5-1 Diffusion d’un message à tous
Eviter les interdépendances . . . . . . . . . . . 5-1 les contrôles d’une fiche . . . . . . . . . . 7-8
Noms des méthodes. . . . . . . . . . . . . . . . 5-2 Appel direct du gestionnaire
Protection des méthodes . . . . . . . . . . . . . 5-3 de message d’un contrôle. . . . . . . . . 7-9
Méthodes qui doivent être publiques . . . . 5-3 Envoi d’un message à l’aide de la file
Méthodes qui doivent être protégées . . . . 5-3 d’attente des messages Windows . . . 7-10
Méthodes abstraites . . . . . . . . . . . . . . 5-4 Envoi d’un message qui ne s’exécute
Rendre virtuelles des méthodes . . . . . . . . . 5-4 pas immédiatement . . . . . . . . . . . 7-10
Déclaration des méthodes . . . . . . . . . . . . 5-4 Réponse aux notifications du système
à l’aide de CLX . . . . . . . . . . . . . . . . . 7-10
Réponse aux signaux . . . . . . . . . . . . . 7-11

ii
Affectation de gestionnaires de signaux Chapitre 9
personnalisés . . . . . . . . . . . . . . . 7-12
Réponse aux événements système . . . . . . 7-12
Modification d’un composant
Evénements couramment utilisés . . . . 7-13 existant 9-1
Surcharge de la méthode EventFilter . . 7-15 Création et recensement du composant . . . . . 9-1
Génération des événements Qt . . . . . . 7-16 Modification de la classe composant. . . . . . . 9-2
Redéfinition du constructeur . . . . . . . . . 9-2
Chapitre 8 Spécification de la nouvelle valeur
Accessibilité des composants par défaut de la propriété . . . . . . . . . . 9-3

au moment de la conception 8-1 Chapitre 10


Recensement des composants . . . . . . . . . . 8-1
Déclaration de la procédure Register . . . . 8-2
Création d’un contrôle graphique 10-1
Création et recensement du composant . . . . 10-1
Ecriture de la procédure Register . . . . . . 8-2
Publication des propriétés héritées . . . . . . . 10-2
Spécification des composants . . . . . . . 8-3
Ajout de fonctionnalités graphiques . . . . . . 10-3
Spécification de la page de palette . . . . 8-3
Détermination de ce qui doit être dessiné . 10-3
Utilisation de la fonction
Déclaration du type de la propriété. . . 10-3
RegisterComponents . . . . . . . . . . . 8-3
Déclaration de la propriété . . . . . . . . 10-4
Fournir l’aide pour vos composants . . . . . . 8-4
Ecriture de la méthode
Création du fichier d’aide. . . . . . . . . . . 8-4
d’implémentation . . . . . . . . . . . . 10-4
Création des entrées . . . . . . . . . . . . 8-4
Redéfinition du constructeur
Aide contextuelle des composants . . . . 8-6
et du destructeur . . . . . . . . . . . . . . 10-5
Ajout des fichiers d’aide
Modification des valeurs par défaut
des composants . . . . . . . . . . . . . . 8-6
des propriétés . . . . . . . . . . . . . . 10-5
Ajout d’éditeurs de propriétés . . . . . . . . . . 8-7
Publication du crayon et du pinceau . . . . 10-5
Dérivation d’une classe éditeur
Déclaration des champs de classe . . . . 10-6
de propriétés . . . . . . . . . . . . . . . . . 8-7
Déclaration des propriétés d’accès . . . 10-6
Modification de la propriété
Initialisation des classes ayant
sous une forme textuelle . . . . . . . . . . 8-8
un propriétaire . . . . . . . . . . . . . . 10-7
Affichage de la valeur de la propriété . . 8-9
Définition des propriétés des classes
Définition de la valeur de la propriété . 8-9
ayant un propriétaire . . . . . . . . . . 10-8
Modification globale de la propriété . . . . 8-10
Dessin de l’image du composant . . . . . . 10-8
Spécification des attributs de l’éditeur . . . 8-11
Adaptation du dessin de la forme . . . . 10-10
Recensement de l’éditeur de propriétés. . . 8-12
Catégories de propriétés . . . . . . . . . . . . . 8-13 Chapitre 11
Recensement d’une propriété à la fois . . . 8-14
Recensement de plusieurs propriétés Personnalisation d’une grille 11-1
en une seule fois . . . . . . . . . . . . . . . 8-14 Création et recensement du composant . . . . 11-1
Spécification de catégories de propriétés . . 8-15 Publication des propriétés héritées . . . . . . . 11-3
Utilisation de la fonction Modification des valeurs initiales . . . . . . . 11-3
IsPropertyInCategory . . . . . . . . . . . . 8-16 Redimensionnement des cellules . . . . . . . . 11-4
Ajout d’éditeurs de composants . . . . . . . . . 8-16 Remplissage des cellules. . . . . . . . . . . . . 11-5
Ajout d’éléments au menu contextuel. . . . 8-17 Suivi de la date . . . . . . . . . . . . . . . . 11-6
Spécification d’éléments de menu . . . . 8-17 Stockage interne de la date. . . . . . . . 11-6
Implémentation des commandes . . . . . 8-18 Accès au jour, au mois et à l’année . . . 11-7
Modification du comportement Génération des numéros de jours . . . . 11-8
suite à un double-clic . . . . . . . . . . . . 8-18 Sélection du jour en cours . . . . . . . .11-10
Ajout de formats de presse-papiers . . . . . 8-19 Navigation de mois en mois
Recensement d’un éditeur de composants . 8-20 et d’année en année . . . . . . . . . . . . . .11-11
Compilation des composants en paquets. . . . 8-20 Navigation de jour en jour . . . . . . . . . . .11-12
Déplacement de la sélection . . . . . . . . .11-12

iii
Fourniture d’un événement OnChange . . 11-13 Chapitre 13
Exclusion des cellules vides . . . . . . . . 11-13
Transformation d’une boîte de dialogue
Chapitre 12 en composant 13-1
Définition de l’interface du composant . . . . 13-2
Contrôles orientés données 12-1 Création et recensement du composant . . . . 13-2
Création d’un contrôle pour scruter Création de l’interface du composant . . . . . 13-3
les données . . . . . . . . . . . . . . . . . . . . 12-2 Inclusion de l’unité de la fiche . . . . . . . 13-3
Création et recensement du composant . . . 12-2 Ajout des propriétés de l’interface . . . . . 13-4
Fonctionnement du contrôle en lecture Ajout de la méthode Execute . . . . . . . . 13-5
seulement . . . . . . . . . . . . . . . . . . . 12-3 Test du composant . . . . . . . . . . . . . . . . 13-6
Ajout de la propriété ReadOnly . . . . . 12-4
Autorisation des mises à jour Chapitre 14
nécessaires . . . . . . . . . . . . . . . . . 12-4 Extensions de l’EDI 14-1
Ajout du lien aux données . . . . . . . . . . 12-5 Présentation de l’API Tools . . . . . . . . . . . 14-2
Déclaration du champ de classe . . . . . 12-6 Conception d’une classe expert . . . . . . . . . 14-3
Déclaration des propriétés d’accès . . . . 12-6 Implémentation des interfaces de l’expert . 14-4
Exemple de déclaration des propriétés Installation du paquet de l’expert. . . . . . 14-5
d’accès . . . . . . . . . . . . . . . . . . . 12-6 Accès aux services de l’API Tools . . . . . . . 14-5
Initialisation du lien de données . . . . . 12-7 Utilisation d’objets natifs de l’EDI . . . . . 14-6
Réponse aux changements de données . . . 12-8 Utilisation de l’interface INTAServices . 14-6
Création d’un contrôle de modification Ajout d’une image à la liste d’images . 14-7
de données . . . . . . . . . . . . . . . . . . . . 12-9 Ajout d’une action à la liste d’actions . 14-7
Modification de la valeur par défaut Suppression de boutons
de FReadOnly . . . . . . . . . . . . . . . . . 12-9 de barres d’outils . . . . . . . . . . . . 14-8
Gestion des messages liés à la souris Débogage d’un expert . . . . . . . . . . . . 14-9
ou au clavier. . . . . . . . . . . . . . . . . 12-10 Numéros de version de l’interface . . . . 14-10
Réponse aux messages indiquant Utilisation des fichiers et des éditeurs . . . . .14-11
la manipulation de la souris. . . . . . 12-10 Utilisation des interfaces de module . . . .14-11
Réponse aux messages indiquant Utilisation des interfaces d’éditeur . . . . 14-12
la manipulation du clavier. . . . . . . 12-11 Création de fiches et de projets . . . . . . . . 14-12
Mise à jour de la classe lien de données Création de modules . . . . . . . . . . . . 14-13
sur un champ . . . . . . . . . . . . . . . . 12-12 Notification d’un expert
Modification de la méthode Change . . . 12-13 des événements de l’EDI. . . . . . . . . . . 14-16
Mise à jour de l’ensemble de données . . 12-13
Index I-1

iv
Chapitre

Présentation générale de la création


Chapitre1
1
d’un composant
Ce chapitre est une présentation générale de la conception des composants et du
processus d’écriture des composants pour les applications Delphi. Vous devez
toutefois être familier de Delphi et de ses composants standard.
• Bibliothèque de classes
• Composants et classes
• Création de composants
• Contenu d’un composant ?
• Création d’un nouveau composant
• Test des composants non installés
• Test des composants installés
• Installation d’un composant dans la palette de composants
Pour des informations sur l’installation de nouveaux composants, voir
“Installation de paquets de composants” au Chapitre 16 du Guide du développeur.

Bibliothèque de classes
Les composants de Delphi résident dans une bibliothèque de composants qui
comprend la bibliothèque des composants visuels (Visual Component Library,
VCL) et la bibliothèque des composants multiplates-formes (Component Library
for Cross Platform, CLX). La Figure 1.1 présente la relation qui existe entre les
classes sélectionnées qui composent la hiérarchie VCL. La hiérarchie CLX est
similaire à celle de la hiérarchie VCL, mais les contrôles Windows sont appelés
des widgets (par exemple, TWinControl est appelé TWidgetControl), et il existe
d’autres différences. Pour plus de détails sur les hiérarchies de classes et les
relations d’héritage entre classes, voir Chapitre 2, “Programmation orientée objet
et écriture des composants”. Pour un aperçu des différences entre les hiérarchies,
voir “WinCLX ou VisualCLX” au Chapitre 15 du Guide du développeur et

Présentation générale de la création d’un composant 1-1


Composants et classes

reportez-vous à la référence en ligne de la CLX pour plus de détails sur les


composants.
La classe TComponent est l’ancêtre partagé de chaque composant de la
bibliothèque de composants. TComponent fournit les propriétés et les événements
de base nécessaires au fonctionnement d’un composant dans l’EDI. Les
différentes branches de la bibliothèque offrent d’autres possibilités plus
spécialisées.
Figure 1.1 Hiérarchie des classes de la bibliothèque de composants visuels

Lorsque vous créez un composant, vous l’ajoutez à la bibliothèque de


composants en dérivant une nouvelle classe de l’un des types de classes existant
dans la hiérarchie.

Composants et classes
Comme les composants sont des classes, les créateurs de composants manipulent
les objets à un niveau différent de celui des développeurs d’applications.
La création de nouveaux composants nécessite de dériver de nouvelles classes.
Brièvement, il existe deux différences principales entre la création des
composants et leur utilisation dans des applications. Pour la création de
composants,
• Vous avez accès à des parties de la classe qui sont inaccessibles aux
programmeurs d’applications.
• Vous ajoutez de nouvelles parties (des propriétés, par exemple) aux
composants.
A cause de ces différences, il faut prendre en compte un plus grand nombre de
conventions, et réfléchir à la manière dont les développeurs d’applications vont
utiliser les composants.

1-2 Guide du concepteur de composants


Création de composants

Création de composants
Un composant peut quasiment constituer tout élément de programme
manipulable à la conception. La création d’un composant consiste à dériver une
nouvelle classe à partir d’une classe existante. Vous pouvez dériver un nouveau
composant de plusieurs façons :
• Modification de contrôles existants
• Création de contrôles fenêtrés
• Création de contrôles graphiques
• Sous-classement de contrôles Windows
• Création de composants non visuels
Le Tableau 1.1 présente les différents types de composants et les classes que vous
utiliserez comme point de départ pour chacun d’eux.

Tableau 1.1 Points de départ de la création de composants


Pour Démarrez avec le type suivant
Modifier un composant existant N’importe quel composant existant tel que TButton ou
TListBox, ou un type de composant abstrait tel que
TCustomListBox
Créer un contrôle fenêtré (ou TWinControl (TWidgetControl dans les applications CLX)
widget dans les applications CLX)
Créer un contrôle graphique TGraphicControl
Sous-classer un contrôle Tout contrôle Windows (applications VCL) ou widget
(applications CLX)
Créer un composant non visuel TComponent

Vous pouvez aussi dériver des classes qui ne sont pas des composants et qui
ne peuvent pas être manipulées dans une fiche comme TRegIniFile et TFont.

Modification de contrôles existants


Le moyen le plus simple de créer un composant est de modifier un composant
existant. Vous pouvez dériver un nouveau composant depuis un composant
quelconque de la bibliothèque de composants
Certains contrôles, tels les boîtes liste et les grilles, possèdent plusieurs variantes
d’un thème de base. Dans ce cas, la bibliothèque de composants comprend un
type de classe abstraite (son nom contient le mot “custom”, comme TCustomGrid)
à partir de laquelle il est possible de dériver les versions personnalisées.
Vous pouvez, par exemple, créer un type particulier de boîte liste ne possédant
pas certaines propriétés de la classe TListBox. Comme il n’est pas possible de
retirer (masquer) une propriété héritée d’une classe ancêtre, il faut dériver le
composant d’un élément situé avant TListBox dans la hiérarchie. Au lieu de vous
forcer à commencer depuis la classe abstraite TWinControl (ou TWidgetControl
dans les applications CLX) et à réinventer toutes les fonctions de boîte liste, la
bibliothèque de composants fournit TCustomListBox, qui implémente toutes les

Présentation générale de la création d’un composant 1-3


Création de composants

propriétés des boîtes liste mais ne les rend pas toutes publiques. En dérivant un
composant à partir de l’une des classes abstraites telles que TCustomListBox, vous
rendez publiques uniquement les propriétés que vous souhaitez mettre à
disposition dans votre composant et vous laissez les autres protégées.
Le Chapitre 3, “Création de propriétés”, explique la publication des propriétés
héritées. Le Chapitre 9, “Modification d’un composant existant”, et le Chapitre 11,
“Personnalisation d’une grille”, montrent des exemples de modification de
contrôles existants.

Création de contrôles fenêtrés


Les contrôles fenêtrés de la bibliothèque de composants sont des objets qui
apparaissent à l’exécution et avec lesquels l’utilisateur peut interagir. Chaque
contrôle fenêtré possède un handle de fenêtre, accessible via sa propriété Handle,
qui permet au système d’exploitation de l’identifier et d’agir sur lui. Dans le cas
d’utilisation de contrôles VCL, le handle permet au contrôle de recevoir la
focalisation de saisie et peut être transmis aux fonctions de l’API Windows. Les
contrôles CLX sont des contrôles basés sur des widgets. Chaque contrôle widget
possède un handle, accessible via sa propriété Handle, identifiant le widget
sous-jacent.
Tous les contrôles fenêtrés descendent de la classe TWinControl (TWidgetControl
dans la CLX). Ils incluent la plupart des contrôles fenêtrés standard, tels les
boutons poussoirs, les boîtes liste et les boîtes de saisie. Bien que vous puissiez
créer un contrôle original (un qui n’est relié à aucun contrôle existant) en le
dérivant directement de TWinControl (TWidgetControl dans CLX), Delphi fournit
pour cela le composant TCustomControl. TCustomControl est un contrôle fenêtré
spécialisé qui permet de réaliser facilement des images visuelles complexes.
Le Chapitre 11, “Personnalisation d’une grille”, présente un exemple de création
d’un contrôle fenêtré.

Création de contrôles graphiques


Si votre contrôle n’a pas besoin de recevoir la focalisation de saisie, vous pouvez
en faire un contrôle graphique. Les contrôles graphiques sont semblables aux
contrôles fenêtrés, mais ils ne possèdent pas de handle de fenêtre et consomment
donc moins de ressources système. Les composants comme TLabel, qui ne
reçoivent jamais la focalisation de saisie, sont des contrôles graphiques. Bien que
ces contrôles ne puissent pas recevoir la focalisation, vous pouvez les créer afin
qu’ils réagissent aux messages souris.
Vous pouvez créer des contrôles personnalisés par l’intermédiaire du composant
TGraphicControl. TGraphicControl est une classe abstraite dérivée de TControl. Bien
qu’il soit possible de dériver des contrôles directement de TControl, il est
préférable de les dériver de TGraphicControl, qui procure un canevas de dessin et
sur Windows gère les messages WM_PAINT ; il vous suffit de surcharger la
méthode Paint.

1-4 Guide du concepteur de composants


Contenu d’un composant ?

Le Chapitre 10, “Création d’un contrôle graphique”, montre un exemple de


création d’un contrôle graphique.

Sous-classement de contrôles Windows


En programmation Windows traditionnelle, vous créez des contrôles
personnalisés en définissant une nouvelle classe fenêtre et en l’enregistrant dans
Windows. La classe fenêtre (semblable aux objets ou aux classes dans la
programmation orientée objet). Vous pouvez baser une nouvelle classe fenêtre
sur une classe existante : cette opération est appelée sous-classement. Vous pouvez
ensuite placer votre contrôle dans une bibliothèque dynamiquement liée (DLL),
comme les contrôles Windows standard, puis lui fournir une interface.
Vous pouvez créer une “enveloppe” de composant autour de n’importe quelle
classe fenêtre existante. Ainsi, si vous possédez déjà une bibliothèque de
contrôles personnalisés que vous souhaitez utiliser dans vos applications Delphi,
vous pouvez créer des composants Delphi se comportant comme ces contrôles et
dériver de nouveaux contrôles à partir d’eux, comme vous le feriez avec
n’importe quel composant.
Pour consulter des exemples de sous-classement des contrôles Windows,
reportez-vous aux composants de l’unité StdCtls qui représentent les contrôles
Windows standard, comme TEdit. Pour les applications CLX, voir QStdCtls.

Création de composants non visuels


Les composants non visuels sont utilisés en tant qu’interfaces pour des éléments
comme les bases de données (TDataSet ou TSQLConnection) et les horloges
système (TTimer), et en tant que marques de réservation pour des boîtes de
dialogue (TCommonDialog (applications VCL) ou TDialog (applications CLX) et ses
descendants). La plupart des composants que vous écrivez sont des contrôles
visuels. Les composants non visuels peuvent être dérivés directement de
TComponent, la classe abstraite de base de tous les composants.

Contenu d’un composant ?


Pour que vos composants s’intègrent de manière sûre à l’environnement de
Delphi, vous devez suivre certaines conventions. Dans cette section, vous allez
apprendre :
• Suppression des dépendances
• Définition des propriétés, méthodes et événements
• Encapsulation des graphiques
• Recensement des composants

Présentation générale de la création d’un composant 1-5


Contenu d’un composant ?

Suppression des dépendances


Une des qualités qui favorisent l’utilisation des composants est le caractère
illimité des opérations que l’on peut programmer dans leur code. Par nature, les
composants peuvent être incorporés aux applications avec diverses combinaisons,
dans des ordres ou des contextes différents. Les composants doivent être conçus
pour pouvoir fonctionner dans n’importe quelle situation, sans condition
préalable.
La propriété Handle des composants TWinControl constitue un exemple de
suppression des dépendances dans les composants. Si vous avez déjà écrit des
applications Windows, vous savez que l’un des points les plus complexes à
traiter et les plus susceptibles de générer des erreurs lors de l’exécution d’un
programme est l’interdiction d’accéder à un contrôle fenêtré avant de l’avoir créé
par un appel à la fonction API CreateWindow. Les contrôles fenêtrés de Delphi
évitent cette difficulté en garantissant qu’un handle de fenêtre correct sera
toujours disponible dès que nécessaire. En utilisant une propriété pour
représenter le handle de fenêtre, le contrôle peut vérifier si la fenêtre a été créée ;
si le handle n’est pas correct, le contrôle crée une fenêtre et renvoie son handle.
Ainsi, chaque fois que le code d’une application accède à la propriété Handle,
il est sûr d’obtenir un handle correct.
En les libérant des tâches d’arrière-plan telles que la création des fenêtres, les
composants Delphi permettent aux développeurs de se concentrer sur ce qu’ils
veulent vraiment réaliser. Pour transmettre un handle de fenêtre à une fonction
API, vous n’avez pas besoin de vérifier que le handle existe ni de créer la
fenêtre. Le programmeur est certain que les opérations vont se dérouler
correctement et n’a pas besoin de le contrôler sans cesse.
Bien que la création de composants sans dépendances soit un peu plus longue, le
temps qui y est consacré est généralement très utile. Non seulement cela évite
aux développeurs répétitions et travail fastidieux, mais cela réduit la quantité de
documentation et de support.

Définition des propriétés, méthodes et événements


En dehors de l’image visible que l’utilisateur du composant manipule dans
le concepteur de fiches, les attributs les plus courants d’un composant sont
les propriétés, les événements et les méthodes. Un chapitre est consacré à chacun
d’eux, mais ce qui suit présente certaines raisons de les utiliser.

Propriétés
Les propriétés donnent au développeur d’applications l’illusion de définir ou
de lire la valeur d’une variable, tout en permettant au concepteur de composants
de dissimuler la structure de données sous-jacente ou de définir un traitement
spécial lorsque la valeur est accédée.

1-6 Guide du concepteur de composants


Contenu d’un composant ?

L’utilisation des propriétés présente plusieurs avantages :


• Les propriétés sont disponibles au moment de la conception. Le développeur
d’applications peut définir ou modifier les valeurs initiales des propriétés sans
écrire de code.
• Les propriétés peuvent contrôler les valeurs ou les formats au moment où le
développeur les définit. La validation de la saisie pendant la conception
empêche de commettre des erreurs.
• Le composant peut construire les valeurs appropriées à la demande. L’erreur
de programmation la plus fréquente est de référencer une variable qui n’a été
initialisée. En représentant les données par une propriété, vous êtes sûr qu’une
valeur leur est toujours disponible sur demande.
• Les propriétés vous permettent de cacher les données sous une interface
simple et cohérente. Vous pouvez modifier la façon dont les informations sont
structurées dans une propriété sans que ce changement ne soit perçu par les
développeurs d’applications.
Le Chapitre 3, “Création de propriétés”, explique comment ajouter des propriétés
à vos composants.

Méthodes
Les méthodes de classes sont des fonctions et procédures qui opèrent sur une
classe plutôt que sur des instances particulières de cette classe. Par exemple, les
méthodes constructeur de composants (Create) sont toutes des méthodes de
classes. Les méthodes de composants sont des procédures et fonctions qui
opèrent sur les instances des composants elles-mêmes. Les développeurs
d’applications utilisent des méthodes pour que les composants effectuent des
actions particulières ou renvoient des valeurs non contenues par des propriétés.
Comme elles nécessitent une exécution de code, les méthodes ne sont disponibles
qu’au moment de l’exécution. Elles sont utiles pour plusieurs raisons :
• Les méthodes encapsulent la fonctionnalité d’un composant dans l’objet même
où résident les données.
• Les méthodes peuvent cacher des procédures compliquées sous une interface
simple et cohérente. Un développeur d’applications peut appeler la méthode
AlignControls d’un composant sans savoir comment elle fonctionne ni si elle
diffère de la méthode AlignControls d’un autre composant.
• Les méthodes permettent de mettre à jour plusieurs propriétés avec un seul
appel.
Le Chapitre 5, “Création de méthodes”, explique comment ajouter des méthodes
à vos composants.

Evénements
Un événement est une propriété spéciale qui appelle du code, pendant
l’exécution, en réponse à une saisie ou à une autre opération. Les événements
permettent aux développeurs d’associer des blocs de code spécifiques à des

Présentation générale de la création d’un composant 1-7


Contenu d’un composant ?

actions spécifiques, telles des manipulations de la souris ou des frappes


au clavier. Le code qui s’exécute lorsqu’un événement survient est appelé
le gestionnaire de l’événement.
Les événements permettent aux développeurs d’applications de spécifier des
réponses différentes en fonction des actions possibles sans avoir à créer de
nouveaux composants.
Le Chapitre 4, “Création d’événements”, explique comment implémenter des
événements standard et comment en définir de nouveaux.

Encapsulation des graphiques


Delphi simplifie les graphiques Windows en encapsulant les différents outils
graphiques dans un canevas. Le canevas représente la surface de dessin d’une
fenêtre ou d’un contrôle ; il contient d’autres classes telles qu’un crayon, un
pinceau et une police de caractères. Un canevas est semblable à un contexte de
périphérique Windows, mais il prend à sa charge toutes les opérations de
gestion.
Si vous avez déjà écrit une application Windows graphique, vous connaissez les
contraintes imposées par l’interface graphique Windows (GDI). Par exemple, GDI
limite le nombre de contextes de périphériques disponibles et requiert la
restauration de l’état initial des objets graphiques avant de les détruire.
Avec Delphi, vous n’avez pas besoin de vous en préoccuper. Pour dessiner sur
une fiche ou un autre composant, accédez à la propriété Canvas du composant.
Si vous voulez personnaliser un crayon ou un pinceau, définissez sa couleur et
son style. Lorsque vous avez terminé, Delphi dispose des ressources. Delphi
conserve les ressources en mémoire cache pour éviter de les recréer, si votre
application utilise fréquemment le même type de ressources.
Vous pouvez toujours accéder à l’interface GDI Windows, mais votre code sera
beaucoup plus simple et s’exécutera plus rapidement si vous utilisez le canevas
intégré aux composants Delphi.
L’encapsulation des graphiques CLX fonctionne d’une manière différente. Un
canevas est plutôt un dispositif de dessin. Pour dessiner sur une fiche ou un
autre composant, accédez à la propriété Canvas du composant. Canvas est une
propriété et c’est aussi un objet appelé TCanvas. TCanvas englobe un dispositif de
dessin Qt accessible via la propriété Handle. Vous pouvez utiliser le handle pour
accéder aux fonctions de la bibliothèque graphique Qt de bas niveau.
Si vous voulez personnaliser un crayon ou un pinceau, définissez sa couleur et
son style. Lorsque vous avez terminé, Delphi ou Kylix dispose des ressources.
Les applications CLX mettent aussi en mémoire cache les ressources.
Vous pouvez utiliser le canevas construit dans les composants CLX par
dérivation. La façon dont les images graphiques fonctionnent dans le composant
dépend du canevas de l’objet à partir duquel votre composant est dérivé.
Les fonctionnalités graphiques sont décrites au Chapitre 6, “Graphiques et
composants”.

1-8 Guide du concepteur de composants


Création d’un nouveau composant

Recensement des composants


Avant de pouvoir installer vos composants dans l’EDI, vous devez les recenser.
Le recensement indique à Delphi où placer le composant sur la palette des
composants. Vous pouvez aussi personnaliser la manière dont Delphi stocke les
composants dans le fichier fiche. Le recensement est décrit dans le Chapitre 8,
“Accessibilité des composants au moment de la conception”.

Création d’un nouveau composant


Vous pouvez créer un nouveau composant de deux façons :
• Création d’un composant avec l’expert composant
• Création manuelle d’un composant
Vous pouvez utiliser l’une ou l’autre de ces méthodes pour créer un composant
aux fonctions minimales, prêt à être installé dans la palette de composants.
Après l’installation, vous pouvez placer votre nouveau composant sur une fiche
et le tester à la fois en mode conception et en mode exécution. Vous pouvez
ensuite ajouter d’autres fonctionnalités au composant, mettre à jour la palette de
composants et poursuivre les tests.
Il y a quelques étapes de base à suivre chaque fois que vous créez un nouveau
composant. Elles sont décrites ci-après ; pour les autres exemples présentés dans
ce document, nous supposerons que vous savez effectuer ces étapes.
1 Création d’une unité pour le nouveau composant.
2 Dérivation du composant à partir d’un type de composant existant.
3 Ajout de propriétés, méthodes et événements.
4 Recensement de votre composant dans l’EDI.
5 Création d’un bitmap pour le composant
6 Création d’un paquet (bibliothèque dynamiquement liée spéciale) pour
pouvoir installer le composant dans l’EDI.
7 Création d’un fichier d’aide pour le composant et ses propriétés, méthodes
et événements.
Remarque Créer un fichier d’aide pour indiquer aux utilisateurs d’un composant
comment utiliser celui-ci n’est pas obligatoire.
Lorsque vous avez terminé, le composant complet est constitué des fichiers
suivants :
• Un fichier paquet (.BPL) ou un fichier collection de paquets (.DPC)
• Un fichier paquet compilé (.DCP)
• Un fichier unité compilée (.DCU)
• Un fichier bitmap pour la palette (.DCR)
• Un fichier d’aide (.HLP)

Présentation générale de la création d’un composant 1-9


Création d’un nouveau composant

Vous pouvez également créer un bitmap pour représenter votre nouveau


composant. Voir “Création de bitmaps pour les composants” à la page 1-14.
Les chapitres du reste de la partie V expliquent tous les aspects de la
construction des composants et offrent des exemples complets d’écriture de
plusieurs sortes de composants.

Création d’un composant avec l’expert composant


L’expert composant simplifie les premières étapes de création d’un composant.
Lorsque vous utilisez l’expert composant, vous devez spécifier :
• La classe à partir de laquelle le composant est dérivé.
• Le nom de classe du nouveau composant.
• La page de la palette de composants où vous voulez qu’il apparaisse.
• Le nom de l’unité dans laquelle le composant est créé.
• Le chemin d’accès à cette unité.
• Le nom du paquet dans lequel vous voulez placer le composant.
L’expert composant exécute les opérations que vous exécuteriez pour créer
manuellement un composant :
• Création d’une unité.
• Dérivation du composant.
• Recensement du composant.
L’expert composant ne peut pas ajouter de composant à une unité existante.
Cela ne peut se faire que manuellement.
1 Pour ouvrir l’expert composant, choisissez l’une de ces deux méthodes :
• Choisissez Composant|Nouveau composant.
• Choisissez Fichier|Nouveau|Autre et double-cliquez sur Composant
2 Remplissez les champs de l’expert composant :
• Dans Type ancêtre, spécifiez la classe à partir de laquelle vous dérivez le
nouveau composant.
Remarque Dans la liste déroulante, de nombreux composants sont présentés deux fois
avec des noms d’unité différents, un pour les applications VCL et l’autre
pour les applications CLX. Les unités spécifiques à la CLX commencent par
Q (telles que QGraphics au lieu de Graphics). Veillez à dériver à partir du
bon composant.
• Dans Nom de classe, spécifiez le nom de classe de votre nouveau
composant.
• Dans Page de palette, spécifiez la page de la palette dans laquelle vous
voulez installer le composant.
• Dans Nom de fichier unité, spécifiez le nom de l’unité dans laquelle vous
voulez déclarer la classe du composant. Si l’unité n’est pas dans le chemin
de recherche, modifiez ce dernier.

1-10 Guide du concepteur de composants


Création d’un nouveau composant

Figure 1.2 L’expert composant

3 Lorsque vous avez rempli les champs de l’expert composant, effectuez l’une
des opérations suivantes :
• Cliquez sur Installer. Pour placer un composant dans un paquet nouveau
ou non, cliquez sur Composant|Installer et spécifiez le paquet dans la boîte
de dialogue qui apparaît. Voir “Test des composants non installés” à la
page 1-17.
4 Cliquez sur OK. L’EDI crée une nouvelle unité.
Attention Si vous dérivez un composant d’une classe dont le nom commence par
“custom” (comme TCustomControl), ne tentez pas de le placer sur une fiche
avant d’avoir surchargé toute méthode abstraite du composant initial. Delphi
ne peut pas créer d’instance d’une classe ayant des propriétés ou des
méthodes abstraites.
Pour voir le code source de votre unité, cliquez sur Voir l’unité. Si l’expert
composant est déjà fermé, ouvrez le fichier unité dans l’éditeur de code en
sélectionnant Fichier|Ouvrir. Delphi crée une nouvelle unité contenant la
déclaration de classe et la procédure Register, et ajoute une clause uses qui
comprend toutes les unités Delphi standard.
L’unité ressemble à cela :
unit MyControl;
interface
uses
Windows, Messages, SysUtils, Types, Classes, Controls;
type
TMyControl = class(TCustomControl)
private
{ Déclarations privées }
protected
{ Déclarations protégées }
public
{ Déclarations publiques }
published
{ Déclarations publiées }

Présentation générale de la création d’un composant 1-11


Création d’un nouveau composant

end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents(’Samples’, [TMyControl]); //Dans CLX, utilisez une autre page
end;
end.
Remarque Aux endroits où les applications CLX utilisent des unités distinctes, celles-ci sont
remplacées par des unités de même nom précédées d’un Q ; par exemple,
QControls remplace Controls. Dans le cas d’une dérivation à partir de
TCustomControl dans l’unité QControls, la seule différence est la clause uses qui
ressemble à ceci :
uses
SysUtils, Types, Classes, QGraphics, QControls, QForms, QDialogs, QStdCtrls;

Création manuelle d’un composant


Le moyen le plus simple de créer un composant est d’utiliser l’expert composant.
Cependant, vous pouvez effectuer manuellement les mêmes étapes.
Pour créer un composant manuellement, suivez ces étapes :
1 Création d’un fichier unité
2 Dérivation du composant
3 Recensement du composant

Création d’un fichier unité


Une unité est un module de code Delphi compilé séparément. Delphi emploie les
unités pour plusieurs raisons. Chaque fiche possède sa propre unité et la plupart
des composants (ou des groupes logiques de composants) possèdent aussi leurs
propres unités
Lorsque vous créez un composant, vous créez une nouvelle unité pour ce
composant ou bien vous l’ajoutez à une unité existante.
Pour créer une nouvelle unité pour un composant :
1 Choisissez l’une des commandes suivantes :
• Fichier|Nouveau|Unité.
• Fichier|Nouveau|Autre pour afficher la boîte de dialogue Nouveaux
éléments, sélectionnez Unité puis OK.
L’EDI crée un nouveau fichier unité et l’ouvre dans l’éditeur de code.
2 Enregistrez ce fichier sous un nom significatif.
3 Dérivez la classe composant.

1-12 Guide du concepteur de composants


Création d’un nouveau composant

Pour ouvrir une unité existante :


1 Choisissez Fichier|Ouvrir et sélectionnez l’unité de code source dans laquelle
vous voulez ajouter vos composants.
Remarque Lorsque vous ajoutez un composant à une unité, vérifiez que cette unité ne
contient que du code de composant. L’ajout d’un code de composant à une
unité qui contient, par exemple, une fiche, provoquera des erreurs dans la
palette de composants.
2 Dérivez la classe composant.

Dérivation du composant
Chaque composant est une classe dérivée de TComponent, de l’un de ses
descendants plus spécialisés (tels que TControl ou TGraphicControl) ou d’une
classe composant existante. “Création de composants” à la page 1-3 indique les
classes à dériver pour obtenir les différentes sortes de composants.
La dérivation des classes est expliquée plus en détail dans la section “Définition
de nouvelles classes” à la page 2-1.
Pour dériver un composant, ajoutez une déclaration de type objet à la partie
interface de l’unité qui contiendra le composant.
Une classe composant simple est un composant non visuel descendant
directement de TComponent.
Pour créer une classe composant simple, ajoutez la déclaration de classe suivante
à la partie interface de votre unité composant :
type
TNewComponent = class(TComponent)
end;
Pour l’instant, le nouveau composant ne fait rien de plus que TComponent. C’est
juste un squelette sur lequel vous allez bâtir votre nouveau composant.
La dérivation des classes est expliquée plus en détail dans “Définition de
nouvelles classes” à la page 2-1.

Recensement du composant
Le recensement est une opération simple qui indique à l’EDI les composants
à ajouter à la bibliothèque des composants et les pages de la palette sur
lesquelles ils doivent apparaître. Pour une présentation plus détaillée du
processus de recensement, voir Chapitre 8, “Accessibilité des composants
au moment de la conception”.
Pour recenser un composant :
1 Ajoutez une procédure nommée Register à la partie interface de l’unité du
composant. Register n’a pas de paramètres, la déclaration est donc très simple :
procedure Register;

Présentation générale de la création d’un composant 1-13


Création d’un nouveau composant

Si vous ajoutez un composant à une unité qui contient déjà des composants,
elle doit déjà avoir la procédure Register déclarée, afin que vous ne soyez pas
obligé de changer la déclaration.
Remarque Bien que Delphi soit un langage qui ne tient pas compte de la distinction
minuscules/majuscules, la procédure Register en tient compte et doit être
orthographiée avec un R majuscule.
2 Ecrivez la procédure Register dans la partie implémentation de l’unité, en
appelant RegisterComponents pour chaque composant que vous voulez recenser.
RegisterComponents est une procédure qui prend deux paramètres : le nom
d’une page de palette de composants et un ensemble de types de composants.
Si vous ajoutez un composant à un recensement existant, vous pouvez soit
ajouter le nouveau composant à l’ensemble dans l’instruction existante, soit
ajouter une nouvelle instruction qui appelle RegisterComponents.
Pour recenser un composant appelé TMyControl et le placer sur la page Exemples
de la palette, vous devrez ajouter la procédure Register suivante à l’unité
contenant la déclaration de TMyControl :
procedure Register;
begin
RegisterComponents(’Samples’, [TNewControl]);
end;
Cette procédure Register place TMyControl sur la page Exemples de la palette des
composants.
Une fois le composant recensé, vous pouvez le compiler dans un paquet
(voir Chapitre 8, “Accessibilité des composants au moment de la conception”)
et l’installer sur la palette des composants.

Création de bitmaps pour les composants


Chaque composant a besoin d’un bitmap pour être représenté sur la palette des
composants. Si vous ne spécifiez pas votre propre bitmap, l’EDI utilise un
bitmap par défaut. Étant donné que les bitmaps de palette sont uniquement
requis à la conception, vous ne les compilez pas en l’unité de compilation du
composant. En revanche, ils doivent être fournis dans un fichier de ressources
Windows portant le même nom que l’unité, mais avec l’extension .dcr (dynamic
component resource). Vous pouvez créer ce fichier de ressources en utilisant un
éditeur d’images.
Lorsque vous créez de nouveaux composants, vous pouvez définir vos propres
bitmaps pour des composants personnalisés.
Pour créer un bitmap :
1 Choisissez Outils|Editeur d’images.
2 Dans la boîte de dialogue Editeur d’images, choisissez Fichier|Nouveau|
Ressources composants (.dcr).

1-14 Guide du concepteur de composants


Création d’un nouveau composant

3 Dans la boîte de dialogue SansTitre1.dcr, cliquez avec le bouton droit sur


Sommaire. Choisissez Nouveau|Bitmap.
4 Dans la boîte de dialogue Propriétés du bitmap, attribuez la valeur 24 pixels
au champ Largeur et au champ Hauteur. Vérifiez que l’option VGA (16
couleurs) est cochée. Cliquez sur OK.
5 Bitmap et Bitmap1 apparaissent sous Sommaire. Sélectionnez Bitmap1, cliquez
avec le bouton droit et choisissez Renommer. Donnez au bitmap le nom de la
classe de votre nouveau composant, y compris le T, en inscrivant toutes les
lettres en majuscules. Par exemple, si le nom de votre nouvelle classe est
TMyNewButton, nommez le bitmap TMYNEWBUTTON.
Remarque Vous devez mettre toutes les lettres en majuscules, quelle que soit la façon
dont vous avez saisi le nom de la classe dans la boîte de dialogue Nouveau
composant.

6 Double-cliquez sur TMYNEWBUTTON pour afficher une boîte de dialogue


contenant un bitmap vide.
7 Utilisez la palette de couleurs située en bas de l’éditeur d’images pour
concevoir votre icône.
8 Choisissez Fichier|Enregistrer sous et donnez au fichier ressource (.dcr ou
.res) le même nom de base que l’unité dans laquelle vous voulez que la classe
du composant soit déclarée. Par exemple, nommez le fichier ressource
MyNewButton.dcr.
9 Choisissez Composant|Nouveau composant. Suivez les instructions de
création d’un nouveau composant avec l’expert Composant à la page 1-10.
Assurez-vous que le source du composant, MyNewButton.pas, est dans le
même répertoire que MyNewButton.dcr.
Pour une classe appelée TMyNewButton, l’expert Composant nomme le source
du composant, ou unité, MyNewButton.cpp et le place par défaut dans le

Présentation générale de la création d’un composant 1-15


Installation d’un composant dans la palette de composants

répertoire LIB. Cliquez sur le bouton Parcourir pour désigner un autre


emplacement pour l’unité générée pour le composant.
Remarque Si vous utilisez un fichier .res pour le bitmap plutôt qu’un fichier .dcr, ajoutez
au source du composant une référence qui lie la ressource. Par exemple, si
votre fichier .res s’appelle MyNewButton.res, après vous être assuré que le
.pas et le .res se trouvent dans le même répertoire, ajoutez ce qui suit à
MyNewButton.pas sous la section type :
{*R *.res}
10 Choisissez Composant|Installer un composant pour installer votre composant
dans un nouveau paquet ou dans un paquet existant. Cliquez sur OK.
Votre nouveau paquet est construit puis installé. Le bitmap représentant votre
nouveau composant apparaît sur la page de la palette de composants choisie
dans l’expert Composant.

Installation d’un composant dans la palette de composants


Pour installer des composants dans un paquet et sur la palette des composants :
1 Choisissez Composant|Installer un composant.
La boîte de dialogue Installation de composant apparaît.
2 Installez le nouveau composant dans un paquet existant ou nouveau en
choisissant la page appropriée.
3 Entrez le nom du fichier .pas contenant le nouveau composant ou choisissez
Parcourir pour rechercher l’unité.
4 Ajustez le chemin de recherche si le fichier .pas du nouveau composant ne se
trouve pas dans l’emplacement par défaut indiqué.
5 Spécifiez le nom du paquet dans lequel installer le composant ou choisissez
Parcourir pour localiser le paquet.
6 Si le composant est installé dans un nouveau paquet, vous pouvez également
spécifier une description du nouveau paquet.
7 Choisissez OK pour fermer la boîte de dialogue Installation de composants.
Cela compile/reconstruit le paquet et installe le composant dans la palette des
composants.
Remarque Les composants nouvellement installés apparaissent initialement dans la page de
la palette de composants qui a été spécifiée par le concepteur du composant.
Vous pouvez déplacer le composant dans une page différente après son
installation dans la palette en utilisant la boîte de dialogue Composant|
Configurer la palette.
Pour plus d’informations sur les concepteurs de composants qui doivent
distribuer leurs composants aux utilisateurs en vue de leur installation sur la
palette des composants, voir “Emplacement des fichiers du composant” à la
page 1-17.

1-16 Guide du concepteur de composants


Test des composants non installés

Emplacement des fichiers du composant


Les concepteurs de composants doivent faire en sorte que tous les fichiers source
utilisés par un composant soient placés dans le même répertoire. Ces fichiers
comprennent des fichiers de code source (.pas) et certains fichiers projet
(.dfm/.xfm, .res, .rc et .dcr).
Le processus d’ajout de composants entraîne la création de plusieurs fichiers. Ces
fichiers sont automatiquement placés dans les répertoires spécifiés par les options
d’environnement de l’EDI (utilisez la commande de menu Outils|Options
d’environnement, et choisissez la page Bibliothèque). Les fichiers .lib sont placés
dans le répertoire de destination DCP. Si l’ajout de composant entraîne la
création d’un nouveau paquet (par opposition à son installation dans un paquet
existant), le fichier .bpl est placé dans le répertoire de destination BPL.

Test des composants non installés


Vous pouvez tester le comportement d’un composant à l’exécution avant de
l’installer sur la palette de composants. Cette technique est particulièrement utile
pour le débogage des composants nouvellement créés, mais vous pouvez
l’utiliser pour tester n’importe quel composant, que celui-ci apparaisse ou non
sur la palette de composants. Pour plus d’informations sur le test des
composants déjà installés, voir “Test des composants installés” à la page 1-19.
Vous pouvez tester un composant non installé en émulant les actions exécutées
par Delphi quand le composant est sélectionné dans la palette et placé dans une
fiche.
Pour tester un composant non installé,
1 Ajoutez le nom de l’unité du composant à la clause uses de l’unité fiche.
2 Ajoutez un champ objet à la fiche pour représenter le composant.
Il s’agit là d’une des différences principales entre votre façon d’ajouter des
composants et celle utilisée par Delphi. Vous ajoutez le champ objet à la partie
publique à la fin de la déclaration de type de fiche. Delphi l’ajouterait
au-dessus, dans la partie de la déclaration de type qu’il gère.
Il ne faut jamais ajouter de champs à la partie gérée par Delphi de la
déclaration de type de fiche. Les éléments de cette partie correspondent à ceux
stockés dans le fichier fiche. L’ajout des noms de composants qui n’existent
pas sur la fiche peut rendre incorrect le fichier de la fiche.
3 Attachez un gestionnaire à l’événement OnCreate de la fiche.
4 Construisez le composant dans le gestionnaire OnCreate de la fiche.
Lors de l’appel au constructeur du composant, vous devez transmettre un
paramètre spécifiant le propriétaire du composant (le composant chargé de la
destruction du composant au moment opportun). Il faut pratiquement toujours
transmettre Self comme propriétaire. Dans une méthode, Self représente une

Présentation générale de la création d’un composant 1-17


Test des composants non installés

référence sur l’objet contenant la méthode. Dans ce cas, dans le gestionnaire


OnCreate de la fiche, Self représente la fiche.
5 Initialisez la propriété Parent.
L’initialisation de la propriété Parent est toujours la première opération à
effectuer après la construction d’un contrôle. Le parent est le composant qui
contient visuellement le contrôle ; le plus souvent c’est sur la fiche que le
contrôle apparaît, mais il peut aussi s’agir d’une boîte groupe ou d’un volet.
Normalement, il faut donner à Parent la valeur Self, c’est-à dire la fiche.
Initialisez toujours Parent avant les autres propriétés du contrôle.
Attention Si votre composant n’est pas un contrôle (c’est-à-dire si TControl n’est pas un
de ses ancêtres), passez cette étape. Si vous définissez accidentellement la
propriété Parent de la fiche (à la place de celle du composant) à la valeur Self,
vous pouvez provoquer un problème du système d’exploitation.
6 Si vous le souhaitez, initialisez d’autres propriétés du composant.
Supposons que vous souhaitiez tester un nouveau composant de type
TMyControl dans une unité appelée MyControl. Créez un nouveau projet, puis
effectuez les étapes nécessaires pour avoir une unité fiche qui ressemble à ceci :
unit Unit1;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, MyControl; { 1. Ajouter NewTest à la clause uses. }
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject); { 2 Attacher un gestionnaire à OnCreate }
private
{ Déclarations privées }
public
{ Déclarations publiques }
MyControl1: TMyControl1; { 3. Ajouter un champ objet }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
MyControl1 := TMyControl.Create(Self); { 4. Construire le composant }
MyControl1.Parent := Self; { 5. Si le composant est un contrôle,
initialiser la propriété Parent. }
MyControl1.Left := 12; { 6. Définir les autres propriétés...
ƒ ...continuer si nécessaire }
end;
end.

1-18 Guide du concepteur de composants


Test des composants installés

Test des composants installés


Vous pouvez tester le comportement d’un composant à la conception après son
installation sur la palette de composants. Cette technique est particulièrement
utile pour le débogage des composants nouvellement créés, mais vous pouvez
l’utiliser pour tester n’importe quel composant, que celui-ci apparaisse ou non
sur la palette de composants. Pour plus d’informations sur le test des
composants qui n’ont pas encore été installés, voir “Test des composants non
installés” à la page 1-17.
Le test de vos composants après l’installation vous permet de déboguer le
composant qui génère seulement des exceptions à la conception lorsqu’il est
déposé sur une fiche.
Testez un composant installé en exécutant une seconde instance de l’EDI :
1 Choisissez Projet|Options et sur la page Répertoires/Conditions, affectez à la
zone Sources débogage le fichier source du composant.
2 Sélectionnez ensuite Outils|Options du débogueur. Sur la page Exceptions du
langage, activez les exceptions à suivre.
3 Ouvrez le fichier source du composant et définissez des points d’arrêt.
4 Sélectionnez Exécuter|Paramètres et affectez au champ Application hôte le
nom et l’emplacement du fichier exécutable de Delphi.
5 Dans la boîte de dialogue Paramètres d’exécution, cliquez sur le bouton
Charger pour démarrer une seconde instance de Delphi.
6 Déposez ensuite les composants à tester sur la fiche, ce qui devrait provoquer
l’arrêt sur les points d’arrêt définis dans le source.

Présentation générale de la création d’un composant 1-19


1-20 Guide du concepteur de composants
Chapitre

Programmation orientée objet


Chapitre2
2
et écriture des composants
Si vous avez écrit des applications avec Delphi, vous savez déjà qu’une classe
contient à la fois du texte et du code, et que les classes sont manipulables aussi
bien au moment de la conception qu’à l’exécution. C’est ainsi que vous êtes
devenu utilisateur de composants.
Lorsque vous créez de nouveaux composants, votre approche des classes n’est
pas celle du développeur d’applications standard. Vous essayez de cacher les
travaux internes du composant aux développeurs qui vont les utiliser. En
choisissant les ancêtres appropriés à vos composants, en concevant des interfaces
qui exposent seulement les propriétés et les méthodes dont les développeurs ont
besoin, en suivant les autres conseils de ce chapitre, vous pourrez créer des
composants réutilisables parfaitement maniables.
Avant de commencer à créer des composants, vous devez comprendre les sujets
suivants qui se rapportent à la programmation orientée objet (OOP) :
• Définition de nouvelles classes
• Ancêtres, descendants et hiérarchies des classes
• Contrôle des accès
• Répartition des méthodes
• Membres abstraits d’une classe
• Classes et pointeurs

Définition de nouvelles classes


La différence entre un concepteur de composants et un développeur
d’applications est la suivante : le concepteur de composants crée de nouvelles
classes et le développeur d’applications manipule les instances de ces classes.

Programmation orientée objet et écriture des composants 2-1


Définition de nouvelles classes

Une classe est d’abord un type. Comme programmeur, vous travaillez sans arrêt
avec les types et les instances, même si vous ne parlez pas en ces termes. Par
exemple, vous créez des variables d’un certain type, par exemple Integer. Les
classes sont habituellement plus complexes que de simples types de données,
mais elles fonctionnent de la même façon. En affectant différentes valeurs aux
instances d’un même type, vous effectuez différentes tâches.
Par exemple, il est courant de créer une fiche contenant deux boutons appelés
OK et Annuler. Chacun correspond à une instance de la classe TButton, mais, en
attribuant une valeur différente à leurs propriétés Caption et différents
gestionnaires à leurs événements OnClick, vous faites se comporter différemment
les deux instances.

Dérivation de nouvelles classes


Deux raisons peuvent vous amener à dériver une nouvelle classe :
• Modification des valeurs par défaut d’une classe pour éviter les répétitions
• Ajout de nouvelles capacités à une classe
L’objectif est le même dans ces deux cas : créer des objets réutilisables. Si vous
concevez vos objets en ayant en tête leur réutilisation, vous gagnerez un temps
considérable. Attribuez à vos classes des valeurs par défaut exploitables mais
rendez-les personnalisables.

Modification des valeurs par défaut d’une classe pour éviter les répétitions
Dans tout programme, les répétitions superflues sont à proscrire. Si vous vous
surprenez à répéter les mêmes lignes de code, vous serez sans doute amené à les
placer à part dans une sous-routine ou fonction, ou encore à construire une
bibliothèque de routines utilisables par un autre programme. Le même
raisonnement s’applique aux composants. Si vous modifiez fréquemment les
mêmes propriétés ou si vous appelez les mêmes méthodes, vous créerez sans
doute un nouveau composant qui effectue ces tâches par défaut.
Par exemple, supposons qu’à chaque création d’une nouvelle application, vous
ajoutez une boîte de dialogue accomplissant une fonction déterminée. Bien qu’il
soit simple de recréer à chaque fois cette boîte de dialogue, c’est superflu. Vous
pouvez concevoir la boîte de dialogue une fois pour toute, définir ses propriétés
puis installer le composant enveloppe associé dans la palette des composants. En
faisant du dialogue un composant réutilisable, non seulement vous éliminez une
tâche répétitive mais renforcez la standardisation et minimisez les erreurs qui
peuvent être occasionnées par chaque nouvelle création de la boîte de dialogue.
Le Chapitre 9, “Modification d’un composant existant”, montre un exemple qui
modifie les propriétés par défaut d’un composant.
Remarque Si vous voulez ne modifier que les propriétés publiées d’un composant existant
ou enregistrer des gestionnaires d’événement spécifiques à un composant ou à
un groupe de composants, vous pourrez accomplir ceci plus facilement en créant
un modèle de composant.

2-2 Guide du concepteur de composants


Ancêtres, descendants et hiérarchies des classes

Ajout de nouvelles capacités à une classe


Une raison classique de créer de nouveaux composants est l’ajout de
fonctionnalités qui ne se trouvent pas dans les composants existants. Pour cela,
vous dérivez le nouveau composant à partir d’un composant existant ou à partir
d’une classe de base abstraite, comme TComponent ou TControl.
Dérivez votre nouveau composant à partir de la classe contenant le
sous-ensemble le plus proche des caractéristiques recherchées. Vous pouvez
ajouter des fonctionnalités à une classe, mais vous ne pouvez pas en soustraire.
Par conséquent, si une classe de composant contient des propriétés que vous ne
souhaitez pas inclure dans la vôtre, effectuez la dérivation à partir de l’ancêtre de
ce composant.
Par exemple, pour ajouter des fonctionnalités à une boîte liste, vous devez
dériver un nouveau composant à partir de TListBox. Mais, si vous souhaitez
ajouter de nouvelles fonctionnalités et exclure certaines de celles des boîtes liste
standard, il vous faut dériver votre boîte liste de l’ancêtre de TListBox,
TCustomListBox. Recréez (ou rendez visibles) les fonctionnalités de la boîte liste
que vous voulez, puis ajoutez vos propres fonctionnalités.
Le Chapitre 11, “Personnalisation d’une grille”, montre un exemple qui
personnalise une classe abstraite de composant.

Déclaration d’une nouvelle classe de composant


Outre les composants standard, Delphi fournit de nombreuses classes abstraites
qui serviront de base pour dériver de nouveaux composants. Le Tableau 1.1 à la
page 1-3 montre les classes que vous pouvez utiliser pour créer vos propres
composants.
Pour déclarer une nouvelle classe de composant, ajoutez une déclaration de
classe au fichier unité du composant.
Voici la déclaration d’un composant graphique simple :
type
TSampleShape = class(TGraphicControl)
end;
Une déclaration de composant achevée comprend généralement la déclaration
des propriétés, des événements et des méthodes avant le end. Mais une
déclaration comme celle ci-dessus est aussi admise et représente un point de
départ pour l’ajout de fonctionnalités à votre composant.

Ancêtres, descendants et hiérarchies des classes


Les développeurs d’applications peuvent se servir du fait que chaque contrôle a
des propriétés Top et Left qui déterminent son emplacement sur la fiche. Pour
eux, il importe peu que tous les contrôles aient hérité ces propriétés d’un ancêtre
commun, TControl. Mais, lorsque vous écrivez un composant, vous devez savoir

Programmation orientée objet et écriture des composants 2-3


Contrôle des accès

à partir de quelle classe vous le dérivez de façon à ce qu’il reçoive en héritage


les éléments que vous souhaitez. Vous devez également connaître toutes les
fonctionnalités dont hérite votre composant de manière à les exploiter sans avoir
à les recréer vous-même.
La classe à partir de laquelle vous effectuez la dérivation est l’ancêtre immédiat.
Chaque composant hérite de son ancêtre immédiat, et lui-même de son ancêtre
immédiat. Toutes les classes dont hérite un composant sont les ancêtres de ce
composant ; le composant est un descendant de ses ancêtres.
L’ensemble des relations ancêtre-descendant de l’application constitue les
hiérarchies des classes. Dans cette hiérarchie, chaque génération contient plus
que ses ancêtres puisqu’une classe hérite de tout ce que contiennent ses ancêtres
et ajoute de nouvelles propriétés et de nouvelles méthodes, ou redéfinit celles
qui existent.
Si vous ne spécifiez aucun ancêtre immédiat, Delphi dérive votre composant à
partir de l’ancêtre par défaut, TObject. TObject est le dernier ancêtre de toutes les
classes de la hiérarchie d’objets.
La règle générale du choix de l’objet de départ de la dérivation est simple :
prenez l’objet qui contient le plus possible ce que vous voulez inclure dans votre
nouvel objet, mais qui ne comprend rien de ce que vous ne voulez pas dans le
nouvel objet. Vous pouvez toujours ajouter des choses à vos objets, mais vous ne
pouvez pas en retirer.

Contrôle des accès


Il existe cinq niveaux de contrôle d’accès, également appelé visibilité, aux
propriétés, méthodes et champs. La visibilité détermine quel code peut accéder
à quelles parties de la classe. En spécifiant la visibilité, vous définissez l’interface
de vos composants.
Le Tableau 2.1 montre les niveaux de visibilité, en allant du plus restrictif au
plus accessible :

Tableau 2.1 Niveaux de visibilité d’un objet


Visibilité Signification Utilisé pour
private Accessible uniquement au code de l’unité où Masquer les détails
est définie la classe. d’implémentation.
protected Accessible au code de ou des unités où sont Définition de l’interface avec le
définis la classe et ses descendants. concepteur des composants.
public Accessible à tout le code. Définition de l’interface
d’exécution.
automated Accessible à tout le code. Les informations Automatisation OLE seulement.
d’automatisation sont générées.
published Accessible à tout le code et accessible depuis Définition de l’interface de
l’inspecteur d’objets. Enregistré dans un conception.
fichier fiche.

2-4 Guide du concepteur de composants


Contrôle des accès

Déclarez les membres en private si vous voulez qu’ils ne soient disponibles que
dans la classe où ils ont été définis. Déclarez-les en protected si vous voulez
qu’ils ne soient disponibles que dans cette classe et ses descendants.
Souvenez-vous que si un membre est disponible n’importe où dans un fichier
unité, il est disponible partout dans ce fichier. Ainsi, si vous définissez deux
classes dans la même unité, elles pourront accéder à l’une ou l’autre des
méthodes privées. Et si vous dérivez une classe dans une unité différente de son
ancêtre, toutes les classes de la nouvelle unité pourront accéder aux méthodes
protégées de l’ancêtre.

Masquer les détails d’implémentation


Déclarer private une partie d’une classe rend cette partie invisible au code situé
hors du fichier unité de la classe. Dans l’unité qui contient la déclaration, le code
peut accéder à cette partie comme si elle était publique.
L’exemple Delphi suivant montre comment le fait de déclarer une donnée
membre private empêche les utilisateurs d’accéder aux informations. La liste
montre les deux unités de fiche VCL. Chaque fiche a un gestionnaire pour son
événement OnCreate qui affecte une valeur à la donnée membre private. Le
compilateur autorise l’affectation à la donnée membre uniquement dans la fiche
où elle a été déclarée.
unit HideInfo;
interface
uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs;
type
TSecretForm = class(TForm) { déclarer une nouvelle fiche }
procedure FormCreate(Sender: TObject);
private { déclare la partie privée }
FSecretCode: Integer; { déclare une donnée membre private }
end;
var
SecretForm: TSecretForm;
implementation
{$R *.dfm}
procedure TSecretForm.FormCreate(Sender: TObject);
begin
FSecretCode := 42; { ceci se compile correctement }
end;
end. { fin de l’unité }
unit TestHide; { il s’agit du fichier fiche principal }
interface
uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs,
HideInfo; { utilise l’unité avec TSecretForm }
type
TTestForm = class(TForm)
procedure FormCreate(Sender: TObject);
end;

Programmation orientée objet et écriture des composants 2-5


Contrôle des accès

var
TestForm: TTestForm;
implementation
procedure TTestForm.FormCreate(Sender: TObject);
begin
SecretForm.FSecretCode := 13; { le compilateur s’arrête avec
"Identificateur de champ attendu" }
end;
end. { fin de l’unité }
Bien qu’un programme utilisant l’unité HideInfo puisse utiliser les classes de type
TSecretForm, il ne peut accéder au champ FSecretCode dans aucune de ces classes.
Remarque Les noms et les emplacements de certaines unités sont différents pour les
applications CLX. Par exemple, l’unité Controls est QControls dans une
application CLX.

Définition de l’interface avec le concepteur des composants


Déclarer protected une partie d’une classe rend cette partie uniquement visible
par cette classe et par ses descendants (et par les autres classes qui partagent
leurs fichiers unité).
Vous pouvez utiliser les déclarations protected pour définir l’interface de
conception des composants d’une classe. Les unités de l’application ne peuvent pas
accéder aux parties protected, mais les classes dérivées le peuvent. Cela signifie
que les concepteurs des composants peuvent modifier la façon dont fonctionne
une classe sans rendre apparents ces détails aux développeurs d’applications.
Remarque Une erreur commune consiste à essayer d’accéder aux méthodes protégées d’un
gestionnaire d’événement. Les gestionnaires d’événements sont généralement des
méthodes de la fiche, et non du composant qui reçoit l’événement. Par
conséquent, ils n’accèdent pas aux méthodes protégées du composant (à moins
que le composant ne soit déclaré dans la même unité que la fiche).

Définition de l’interface d’exécution


Déclarer public une partie d’une classe rend cette partie visible par tout code qui
dispose d’un accès global à cette classe.
Les parties publiques sont disponibles à l’ensemble du code lors de l’exécution,
et constituent par là même l’interface d’exécution. L’interface d’exécution sert aux
éléments qui sont sans signification ou inappropriés au moment de la
conception ; comme les propriétés contenant des informations uniquement
disponibles à l’exécution ou accessibles uniquement en lecture. Les méthodes
destinées à être appelées par les développeurs d’applications doivent également
être publiques.

2-6 Guide du concepteur de composants


Contrôle des accès

Voici un exemple montrant deux propriétés accessibles en lecture uniquement et


déclarées comme faisant partie de l’interface d’exécution d’un composant :
type
TSampleComponent = class(TComponent)
private
FTempCelsius: Integer; { les détails de l’implémentation sont privés }
function GetTempFahrenheit: Integer;
public
property TempCelsius: Integer read FTempCelsius; { les propriétés sont publiques }
property TempFahrenheit: Integer read GetTempFahrenheit;
end;
ƒ
function TSampleComponent.GetTempFahrenheit: Integer;
begin
Result := FTempCelsius * 9 div 5 + 32;
end;

Définition de l’interface de conception


Déclarer published une partie d’une classe rend publique cette partie et génère
également les informations de types à l’exécution. Entre autres rôles, les
informations de types à l’exécution permettent à l’inspecteur d’objets d’accéder
aux propriétés et aux événements.
Parce qu’elles apparaissent dans l’inspecteur d’objets, les parties published d’une
classe définissent l’interface de conception de cette classe. L’interface de conception
doit inclure toutes les caractéristiques d’une classe qu’un développeur
d’applications peut vouloir personnaliser au moment de la conception, tout en
excluant toutes les propriétés qui dépendent d’une information spécifique issue
de l’environnement d’exécution.
Les propriétés accessibles en lecture uniquement ne peuvent pas faire partie de
l’interface de conception car le développeur d’applications ne peut pas leur
affecter des valeurs directement. Les propriétés accessibles en lecture uniquement
doivent donc être déclarées public plutôt que published.
Voici l’exemple d’une propriété published nommée Temperature. De ce fait, elle
apparaît dans l’inspecteur d’objets au moment de la conception.
type
TSampleComponent = class(TComponent)
private
FTemperature: Integer; { les détails de l’implémentation sont privés }
published
property Temperature: Integer read FTemperature write FTemperature; { peut s’écrire ! }
end;

Programmation orientée objet et écriture des composants 2-7


Répartition des méthodes

Répartition des méthodes


Le terme de Dispatch fait référence à la façon dont un programme détermine où
il doit rechercher une méthode de classe lorsqu’il rencontre un appel à cette
méthode. Le code qui appelle une méthode de classe ressemble à tout autre
appel de fonction. Mais les classes ont des façons différentes de répartir les
méthodes.
Les trois types de répartition de méthodes sont
• Statique
• Virtuel
• Dynamique

Méthodes statiques
Toutes les méthodes sont statiques à moins que vous ne les déclariez
spécifiquement autrement. Les méthodes statiques fonctionnent comme des
procédures ou des fonctions normales. Le compilateur détermine l’adresse exacte
de la méthode et la lie au moment de la compilation.
Le premier avantage des méthodes statiques est qu’elles sont réparties très
rapidement. Comme le compilateur peut déterminer l’adresse exacte de la
méthode, il la lie directement. Les méthodes virtuelles et dynamiques, au
contraire, utilisent des moyens indirects pour donner l’adresse de leurs méthodes
lors de l’exécution, ce qui prend davantage de temps.
Une méthode statique ne change pas lorsqu’elle est héritée d’une classe
descendante. Si vous déclarez une classe comprenant une méthode statique, puis
dérivez une nouvelle classe à partir de celle-ci, la classe dérivée partage
exactement la même méthode à la même adresse. Cela signifie que vous ne
pouvez pas redéfinir des méthodes statiques. Une méthode statique réalise
toujours la même chose quelle que soit la classe qui y est appelée. Si vous
déclarez une méthode dans une classe dérivée ayant le même nom qu’une
méthode statique dans la classe ancêtre, la nouvelle méthode remplace
simplement celle héritée dans la classe dérivée.

Exemple de méthodes statiques


Dans le code suivant, le premier composant déclare deux méthodes statiques. Le
deuxième déclare deux méthodes statiques de même nom qui remplacent les
méthodes héritées du premier composant.
type
TFirstComponent = class(TComponent)
procedure Move;
procedure Flash;
end;
TSecondComponent = class(TFirstComponent)
procedure Move; { différent de la méthode héritée, malgré la même déclaration }
function Flash(HowOften: Integer): Integer; { c’est aussi différent }
end;

2-8 Guide du concepteur de composants


Répartition des méthodes

Méthodes virtuelles
Les méthodes virtuelles emploient un mécanisme de répartition plus compliqué
et plus souple que les méthodes statiques. Une méthode virtuelle peut être
redéfinie dans des classes descendantes, mais est toujours appelée dans la classe
ancêtre. L’adresse d’une méthode virtuelle n’est pas déterminée lors de la
compilation ; à la place, l’objet où la méthode est définie donne l’adresse lors de
l’exécution.
Pour qu’une méthode soit virtuelle, ajoutez la directive virtual après la
déclaration de la méthode. La directive virtual crée une entrée dans le tableau de
méthode virtuelle, de l’objet, ou VMT, qui contient les adresses de toutes les
méthodes virtuelles d’un type objet.
Lorsque vous dérivez une nouvelle classe d’une classe existante, la nouvelle
classe a son propre VMT, qui comprend toutes les entrées provenant du VMT de
l’ancêtre plus toutes les méthodes virtuelles supplémentaires déclarées dans la
nouvelle classe.

Redéfinition des méthodes


Surcharger une méthode signifie l’étendre ou la redéfinir plutôt que la remplacer.
Une classe descendante peut surcharger toutes ses méthodes virtuelles héritées.
Pour surcharger une méthode dans une classe descendante, ajoutez la directive
override à la fin de la déclaration de méthode.
La surcharge d’une méthode provoque une erreur de compilation si
• La méthode n’existe pas dans la classe ancêtre.
• La méthode de l’ancêtre du même nom est statique.
• Les déclarations ne sont pas identiques (le numéro et le type des paramètres
arguments diffèrent).
Le code suivant montre la déclaration de deux composants simples. Le premier
déclare trois méthodes, chacune avec un type de répartition différent. L’autre,
dérivé du premier, remplace la méthode statique et surcharge les méthodes
virtuelles.
type
TFirstComponent = class(TCustomControl)
procedure Move; { méthode statique }
procedure Flash; virtual; { méthode virtuelle }
procedure Beep; dynamic; { méthode virtuelle dynamique }
end;
TSecondComponent = class(TFirstComponent)
procedure Move; { déclare une nouvelle méthode }
procedure Flash; override; { redéfinit la méthode héritée }
procedure Beep; override; { redéfinit la méthode héritée }
end;

Programmation orientée objet et écriture des composants 2-9


Membres abstraits d’une classe

Méthodes dynamiques
Les méthodes dynamiques sont des méthodes virtuelles avec un mécanisme de
répartition légèrement différent. Comme les méthodes dynamiques n’ont pas
d’entrées dans le tableau de méthode virtuelle de l’objet, elles peuvent réduire la
taille de la mémoire consommée par les objets. Cependant les méthodes de
répartition dynamiques sont quelque peu plus lentes que les méthodes de
répartition virtuelles normales. Si une méthode est fréquemment appelée, ou si
son exécution nécessite un temps court, vous devrez probablement la déclarer
virtuelle plutôt que dynamique.
Les objets doivent stocker les adresses de leurs méthodes dynamiques. Mais
plutôt que de recevoir les entrées dans le tableau de méthode virtuelle, les
méthodes dynamiques sont indiquées séparément. La liste des méthodes
dynamiques contient des entrées uniquement pour les méthodes introduites ou
redéfinies par une classe particulière. (le tableau de méthode virtuelle, à
l’inverse, comprend toutes les méthodes virtuelles de l’objet, à la fois héritées et
introduites). Les méthodes dynamiques héritées sont réparties en cherchant
chaque liste de méthode dynamique de l’ancêtre, en allant en arrière dans
l’arborescence de l’héritage.
Pour rendre une méthode dynamique, ajoutez la directive dynamic après la
déclaration de méthode.

Membres abstraits d’une classe


Lorsqu’une méthode est déclarée abstract dans une classe ancêtre, vous devez la
surfacer (c’est-à-dire la redéclarer et l’implémenter) dans tout composant
descendant avant d’utiliser le nouveau composant dans les applications. Delphi
ne peut créer d’instance d’une classe contenant des membres abstraits. Pour plus
d’informations sur le surfaçage des constituants hérités des classes, voir
Chapitre 3, “Création de propriétés”, et Chapitre 5, “Création de méthodes”.

Classes et pointeurs
Chaque classe (et par conséquent chaque composant) est en fait un pointeur. Le
compilateur déréférence automatiquement les pointeurs de classe à votre place,
aussi n’avez-vous généralement pas besoin de vous poser ces questions. Le statut
des classes en tant que pointeurs devient important lorsque vous passez une
classe comme paramètre. En général, vous transmettrez les classes par valeur
plutôt que par référence. Car les classes sont déjà des pointeurs, c’est-à-dire des
références ; transmettre une classe par référence serait transmettre une référence à
une référence.

2-10 Guide du concepteur de composants


Chapitre

Création de propriétés
Chapitre3
3
Les propriétés sont la partie la plus visible des composants. Le développeur
d’applications a la possibilité de les voir et de les manipuler au moment de la
conception et dispose d’un retour immédiat au fur et à mesure que réagissent les
composants dans le concepteur de fiches. Des propriétés bien conçues simplifient
l’utilisation de vos composants par d’autres programmeurs et en facilitent la
maintenance.
Pour utiliser de façon optimale les propriétés de vos composants, vous devez
connaître les points suivants :
• Pourquoi créer des propriétés ?
• Types de propriétés
• Publication des propriétés héritées
• Définition des propriétés
• Création de propriétés tableau
• Stockage et chargement des propriétés

Pourquoi créer des propriétés ?


Du point de vue du développeur d’applications, les propriétés ressemblent à des
variables. Les développeurs peuvent définir ou lire les valeurs des propriétés
comme s’il s’agissait de champs. La seule opération interdite avec une propriété
et autorisée avec une variable consiste à la transmettre comme paramètre var.
Les propriétés ont une puissance bien supérieure à celle de simples champs car
• Les développeurs d’applications peuvent définir des propriétés au moment de
la conception. Contrairement aux méthodes, qui ne sont accessibles qu’à
l’exécution, les propriétés permettent au développeur de personnaliser les
composants avant l’exécution de l’application. Les propriétés apparaissent
dans l’inspecteur d’objets, ce qui simplifie le travail du programmeur ; au lieu
de traiter plusieurs paramètres pour construire un objet, l’inspecteur d’objets

Création de propriétés 3-1


Types de propriétés

fournit valeurs. L’inspecteur d’objets valide les affectations des valeurs aux
propriétés dès qu’elles sont effectuées.
• Les propriétés peuvent masquer les détails de l’implémentation. Par exemple,
des données stockées de façon interne sous une forme cryptée peuvent
apparaître non cryptées en tant que la valeur d’une propriété ; bien que
la valeur puisse être un simple nombre, le composant peut rechercher cette
valeur dans une base de données ou effectuer des calculs complexes afin de
la récupérer. Les propriétés permettent d’associer des opérations complexes
à une simple affectation ; ce qui apparaît comme l’affectation d’un champ
correspond en fait à un appel de méthode et cette dernière peut accomplir
à peu près n’importe quelle tâche.
• Les propriétés peuvent être virtuelles. Ce qui paraît être une seule propriété
pour le développeur d’applications peut être implémenté de manière
différente dans des composants différents.
Un exemple simple est la propriété Top que tous les contrôles possèdent.
L’attribution d’une nouvelle valeur à Top n’a pas pour seul effet de modifier une
valeur mémorisée ; elle provoque aussi le déplacement et le réaffichage du
contrôle. Les effets de la définition d’une propriété ne se limitent pas
nécessairement à un composant unique ; par exemple, donner la valeur True
à la propriété Down d’un turbobouton a pour effet d’attribuer la valeur False
à la propriété Down de tous les autres turboboutons du même groupe.

Types de propriétés
Une propriété peut avoir un type quelconque. Les divers types sont affichés
de manière différente dans l’inspecteur d’objets, ce qui valide l’affectation des
propriétés effectuées au moment de la conception.

Tableau 3.1 Affichage des propriétés dans l’inspecteur d’objets


Type de
propriété Traitement de l’inspecteur d’objets
Simple Les propriétés de type numérique, caractère et chaîne apparaissent dans
l’inspecteur d’objets comme des nombres, caractères et chaînes.
Le développeur d’applications peut modifier directement la valeur de
ces propriétés.
Enuméré Les propriétés de type énuméré (y compris le type Boolean) apparaissent
comme des chaînes éditables. Le développeur peut également passer en revue
toutes les valeurs possibles en double-cliquant sur la colonne contenant la
valeur et il existe une liste déroulante montrant toutes les valeurs possibles.
Ensemble Les propriétés de type ensemble apparaissent dans l’inspecteur d’objets
comme des ensembles. En double-cliquant sur la propriété, le développeur
peut développer l’ensemble et traiter chacun des éléments comme une valeur
booléenne (true si cet élément appartient à l’ensemble).

3-2 Guide du concepteur de composants


Publication des propriétés héritées

Tableau 3.1 Affichage des propriétés dans l’inspecteur d’objets (suite)


Type de
propriété Traitement de l’inspecteur d’objets
Objet Les propriétés qui sont elles-mêmes des classes ont souvent leur propre
éditeur de propriétés, qui est spécifié dans la procédure de recensement du
composant. Si la classe d’une propriété a ses propres propriétés publiées
(published), l’inspecteur d’objets permet au développeur d’étendre la liste
(en double-cliquant) afin d’inclure ces propriétés et de les modifier
individuellement. Les propriétés doivent descendre de TPersistent.
Interface Les propriétés qui sont des interfaces peuvent apparaître dans l’inspecteur
d’objets tant que la valeur est une interface implémentée par un composant
(un descendant de TComponent). Les propriétés interface ont souvent leur
propre éditeur de propriétés.
Tableau Les propriétés tableau doivent disposer d’un éditeur de propriétés spécifique.
L’inspecteur d’objets ne dispose d’aucune fonction intégrée permettant de
modifier les propriétés de ce type. Vous pouvez spécifier un éditeur de
propriétés lorsque vous recensez vos composants.

Publication des propriétés héritées


Tous les composants héritent des propriétés de leurs classes ancêtre. Lorsque
vous dérivez un composant à partir d’un composant existant, le nouveau
composant hérite de toutes les propriétés de l’ancêtre immédiat. Si vous effectuez
la dérivation à partir d’une des classes abstraites, aucune des propriétés héritées
n’est published, la plupart sont protected ou public.
Pour rendre disponible dans l’inspecteur d’objets une propriété protected ou
private au moment de la conception, vous devez redéclarer published la
propriété. Redéclarer une propriété signifie ajouter la déclaration d’une propriété
héritée à la déclaration de la classe descendante.
Si vous dérivez un composant de TWinControl, par exemple, il hérite de la
propriété protégée DockSite. En redéclarant DockSite dans votre nouveau
composant, vous pouvez changer le niveau de protection en public ou publié.
Le code suivant montre une redéclaration de DockSite publié, le rendant disponible
lors de la conception.
type
TSampleComponent = class(TWinControl)
published
property DockSite;
end;
Lorsque vous redéclarez une propriété, vous spécifiez uniquement le nom de la
propriété, non le type ni les autres informations décrites plus loin dans
“Définition des propriétés”. Vous pouvez aussi déclarer de nouvelles valeurs par
défaut et spécifier si la propriété est ou non stockée.
Les redéclarations peuvent augmenter la visibilité d’une propriété, mais pas la
réduire. Vous pouvez ainsi rendre une propriété protégée publique, mais vous
ne pouvez pas masquer une propriété publique en la redéclarant protégée.

Création de propriétés 3-3


Définition des propriétés

Définition des propriétés


Cette section montre comment déclarer de nouvelles propriétés et explique
certaines conventions respectées par les composants standard. Ces rubriques
comprennent :
• Déclarations des propriétés
• Stockage interne des données
• Accès direct
• Méthodes d’accès
• Valeurs par défaut d’une propriété

Déclarations des propriétés


Une propriété est déclarée dans la déclaration de sa classe composant. Pour
déclarer une propriété, vous devez spécifier les trois éléments suivants :
• Le nom de la propriété.
• Le type de la propriété.
• Les méthodes utilisées pour lire et écrire la valeur de la propriété. Si aucune
méthode d’écriture n’est déclarée, la propriété est accessible uniquement en
lecture.
Les propriétés déclarées dans une section published de la déclaration de classe
du composant sont modifiables dans l’inspecteur d’objets lors de la conception.
La valeur d’une propriété published est enregistrée avec le composant dans le
fichier fiche. Les propriétés déclarées dans une section public sont accessibles à
l’exécution et peuvent être lues ou définies par le code du programme.
Voici une déclaration typique pour une propriété appelée Count.
type
TYourComponent = class(TComponent)
private
FCount: Integer; { utilisé pour le stockage interne }
procedure SetCount (Value: Integer); { méthode d’écriture }
public
property Count: Integer read FCount write SetCount;
end;

Stockage interne des données


Il n’existe aucune restriction quant au stockage des données d’une propriété.
Toutefois, les composants Delphi respectent généralement les conventions
suivantes :
• Les données des propriétés sont stockées dans des champs.
• Les champs utilisés pour stocker les données d’une propriété sont déclarés
private et ne peuvent être accédées qu’à partir du composant lui-même.

3-4 Guide du concepteur de composants


Définition des propriétés

Les composants dérivés doivent utiliser la propriété héritée ; ils ne nécessitent


pas un accès direct au stockage interne des données de la propriété.
• Les identificateurs de ces champs sont composés de la lettre F suivie du nom
de la propriété. Par exemple, la donnée brute de la propriété Width définie
pour TControl est stockée dans un champ appelé FWidth.
Le principe qui sous-tend ces conventions est le suivant : seules les méthodes
d’implémentation d’une propriété doivent pouvoir accéder aux données associées
à cette propriété. Si une méthode ou une autre propriété a besoin de changer ces
données, elle doit le faire via la propriété et non directement par un accès aux
données stockées. Cela garantit que l’implémentation d’une propriété héritée
puisse être modifiée sans invalider les composants dérivés.

Accès direct
L’accès direct est le moyen le plus simple d’accéder aux données d’une propriété.
Autrement dit, les parties read et write de la déclaration d’une propriété
spécifient que l’affectation ou la lecture de la valeur de la propriété s’effectue
directement dans le champ de stockage interne sans appel à une méthode
d’accès. L’accès direct est utile lorsque vous voulez rendre une propriété
accessible dans l’inspecteur d’objets, mais que vous ne voulez pas que le
changement de sa valeur déclenche un processus immédiatement.
En général, vous définirez un accès direct pour la partie read d’une déclaration
de propriété et utiliserez une méthode d’accès pour la partie write. Cela permet
de mettre à jour l’état du composant lorsque la valeur de la propriété change.
La déclaration de type composant suivante montre une propriété qui utilise
l’accès direct pour les parties read et write.
type
TSampleComponent = class(TComponent)
private { le stockage interne est privé }
FMyProperty: Boolean; { déclare la donnée membre pour contenir la valeur }
published { rend la propriété disponible à la conception }
property MyProperty: Boolean read FMyProperty write FMyProperty;
end;

Méthodes d’accès
Vous pouvez spécifier une méthode d’accès plutôt qu’un champ dans les parties
read et write d’une déclaration de propriété. Les méthodes d’accès doivent être
protected, et sont habituellement déclarées comme virtual ; cela autorise les
composants descendants à surcharger l’implémentation de la propriété.
Evitez de rendre publiques les méthodes d’accès. Les conserver protected vous
prémunit contre toute modification accidentelle d’une propriété par un
développeur d’applications qui appellerait ces méthodes.

Création de propriétés 3-5


Définition des propriétés

Voici une classe qui déclare trois propriétés en utilisant le spécificateur d’index,
qui autorise aux trois propriétés d’avoir les mêmes méthodes d’accès en lecture
et en écriture :
type
TSampleCalendar = class(TCustomGrid)
public
property Day: Integer index 3 read GetDateElement write SetDateElement;
property Month: Integer index 2 read GetDateElement write SetDateElement;
property Year: Integer index 1 read GetDateElement write SetDateElement;
private
function GetDateElement(Index: Integer): Integer; { notez le paramètre Index }
procedure SetDateElement(Index: Integer; Value: Integer);
ƒ
Comme chaque élément de la date (day, month et year) est un int et comme la
définition de chacun requiert le codage de la date lorsqu’elle est définie, le code
évite la duplication en partageant les méthodes de lecture et d’écriture pour les
trois propriétés. Vous n’avez besoin que d’une seule méthode pour lire un
élément date et une autre pour écrire l’élément date.
Voici la méthode read qui obtient l’élément date :
function TSampleCalendar.GetDateElement(Index: Integer): Integer;
var
AYear, AMonth, ADay: Word;
begin
DecodeDate(FDate, AYear, AMonth, ADay); { éclate la date encodée en éléments }
case Index of
1: Result := AYear;
2: Result := AMonth;
3: Result := ADay;
else Result := -1;
end;
end;
Voici la méthode write qui définit l’élément date approprié :
procedure TSampleCalendar.SetDateElement(Index: Integer; Value: Integer);
var
AYear, AMonth, ADay: Word;
begin
if Value > 0 then { tous les éléments doivent être positifs }
begin
DecodeDate(FDate, AYear, AMonth, ADay); { récupère les éléments courants de la date }
case Index of { définit le nouvel élément selon l’index }
1: AYear := Value;
2: AMonth := Value;
3: ADay := Value;
else Exit;
end;
FDate := EncodeDate(AYear, AMonth, ADay); { encode la date modifiée }
Refresh; { mise à jour du calendrier visible }
end;
end;

3-6 Guide du concepteur de composants


Définition des propriétés

Méthode read
La méthode read d’une propriété est une fonction qui n’accepte aucun paramètre
(sauf pour ce qui est mentionné ci-après) et renvoie une valeur du même type
que la propriété. Par convention, le nom de la fonction est Get suivi du nom de
la propriété. Par exemple, la méthode read pour une propriété intitulée Count
serait GetCount. La méthode read manipule les données internes afin de générer
une valeur de la propriété respectant le type demandé.
Les seules exceptions à la règle “aucun paramètre” sont les propriétés tableau et
les propriétés qui utilisent un spécificateur d’index (voir “Création de propriétés
tableau” à la page 3-9), pour lesquelles cet index est transmis comme paramètre.
Utilisez des spécificateurs d’index pour créer une méthode read unique partagée
par plusieurs propriétés. Pour plus d’informations sur les spécificateurs d’index,
voir le Guide du langage Delphi.)
Si vous ne déclarez aucune méthode read, la propriété fonctionne uniquement en
écriture. Les propriétés fonctionnant en écriture uniquement sont très rares.

Méthode write
La méthode write d’une propriété est une procédure acceptant un seul paramètre
(sauf pour ce qui est mentionné ci-après) du même type que la propriété. Le
paramètre peut être transmis par référence ou par valeur et peut porter le nom
de votre choix. Par convention, le nom de la méthode write est Set suivi du nom
de la propriété. Par exemple, la méthode write d’une propriété intitulée Count
serait SetCount. La valeur transmise en paramètre devient la nouvelle valeur de
la propriété ; la méthode write doit accomplir les manipulations nécessaires pour
placer les données concernées à l’emplacement de stockage interne de la
propriété.
Les seules exceptions à la règle “paramètre unique” sont les propriétés tableau et
les propriétés qui utilisent un spécificateur d’index, pour lesquelles cet index est
transmis comme second paramètre. Utilisez des spécificateurs d’index pour créer
une méthode read unique partagée par plusieurs propriétés. Pour plus
d’informations sur les spécificateurs d’index, voir le Guide du langage Delphi.)
Si vous ne déclarez aucune méthode write, la propriété fonctionne uniquement
en lecture.
Les méthodes write testent normalement si une nouvelle valeur diffère de la
valeur actuelle avant de modifier la propriété. Par exemple, voici une méthode
write simple d’une propriété de type entier appelée Count stockant sa valeur
courante dans un champ appelé FCount.
procedure TMyComponent.SetCount(Value: Integer);
begin
if Value <> FCount then
begin
FCount := Value;
Update;
end;
end;

Création de propriétés 3-7


Définition des propriétés

Valeurs par défaut d’une propriété


Lorsque vous déclarez une propriété, vous pouvez déclarer une valeur par défaut.
Delphi utilise cette valeur par défaut pour déterminer si une propriété doit être
stockée dans un fichier fiche. Si vous ne donnez pas de valeur par défaut à une
propriété, Delphi stocke toujours cette propriété.
Pour spécifier une valeur par défaut pour une propriété, ajoutez la directive
default à la déclaration (ou à la redéclaration) de la propriété, suivie par la
valeur par défaut. Par exemple,
property Cool Boolean read GetCool write SetCool default True;
Remarque Déclarer une valeur par défaut pour une propriété n’a pas pour effet de définir
cette propriété par cette valeur. La méthode constructeur du composant doit
initialiser la valeur des propriétés lorsque c’est nécessaire. Toutefois, comme les
objets initialisent toujours leurs champs à 0, il n’est pas nécessaire que le
constructeur initialise les propriétés entières à 0, les propriétés chaînes à null ni
les propriétés booléennes à False.

Spécification d’aucune valeur par défaut


Lorsque vous redéclarez une propriété, vous pouvez indiquer que la propriété ne
possède pas de valeur par défaut, même si une telle valeur est définie pour la
propriété reçue en héritage.
Pour indiquer qu’une propriété n’a pas de valeur par défaut, ajoutez la directive
nodefault à la déclaration de la propriété. Par exemple,
property FavoriteFlavor string nodefault;
Lorsque vous déclarez une propriété pour la première fois, vous n’êtes pas
obligé de spécifier nodefault. L’absence d’une valeur par défaut déclarée indique
l’inexistence d’une valeur par défaut.
Voici la déclaration d’un composant qui comprend une propriété booléenne
unique appelée IsTrue dont la valeur par défaut est True. La déclaration
ci-dessous (dans la section implémentation de l’unité) représente le constructeur
qui initialise la propriété.
type
TSampleComponent = class(TComponent)
private
FIsTrue: Boolean;
public
constructor Create(AOwner: TComponent); override;
published
property IsTrue: Boolean read FIsTrue write FIsTrue default True;
end;
ƒ
constructor TSampleComponent.Create(AOwner: TComponent);
begin
inherited Create(AOwner); { appelle le constructeur hérité }
FIsTrue := True; { définit la valeur par défaut }
end;

3-8 Guide du concepteur de composants


Création de propriétés tableau

Création de propriétés tableau


Certaines propriétés se prêtent à l’indexation. Par exemple, la propriété Lines de
TMemo est la liste indexée des chaînes qui constituent le texte du mémo et vous
pouvez la traiter comme un tableau de chaînes. Lines fournit un accès à un
élément particulier (une chaîne) dans un ensemble plus large de données (le
texte du mémo).
Les propriétés tableau sont déclarées comme les autres propriétés. Les seules
différences sont les suivantes :
• La déclaration de la propriété doit comprendre un ou plusieurs index ayant
chacun un type défini. Les index peuvent avoir n’importe quel type.
• Les parties read et write de la déclaration de la propriété, lorsqu’elles sont
spécifiées, doivent être des méthodes. Il ne peut s’agir de champs.
Les méthodes read et write d’une propriété tableau acceptent des paramètres
supplémentaires correspondant aux index. Les paramètres doivent respecter
l’ordre et le type des index spécifiés dans la déclaration.
Bien qu’ils se ressemblent, il existe quelques différences importantes entre les
tableaux et les propriétés tableau. Contrairement aux indices d’un tableau,
l’index d’une propriété tableau n’est pas obligatoirement de type entier. Par
exemple, vous pouvez indexer une propriété en utilisant une chaîne. En outre,
vous ne pouvez référencer qu’un seul élément d’une propriété et non une plage
d’éléments.
L’exemple suivant est la déclaration d’une propriété renvoyant une chaîne en
fonction de la valeur d’un index de type entier.
type
TDemoComponent = class(TComponent)
private
function GetNumberName(Index: Integer): string;
public
property NumberName[Index: Integer]: string read GetNumberName;
end;
ƒ
function TDemoComponent.GetNumberName(Index: Integer): string;
begin
Result := ’Inconnu’;
case Index of
-MaxInt..-1: Result := ’Négatif’;
0: Result := ’Zéro’;
1..100: Result := ’Petit’;
101..MaxInt: Result := ’Grand’;
end;
end;

Création de propriétés 3-9


Création de propriétés pour sous-composants

Création de propriétés pour sous-composants


Par défaut, lorsque la valeur d’une propriété est un autre composant, vous lui
attribuez une valeur en ajoutant à la fiche ou au module de données une
instance de cet autre composant et en assignant ensuite le composant comme
valeur à la propriété. Mais, il est aussi possible que votre composant crée sa
propre instance de l’objet qui implémente la valeur de la propriété. Un tel
composant dédié s’appelle un sous-composant.
Les sous-composants peuvent être constitué de n’importe quel objet persistant
(tout descendant de TPersistent). Au contraire des composants séparés qui
peuvent se trouver assignés comme valeur d’une propriété, les propriétés
publiées des sous-composants sont enregistrées avec le composant qui les crée.
Mais, pour que cela fonctionne, les conditions suivantes doivent être respectées :
• Le Owner du sous-composant doit être le composant qui le crée et l’utilise
comme valeur d’une propriété publiée. Pour les sous-composants descendant
de TComponent, vous pouvez réaliser cela en définissant la propriété Owner du
sous-composant. Pour les autres sous-composants, vous devez surcharger la
méthode GetOwner de l’objet persistant de façon à ce qu’elle renvoie le
composant créateur.
• Si le sous-composant est un descendant de TComponent, il doit indiquer qu’il
est un sous-composant en appelant la méthode SetSubComponent.
Habituellement, cet appel est effectué par le propriétaire lorsqu’il crée le
sous-composant ou par le constructeur du sous-composant.
En général, les propriétés dont les valeurs sont des sous-composants sont
accessibles en lecture seulement. Si vous autorisez la modification d’une
propriété dont la valeur est un sous-composant, la méthode définissant la
propriété, le setter, doit libérer le sous-composant lorsqu’un autre composant est
assigné comme valeur à la propriété. De plus, le composant ré-instancie souvent
son sous-composant lorsque la propriété est définie par nil. Sinon, lorsque la
propriété a été définie par un autre composant, le sous-composant ne peut plus
être restauré à la conception. L’exemple suivant illustre le setter d’une propriété
dont la valeur est un TTimer :
procedure TDemoComponent.SetTimerProp(Value: TTimer);
begin
if Value <> FTimer then
begin
if Value <> nil then
begin
if Assigned(FTimer) and (FTimer.Owner = Self) then
FTimer.Free;
FTimer := Value;
FTimer.FreeNotification(self);
end
else //valeur nil
begin
if Assigned(FTimer) and (FTimer.Owner <> Self) then

3-10 Guide du concepteur de composants


Création des propriétés pour interfaces

begin
FTimer := TTimer.Create(self);
FTimer.Name := ’Timer’; bit facultatif, rend le résultat plus agréable
FTimer.SetSubComponent(True);
FTimer.FreeNotification(self);
end;
end;
end;
end;
Remarquez que le setter de la propriété a appelé la méthode FreeNotification du
composant défini comme valeur de la propriété. Cet appel garantit que le
composant servant de valeur à la propriété envoie une notification au moment
où il est sur le point d’être détruit. Il envoie cette notification en appelant la
méthode Notification. Vous gérez cet appel en surchargeant la méthode
Notification, comme suit :
procedure TDemoComponent.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Operation = opRemove) and (AComponent = FTimer) then
FTimer := nil;
end;

Création des propriétés pour interfaces


Vous pouvez utiliser une interface comme valeur d’une propriété publiée,
presque comme si vous utilisiez un objet. Cependant, le mécanisme par lequel
votre composant reçoit les notifications de l’implémentation de l’interface est
différent. Dans la rubrique précédente, le setter de la propriété appelait la
méthode FreeNotification du composant assigné comme valeur à la propriété.
Cela permettait au composant de se mettre à jour lorsqu’était libéré le composant
servant de valeur à la propriété. Mais, lorsque la valeur de la propriété est une
interface, vous n’avez pas accès au composant implémentant cette interface.
Il s’ensuit que vous ne pouvez pas appeler sa méthode FreeNotification.
Pour gérer une telle situation, vous pouvez appeler la méthode ReferenceInterface
de votre composant :
procedure TDemoComponent.SetMyIntfProp(const Value: IMyInterface);
begin
ReferenceInterface(FIntfField, opRemove);
FIntfField := Value;
ReferenceInterface(FIntfField, opInsert);
end;
Appeler ReferenceInterface avec une interface est équivalent à appeler la méthode
FreeNotification d’un autre composant. Donc, après l’appel de ReferenceInterface

Création de propriétés 3-11


Stockage et chargement des propriétés

depuis le setter de la propriété, vous pouvez surcharger la méthode Notification


pour gérer les notifications depuis l’implémenteur de l’interface :
procedure TDemoComponent.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Assigned(MyIntfProp)) and (AComponent.IsImplementorOf(MyInftProp)) then
MyIntfProp := nil;
end;
Notez que le code de Notification assigne la valeur nil à la propriété MyIntfProp,
et non au champ privé (FIntfField). Cela garantit que Notification appelle le setter
de la propriété, qui appelle ReferenceInterface pour annuler la demande de
notification établie lorsque la valeur de la propriété a été définie au préalable.
Toutes les assignations de la propriété interface doivent se faire via le setter de
la propriété.

Stockage et chargement des propriétés


Delphi stocke les fiches et leurs composants dans des fichiers fiche (.dfm dans
les applications VCL et .xfm dans les applications CLX). Un fichier fiche stocke
les propriétés d’une fiche et de ses composants. Lorsque les développeurs Delphi
ajoutent à leurs fiches les composants que vous avez écrits, vos composants
doivent être capables d’enregistrer leurs propriétés dans le fichier fiche lors de sa
sauvegarde. De même, lorsqu’ils sont chargés dans Delphi ou exécutés comme
éléments d’une application, vos composants doivent être capables de se restituer
eux-mêmes à partir du fichier fiche.
La plupart du temps, vous n’aurez rien à faire pour que vos composants
fonctionnent avec un fichier fiche car la fonction de stockage et de chargement
d’une représentation fait partie du comportement reçu en héritage par tous les
composants. Toutefois, dans certaines circonstances, vous pouvez souhaiter
modifier le stockage d’un composant ou son initialisation au chargement.
C’est pourquoi il est conseillé de comprendre les mécanismes sous-jacents.
Les aspects du stockage de propriétés qu’il est nécessaire d’expliquer sont les
suivants :
• Utilisation du mécanisme de stockage et de chargement
• Spécification des valeurs par défaut
• Détermination du stockage
• Initialisation après chargement
• Stockage et chargement des propriétés non publiées

Utilisation du mécanisme de stockage et de chargement


La description d’une fiche est la liste des propriétés de la fiche accompagnée
d’une liste semblable pour chacun de ses composants. Chaque composant,
y compris la fiche elle-même, est responsable du stockage et du chargement de sa
propre description.

3-12 Guide du concepteur de composants


Stockage et chargement des propriétés

Lorsqu’il se stocke lui-même, un composant écrit implicitement les valeurs de


toutes ses propriétés publiées si celles-ci sont différentes de leurs valeurs par
défaut, en respectant l’ordre dans lequel ont été déclarées ces valeurs.
Au chargement, le composant commence par se construire lui-même, toutes
les propriétés récupérant leurs valeurs par défaut, puis il lit les valeurs stockées
des propriétés dont les valeurs ne correspondent pas aux valeurs par défaut.
Ce mécanisme implicite répond à la plupart des besoins des composants et ne
nécessite aucune intervention particulière de la part de l’auteur du composant.
Néanmoins, il existe plusieurs moyens de personnaliser le processus de stockage
et de chargement pour répondre aux besoins particuliers d’un composant.

Spécification des valeurs par défaut


Les composants Delphi ne stockent la valeur des propriétés que si elles diffèrent
des valeurs par défaut. Sauf indication contraire, Delphi suppose qu’une
propriété n’a pas de valeur par défaut, ce qui a pour conséquence que le
composant stocke toujours la propriété, quelle que soit sa valeur.
Pour spécifier une valeur par défaut pour une propriété, ajoutez la directive
default et la nouvelle valeur par défaut à la fin de la déclaration de la propriété.
Vous pouvez également spécifier une valeur par défaut en redéclarant une
propriété. De fait, l’attribution d’une autre valeur par défaut est l’une des raisons
qui peut vous amener à redéclarer une propriété.
Remarque La spécification d’une valeur par défaut n’a pas pour effet d’attribuer cette
valeur à la propriété lorsque l’objet est créé. Le constructeur du composant doit
s’en charger. Une propriété dont la valeur n’est pas définie par le constructeur du
composant, a la valeur zéro, ou la valeur affichée par la propriété quand son
stockage en mémoire est 0. Par défaut, les nombres valent donc 0, les booléens
False, les pointeurs nil, etc. En cas de doute, affectez une valeur dans la méthode
du constructeur.
Le code suivant montre la déclaration d’un composant qui attribue une valeur
par défaut à la propriété Align, ainsi que l’implémentation du constructeur du
composant qui affecte cette valeur. Dans notre exemple, le nouveau composant
est un cas particulier du composant volet standard utilisé en tant que barre
d’état dans une fenêtre. Il doit donc implicitement s’aligner sur la bordure
inférieure de son propriétaire.
type
TStatusBar = class(TPanel)
public
constructor Create(AOwner: TComponent); override; { surcharge pour définir la
nouvelle valeur par défaut }
published
property Align default alBottom; { redéclare avec la nouvelle valeur par défaut }
end;
ƒ
constructor TStatusBar.Create(AOwner: TComponent);
begin
inherited Create(AOwner); { effectue une initialisation héritée }

Création de propriétés 3-13


Stockage et chargement des propriétés

Align := alBottom; { affecte une nouvelle valeur par défaut à Align }


end;

Détermination du stockage
Vous pouvez choisir si Delphi stocke ou non chacune des propriétés de vos
composants. Par défaut, sont stockées toutes les propriétés de la partie published
de la déclaration de classe. Vous pouvez choisir de ne pas stocker une propriété
ou de désigner une fonction qui décidera, de manière dynamique, du stockage
de la propriété.
Pour contrôler le stockage par Delphi d’une propriété, ajoutez la directive stored
à la déclaration de propriété, suivie par true, false ou le nom d’une fonction
booléenne.
Le code suivant montre un composant avec la déclaration de trois nouvelles
propriétés. La première est toujours stockée, la deuxième ne l’est jamais et la
troisième est stockée selon la valeur d’une fonction booléenne :
type
TSampleComponent = class(TComponent)
protected
function StoreIt: Boolean;
public
ƒ
published
property Important: Integer stored True; { toujours stockée }
property Unimportant: Integer stored False; { jamais stockée }
property Sometimes: Integer stored StoreIt; { dépend de la valeur de la fonction }
end;

Initialisation après chargement


Après qu’un composant a lu les valeurs de toutes ses propriétés dans sa
description stockée, il appelle une méthode virtuelle appelée Loaded, qui effectue
toutes les initialisations nécessaires. L’appel de Loaded s’exécute avant que ne
s’affichent la fiche et ses contrôles, ainsi vous n’avez pas à vous soucier du
scintillement de l’écran provoqué par ces initialisations.
Pour initialiser un composant après le chargement des valeurs des propriétés,
vous devez surcharger la méthode Loaded.
Remarque La première opération à accomplir dans une méthode Loaded consiste à appeler
la méthode Loaded reçue en héritage. Ceci afin d’être sûr que toutes les
propriétés reçues en héritage sont correctement initialisées avant d’effectuer
l’initialisation de votre propre composant.
Le code suivant provient du composant TDatabase. Après chargement, la base
de données essaie de rétablir toutes les connexions ouvertes au moment du
stockage, et spécifie comment gérer toutes les exceptions qui se produisent
pendant la connexion.

3-14 Guide du concepteur de composants


Stockage et chargement des propriétés

procedure TDatabase.Loaded;
begin
inherited Loaded; { appelle d’abord la méthode héritée }
try
if FStreamedConnected then Open { rétablit les connexions }
else CheckSessionName(False);
except
if csDesigning in ComponentState then { lors de la conception... }
Application.HandleException(Self) { permet à Delphi de gérer l’exception }
else raise; { sinon, redéclenche }
end;
end;

Stockage et chargement des propriétés non publiées


Par défaut, seules les propriétés publiées sont chargées et enregistrées avec un
composant. Cependant, il est possible de charger et d’enregistrer des propriétés
non publiées. Ceci permet d’obtenir des propriétés persistantes n’apparaissant
pas dans l’inspecteur d’objets. Cela permet aussi le stockage et le chargement des
valeurs de propriétés par les composants, valeurs de propriétés que Delphi ne
sait pas comment lire ni écrire car elles sont trop complexes. Par exemple, l’objet
TStrings ne peut pas compter sur le comportement automatique de Delphi pour
stocker et charger les chaînes qu’il représente et doit utiliser le mécanisme
suivant.
Vous pouvez enregistrer des propriétés non publiées en ajoutant du code
indiquant à Delphi comment charger et enregistrer la valeur de propriété.
Pour écrire votre propre code afin de charger et d’enregistrer des propriétés,
utilisez les étapes suivantes :
1 Création de méthodes pour le stockage et le chargement de valeurs de
propriétés.
2 Redéfinition de la méthode DefineProperties, en transmettant ces méthodes à un
objet filer.

Création de méthodes pour le stockage et le chargement de valeurs de


propriétés
Pour stocker et charger des propriétés non publiées, vous devez d’abord créer
une méthode afin de stocker la valeur de propriété et une autre méthode pour la
charger. Deux possibilités s’offrent à vous :
• Créer une méthode de type TWriterProc afin de stocker la valeur de propriété
et une méthode de type TReaderProc pour la charger. Cette approche vous
permet de profiter des capacités intégrées de Delphi concernant
l’enregistrement et le chargement de types simples. Si la valeur de votre
propriété est construite à partir de types que Delphi sait enregistrer et charger,
utilisez cette approche.

Création de propriétés 3-15


Stockage et chargement des propriétés

• Créer deux méthodes de type TStreamProc, une pour stocker et une pour
charger la valeur de propriété. TStreamProc accepte un flux comme argument
et vous pouvez utiliser les méthodes du flux afin d’écrire et lire les valeurs de
propriétés.
Prenons pour exemple une propriété représentant un composant créé à
l’exécution. Delphi sait comment écrire cette valeur, mais ne le fait pas
automatiquement car le composant n’est pas créé dans le concepteur de fiches.
Puisque le système de flux peut dès à présent charger et enregistrer des
composants, vous pouvez utiliser la première approche. Les méthodes suivantes
chargent et stockent le composant créé dynamiquement qui représente la valeur
d’une propriété appelée MyCompProperty :
procedure TSampleComponent.LoadCompProperty(Reader: TReader);
begin
if Reader.ReadBoolean then
MyCompProperty := Reader.ReadComponent(nil);
end;
procedure TSampleComponent.StoreCompProperty(Writer: TWriter);
begin
Writer.WriteBoolean(MyCompProperty <> nil);
if MyCompProperty <> nil then
Writer.WriteComponent(MyCompProperty);
end;

Redéfinition de la méthode DefineProperties


Après avoir créé des méthodes de stockage et de chargement de la valeur de
propriété, vous pouvez redéfinir la méthode DefineProperties du composant.
Delphi appelle cette méthode lors du chargement ou du stockage du composant.
Dans la méthode DefineProperties, vous devez appeler la méthode DefineProperty
ou DefineBinaryProperty du filer en cours, en lui transmettant la méthode à
utiliser pour le chargement ou l’enregistrement de la valeur de propriété. Si vos
méthodes de chargement et de stockage sont des types TWriterProc et
TReaderProc, vous appelez alors la méthode DefineProperty du filer. Si vous avez
créé des méthodes de type TStreamProc, appelez plutôt DefineBinaryProperty.
Peu importe la méthode utilisée pour définir la propriété, vous lui transmettez
les méthodes enregistrant et chargeant votre valeur de propriété ainsi qu’une
valeur booléenne indiquant si la valeur de propriété doit être écrite ou non.
S’il peut s’agir d’une valeur héritée ou si elle a une valeur par défaut, il n’est pas
nécessaire de l’écrire.
Soit par exemple la méthode LoadCompProperty du type TReaderProc et la
méthode StoreCompProperty du type TWriterProc, vous redéfiniriez DefineProperties
de la manière suivante :
procedure TSampleComponent.DefineProperties(Filer: TFiler);
function DoWrite: Boolean;
begin
if Filer.Ancestor <> nil then { recherche l’ancêtre pour une valeur héritée }
begin
if TSampleComponent(Filer.Ancestor).MyCompProperty = nil then
Result := MyCompProperty <> nil

3-16 Guide du concepteur de composants


Stockage et chargement des propriétés

else if MyCompProperty = nil or


TSampleComponent(Filer.Ancestor).MyCompProperty.Name <> MyCompProperty.Name then
Result := True
else Result := False;
end
else { aucune valeur héritée -- cherche la valeur par défaut (nil) }
Result := MyCompProperty <> nil;
end;
begin
inherited; { autorise la définition des propriétés par les classes de base }
Filer.DefineProperty(’MyCompProperty’, LoadCompProperty, StoreCompProperty, DoWrite);
end;

Création de propriétés 3-17


3-18 Guide du concepteur de composants
Chapitre

Création d’événements
Chapitre4
4
Un événement est un lien entre une occurrence du système (une action
de l’utilisateur ou un changement de focalisation, par exemple) et le fragment
de code qui assure effectivement la réponse. Le code de réponse est le
gestionnaire de l’événement, dont l’écriture est presque toujours du ressort du
développeur de l’application. Grâce aux événements, les développeurs peuvent
personnaliser le comportement des composants sans avoir besoin de modifier
les classes elles-mêmes. Cela s’appelle la délégation.
Les événements associés aux actions utilisateur les plus usuelles (par exemple,
les actions de la souris) sont intégrés dans les composants standard, mais vous
pouvez aussi définir de nouveaux événements. Pour être capable de créer des
événements dans un composant, vous devez avoir compris ce qui suit :
• Qu’est-ce qu’un événement ?
• Implémentation des événements standard
• Définition de vos propres événements
Les événements étant implémentés en tant que propriétés, il vaut mieux avoir
bien compris le Chapitre 3, “Création de propriétés”, avant de créer ou de
modifier les événements d’un composant.

Qu’est-ce qu’un événement ?


Un événement est un mécanisme qui établit un lien entre une occurrence et une
partie de code. Plus précisément, un événement est un pointeur de méthode qui
pointe sur une méthode dans une instance de classe spécifique.
Du point de vue du développeur d’applications, l’événement est simplement
un nom relié à une occurrence du système, comme OnClick, auquel du code peut
être attaché. Par exemple, un bouton-poussoir appelé Button1 dispose d’une
méthode OnClick. Par défaut, lorsque vous attribuez une valeur à l’événement
OnClick, le concepteur de fiche génère l’événement appelé Button1Click dans

Création d’événements 4-1


Qu’est-ce qu’un événement ?

la fiche contenant le bouton et l’associe à l’événement OnClick. Lorsqu’un


événement clic de souris se produit sur le bouton, ce dernier appelle la méthode
associée à OnClick qui est pour notre exemple Button1Click.
Pour écrire un événement, vous devez comprendre ceci :

Button1.OnClick pointe sur Form1.Button1Click


L’utilisateur clique sur Button1
Form1.Button1Click s’exécute

Occurrence Evénement Gestionnaire d’événement

• Les événements sont des pointeurs de méthodes.


• Les événements sont des propriétés.
• Les types d’événements sont des types de pointeurs de méthodes.
• Les types gestionnaire d’événement sont des procédures.
• Les gestionnaires d’événements sont facultatifs.

Les événements sont des pointeurs de méthodes


Delphi utilise les pointeurs de méthodes pour implémenter les événements.
Un pointeur de méthode est un type particulier de pointeur qui pointe sur une
méthode précise située dans une instance de classe. En tant qu’auteur de
composant, vous pouvez voir les pointeurs de méthodes comme des marques
de réservation. Après la détection d’un événement par votre code, vous appelez
la méthode (si elle existe) définie par l’utilisateur pour gérer cet événement.
Les pointeurs de méthodes fonctionnent comme les autres types procéduraux,
mais ils maintiennent un pointeur caché sur une instance de classe. Quand le
développeur d’applications associe un gestionnaire à un événement du
composant, l’association ne s’effectue pas seulement avec une méthode ayant un
nom particulier, mais avec une méthode d’une instance de classe particulière.
Sans être obligatoire, cette instance correspond généralement à la fiche contenant
le composant.
Tous les contrôles, par exemple, héritent d’une méthode dynamique appelée Click
pour la gestion des événements de clic avec la souris :
procedure Click; dynamic;
L’implémentation de Click appelle le gestionnaire utilisateur des événements liés
aux clics de la souris, s’il existe. Si l’utilisateur a affecté un gestionnaire à
l’événement OnClick d’un contrôle, le fait de cliquer sur ce dernier génère l’appel
de la méthode. Si aucun gestionnaire n’est affecté, rien ne se produit.

Les événements sont des propriétés


Ces composants utilisent les propriétés pour implémenter les événements. Mais,
contrairement à la plupart des propriétés, les propriétés événement ne font pas
appel à des méthodes pour implémenter leurs parties read et write. Elles
utilisent plutôt un champ de classe privé de même type que la propriété.

4-2 Guide du concepteur de composants


Qu’est-ce qu’un événement ?

Par convention, le nom de la donnée membre est le même que celui de la


propriété précédé de la lettre F. Par exemple, le pointeur de la méthode OnClick
est stocké dans une donnée membre intitulée FOnClick de type TNotifyEvent ; la
déclaration de la propriété de l’événement OnClick ressemble à ceci :
type
TControl = class(TComponent)
private
FOnClick: TNotifyEvent; { déclare un champ pour contenir le pointeur de méthode }
ƒ
protected
property OnClick: TNotifyEvent read FOnClick write FOnClick;
end;
Pour en savoir davantage sur TNotifyEvent et sur les autres types d’événements,
voir la prochaine section, “Les types d’événements sont des types de pointeurs
de méthodes”.
Comme pour toute autre propriété, vous pouvez définir ou modifier la valeur
d’un événement au moment de l’exécution. L’avantage principal des événements
implémentés sous la forme de propriétés est que l’utilisateur de composant peut
lui associer un gestionnaire au moment de la conception à l’aide de l’inspecteur
d’objets.

Les types d’événements sont des types de pointeurs de méthodes


Comme un événement est un pointeur sur un gestionnaire d’événement, le type
d’une propriété événement correspond nécessairement à un pointeur de
méthode. De même, tout code utilisé comme gestionnaire d’événements doit être
de type méthode de classe.
Toutes les méthodes gestionnaire d’événement sont des procédures. Pour être
compatible avec un événement d’un type particulier, une méthode gestionnaire
d’événements doit avoir le même nombre de paramètres, les paramètres étant de
même type et transmis dans le même ordre.
Delphi définit des types de méthode pour tous les événements standard. Lorsque
vous créez vos propres événements, vous pouvez utiliser un type existant s’il est
approprié ou définir votre propre type.

Les types gestionnaire d’événement sont des procédures


Bien que le compilateur vous permet de déclarer des types pointeur de méthode
qui sont des fonctions, vous ne devrez jamais le faire pour la gestion
d’événements. Comme une fonction vide renvoie un résultat non défini, un
gestionnaire d’événement vide qui était une fonction ne pourra pas toujours être
correct. Pour cette raison, tous vos événements et leurs gestionnaires
d’événements associés doivent être des procédures.
Bien qu’un gestionnaire d’événement ne puisse pas être une fonction, vous
pouvez toujours obtenir les informations à partir du code du développeur de
l’application en utilisant les paramètres var. Lorsque vous effectuerez ceci,
vérifiez que vous affectez une valeur correcte au paramètre avant d’appeler le

Création d’événements 4-3


Qu’est-ce qu’un événement ?

gestionnaire afin de ne pas rendre obligatoire la modification de la valeur par le


code de l’utilisateur.
Un exemple de transmission des paramètres var à un gestionnaire d’événement
est fourni par l’événement OnKeyPress, de type TKeyPressEvent. TKeyPressEvent
définit deux paramètres, l’un indiquant l’objet ayant généré l’événement et
l’autre la touche enfoncée :
type
TKeyPressEvent = procedure(Sender: TObject; var Key: Char) of object;
Normalement, le paramètre Key contient le caractère enfoncé par l’utilisateur.
Toutefois dans certaines circonstances, l’utilisateur de composant peut souhaiter
changer ce caractère. Par exemple, pour forcer tous les caractères en majuscules
dans un éditeur. Dans un cas comme celui-là, l’utilisateur doit définir le
gestionnaire suivant pour gérer les frappes de touche :
procedure TForm1.Edit1KeyPressed(Sender: TObject; var Key: Char);
begin
Key := UpCase(Key);
end;
Vous pouvez également utiliser les paramètres var pour permettre à l’utilisateur
de redéfinir la gestion par défaut.

Les gestionnaires d’événements sont facultatifs


Lorsque vous créez des composants, n’oubliez pas que les développeurs qui les
utilisent ne vont pas forcément leur associer des gestionnaires. Cela signifie que
l’exécution de vos composants ne doit pas échouer ni générer d’erreur parce que
l’utilisateur n’a pas associé un gestionnaire à un événement. Le mécanisme pour
appeler un gestionnaire et gérer les événements, alors que l’utilisateur n’a pas
associé de gestionnaire, est décrit dans “Appel de l’événement” à la page 4-9.
Des événements se produisent en permanence dans une application GUI. Le
simple fait de déplacer le pointeur de la souris sur un composant visuel envoie
de nombreux messages de déplacement de la souris que le composant traduit en
événements OnMouseMove. Dans la plupart des cas, les développeurs ne veulent
pas gérer les événements déplacement de souris et il ne faut pas que cela pose
un problème. Aussi, les composants que vous créez doivent posséder des
gestionnaires pour ces événements.
Mais surtout, les développeurs d’applications peuvent écrire le code qu’ils
veulent dans un gestionnaire d’événement. Les gestionnaires d’événements des
composants de la bibliothèque de classes sont écrits de façon à minimiser le
risque de génération d’erreurs. Evidemment, vous ne pouvez pas empêcher les
erreurs de logique dans le code de l’application, mais vous pouvez vous assurer
que les structures de données sont initialisées avant que les événements ne soient
appelés, de sorte que les développeurs ne tentent pas d’accéder à des données
incorrectes.

4-4 Guide du concepteur de composants


Implémentation des événements standard

Implémentation des événements standard


Les contrôles fournis avec la bibliothèque de composants héritent des
événements correspondant aux occurrences les plus courantes. Ces événements
sont appelés événements standard. Bien que tous ces événements soient intégrés
aux contrôles standard, ils sont souvent déclarés protected, pour que les
développeurs ne puissent pas leur associer de gestionnaire. Lorsque vous créez
un contrôle, vous pouvez choisir de rendre visibles certains événements aux
utilisateurs de votre contrôle.
Les trois points suivants sont à prendre en compte pour incorporer des
événements standard dans vos contrôles :
• Identification des événements standard
• Rendre visibles des événements
• Changement de la gestion des événements standard

Identification des événements standard


Il existe deux catégories d’événements standard : ceux définis pour tous les
contrôles et ceux définis uniquement pour les contrôles fenêtrés standard.

Evénements standard pour tous les contrôles


Les événements de base sont définis dans la classe TControl. Tous les contrôles,
qu’ils soient fenêtrés, graphiques ou personnalisés, héritent de ces événements.
Les événements suivants sont disponibles pour l’ensemble des contrôles :

OnClick OnDragDrop OnEndDrag OnMouseMove


OnDblClick OnDragOver OnMouseDown OnMouseUp

Les événements standard disposent de méthodes virtuelles protégées, déclarées


dans TControl, dont les noms correspondent aux noms des événements. Par
exemple, les événements OnClick appellent une méthode nommée Click, et les
événements OnEndDrag appellent une méthode nommée DoEndDrag.

Evénements standard pour les contrôles standard


Outre les événements communs à tous les contrôles, les contrôles fenêtrés
standard (ceux descendant de TWinControl dans les applications VCL et de
TWidgetControl dans les applications CLX) disposent des événements suivants :

OnEnter OnKeyDown OnKeyPress


OnKeyUp OnExit

Comme les événements standard de TControl, les événements des contrôles


fenêtrés disposent de méthodes correspondantes. Les événements de touches
standard présentés ci-dessus répondent à toutes les frappes de touches normales.

Création d’événements 4-5


Implémentation des événements standard

Remarque Pour répondre aux frappes de touches spéciales (comme la touche Alt), vous
devez répondre au message WM_GETDLGCODE ou CM_WANTSPECIALKEYS
de Windows. Voir Chapitre 7, “Gestion des messages et des notifications
système”, pour plus d’informations sur l’écriture de gestionnaires de messages.

Rendre visibles des événements


Les événements standard de TControl et TWinControl (TWidgetControl dans les
applications CLX) sont déclarés protected, de même que les méthodes
correspondantes. Si vous héritez de l’une de ces classes abstraites et voulez
rendre leurs événements accessibles à l’exécution ou pendant la conception, vous
devez redéclarer les événements soit public, soit published.
La redéclaration d’une propriété sans spécifier d’implémentation conserve les
mêmes méthodes d’implémentation en ne modifiant que leur niveau de
protection. Vous pouvez donc prendre en compte un événement défini dans
TControl mais non visible, et le rendre visible en le déclarant public ou
published.
Par exemple, pour créer un composant qui rende visible en mode conception
l’événement OnClick, ajoutez les lignes suivantes à la déclaration de classe du
composant.
type
TMyControl = class(TCustomControl)
ƒ
published
property OnClick;
end;

Changement de la gestion des événements standard


Pour modifier la façon dont votre composant répond à un certain type
d’événement, vous pouvez être tenté d’écrire un fragment de code pour
l’associer à l’événement en question. C’est précisément ce que ferait le
développeur d’applications. Mais lorsque vous créez un composant, vous devez
faire en sorte que l’événement reste disponible pour les développeurs qui vont
utiliser le composant.
C’est ce qui justifie les méthodes protégées de l’implémentation associées à
chaque événement standard. En redéfinissant la méthode d’implémentation, vous
pouvez modifier la gestion interne de l’événement et en appelant la méthode
reçue en héritage, vous préservez la gestion standard, y compris la gestion de
l’événement par le code du développeur d’applications.
L’ordre dans lequel vous appelez les méthodes est significatif. En règle générale,
vous appelez d’abord la méthode héritée pour que le gestionnaire d’événement
du développeur d’applications s’exécute avant vos modifications (et parfois,
empêche l’exécution de vos modifications). Toutefois, dans certaines situations,
vous voulez exécuter votre code avant d’appeler la méthode héritée. Par
exemple, si le code reçu en héritage dépend d’une façon ou d’une autre de l’état

4-6 Guide du concepteur de composants


Définition de vos propres événements

de composant et si votre code agit sur cet état, vous devez d’abord effectuer ces
changements d’état avant d’autoriser le code de l’utilisateur à y répondre.
Supposons que vous écrivez un composant et que vous souhaitez modifier la
façon dont il répond aux clics de souris. Au lieu d’associer un gestionnaire à
l’événement OnClick, comme le ferait le développeur d’applications, redéfinissez
la méthode protégée Click :
procedure click override { déclaration forward }
ƒ
procedure TMyControl.Click;
begin
inherited Click; { exécute la gestion standard, y compris l’appel au gestionnaire }
ƒ { vos modifications s’insèrent ici }
end;

Définition de vos propres événements


Il est relativement rare de définir des événements entièrement nouveaux.
Toutefois, il peut arriver qu’un comportement complètement différent soit
introduit par un composant et il faut alors lui définir un événement.
Voici les étapes qui interviennent dans la définition d’un événement :
• Déclenchement de l’événement
• Définition du type de gestionnaire
• Déclaration de l’événement
• Appel de l’événement

Déclenchement de l’événement
Vous avez besoin de savoir ce qui a déclenché l’événement. Pour certains
événements, la réponse est évidente. Par exemple, un événement associé à
l’enfoncement du bouton de souris se produit lorsque l’utilisateur clique avec
le bouton gauche de la souris provoquant l’envoi par Windows d’un message
WM_LBUTTONDOWN à l’application. La réception de ce message provoque
l’appel de la méthode MouseDown d’un composant qui à son tour appelle le code
que l’utilisateur a associé à l’événement OnMouseDown.
Néanmoins, certains événements sont liés de façon moins évidente à des
occurrences externes moins spécifiques. Par exemple, une barre de défilement
dispose d’un événement OnChange qui peut être déclenché par plusieurs
occurrences, telles des frappes de touche, des clics de souris, ou des
modifications dans d’autres contrôles. Lorsque vous définissez vos événements,
assurez-vous que les occurrences appellent tous les événements appropriés.
Remarque Pour les applications CLX, voir “Réponse aux notifications du système à l’aide
de CLX” à la page 7-10.

Création d’événements 4-7


Définition de vos propres événements

Deux sortes d’événements


Il existe deux types d’occurrence pour lesquels vous pouvez être amené à fournir
des événements : les interactions utilisateur et les modifications d’état. Les
événements de type interaction utilisateur sont pratiquement toujours déclenchés
par un message issu de Windows indiquant que l’utilisateur a agi sur votre
composant d’une façon qui peut nécessiter une réponse de votre part. Les
événements de modification d’état peuvent aussi être le fait de messages issus de
Windows (par exemple, des changements de focalisation ou d’activation).
Cependant, ils peuvent également survenir à la suite d’une modification de
propriété ou de l’exécution d’une autre partie de code.
Vous disposez d’un contrôle total sur le déclenchement des événements que vous
avez vous-même définis. Définissez les événements avec soin de sorte que les
développeurs soient capables de les comprendre et de les utiliser.

Définition du type de gestionnaire


Après avoir détecté que l’un de vos événements s’est produit, vous devez définir
la façon de le gérer. Cela implique que vous devez déterminer le type du
gestionnaire d’événement. Dans la plupart des cas, les gestionnaires
d’événements que vous définissez vous-même seront des notifications simples ou
spécifiques à des événements particuliers. Il est également possible de récupérer
de l’information en provenance du gestionnaire.

Notifications simples
Un événement de type notification ne fait qu’indiquer qu’un événement
particulier s’est produit sans fournir aucune information sur le moment et
l’endroit où il s’est produit. Les notifications utilisent le type TNotifyEvent, qui
véhiculent un paramètre unique correspondant à l’émetteur de l’événement. Les
seuls éléments “connus” du gestionnaire associé à une notification sont donc le
type d’événement et le composant impliqué. Par exemple, les événements clic de
souris sont des notifications. Lorsque vous écrivez un gestionnaire pour un
événement de ce type, vous ne récupérez que deux informations : le fait qu’un
clic s’est produit et le composant impliqué.
Une notification est un processus à sens unique. Il n’existe aucun mécanisme
pour renvoyer une information en retour ou pour inhiber la gestion d’une
notification.

Gestionnaires d’événements spécifiques


Dans certains cas, savoir qu’un événement s’est produit et connaître le
composant impliqué n’est pas suffisant. Par exemple, si l’événement correspond
à l’enfoncement d’une touche, le gestionnaire voudra savoir quelle est cette
touche. Dans un cas comme celui-là, vous devez disposer d’un gestionnaire qui
accepte des paramètres pour ces informations supplémentaires.

4-8 Guide du concepteur de composants


Définition de vos propres événements

Si votre événement a été généré en réponse à un message, les paramètres


transmis au gestionnaire d’événement seront vraisemblablement issus des
paramètres du message.

Renvoi d’informations à partir du gestionnaire


Comme tous les gestionnaires d’événements sont des procédures, la seule façon
de renvoyer des informations à partir d’un gestionnaire consiste à faire appel
à un paramètre var. Vos composants peuvent utiliser les informations ainsi
récupérées pour déterminer le traitement éventuel d’un événement après
l’exécution du gestionnaire de l’utilisateur.
Par exemple, tous les événements liés aux touches (OnKeyDown, OnKeyUp et
OnKeyPress) transmettent par référence la valeur de la touche enfoncée dans un
paramètre intitulé Key. Le gestionnaire d’événement peut changer Key de façon à
donner l’impression à l’application qu’une touche différente est impliquée dans
l’événement. Cela permet par exemple de forcer en majuscules les caractères
tapés.

Déclaration de l’événement
Une fois déterminé le type de votre gestionnaire d’événement, vous pouvez
déclarer le pointeur de méthode et la propriété pour l’événement. N’oubliez pas
d’attribuer un nom à l’événement qui soit à la fois significatif et descriptif pour
que l’utilisateur puisse comprendre son rôle. Dans la mesure du possible,
choisissez des noms de propriétés qui ressemblent à ceux de composants déjà
définis.

Les noms d’événement débutent par “On”


Dans Delphi, les noms de la plupart des événements commencent par “On”.
Il s’agit d’une simple convention ; le compilateur n’impose pas cette restriction.
L’inspecteur d’objets détermine qu’une propriété est un événement en examinant
le type de la propriété : toutes les propriétés de type pointeur de méthode sont
interprétées comme des événements et apparaissent donc dans la page
Evénements.
Les développeurs s’attendent à trouver les événements dans la liste alphabétique
à l’endroit des noms commençant par “On.” Vous risquez d’introduire une
certaine confusion en utilisant une autre convention.
Remarque Exception principale à cette règle : les nombreux événements qui se produisent
avant ou après certaines occurrences commencent par “Before” ou “After”.

Appel de l’événement
Il est préférable de centraliser tous les appels à un événement. Autrement dit,
créez une méthode virtuelle dans votre composant qui appelle le gestionnaire
d’événement de l’application (s’il a été défini) et qui fournit une gestion par
défaut.

Création d’événements 4-9


Définition de vos propres événements

Le fait de rassembler tous les appels à un événement en un seul endroit vous


permet d’être sûr qu’un programmeur, qui dérive un nouveau composant à
partir du vôtre, pourra personnaliser la gestion de l’événement en redéfinissant
cette méthode sans avoir à parcourir votre code pour repérer les endroits où
l’événement est appelé.
Deux autres considérations sont à prendre en compte concernant l’appel de
l’événement :
• Les gestionnaires vides doivent être valides.
• Les utilisateurs peuvent redéfinir la gestion par défaut.

Les gestionnaires vides doivent être valides


Vous ne devez jamais créer une situation dans laquelle un gestionnaire
d’événement vide provoque une erreur, ou dans laquelle le bon fonctionnement
d’un composant dépend d’une réponse spécifique provenant du code de gestion
d’un événement dans l’application.
Un gestionnaire vide doit produire le même effet qu’un gestionnaire absent.
Aussi, le code pour appeler le gestionnaire d’événement dans une application
doit ressembler à ceci :
if Assigned(OnClick) then OnClick(Self);
ƒ { exécute la gestion par défaut }
Il ne doit en aucun cas ressembler à ceci :
if Assigned(OnClick) then OnClick(Self)
else { exécute la gestion par défaut};

Les utilisateurs peuvent redéfinir la gestion par défaut


Pour certains types d’événements, les développeurs peuvent vouloir remplacer
la gestion par défaut ou même supprimer l’ensemble des réponses. Pour
permettre cela, vous devez transmettre au gestionnaire un argument par
référence et vérifier si le gestionnaire renvoie une certaine valeur.
Cela reste dans la lignée de l’affirmation qu’un gestionnaire vide doit produire
le même effet qu’un gestionnaire absent : puisqu’un gestionnaire vide ne modifie
en rien la valeur des arguments passés par référence, la gestion par défaut se
déroule toujours après l’appel du gestionnaire vide.
Par exemple, lors de la gestion des événements frappe de touches, le
développeur d’applications peut omettre la gestion par défaut de la frappe de
touches du composant en attribuant le caractère null (#0) au paramètre var Key.
La logique de programmation sous-jacente est la suivante :
if Assigned(OnKeyPress) then OnKeyPress(Self, Key);
if Key <> #0 then ... { exécute la gestion par défaut}
Le code réel est légèrement différent car il prend également en compte les
messages Windows mais la logique reste la même. Par défaut, le composant
appelle le gestionnaire défini par l’utilisateur avant d’exécuter la gestion
standard. Si le gestionnaire défini par l’utilisateur attribue le caractère null à Key,
le composant omet l’exécution de la gestion par défaut.

4-10 Guide du concepteur de composants


Chapitre

Création de méthodes
Chapitre5
5
Les méthodes des composants sont des procédures et des fonctions intégrées
dans la structure d’une classe. Il n’existe pratiquement aucune restriction sur
ce que peuvent réaliser les méthodes d’un composant, mais Delphi n’en respecte
pas moins un certain nombre de standards qu’il est préférable de suivre.
Ce sont :
• Eviter les interdépendances
• Noms des méthodes
• Protection des méthodes
• Rendre virtuelles des méthodes
• Déclaration des méthodes
En général, les composants ne doivent pas contenir beaucoup de méthodes
et vous devez chercher à minimiser le nombre des méthodes appelées par une
application. Il est préférable d’encapsuler sous la forme de propriétés des
caractéristiques qu’il serait tentant d’implémenter sous forme de méthodes.
Les propriétés fournissent une interface qui s’inscrit parfaitement dans Delphi et
sont accessibles au moment de la conception.

Eviter les interdépendances


Lorsque vous écrivez un composant, vous devez réduire au minimum les
conditions préalables imposées aux développeurs. Dans toute la mesure du
possible, les développeurs doivent pouvoir faire ce qu’ils veulent de votre
composant, et à tout moment. Il existe des situations où vous ne pourrez
répondre à cette exigence mais le but n’en demeure pas moins de s’en approcher
au plus près.
La liste suivante donne quelques indications sur ce qu’il faut éviter :
• Les méthodes qu’un utilisateur doit obligatoirement appeler pour utiliser
un composant.

Création de méthodes 5-1


Noms des méthodes

• Les méthodes qui doivent s’exécuter selon un ordre défini.


• Les méthodes qui placent le composant dans un état ou un mode pour lequel
certains événements ou certaines méthodes deviennent incorrectes.
La meilleure façon de gérer les situations de ce type est de fournir le moyen
d’en sortir. Par exemple, si l’appel d’une méthode a pour effet de placer votre
composant dans un état où l’appel d’une autre méthode s’avère incorrect, vous
devez modifier cette seconde méthode de telle manière que si elle est appelée
alors que le composant se trouve dans un état impropre, elle corrige cet état
avant d’exécuter son propre code principal. Faute de mieux, vous devrez
déclencher une exception si l’utilisateur appelle une méthode non valide.
En d’autres termes, si vous générez une situation dans laquelle il existe des
interdépendances entre certaines parties de votre code, il est de votre
responsabilité de vous assurer qu’une utilisation incorrecte du code n’engendre
pas de problème. Un message d’avertissement, par exemple, est préférable à une
fin d’exécution anormale si l’utilisateur n’a pas respecté ces interdépendances.

Noms des méthodes


Delphi n’impose aucune restriction quant à la façon de nommer les méthodes et
leurs paramètres. Toutefois, certaines conventions facilitent l’exploitation des
méthodes par les développeurs d’applications. Souvenez-vous que l’architecture
même d’un composant a son influence sur les différents types de personnes qui
pourront utiliser ce composant.
Si vous avez l’habitude d’écrire du code qui ne s’adresse qu’à un nombre
restreint de programmeurs, vous ne vous êtes sans doute jamais interrogé sur le
choix du nom des entités que vous manipulez. Il est souhaitable de choisir des
noms compréhensibles car vos composants s’adressent à tous, y compris à ceux
qui ne connaissent pas bien votre code (ou qui maîtrisent imparfaitement la
programmation).
Voici quelques suggestions pour définir des noms de méthode compréhensibles :
• Choisissez des noms descriptifs. Utilisez des verbes d’action.
Un nom tel que CollerPressepapiers est plus explicite que Coller ou CP.
• Les noms de fonctions doivent refléter la nature de ce qu’elles renvoient.
Bien qu’il puisse paraître évident, à vous programmeur, que le rôle d’une
fonction intitulée X soit de renvoyer la coordonnée horizontale d’un élément,
un nom tel que ObtenirPositionHorizontale sera compris par tout le monde.
Comme dernière considération, assurez-vous que votre méthode ait réellement
besoin d’être créée comme telle. Que le nom de votre méthode puisse être un
verbe est un bon repère. Si ce n’est pas le cas, demandez-vous s’il ne serait pas
préférable de transformer votre méthode en propriété.

5-2 Guide du concepteur de composants


Protection des méthodes

Protection des méthodes


Toutes les parties des classes, y compris les champs, les méthodes et les
propriétés, ont différents niveaux de protection ou de “visibilité”, comme
l’explique “Contrôle des accès” à la page 2-4. Il est facile de choisir le niveau qui
convient.
La plupart des méthodes écrites dans vos composants sont publiques ou
protégées. Il n’y a généralement pas lieu de déclarer une méthode private, à
moins qu’elle soit réellement spécifique à ce type de composant, au point que
même les composants dérivés ne peuvent pas y accéder.

Méthodes qui doivent être publiques


Toutes les méthodes qui peuvent être appelées par les développeurs
d’applications doivent être déclarées public. N’oubliez pas que la plupart des
appels aux méthodes ont lieu dans les gestionnaires d’événements, aussi les
méthodes doivent éviter de gaspiller les ressources système ou de placer le
système d’exploitation dans un état où il n’est plus en mesure de répondre
à l’utilisateur.
Remarque Les constructeurs et destructeurs sont toujours déclarés public.

Méthodes qui doivent être protégées


Toute méthode d’implémentation d’un composant doit être déclarée protected
afin d’empêcher les applications de les appeler à un moment inopportun. Si vous
avez défini des méthodes qui doivent demeurer inaccessibles au code, tout en
restant accessibles aux classes dérivées, vous devez les déclarer protected.
Par exemple, supposons une méthode dont l’exécution dépend de l’initialisation
préalable d’une donnée. Si cette méthode est déclarée public, il peut arriver que
les applications tentent de l’appeler avant l’initialisation de la donnée. Mais, en
la déclarant protected, les applications ne peuvent le faire directement. Vous
pouvez alors définir d’autres méthodes public qui se chargent d’initialiser la
donnée avant d’appeler la méthode protected.
Les méthodes d’implémentation des propriétés doivent être déclarées comme
virtuelles et protected. Les méthodes ainsi déclarées permettent aux
développeurs d’applications de surcharger l’implémentation des propriétés,
augmentant leurs fonctionalités ou les remplaçant complètement. De telles
propriétés sont complètement polymorphes. Instaurer un accès protected à ces
méthodes garantit que les développeurs ne pourront pas les appeler par accident
ni modifier la propriété par inadvertance.

Création de méthodes 5-3


Rendre virtuelles des méthodes

Méthodes abstraites
Une méthode est parfois déclarée abstract dans un composant Delphi. Dans la
bibliothèque de composants, les méthodes abstraites se produisent habituellement
dans les classes dont les noms commencent par “custom”, comme dans
TCustomGrid TCustomGrid. De telles classes sont elles-mêmes abstraites, au sens
où elles ne servent qu’à la dérivation de classes descendantes.
Bien que vous puissiez créer un objet instance d’une classe contenant un membre
abstrait, ce n’est pas recommandé. L’appel du membre abstrait entraîne une
exception EAbstractError.
La directive abstract est utilisée pour indiquer des parties de classes qui doivent
être surfacées et définies dans des composants descendants ; cela force les
écrivains de composants à redéclarer le membre abstrait dans des classes
descendantes avant que des instances actuelles de la classe puissent être créées.

Rendre virtuelles des méthodes


Vous rendrez virtuelles les méthodes lorsque vous souhaitez que des types
différents puissent exécuter des codes différents en réponse au même appel de
méthode.
Si vous créez des composants pour qu’ils soient exploitables par les
développeurs d’applications directement, vous voudrez probablement rendre non
virtuelles vos méthodes. D’autre part, si vous créez des composants abstraits
desquels d’autres composants vont dériver, vous devez envisager de rendre
virtuelles les méthodes ajoutées. De cette façon, les composants dérivés pourront
surcharger les méthodes virtuelles reçues en héritage.

Déclaration des méthodes


La déclaration d’une méthode dans un composant ne diffère en rien de celle
d’une méthode d’une autre classe.
Pour déclarer une nouvelle méthode dans un composant, vous devez :
• Ajouter la déclaration à la déclaration de type du composant dans le fichier
en-tête de ce dernier.
• Implémenter la méthode dans la partie implementation de l’unité du
composant.
Le code suivant montre un composant qui définit deux nouvelles méthodes,
l’une est déclarée protected static et l’autre public et virtual.
type
TSampleComponent = class(TControl)
protected
procedure MakeBigger; { déclare la méthode protected static }

5-4 Guide du concepteur de composants


Déclaration des méthodes

public
function CalculateArea: Integer; virtual; { déclare la méthode public virtual }
end;
ƒ
implementation
ƒ
procedure TSampleComponent.MakeBigger; { implémente la première méthode }
begin
Height := Height + 5;
Width := Width + 5;
end;
function TSampleComponent.CalculateArea: Integer; { implémente la deuxième méthode }
begin
Result := Width * Height;
end;

Création de méthodes 5-5


5-6 Guide du concepteur de composants
Chapitre

Graphiques et composants
Chapitre6
6
Windows fournit une puissante interface GDI (Graphics Device Interface) servant
à dessiner des graphiques indépendamment des périphériques.
Malheureusement, GDI impose au programmeur des contraintes supplémentaires
telles que la gestion des ressources graphiques. Delphi prend en charge toutes
ces tâches GDI ingrates, vous laisse vous concentrer sur le travail productif, vous
épargnant les recherches de handles perdus ou de ressources non restituées.
De même que toute partie de l’API Windows, vous pouvez appeler les fonctions
GDI directement depuis votre application Delphi Toutefois, vous vous rendrez
vite compte que l’utilisation de l’encapsulation Delphi des fonctions graphiques
est un moyen plus efficace et plus rapide de créer des graphiques.
Remarque Les fonctions GDI sont particulières à Windows et ne s’appliquent pas aux
applications CLX. Mais, des composants CLX utilisent la bibliothèque Qt.
Les rubriques de cette section comprennent :
• Présentation des graphiques
• Utilisation du canevas
• Travail sur les images
• Bitmaps hors écran
• Réponse aux changements

Présentation des graphiques


Delphi encapsule à différents niveaux les fonctions GDI de Windows (Qt dans
les applications CLX). Le programmeur qui écrit des composants doit
comprendre comment ceux-ci affichent leurs images à l’écran. Lorsque vous
appelez directement les fonctions GDI, vous devez disposer d’un handle sur un
contexte de périphérique dans lequel vous avez sélectionné des outils de dessin
comme les crayons, les pinceaux et les fontes. Après avoir tracé vos images, vous

Graphiques et composants 6-1


Présentation des graphiques

devez remettre le contexte de périphérique dans son état initial avant de le


restituer.
Au lieu de vous contraindre à gérer les graphiques au niveau détaillé, Delphi
met à votre disposition une interface simple mais complète : il s’agit de la
propriété Canvas des composants. Le canevas garantit qu’un contexte de
périphérique valide est disponible et restitue le contexte lorsqu’il est inutilisé.
Il dispose également de propriétés spécifiques pour représenter la fonte, le
crayon et le pinceau en cours.
Le canevas gère toutes ces ressources à votre place et vous n’avez donc pas le
souci de créer, de sélectionner ou de restituer les éléments comme le handle d’un
crayon. Vous n’avez qu’à indiquer au canevas le crayon à utiliser et il se charge
du reste.
Un des avantage à laisser Delphi gérer les ressources graphiques est que Delphi
peut les mettre en cache pour les utiliser ultérieurement, ce qui peut accélérer les
opérations répétitives. Par exemple, supposons qu’un programme crée, utilise
puis restitue un outil crayon d’un certain type plusieurs fois de suite, vous devez
répéter ces étapes chaque fois que vous l’utilisez. Comme Delphi met en cache
les ressources graphiques, il est probable que l’outil que vous utilisez souvent est
encore dans le cache et, au lieu d’avoir à recréer un outil, Delphi utilise celui qui
existe.
Par exemple, vous pouvez imaginer une application avec des dizaines de fiches
ouvertes et des centaines de contrôles. Chacun de ces contrôles peut présenter
une ou plusieurs propriétés TFont. Comme cela peut générer des centaines ou
des milliers d’instances d’objets TFont, la plupart des applications n’utiliseront
que deux ou trois handles de fontes grâce à un cache de fontes.
Voici deux exemples montrant à quel point le code Delphi manipulant des
graphiques peut être simple. Le premier utilise les fonctions GDI standard pour
dessiner une ellipse jaune bordée de bleu comme le ferait d’autres outils de
développement. Le second utilise un canevas pour dessiner la même ellipse dans
une application écrite avec Delphi.
procedure TMyWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct);
var
PenHandle, OldPenHandle: HPEN;
BrushHandle, OldBrushHandle: HBRUSH;
begin
PenHandle := CreatePen(PS_SOLID, 1, RGB(0, 0, 255)); { crée un crayon bleu }
OldPenHandle := SelectObject(PaintDC, PenHandle); { indique à DC d’utiliser
le crayon bleu }
BrushHandle := CreateSolidBrush(RGB(255, 255, 0)); { crée un pinceau jaune }
OldBrushHandle := SelectObject(PaintDC, BrushHandle); { indique à DC d’utiliser
le pinceau jaune }
Ellipse(HDC, 10, 10, 50, 50); { dessine l’ellipse }
SelectObject(OldBrushHandle); { restaure le pinceau d’origine}
DeleteObject(BrushHandle); { supprime le pinceau jaune }
SelectObject(OldPenHandle); { restaure le crayon d’origine }
DeleteObject(PenHandle); { détruit le crayon bleu }
end;

6-2 Guide du concepteur de composants


Utilisation du canevas

procedure TForm1.FormPaint(Sender: TObject);


begin
with Canvas do
begin
Pen.Color := clBlue; { attribue la couleur bleu au crayon }
Brush.Color := clYellow; { attribue la couleur jaune au pinceau }
Ellipse(10, 10, 50, 50); { dessine l’ellipse }
end;
end;

Utilisation du canevas
La classe canevas encapsule les contrôles graphiques à plusieurs niveaux, allant
des fonctions de haut niveau (pour dessiner des lignes, des formes et du texte)
aux propriétés de niveau intermédiaire pour manipuler les fonctionnalités de
dessin du canevas ; et dans la bibliothèque de composants, offre un accès de bas
niveau au GDI Windows.
Le Tableau 6.1 résume les possibilités du canevas.

Tableau 6.1 Résumé des possibilités du canevas


Niveau Opération Outils
Haut Dessin de lignes et de formes Méthodes comme MoveTo, LineTo, Rectangle
et Ellipse
Affichage et mesure de texte Méthodes TextOut, TextHeight, TextWidth
et TextRect
Remplissage de zones Méthodes FillRect et FloodFill
Intermédiaire Personnalisation de texte Propriétés Pen, Brush et Font
et des graphiques
Manipulation de pixels Propriété Pixels.
Copie et fusion d’images Méthodes Draw, StretchDraw, BrushCopy
et CopyRect ; propriété CopyMode
Bas Appel des fonctions GDI Propriété Handle
de Windows

Pour plus d’informations sur les classes canevas, leurs méthodes et leurs
propriétés, reportez-vous à l’aide en ligne.

Travail sur les images


Dans Delphi, la part la plus importante de votre travail sur les graphiques se
limitera au dessin direct sur le canevas des composants et des fiches. Mais
Delphi fournit également les moyens de gérer les images graphiques
indépendantes, comme les bitmaps, les métafichiers et les icônes, en assurant
dans le même temps la gestion automatique des palettes.

Graphiques et composants 6-3


Travail sur les images

Les trois sujets suivants sont nécessaires à la compréhension du travail sur les
images dans Delphi :
• Utilisation d’une image, d’un graphique ou d’un canevas
• Chargement et stockage des graphiques
• Gestion des palettes

Utilisation d’une image, d’un graphique ou d’un canevas


Il existe trois sortes de classes dans Delphi intervenant sur les graphiques :
• Un canevas représente une surface de dessin point par point dans une fiche,
un contrôle graphique, une imprimante ou un bitmap. Un canevas est toujours
une propriété de quelque chose d’autre, jamais une classe autonome.
• Un graphique représente une image graphique telle qu’elle se trouve dans un
fichier ou une ressource, comme un bitmap, une icône ou un métafichier.
Delphi définit les classes TBitmap, TIcon et TMetafile (VCL uniquement), toutes
descendants de l’objet générique TGraphic. Vous pouvez aussi définir vos
propres classes graphiques. En définissant une interface standard minimale
pour tous les graphiques, TGraphic fournit un mécanisme simple destiné aux
applications pour qu’elles puissent exploiter facilement les différentes sortes de
graphiques disponibles.
• Une image est le conteneur d’un graphique, elle peut donc contenir n’importe
quelle classe graphique. Autrement dit, un élément de type TPicture peut
contenir un bitmap, une icône, un métafichier, un type de graphique défini
par l’utilisateur, et l’application y accède d’une seule façon par l’intermédiaire
de la classe image. Par exemple, un contrôle image dispose d’une propriété
Picture, de type TPicture, qui le rend capable d’afficher les images de plusieurs
sortes de graphiques.
Souvenez-vous qu’une classe image a toujours un graphique et qu’un graphique
peut avoir un canevas. Le seul graphique standard ayant un canevas est TBitmap.
Normalement, lorsque vous avez affaire à une image, vous travaillez uniquement
avec les constituants de la classe graphique exposés via TPicture. Si vous voulez
accéder aux constituants spécifiques de la classe graphique elle-même, vous
pouvez faire référence à la propriété Graphic de l’image.

Chargement et stockage des graphiques


Toutes les images et tous les graphiques de Delphi peuvent charger leurs images
à partir d’un fichier et les stocker en retour dans ce même fichier (ou dans un
autre). Ceci peut s’effectuer à tout moment.
Remarque Vous pouvez aussi charger des images à partir d’un fichier et les enregistrer
dans une source MIME Qt ou un objet flux en cas de création de composants
multiplates-formes.

6-4 Guide du concepteur de composants


Travail sur les images

Pour charger une image dans un objet image à partir d’un fichier, appelez la
méthode LoadFromFile de l’objet image. Pour enregistrer une image dans un
fichier à partir d’un objet image, appelez la méthode SaveToFile de l’objet image.
LoadFromFile et SaveToFile acceptent le nom d’un fichier comme seul paramètre.
LoadFromFile utilise l’extension du fichier pour déterminer le type d’objet
graphique créé ou chargé. SaveToFile utilise le type de fichier approprié au type
d’objet graphique enregistré.
Pour charger un bitmap dans l’objet image d’un contrôle image, par exemple,
vous devez transmettre le nom du fichier bitmap à la méthode LoadFromFile de
l’objet image :
procedure TForm1.LoadBitmapClick(Sender: TObject);
begin
Image1.Picture.LoadFromFile(’RANDOM.BMP’);
end;
L’objet image reconnaît .bmp comme extension par défaut des fichiers bitmap,
elle crée donc le graphique en tant que TBitmap, avant d’appeler la méthode
LoadFromFile du graphique. Puisque le graphique est un bitmap, l’image est
chargée depuis le fichier en tant que bitmap.

Gestion des palettes


Pour les composants VCL et CLX, lorsque l’exécution fait intervenir un
périphérique supportant les palettes (typiquement un mode vidéo 256 couleurs),
Delphi assure automatiquement la réalisation des palettes. Si un contrôle dispose
d’une palette, vous pouvez utiliser deux méthodes héritées de TControl pour
contrôler la façon dont Windows prend en compte la palette.
Les points suivants sont à prendre en compte lorsque vous travaillez avec des
palettes :
• Spécification d’une palette pour un contrôle
• Réponse aux changements de palette
Toutefois, dans le cas des contrôles contenant des images graphiques riches en
couleurs (tels que le contrôle image) une interaction entre Windows et le pilote
de périphérique écran peut être nécessaire pour afficher correctement le contrôle.
Dans Windows, ce processus est appelé réalisation de palettes.
La réalisation de palettes est le processus mis en œuvre pour que la fenêtre en
avant-plan utilise la totalité de la palette qui lui est associée. Quant aux fenêtres
en arrière-plan, elles exploitent autant que possible les couleurs de leurs palettes
propres, l’approximation des couleurs manquantes se faisant en prenant la
couleur disponible la plus proche dans la palette “réelle”. Windows effectue un
travail permanent de réalisation des palettes au fur et à mesure que les fenêtres
sont déplacées d’avant en arrière-plan.
Remarque Delphi n’assure ni la création, ni la maintenance des palettes autres que celles
des bitmaps. Toutefois, si vous disposez d’un handle de palette, les contrôles
Delphi peuvent se charger de sa gestion.

Graphiques et composants 6-5


Bitmaps hors écran

Spécification d’une palette pour un contrôle


Pour spécifier la palette d’un contrôle, vous devez surcharger la méthode
GetPalette du contrôle pour qu’elle renvoie le handle de la palette.
Le fait de spécifier la palette d’un contrôle a deux conséquences pour votre
application :
• Cela informe l’application que la palette de votre contrôle doit être réalisée.
• Cela désigne la palette à utiliser pour cette réalisation.

Réponse aux changements de palette


Si votre contrôle VCL ou CLX spécifie une palette en surchargeant GetPalette,
Delphi se chargera de répondre automatiquement aux messages de palette en
provenance de Windows. La méthode qui gère les messages de palette est
PaletteChanged.
Le rôle primaire de PaletteChanged est de déterminer s’il est nécessaire de réaliser
les palettes des contrôles en avant et arrière-plan. Windows gère la réalisation
des palettes en faisant en sorte que la fenêtre la plus en avant dispose de la
palette d’avant-plan, la résolution des couleurs des autres fenêtres se faisant à
l’aide des palettes d’arrière-plan. Delphi va même plus loin car il réalise les
palettes des contrôles d’une fenêtre en respectant l’ordre de tabulation. Seul cas
où vous voudrez peut-être redéfinir ce comportement : lorsqu’un contrôle, autre
que le premier dans l’ordre de tabulation, doit récupérer la palette d’avant-plan.

Bitmaps hors écran


Lorsque vous dessinez des images graphiques complexes, une technique de
programmation graphique habituelle consiste à créer d’abord un bitmap hors
écran, puis à dessiner l’image dans ce bitmap et à copier ensuite la totalité de
l’image du bitmap vers sa destination finale à l’écran. L’utilisation d’une image
hors-écran réduit le scintillement causé par un tracé répétitif à l’écran.
Dans Delphi, la classe bitmap qui représente les images point par point stockées
dans les ressources et les fichiers, peut également fonctionner en tant qu’image
hors écran.
Les points suivants sont à prendre en compte lorsque vous travaillez avec des
bitmaps hors écran :
• Création et gestion des bitmaps hors écran.
• Copie des images bitmap.

Création et gestion des bitmaps hors écran


Lorsque vous créez des images graphiques complexes, vous devez généralement
éviter de les dessiner directement sur le canevas qui apparaît à l’écran. Au lieu
de les dessiner sur le canevas d’un formulaire ou d’un contrôle, vous devez

6-6 Guide du concepteur de composants


Réponse aux changements

plutôt construire un objet bitmap puis dessiner sur son canevas avant de copier
la totalité de l’image sur le canevas affiché.
La méthode Paint d’un contrôle graphique est un exemple d’utilisation typique
d’un bitmap hors écran. Comme avec tout objet temporaire, l’objet bitmap doit
être protégé par un bloc try..finally :
type
TFancyControl = class(TGraphicControl)
protected
procedure Paint; override; { surcharge la méthode Paint }
end;
procedure TFancyControl.Paint;
var
Bitmap: TBitmap; { variable temporaire pour le bitmap hors écran }
begin
Bitmap := TBitmap.Create; { construit l’objet bitmap }
try
{ dessine sur le bitmap }
{ copie le résultat dans le canevas du contrôle }
finally
Bitmap.Free; { détruit l’objet bitmap }
end;
end;

Copie des images bitmap


Delphi fournit quatre moyens pour copier des images d’un canevas à un autre.
Selon l’effet recherché, vous pouvez appeler différentes méthodes.
Le Tableau 6.2 résume les méthodes de copie d’image des objets canevas.

Tableau 6.2 Méthodes de copie des images


Pour créer cet effet Appelez cette méthode
Copie d’un graphique entier. Draw
Copie et redimensionnement d’un graphique. StretchDraw
Copie d’une partie d’un canevas. CopyRect
Copie d’un bitmap avec effets. BrushCopy (VCL)
Copie d’un graphique de manière répétitive afin de remplir TiledDraw (CLX)
une zone bitmap avec effets.

Réponse aux changements


Tous les objets graphiques, y compris les canevas et les objets dont ils sont
propriétaires (crayons, pinceaux et fontes), disposent d’événements intégrés pour
répondre aux changements. Grâce à ces événements, vous pouvez faire en sorte
que vos composants (ou les applications qui les utilisent) répondent aux
changements en redessinant leurs images.

Graphiques et composants 6-7


Réponse aux changements

S’agissant d’objets graphiques, la réponse aux changements est particulièrement


importante si ces objets sont publiés comme éléments accessibles de l’interface de
conception de vos composants. La seule façon d’être certain que l’aspect d’un
composant au moment de la conception corresponde aux propriétés définies dans
l’inspecteur d’objets consiste à répondre aux changements apportés aux objets.
Pour répondre aux modifications d’un objet graphique, vous devez associer une
méthode à l’événement OnChange de sa classe.
Le composant forme publie les propriétés qui représentent le crayon et le
pinceau qu’il utilise pour tracer sa forme. Le constructeur du composant associe
une méthode à l’événement OnChange de chacun, ce qui a pour effet de
provoquer le rafraîchissement de l’image du composant si le crayon ou le
pinceau est modifié :
type
TShape = class(TGraphicControl)
public
procedure StyleChanged(Sender: TObject);
end;
ƒ
implementation
ƒ
constructor TShape.Create(AOwner: TComponent);
begin
inherited Create(AOwner); { appelez toujours le constructeur hérité ! }
Width := 65;
Height := 65;
FPen := TPen.Create; { construit le crayon }
FPen.OnChange := StyleChanged; { affecte la méthode à l’événement OnChange }
FBrush := TBrush.Create; { construit le pinceau }
FBrush.OnChange := StyleChanged; { affecte la méthode à l’événement OnChange }
end;
procedure TShape.StyleChanged(Sender: TObject);
begin
Invalidate(); { détruit et redessine le composant }
end;

6-8 Guide du concepteur de composants


Chapitre

Gestion des messages


Chapitre7
7
et des notifications système
Les composants ont souvent à répondre à des notifications émises par le système
d’exploitation sous-jacent. Le système d’exploitation informe l’application de ce
qui se produit, par exemple ce que fait l’utilisateur avec la souris et le clavier.
Certains contrôles génèrent également des notifications, comme le résultat des
actions de l’utilisateur, par exemple la sélection d’un élément dans une liste.
La bibliothèque de composants gère déjà la majorité des notifications les plus
communes. Il est cependant possible que vous ayez à écrire votre propre code
de gestion de notifications.
Pour les applications VCL, les notifications se présentent sous la forme de
messages. Ces messages peuvent provenir de n’importe quelle source, y compris
de Windows, des composants VCL et des composants que vous avez définis.
Il y a trois aspects à prendre en considération lorsque vous travaillez avec
des messages :
• Compréhension du système de gestion des messages.
• Modification de la gestion des messages.
• Création de nouveaux gestionnaires de messages.
Pour les applications CLX, les notifications se présentent sous la forme de
signaux et d’événements système et non de messages Windows. Voir “Réponse aux
notifications du système à l’aide de CLX” à la page 7-10 pour les détails du
fonctionnement des notifications système dans CLX.

Compréhension du système de gestion des messages


Toutes les classes VCL disposent d’un mécanisme intégré pour gérer les
messages : ce sont les méthodes de gestion des messages ou gestionnaires de messages.
L’idée sous-jacente aux gestionnaires de messages est la suivante : un objet reçoit

Gestion des messages et des notifications système 7-1


Compréhension du système de gestion des messages

des messages qu’il répartit selon le message en appelant une méthode choisie
dans un ensemble de méthodes spécifiques. Un gestionnaire par défaut est
appelé si aucune méthode n’est définie pour le message.
Le diagramme suivant illustre le fonctionnement du système de répartition
de message :
Evénement MainWndProc WndProc Dispatch Gestionnaire

La bibliothèque des composants visuels définit un système de répartition des


messages qui convertit tous les messages Windows (y compris ceux définis par
l’utilisateur) destinés à une classe spécifique en appels à des méthodes. Vous
n’aurez sans doute jamais besoin de modifier le mécanisme de répartition des
messages. En revanche, vous aurez à écrire des méthodes de gestion des
messages. Voir “Déclaration d’une nouvelle méthode de gestion d’un message” à
la page 7-7, pour plus de détails sur ce sujet.

Que contient un message Windows ?


Un message Windows est un enregistrement de données contenant plusieurs
données membres exploitables. Le plus important est celui qui contient une
valeur de la taille d’un entier identifiant le message. Windows définit de
nombreux messages et l’unité Messages déclare tous leurs identificateurs.
Les autres informations utiles d’un message figurent dans deux champs
paramètre et un champ résultat.
Un paramètre contient 16 bits, l’autre 32 bits. Vous voyez souvent du code
Windows qui fait référence à ces valeurs avec wParam et lParam, comme
paramètre de type word et paramètre de type long. Souvent, chaque paramètre
contient plusieurs informations, et vous voyez les références aux noms comme
lParamHi, qui font référence au mot de poids fort dans le paramètre de type
long.
A l’origine, un programmeur Windows devait mémoriser le contenu de chaque
paramètre ou consulter les API Windows. Microsoft a désormais donné un nom
aux paramètres. Ces décomposeurs de message ainsi appelés simplifient la
compréhension des informations accompagnant chaque message. Par exemple,
les paramètres pour le message WM_KEYDOWN maintenant appelés nVirtKey
et lKeyData, donnent plus d’informations spécifiques que wParam et lParam.
Pour chaque type de message, Delphi définit un type d’enregistrement qui donne
un nom mnémonique à chaque paramètre. Les messages souris transmettent par
exemple les coordonnées x et y de l’événement souris dans le paramètre de type
long, une dans le mot de poids fort, et l’autre dans le mot de poids faible. Avec
l’utilisation de la structure souris-message, vous n’avez pas à vous soucier du
mot dont il s’agit, car vous faites référence aux paramètres par les noms XPos
et YPos au lieu de lParamLo et lParamHi.

7-2 Guide du concepteur de composants


Compréhension du système de gestion des messages

Répartition des messages


Lorsqu’une application crée une fenêtre, elle recense une procédure fenêtre avec
le modèle Windows. La procédure fenêtre représente la routine qui gère les
messages pour la fenêtre. Habituellement, la procédure fenêtre contient une
instruction longue case avec des entrées pour chaque message devant être géré
par la fenêtre. N’oubliez pas que “fenêtre” dans ce sens signifie seulement
quelque chose sur l’écran : chaque fenêtre, chaque contrôle, etc. A chaque fois
que vous créez un nouveau type de fenêtre, vous devez créer une procédure
fenêtre complète.
La VCL simplifie la répartition des messages de plusieurs manières :
• Chaque composant hérite d’un système complet de répartition de message.
• Le système de répartition de message dispose d’une gestion par défaut. Vous
ne définissez de gestionnaire que pour les messages auxquels vous souhaitez
spécifiquement répondre.
• Vous pouvez modifier des parties de la gestion de message en vous appuyant
sur les méthodes reçues en héritage pour la majeure partie du traitement.
Le bénéfice le plus évident de cette répartition de message est le suivant : à tout
moment, vous pouvez envoyer n’importe quel message à n’importe quel
composant. Si le composant n’a pas de gestionnaire défini pour ce message, le
système de gestion par défaut s’en charge, généralement en ignorant le message.

Suivi du flux des messages


La méthode MainWndProc est recensée par la VCL comme procédure de fenêtre
pour tous les types de composants d’une application. MainWndProc contient un
bloc de gestion des exceptions qui transmet la structure du message en
provenance de Windows à la méthode virtuelle WndProc, gérant les exceptions
éventuelles à l’aide de la méthode HandleException de la classe application.
MainWndProc est une méthode non virtuelle qui n’effectue aucune gestion
particulière des messages. Cette gestion a lieu dans WndProc, chaque type de
composant ayant la possibilité de surcharger cette méthode pour répondre à ses
besoins spécifiques.
Les méthodes WndProc vérifient les conditions spéciales qui peuvent affecter le
traitement et “interceptent”, s’il le faut, les messages non souhaités. Par exemple,
lorsque vous faites glisser un composant, celui-ci ignore les événements du
clavier, et la méthode WndProc de TWinControl ne transmet ces événements que
si l’utilisateur ne fait pas glisser le composant. Enfin, WndProc appelle Dispatch,
une méthode non virtuelle héritée de TObject, qui détermine la méthode à
appeler pour gérer le message.
Dispatch utilise le champ Msg de l’enregistrement du message pour déterminer
comment répartir le message particulier. Si le composant définit un gestionnaire
pour ce message, Dispatch appelle cette méthode. Si aucun gestionnaire n’est
défini, Dispatch appelle DefaultHandler.

Gestion des messages et des notifications système 7-3


Modification de la gestion des messages

Modification de la gestion des messages


Avant de modifier la gestion des messages de vos composants, vous devez être
certain de ce que vous voulez effectivement faire. La VCL convertit la plupart
des messages en événements que l’auteur ou l’utilisateur du composant peut
gérer. Plutôt que de modifier le comportement de la gestion du message, vous
modifierez généralement le comportement de la gestion de l’événement.
Pour modifier la gestion d’un message dans les composants VCL, vous devez
surcharger la méthode qui gère ce message. En outre, dans certaines
circonstances, vous pouvez empêcher un composant de gérer un message en
interceptant ce message.

Surcharge de la méthode du gestionnaire


Pour modifier la façon dont un composant gère un message en particulier, vous
devez surcharger la méthode qui le gère. Si le composant ne gère pas le message
en question, vous devez déclarer une nouvelle méthode de gestion du message.
Pour surcharger la méthode de gestion d’un message, déclarez une nouvelle
méthode dans votre composant avec le même index de message que la méthode
surchargée. N’utilisez pas la directive override ; vous devez utiliser la directive
message et un index de message correspondant.
Remarquez qu’il n’est pas nécessaire que le nom de la méthode et le type du
paramètre var simple correspondent à la méthode surchargée. Seul l’index de
message est significatif. Pour plus de clarté, cependant, il est préférable de suivre
la convention d’appel des méthodes de gestion de message après les messages
qu’elles gèrent.
Par exemple, pour surcharger la gestion du message WM_PAINT d’un
composant, redéclarez la méthode WMPaint :
type
TMyComponent = class(...)
ƒ
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
end;

Utilisation des paramètres d’un message


Une fois à l’intérieur d’une méthode de gestion de message, votre composant
peut accéder à tous les paramètres de la structure du message. Puisque le
paramètre passé au gestionnaire message est un paramètre var, le gestionnaire
peut modifier la valeur du paramètre si c’est nécessaire. Le seul paramètre qui
change fréquemment est le champ Result du message : il s’agit de la valeur
renvoyée par l’appel de SendMessage qui a émis le message.
Comme le type du paramètre Message transmis à la méthode de gestion dépend
du message géré, vous devez vous reporter à la documentation des messages

7-4 Guide du concepteur de composants


Modification de la gestion des messages

Windows pour connaître le nom et la signification de chaque paramètre. Si pour


une raison ou pour une autre, vous avez à vous référer aux paramètres d’un
message en utilisant l’ancienne convention d’appellation (WParam, LParam, etc.),
vous devez transtyper Message vers le type générique TMessage, qui utilise ces
noms de paramètres.

Interception des messages


Dans certaines circonstances, vous pouvez souhaiter que certains messages soient
ignorés par vos composants. Autrement dit, vous voulez empêcher le composant
de répartir un message à son gestionnaire. Pour intercepter un message de cette
façon, vous devez surcharger la méthode virtuelle WndProc.
Pour les composants VCL, la méthode WndProc sélectionne les messages avant
de les transmettre à la méthode Dispatch qui, à son tour, détermine la méthode
qui gère le message. En surchargeant WndProc, votre composant a la possibilité
de filtrer certains messages avant qu’ils ne soient transmis. La surcharge de
WndProc pour un contrôle dérivé de TWinControl ressemble à ceci :

procedure TMyControl.WndProc(var Message: TMessage);


begin
{ test pour déterminer la poursuite du traitement }
inherited WndProc(Message);
end;
Le composant TControl définit des plages entières de messages liés à la souris
qu’il filtre lorsqu’un utilisateur fait glisser puis lâche un contrôle. Une méthode
WndProc surchargée peut agir par deux moyens :
• Elle peut filtrer des plages entières de messages au lieu de spécifier un
gestionnaire pour chacun d’eux.
• Elle peut inhiber totalement la répartition des messages de façon à ce que les
gestionnaires ne soient jamais appelés.
Voici une partie de la méthode WndProc pour TControl, par exemple :
procedure TControl.WndProc(var Message: TMessage);
begin
ƒ
if (Message.Msg >= WM_MOUSEFIRST) and (Message.Msg <= WM_MOUSELAST) then
if Dragging then { gestion spécifique d’une opération glisser }
DragMouseMsg(TWMMouse(Message))
else
ƒ { gestion normale des autres opérations }
end;
ƒ { autrement gestion normale }
end;

Gestion des messages et des notifications système 7-5


Création de nouveaux gestionnaires de messages

Création de nouveaux gestionnaires de messages


Puisque la VCL fournit des gestionnaires pour la plupart des messages standard,
vous avez à définir de nouveaux gestionnaires de message uniquement lorsque
vous définissez vous-mêmes vos propres messages. Pour travailler avec des
messages définis par l’utilisateur, nous allons étudier les trois points suivants :
• Définition de vos propres messages.
• Déclaration d’une nouvelle méthode de gestion d’un message.
• Envoi des messages.

Définition de vos propres messages


De nombreux composants standard définissent des messages pour leur usage
interne. Définir des messages peut servir à émettre des informations qui ne sont
pas prises en compte par les messages standard ou à notifier un changement
d’état. Vous pouvez définir vos propres messages dans la VCL.
La définition d’un message est un processus à deux étapes : Les étapes sont
les suivantes :
1 Déclaration d’un identificateur de message.
2 Déclaration d’un type enregistrement de message.

Déclaration d’un identificateur de message


Un identificateur de message est une constante de la taille d’un entier. Windows
se réserve pour son propre usage les messages dont le numéro est inférieur à
1 024. Lorsque vous déclarez vos propres messages, vous devez donc toujours
débuter par un numéro supérieur.
La constante WM_APP représente le numéro de départ pour les messages définis
par l’utilisateur. Lorsque vous définissez un identificateur de message, utilisez
WM_APP.
Notez que certains contrôles Windows standard utilisent des messages compris
dans la plage des messages utilisateur. Entre autres contrôles, il y a les boîtes
liste, les boîtes à options, les boîtes de saisie et les boutons de commande. Si
vous dérivez un composant à partir de l’un d’eux et si vous voulez lui associer
un nouveau message, vérifiez le contenu de l’unité Messages pour voir quels
messages Windows sont déjà définis pour ce contrôle.
Le code suivant définit deux messages utilisateur :
const
MY_MYFIRSTMESSAGE = WM_APP + 400;
MY_MYSECONDMESSAGE = WM_APP + 401;

Déclaration d’un type enregistrement de message


Si vous voulez attribuer un nom explicite aux paramètres de votre message, vous
devez déclarer un type enregistrement pour le message. L’enregistrement de
message correspond au type du paramètre transmis à la méthode de gestion

7-6 Guide du concepteur de composants


Création de nouveaux gestionnaires de messages

du message. Si vous n’utilisez pas les paramètres du message ou si vous


souhaitez utiliser l’ancienne notation (wParam, lParam, etc.), utilisez
l’enregistrement de message implicite, TMessage.
Pour déclarer un type enregistrement de message, respectez les conventions
suivantes :
1 Nommez le type enregistrement d’après le message en rajoutant le préfixe T à
son nom.
2 Donnez au premier champ de l’enregistrement le nom Msg et le type
TMsgParam.
3 Définissez les deux octets suivants pour qu’ils correspondent au paramètre
Word, et les deux suivants inutilisés.
Ou
Définissez les quatre octets suivants pour qu’ils correspondent au paramètre
Longint.
4 Ajoutez un dernier champ intitulé Result de type Longint.
Par exemple, voici un enregistrement de message pour tous les messages de
souris, TWMMouse, qui utilisent un enregistrement variant pour définir deux
ensembles de noms pour les mêmes paramètres.
type
TWMMouse = record
Msg: TMsgParam; ( ID de message en premier )
Keys: Word; ( ceci est le wParam )
case Integer of ( deux manières de voir lParam )
0: {
XPos: Integer; ( sous la forme de coordonnées x et y...)
YPos: Integer);
1: {
Pos: TPoint; ( ... ou d’un point unique )
Result: Longint); ( en dernier lieu, le champ résultat )
end;

Déclaration d’une nouvelle méthode de gestion d’un message


Deux circonstances vous amènent à déclarer de nouvelles méthodes de gestion
des messages :
• Votre composant a besoin de gérer un message Windows qui n’est pas pris en
compte par les composants standard.
• Vous avez défini votre propre message et vous souhaitez l’utiliser avec vos
composants.
Pour déclarer la méthode de gestion d’un message, suivez les étapes ci-après :
1 Déclarez la méthode dans une partie protected de la déclaration de la classe
du composant.
2 Faites de la méthode une procédure.

Gestion des messages et des notifications système 7-7


Création de nouveaux gestionnaires de messages

3 Nommez la méthode suivant le message qu’elle gère en supprimant les


caractères de soulignement de son nom.
4 Transmettez un seul paramètre var appelé Message, du type défini par
l’enregistrement du message.
5 A l’intérieur de l’implémentation de la méthode message, écrivez le code de
gestion spécifique au composant.
6 Appelez le gestionnaire de message transmis en héritage.
Voici la déclaration, par exemple, d’un gestionnaire de message pour un message
utilisateur intitulé CM_CHANGECOLOR.
const
CM_CHANGECOLOR = WM_APP + 400;
type
TMyComponent = class(TControl)
ƒ
protected
procedure CMChangeColor(var Message: TMessage); message CM_CHANGECOLOR;
end;
procedure TMyComponent.CMChangeColor(var Message: TMessage);
begin
Color := Message.lParam;
inherited;
end;

Envoi des messages


Généralement, une application émet un message pour envoyer des notifications
de changement d’état ou pour diffuser des informations. Votre composant peut
diffuser des messages vers tous les contrôles d’une fiche, envoyer des messages à
un contrôle particulier (ou à l’application elle-même), ou s’envoyer des messages
à lui-même.
Il existe plusieurs façons d’envoyer un message Windows. La méthode à utiliser
dépend de la raison pour laquelle vous envoyez le message. Les rubriques
suivantes décrivent les différentes façons d’envoyer des messages Windows.

Diffusion d’un message à tous les contrôles d’une fiche


Quand votre composant modifie les paramètres globaux qui affectent tous les
contrôles d’une fiche ou d’un autre conteneur, vous pouvez envoyer un message
à ces contrôles pour qu’ils puissent se mettre à jour eux-mêmes en conséquence.
Les contrôles ne seront pas tous concernés par cette notification, mais grâce à la
diffusion du message, vous informez tous les contrôles qui savent comment
répondre tout en permettant aux autres d’ignorer le message.
Pour diffuser un message à tous les contrôles d’un autre contrôle, utilisez la
méthode Broadcast. Avant de diffuser un message, mettez dans un enregistrement
les informations que vous voulez envoyer.

7-8 Guide du concepteur de composants


Création de nouveaux gestionnaires de messages

(Voir “Déclaration d’un type enregistrement de message” à la page 7-6, pour


plus d’informations sur les enregistrements message.)
var
Msg: TMessage;
begin
Msg.Msg := MY_MYCUSTOMMESSAGE;
Msg.WParam := 0;
Msg.LParam := Longint(Self);
Msg.Result := 0;
Ensuite, passez l’enregistrement de ce message au parent de tous les contrôles
qui doivent être notifiés. Cela peut être n’importe quel contrôle de l’application.
Par exemple, ce peut être le parent d’un contrôle que vous êtes en train d’écrire :
Parent.Broadcast(Msg);
Ce peut être la fiche contenant votre contrôle :
GetParentForm(self).Broadcast(Msg);
Ce peut être la fiche active :
Screen.ActiveForm.Broadcast(Msg);
Ce peut même être toutes les fiches de votre application :
for I:= 0 to Screen.FormCount - 1 do
Screen.Forms[I].Broadcast(Msg);

Appel direct du gestionnaire de message d’un contrôle


Parfois, un seul contrôle a besoin de répondre à votre message. Si vous savez
quel contrôle doit recevoir le message, le moyen le plus simple et le plus court
d’envoyer le message est d’appeler la méthode Perform du contrôle.
Il existe deux raisons majeures d’appeler la méthode Perform d’un contrôle :
• Vous voulez que le contrôle fasse la même réponse qu’à un message Windows
(ou autre) standard. Par exemple, quand un contrôle grille reçoit un message
de frappe de touche, il crée un contrôle d’édition en ligne puis envoie le
message de frappe de touche à ce contrôle d’édition.
• Vous pouvez savoir quel contrôle notifier, mais ne pas connaître son type.
Comme vous ne connaissez pas le type du contrôle cible, vous ne pouvez
utiliser aucune de ses méthodes spécialisées, mais tous les contrôles offrant
des fonctionnalités de gestion des messages, vous pouvez toujours lui envoyer
un message. Si le contrôle dispose d’un gestionnaire pour le message que
vous envoyez, il répondra de façon appropriée. Sinon, il ignorera le message
envoyé et renverra 0.
Pour appeler la méthode Perform, vous n’avez pas besoin de créer un
enregistrement message. Il suffit de passer comme paramètres l’identificateur du
message, WParam et LParam. Perform renvoie le message résultant.

Gestion des messages et des notifications système 7-9


Réponse aux notifications du système à l’aide de CLX

Envoi d’un message à l’aide de la file d’attente des messages Windows


Dans une application multithread, vous ne pouvez pas vous contenter d’appeler
la méthode Perform car le contrôle cible se trouve dans un thread différent de
celui qui s’exécute. Mais, en utilisant la file d’attente des messages Windows,
vous pouvez communiquer en toute sécurité avec les autres threads. La gestion
des messages se passe toujours dans le thread VCL principal, mais vous pouvez
envoyer un message avec la file d’attente des messages Windows depuis
n’importe quel thread de l’application. Un appel à SendMessage est synchrone.
C’est-à-dire que SendMessage ne revient que lorsque le contrôle cible a géré le
message, même si cela se passe dans un autre thread.
Utilisez l’appel à l’API Windows, SendMessage, pour envoyer un message à un
contrôle à l’aide de la file d’attente des messages Windows. SendMessage prend
les mêmes paramètres que la méthode Perform, sauf que vous devez identifier le
contrôle cible en passant son handle de fenêtre. Ainsi, au lieu d’écrire :
MsgResult := TargetControl.Perform(MY_MYMESSAGE, 0, 0);
il faut écrire
MsgResult := SendMessage(TargetControl.Handle, MYMESSAGE, 0, 0);
Pour davantage d’informations sur la fonction SendMessage, voir la
documentation Microsoft MSDN. Pour davantage d’informations sur l’écriture de
threads multiples pouvant être exécutés simultanément, voir “Coordination de
threads” au Chapitre 13 du Guide du développeur.

Envoi d’un message qui ne s’exécute pas immédiatement


Dans certains cas, vous voulez envoyer un message mais vous ne savez pas si
l’exécution immédiate de la cible du message peut se faire en toute sécurité. Par
exemple, si le code qui envoie un message est appelé depuis un gestionnaire
d’événement du contrôle cible, il vous faudra vérifier que l’exécution du
gestionnaire d’événement est terminée avant que le contrôle exécute votre
message. Vous pouvez gérer cette situation pendant tout le temps où vous
n’avez pas besoin de connaître le résultat du message.
Utilisez l’appel à l’API Windows, PostMessage, pour envoyer un message à un
contrôle mais laisser au contrôle le temps d’en finir avec les autres messages
avant de gérer le vôtre. PostMessage prend exactement les mêmes paramètres que
SendMessage.
Pour davantage d’informations sur la fonction PostMessage, voir la documentation
Microsoft MSDN.

Réponse aux notifications du système à l’aide de CLX


Sous Windows, le système d’exploitation envoie des notifications directement à
votre application et aux contrôles qu’elle contient en utilisant les messages
Windows. Cette approche, cependant, ne convient pas aux applications CLX,
parce que CLX est une bibliothèque multiplate-forme et que les messages

7-10 Guide du concepteur de composants


Réponse aux notifications du système à l’aide de CLX

Windows ne sont pas utilisés sous Linux. A la place, CLX utilise un moyen
ne dépendant pas de la plate-forme pour répondre aux notifications système.
Dans CLX, l’équivalent des messages Windows est un système de signaux
à partir de la couche widget sous-jacente. Alors que dans la VCL, les messages
Windows proviennent soit du système d’exploitation, soit des contrôles Windows
natifs que la VCL enveloppe, la couche widget utilisée par CLX fait une
distinction entre les deux. Si la notification provient d’un widget, on l’appelle un
signal. Si la notification provient du système d’exploitation, on l’appelle un
événement système. La couche widget communique les événements système
à vos composants CLX sous forme d’un signal de type événement.

Réponse aux signaux


La couche widget sous-jacente émet divers signaux, chacun représentant un type
de notification différent. Ces signaux comprennent les événements système
(le signal événement) aussi bien que les notifications spécifiques au widget qui
les génère. Par exemple, tous les widgets génèrent un signal détruit lorsque le
widget est libéré, les widgets barres graduées génèrent un signal valueChanged,
les contrôles en-tête génèrent un signal sectionClicked, etc.
Chaque composant CLX répond aux signaux depuis sa couche widget
sous-jacente en affectant une méthode comme gestionnaire du signal. Il le fait
en utilisant un objet intercepteur spécial associé au widget sous-jacent. L’objet
intercepteur est un objet léger qui est en réalité une simple collection de
pointeurs sur des méthodes, chacun étant spécifique à un signal particulier.
Quand une méthode d’un composant CLX a été affectée à l’objet intercepteur
comme gestionnaire d’un signal spécifique, alors, chaque fois que le widget
génère ce signal, la méthode du composant CLX est appelée. Cela est illustré
à la Figure 7.1.
Figure 7.1 Routage des signaux

Remarque Les méthodes de chaque objet intercepteur sont déclarées dans l’unité Qt.
Les méthodes sont présentées linéairement dans des routines globales avec des
noms qui reflètent l’objet intercepteur auquel elles appartiennent. Par exemple,
toutes les méthodes de l’objet intercepteur associé au widget application
(QApplication) commencent par ‘QApplication_hook.’ Cette dé-hiérarchisation est
nécessaire pour que l’objet CLX Delphi puisse accéder aux méthodes depuis
l’objet intercepteur C++.

Gestion des messages et des notifications système 7-11


Réponse aux notifications du système à l’aide de CLX

Affectation de gestionnaires de signaux personnalisés


De nombreux contrôles CLX disposent déjà de méthodes permettant de gérer les
signaux provenant du widget sous-jacent. Généralement, ces méthodes sont
privées et non virtuelles. Donc, si vous voulez écrire votre propre méthode pour
répondre à un signal, vous devez l’affecter à l’objet intercepteur associé à votre
widget. Pour ce faire, surchargez la méthode HookEvents.
Remarque Si le signal auquel vous voulez répondre est un événement système, vous ne
pouvez pas utiliser une surcharge de la méthode HookEvents. Pour avoir des
détails sur la façon de répondre aux événements systèmes, voir “Réponse aux
événements système”, plus loin.
Dans la surcharge de la méthode HookEvents, déclarez une variable de type
TMethod. Puis, pour chaque méthode que vous voulez affecter à l’objet
intercepteur en tant que gestionnaire de signal, faites ce qui suit :
1 Initialisez la variable de type TMethod par la méthode qui sera le gestionnaire
du signal.
2 Affectez cette variable à l’objet intercepteur. Vous pouvez accéder à l’objet
intercepteur en utilisant la propriété Hooks que votre composant hérite de
THandleComponent ou de TWidgetControl.
Dans la surcharge, appelez toujours la méthode HookEvents héritée, de sorte que
les gestionnaires de signaux affectés par les classes de base soient également
associés.
Le code suivant est la méthode HookEvents de TTrackBar. Il montre comment
surcharger la méthode HookEvents pour ajouter des gestionnaires de signaux
personnalisés.
procedure TTrackBar.HookEvents;
var
Method: TMethod;
begin
// initialisez Method pour représenter un gestionnaire du signal valueChanged de QSlider
// ValueChangedHook est une méthode de TTrackBar qui répond au signal.
QSlider_valueChanged_Event(Method) := ValueChangedHook;
// Affectez Method à l’objet intercepteur. Remarquez que vous pouvez transtyper Hooks
// dans le type de l’objet intercepteur associé au widget sous-jacent.
QSlider_hook_hook_valueChanged(QSlider_hookH(Hooks), Method);
// Répétez le processus pour l’événement sliderMoved :
QSlider_sliderMoved_Event(Method) := ValueChangedHook;
QSlider_hook_hook_valueChanged(QSlider_hookH(Hooks), Method);
// Appelez la méthode dérivée pour que les gestionnaires de signaux hérités
// soient associés :
inherited HookEvents;
end;

Réponse aux événements système


Quand la couche widget reçoit la notification d’un événement depuis le système
d’exploitation, elle génère un objet événement spécial (QEvent ou l’un de ses

7-12 Guide du concepteur de composants


Réponse aux notifications du système à l’aide de CLX

descendants) pour représenter l’événement. L’objet événement contient des


informations en lecture seule sur l’événement qui s’est produit. Le type de l’objet
événement indique le type d’événement qui s’est produit.
La couche widget notifie les événements système à vos composants CLX en
utilisant un signal spécial de type événement. Elle passe l’objet QEvent au
gestionnaire de signal de l’événement. Le traitement du signal événement est un
petit peu plus compliqué que celui des autres signaux, car il va en premier vers
l’objet application. Cela signifie qu’une application a deux possibilités de
répondre à un événement système : une fois au niveau de l’application
(TApplication) et une fois au niveau du composant individuel (votre descendant
de TWidgetControl ou de THandleComponent.) Toutes ces classes (TApplication,
TWidgetControl et THandleComponent) affectent déjà un gestionnaire de signal
pour l’événement depuis la couche widget. C’est-à-dire que tous les événements
système sont automatiquement dirigés vers la méthode EventFilter, qui joue un
rôle similaire à la méthode WndProc sur les contrôles VCL. La gestion des
événements système est illustrée dans la Figure 7.2.
Figure 7.2 Routage des événements système

EventFilter gère la plupart des notifications système communément utilisées, les


convertissant en événements introduits par les classes de base de vos
composants. Par exemple, la méthode EventFilter de TWidgetControl répond aux
événements de souris (QMouseEvent) en générant les événements OnMouseDown,
OnMouseMove et OnMouseUp, aux événements de clavier (QKeyEvent) en générant
les événements OnKeyDown, OnKeyPress, OnKeyString et OnKeyUp, etc.

Evénements couramment utilisés


La méthode EventFilter de TWidgetControl gère une grande partie des
notifications systèmes courantes en appelant des méthodes protégées introduites
dans TControl ou TWidgetControl. La plupart de ces méthodes sont virtuelles ou
dynamiques, de sorte que vous pouvez les surcharger quand vous écrivez vos
propres composants et implémentez vos propres réponses aux événements
systèmes. Quand vous surchargez ces méthodes, vous n’avez pas besoin de vous

Gestion des messages et des notifications système 7-13


Réponse aux notifications du système à l’aide de CLX

préoccuper de manipuler l’objet événement ou (dans la plupart des cas) de l’un


quelconque des autres objets de la couche widget sous-jacente.
Quand vous voulez que votre composant CLX réponde aux notifications système,
c’est une bonne idée de vérifier d’abord s’il existe une méthode protégée qui
réponde toujours à la notification. Vous pouvez consulter la documentation sur
TControl ou TWidgetControl (et sur toute autre classe de base de laquelle dérive
votre composant) pour voir s’il existe une méthode protégée qui réponde à
l’événement qui vous intéresse. Le Tableau 7.1 contient les méthodes protégées
les plus courantes de TControl et de TWidgetControl que vous pouvez utiliser.

Tableau 7.1 Méthodes protégées de TWidgetControl pour la réponse aux notifications système
Méthode Description
BeginAutoDrag Appelée quand l’utilisateur clique sur le bouton gauche de la souris si le
contrôle a un DragMode de dmAutomatic.
Click Appelée quand l’utilisateur relâche le bouton de la souris au-dessus du
contrôle.
DblClick Appelée quand l’utilisateur double-clique avec la souris au-dessus du
contrôle.
DoMouseWheel Appelée quand l’utilisateur quand l’utilisateur tourne la molette de la
souris.
DragOver Appelée quand l’utilisateur fait glisser le curseur de la souris au-dessus
du contrôle.
KeyDown Appelée quand l’utilisateur appuie sur une touche alors que le contrôle
détient la focalisation.
KeyPress Appelée après KeyDown si KeyDown ne gère pas la frappe de touche.
KeyString Appelée quand l’utilisateur entre une frappe de touche et que le système
utilise un jeu de caractères multi-octets.
KeyUp Appelée quand l’utilisateur relâche une touche alors que le contrôle
détient la focalisation.
MouseDown Appelée quand l’utilisateur clique sur le bouton de la souris au-dessus
du contrôle.
MouseMove Appelée quand l’utilisateur déplace le curseur de la souris au-dessus du
contrôle.
MouseUp Appelée quand l’utilisateur relâche le bouton de la souris au-dessus du
contrôle.
PaintRequest Appelée quand système a besoin de redessiner le contrôle.
WidgetDestroyed Appelée lorsqu’un widget sous-jacent à un contrôle est détruit.

Dans la surcharge, appelez la méthode héritée pour que les processus par défaut
répondent aux signaux.
Remarque Outre les méthodes qui répondent aux événements système, les contrôles
incluent un certain nombre de méthodes similaires provenant de TControl ou de
TWidgetControl pour notifier au contrôle divers événements. Bien qu’elles ne
répondent pas aux événements système, elles effectuent les mêmes tâches que la

7-14 Guide du concepteur de composants


Réponse aux notifications du système à l’aide de CLX

plupart des messages Windows envoyés aux contrôles VCL. Le Tableau 7.1
contient la liste de ces méthodes.

Tableau 7.2 Méthodes protégées de TWidgetControl pour répondre aux événements des contrôles
Méthode Description
BoundsChanged Appelée quand le contrôle est redimensionné.
ColorChanged Appelée quand la couleur du contrôle change.
CursorChanged Appelée quand le curseur change de forme. Le curseur de la souris
adopte cette forme lorsqu’il est au-dessus de ce widget.
EnabledChanged Appelée lorsqu’une application change l’état activé d’une fenêtre ou
d’un contrôle.
FontChanged Appelée quand l’ensemble des ressources de polices change.
PaletteChanged Appelée quand la palette du widget change.
ShowHintChanged Appelée quand les conseils d’aide d’un contrôle sont affichés ou cachés.
StyleChanged Appelée quand les styles de l’interface graphique utilisateur de la fenêtre
ou du contrôle changent.
TabStopChanged Appelée quand l’ordre de tabulation de la fiche change.
TextChanged Appelée quand le texte du contrôle change.
VisibleChanged Appelée quand un contrôle est caché ou rendu visible.

Surcharge de la méthode EventFilter


Si vous voulez répondre à la notification d’un événement et qu’il n’existe pas de
méthode protégée à surcharger pour cet événement, vous pouvez surcharger la
méthode EventFilter elle-même. Dans votre surcharge, testez le type du paramètre
Event de la méthode EventFilter et exécutez votre traitement spécial lorsqu’il
représente le type de notification à laquelle vous voulez répondre. Vous pouvez
empêcher un traitement ultérieur de la notification de l’événement, si votre
méthode EventFilter renvoie True.
Remarque Voir la documentation Qt de TrollTech pour des détails sur les différents types
des objets QEvent.
Le code suivant est la méthode EventFilter de TCustomControl. Il montre comment
obtenir le type de l’événement depuis l’objet QEvent en surchargeant EventFilter.
Remarquez que, même si cela n’est pas montré ici, vous pouvez transtyper
l’objet QEvent en un descendant spécialisé de QEvent (comme QMouseEvent) une
fois que vous avez identifié le type de l’événement.
function TCustomControl.EventFilter(Sender: QObjectH; Event: QEventH): Boolean;
begin
Result := inherited EventFilter(Sender, Event);
case QEvent_type(Event) of
QEventType_Resize,
QEventType_FocusIn,
QEventType_FocusOut:
UpdateMask;
end;
end;

Gestion des messages et des notifications système 7-15


Réponse aux notifications du système à l’aide de CLX

Génération des événements Qt


De la même façon qu’un contrôle VCL peut définir et envoyer des messages
Windows personnalisés, vous pouvez faire en sorte que votre contrôle CLX
définisse et génère des événements système Qt. La première étape consiste
à définir un ID unique pour l’événement (tout comme vous devez définir un ID
de message lorsque vous créez un message Windows personnalisé) :
const
MyEvent_ID = Integer(QCLXEventType_ClxUser) + 50;
A l’endroit du code où vous voulez générer l’événement, utilisez la fonction
QCustomEvent_create (déclarée dans l’unité Qt) pour créer un objet événement
avec votre nouvel ID d’événement. Un deuxième paramètre facultatif vous
permet de fournir à l’objet événement une valeur qui est un pointeur sur
l’information que vous voulez associer à l’événement :
var
MyEvent: QCustomEventH;
begin
MyEvent := QCustomEvent_create(MyEvent_ID, self);
Une fois l’objet événement créé, vous pouvez l’envoyer en appelant la méthode
QApplication_postEvent :
QApplication_postEvent(Application.Handle, MyEvent);
Pour qu’un composant quelconque réponde à cette notification, il suffit de
surcharger sa méthode EventFilter, en testant le type d’événement de
MyEvent_ID. La méthode EventFilter peut récupérer la valeur que vous avez
fournie au constructeur en appelant la méthode QCustomEvent_data déclarée dans
l’unité Qt.

7-16 Guide du concepteur de composants


Chapitre

Accessibilité des composants


Chapitre8
8
au moment de la conception
Ce chapitre décrit les étapes permettant de rendre les composants que vous créez
accessibles dans l’EDI. Rendre vos composants accessibles à la conception est un
processus qui nécessite plusieurs étapes :
• Recensement des composants
• Fournir l’aide pour vos composants
• Ajout d’éditeurs de propriétés
• Ajout d’éditeurs de composants
• Compilation des composants en paquets
Toutes les étapes ne s’appliquent pas à tous les composants. Par exemple, si vous
ne définissez ni propriété ni événement nouveau, il n’est pas nécessaire de
fournir de l’aide. Le recensement et la compilation sont les seules étapes
obligatoires.
Une fois que vos composants ont été recensés et compilés en paquets, ils
peuvent être distribués à d’autres développeurs et installés dans l’EDI. Pour des
informations sur l’installation de paquets dans l’EDI, voir “Installation de
paquets de composants” au Chapitre 16 du Guide du développeur.

Recensement des composants


Le recensement fonctionne en ayant l’unité de compilation comme base. Si vous
créez plusieurs composants dans la même unité de compilation, ils seront tous
recensés en même temps.
Pour recenser un composant, ajoutez une procédure Register à l’unité. Dans la
procédure Register, vous recensez les composants et déterminez leur
emplacement sur la palette des composants.

Accessibilité des composants au moment de la conception 8-1


Recensement des composants

Remarque Si vous créez votre composant en choisissant Composant|Nouveau Composant


dans l’EDI, le code requis pour recenser votre composant est automatiquement
ajouté.
Les étapes d’un recensement manuel de composant sont :
• Déclaration de la procédure Register
• Ecriture de la procédure Register

Déclaration de la procédure Register


Le recensement implique l’écriture d’une procédure unique dans l’unité, qui doit
être nommée Register. La procédure Register doit apparaître dans la partie
interface de l’unité et, contrairement au reste du Delphi, son nom tient compte
des différences majuscules/minuscules.
Remarque Bien que Delphi soit un langage qui ne tient pas compte de la distinction
minuscules/majuscules, la procédure Register en tient compte et doit être
orthographiée avec un R majuscule.
Le code suivant montre la présentation d’une seule unité qui crée et recense
des nouveaux composants :
unit MyBtns;
interface
type
ƒ { déclarez vos composants ici }
procedure Register; { ceci doit apparaître dans la section interface }
implementation
ƒ { l’implémentation de composant vient ici }
procedure Register;
begin
ƒ { recense les composants }
end;
end.
Dans la procédure Register, appelez RegisterComponents pour chaque composant
que vous souhaitez ajouter à la palette des composants. Si l’unité contient
plusieurs composants, vous pouvez les recenser en une seule fois.

Ecriture de la procédure Register


Dans la procédure Register d’une unité contenant des composants, vous devez
recenser chaque composant que vous voulez ajouter à la palette des composants.
Si l’unité contient plusieurs composants, vous pouvez les recenser en une
seule fois.
Pour recenser un composant, appelez la procédure RegisterComponents pour
chacune des pages de la palette auxquelles vous souhaitez ajouter des
composants. RegisterComponents nécessite trois informations importantes :
1 Spécification des composants.

8-2 Guide du concepteur de composants


Recensement des composants

2 Spécification de la page de palette.


3 Utilisation de la fonction RegisterComponents.

Spécification des composants


Dans la procédure Register, transmettez les noms de composant dans un tableau
ouvert, que vous pouvez construire dans l’appel à RegisterComponents.
RegisterComponents(’Miscellaneous’, [TMyComponent]);
Vous pouvez aussi recenser plusieurs composants sur la même page ou
différents composants sur des pages distinctes, comme l’illustre le code suivant :
procedure Register;
begin
RegisterComponents(’Miscellaneous’, [TFirst, TSecond]); { deux sur cette page... }
RegisterComponents(’Assorted’, [TThird]); { ...un sur une autre... }
RegisterComponents(LoadStr(srStandard), [TFourth]); { ...et un sur la page Standard }
end;

Spécification de la page de palette


Le nom de la page de palette est une chaîne. Si le nom que vous donnez pour la
page de palette n’existe pas, Delphi crée une nouvelle page avec ce nom. Delphi
stocke les noms des pages standard dans des ressources liste de chaînes afin que
les versions internationales du produit puissent nommer les pages dans leur
langue. Si vous voulez installer un composant dans l’une des pages standard,
vous obtiendrez la chaîne du nom de cette page en appelant la fonction LoadStr
et en transmettant la constante représentant la ressource chaîne de cette page
(par exemple srSystem pour la page Système).

Utilisation de la fonction RegisterComponents


Dans la procédure Register, appelez RegisterComponents pour recenser les
composants dans le tableau des classes. RegisterComponents est une fonction qui
présente deux paramètres : le nom de page de palette des composants et le
tableau des classes de composants.
Donnez au paramètre Page le nom de la page de la palette de composants où
doivent se placer les composants. Si la page nommée existe déjà, les composants
y sont ajoutés. Si elle n’existe pas, Delphi crée une nouvelle page sur la palette
ayant ce nom.
Appelez RegisterComponents depuis l’implémentation de la routine Register
dans une des unités définissant les composants personnalisés. Les unités
définissant les composants doivent alors être compilées en un paquet et ce
dernier doit être installé avant l’ajout des composants personnalisés à la palette
des composants.
procedure Register;
begin
RegisterComponents(’System’, [TSystem1, TSystem2]); {ajout à la page system }
RegisterComponents(’MyCustomPage’,[TCustom1, TCustom2]); { nouvelle page }
end;

Accessibilité des composants au moment de la conception 8-3


Fournir l’aide pour vos composants

Fournir l’aide pour vos composants


Lorsque vous sélectionnez un composant standard dans une fiche, ou une
propriété ou un événement dans l’inspecteur d’objets, vous pouvez appuyer
sur F1 pour obtenir de l’aide concernant cet élément. Les développeurs pourront
accéder au même type d’information sur vos composants si vous créez les
fichiers d’aide appropriés.
Vous pouvez fournir un fichier d’aide de faible encombrement pour décrire vos
composants et votre fichier d’aide devient partie intégrante du système d’aide
global de Delphi.
Voir ci-après “Création du fichier d’aide”, pour des informations sur la manière
de composer le fichier d’aide à utiliser avec un composant.

Création du fichier d’aide


Vous pouvez utiliser l’outil de votre choix pour créer les fichiers d’aide
Windows (au format .rtf). Delphi inclut le Microsoft Help Workshop, qui compile
les fichiers d’aide et contient un guide en ligne destiné à l’auteur du système
d’aide. Vous y trouverez toutes les informations nécessaires à la création des
systèmes d’aide.
La composition de fichiers d’aide pour les composants s’effectue en plusieurs
étapes :
• Création des entrées.
• Aide contextuelle des composants.
• Ajout des fichiers d’aide des composants.

Création des entrées


Pour que l’aide associée à votre composant fonctionne de façon transparente avec
celle des autres composants de la bibliothèque, vous devez respecter les
conventions suivantes :
1 Chaque composant doit avoir une rubrique d’aide.
La rubrique associée au composant doit montrer dans quelle unité est déclaré
le composant et fournir une brève description du composant. La rubrique du
composant doit proposer des liens vers des fenêtres secondaires décrivant la
position du composant dans la hiérarchie des objets et répertorier l’ensemble
de ses propriétés, événements et méthodes. Les développeurs d’applications
accéderont à cette rubrique en sélectionnant le composant dans une fiche et en
appuyant sur F1. Pour avoir un exemple d’une rubrique associée à un
composant, positionnez-vous sur un composant quelconque dans une fiche et
appuyez sur F1.
La rubrique de composant doit avoir une note de bas de page # avec une
valeur unique de rubrique. La note de bas de page # identifie de manière
unique chaque rubrique du système d’aide.

8-4 Guide du concepteur de composants


Fournir l’aide pour vos composants

La rubrique associée au composant doit avoir une note de bas de page “K”
(qui indique les mots clé de recherche) comprenant le nom de la classe du
composant. Par exemple, la note de bas de page des mots clés pour le
composant TMemo contient “TMemo”.
La rubrique associée au composant doit également comprendre une note de
bas de page $ qui indique le titre de la rubrique. Le titre apparaît dans la
boîte de dialogue des rubriques, la boîte de dialogue Signet et la fenêtre
Historique.
2 Chaque composant doit inclure les rubriques de navigation secondaires
suivantes :
• Une rubrique de hiérarchie offrant des liens vers chaque ancêtre
du composant appartenant à sa hiérarchie.
• Une liste de toutes les propriétés disponibles dans le composant,
avec des liens vers les entrées décrivant ces propriétés.
• Une liste de tous les événements disponibles dans le composant,
avec des liens vers les entrées décrivant ces événements.
• Une liste de toutes les méthodes disponibles dans le composant,
avec des liens vers les entrées décrivant ces méthodes.
Les liens vers les classes d’objets, les propriétés ou les événements dans le
système d’aide de Delphi peuvent être réalisés à l’aide de Alinks. Alink opère
la liaison vers une classe d’objet en utilisant le nom de classe de l’objet suivi
d’un caractère de soulignement et de la chaîne “object”. Par exemple, pour
réaliser un lien vers l’objet TCustomPanel, utilisez le code suivant :
!AL(TCustomPanel_object,1)
Pour réaliser un lien vers une propriété, une méthode ou un événement, faites
précéder son nom par celui de l’objet qui l’implémente et par un caractère de
soulignement. Par exemple, pour réaliser un lien vers la propriété Text
implémentée par TControl, utilisez le code suivant :
!AL(TControl_Text,1)
Pour voir un exemple de rubriques de navigation secondaires, affichez l’aide
d’un composant quelconque et cliquez sur les liens étiquetés hiérarchie,
propriétés, méthodes ou événements.
3 Chaque propriété, événement ou méthode déclaré à l’intérieur du composant
doit avoir une rubrique.
Une rubrique décrivant une propriété, un événement ou une méthode doit
indiquer la déclaration de l’élément et décrire son rôle. Les développeurs
d’applications accéderont à cette rubrique en sélectionnant l’élément dans
l’inspecteur d’objets et en appuyant sur F1, ou en plaçant le curseur dans
l’éditeur de code sur le nom de l’élément et en appuyant sur F1. Pour avoir
un exemple de rubrique associée à une propriété, sélectionnez un élément
quelconque dans l’inspecteur d’objets et appuyez sur F1.

Accessibilité des composants au moment de la conception 8-5


Fournir l’aide pour vos composants

Les rubriques de propriété, d’événement et de méthode doivent inclure une


note de bas de page K qui indique le nom de la propriété, de l’événement
et de la méthode, son nom et celui du composant. Ainsi, la propriété Text de
TControl présente la note de bas de page K suivante :
Text,TControl;TControl,Text;Text,
Les rubriques de propriété, de méthode ou d’événement doivent également
comporter une note de bas de page $ indiquant le titre de la rubrique, tel que
TControl.Text.
Toutes ces rubriques doivent disposer d’un identificateur de rubrique unique à la
rubrique, entré sous la forme d’une note de bas de page #.

Aide contextuelle des composants


Chaque rubrique de composant, de propriété, de méthode et d’événement doit
comporter une note de bas de page A. La note de bas de page A permet
d’afficher la rubrique lorsque l’utilisateur sélectionne un composant et appuie sur
F1, ou lorsqu’il sélectionne une propriété ou un événement dans l’inspecteur
d’objets et appuie sur F1. Les notes de bas de page A doivent suivre certaines
conventions d’appellation :
Si la rubrique d’aide est destinée à un composant, la note de bas de page A
comprend deux entrées séparées par un point-virgule selon la syntaxe suivante :
ComponentClass_Object;ComponentClass
où ComponentClass est le nom de la classe du composant.
Si la rubrique d’aide est destinée à une propriété ou à un événement, la note de
bas de page A comprend trois entrées séparées par des points-virgules selon la
syntaxe suivante :
ComponentClass_Element;Element_Type;Element
où ComponentClass est le nom de la classe du composant, Element est le nom de
la propriété, de la méthode ou de l’événement et Type est la propriété, la
méthode ou l’événement
Par exemple, en supposant une propriété BackgroundColor d’un composant
TMyGrid, la note de bas de page A est
TMyGrid_BackgroundColor;BackgroundColor_Property;BackgroundColor

Ajout des fichiers d’aide des composants


Pour ajouter votre fichier d’aide à Delphi, utilisez l’utilitaire OpenHelp (appelé
oh.exe) situé dans le répertoire bin ou en utilisant Aide|Personnaliser dans l’EDI.
Vous obtiendrez des informations sur le fichier OpenHelp.hlp sur l’utilisation de
OpenHelp, ainsi que sur l’ajout de votre fichier d’aide au système d’aide.

8-6 Guide du concepteur de composants


Ajout d’éditeurs de propriétés

Ajout d’éditeurs de propriétés


L’inspecteur d’objets permet par défaut de modifier tous les types de propriétés.
Vous pouvez toutefois créer un éditeur pour des propriétés spécifiques en
l’écrivant et en le recensant. Vous pouvez recenser les éditeurs de propriétés afin
qu’ils s’appliquent uniquement aux propriétés des composants dont vous êtes
l’auteur, ou à toutes les propriétés du type spécifié.
A la base, un éditeur de propriétés peut opérer selon deux modes : affichage
sous la forme d’une chaîne texte permettant à l’utilisateur la modification de la
valeur courante ; affichage d’une boîte de dialogue permettant des modifications
d’une autre sorte. Selon la propriété en cours de modification, vous pourrez faire
appel à l’un ou l’autre mode.
L’écriture d’un éditeur de propriété se déroule en cinq étapes :
1 Dérivation d’une classe éditeur de propriétés.
2 Modification de la propriété sous une forme textuelle.
3 Modification globale de la propriété.
4 Spécification des attributs de l’éditeur.
5 Recensement de l’éditeur de propriétés.

Dérivation d’une classe éditeur de propriétés


La bibliothèque de composants définit plusieurs sortes d’éditeurs de propriétés,
tous descendant de TPropertyEditor. Lorsque vous créez un éditeur de propriétés,
votre classe éditeur de propriétés peut descendre directement de TPropertyEditor
ou d’un des types d’éditeurs de propriétés décrits dans le Tableau 8.1. Les classes
de l’unité DesignEditors peuvent être utilisées dans les applications VCL et CLX.
Néanmoins, certaines classes éditeur de propriété fournissent des boîtes de
dialogue spécialisées et sont ainsi spécialisées VCL ou CLX. Elles peuvent être
trouvées respectivement dans les unités VCLEditors et CLXEditors.
Remarque Ce qui est absolument nécessaire pour un éditeur de propriété est qu’il dérive de
TBasePropertyEditor et qu’il prenne en charge l’interface IProperty. Néanmoins,
TPropertyEditor fournit une implémentation par défaut de l’interface IProperty.
La liste du Tableau 8.1 n’est pas complète. Les unités VCLEditors et CLXEditors
définissent également certains éditeurs spécialisés utilisés exclusivement par
certaines propriétés comme le nom de composant. Les éditeurs de propriétés
ci-dessous sont les plus utiles aux concepteurs de propriétés définies par
l’utilisateur.

Tableau 8.1 Types d’éditeurs de propriétés prédéfinis


Type Propriétés modifiables
TOrdinalProperty Tous les éditeurs de valeurs ordinales (propriétés de type entier,
caractères, énuméré) sont des descendants de TOrdinalProperty.
TIntegerProperty Tous les types entiers y compris ceux prédéfinis ainsi que les
intervalles utilisateur.

Accessibilité des composants au moment de la conception 8-7


Ajout d’éditeurs de propriétés

Tableau 8.1 Types d’éditeurs de propriétés prédéfinis (suite)


Type Propriétés modifiables
TCharProperty Le type Char et les intervalles de valeurs Char tels que ‘A’..’Z’.
TEnumProperty Tous les types énumérés.
TFloatProperty Tous les nombres à virgule flottante.
TStringProperty Chaînes.
TSetElementProperty Les éléments des ensembles comme valeurs booléennes.
TSetProperty Tous les ensembles. Les ensembles ne sont pas directement
modifiables mais peuvent être développés sous la forme d’une liste
de propriétés que sont les éléments de l’ensemble.
TClassProperty Classes. Affiche le nom de la classe et se développe pour afficher les
propriétés de la classe.
TMethodProperty Pointeurs sur des méthodes, le plus souvent des événements.
TComponentProperty Les composants de la même fiche. Ne permet pas la modification des
propriétés des composants, mais peut pointer sur un composant
spécifique de type compatible.
TColorProperty Les couleurs d’un composant. Montre si possible les constantes de
couleurs ou à défaut leurs valeurs en hexadécimal. Une liste
déroulante affiche les constantes de couleurs. Un double-clic a pour
effet d’ouvrir la boîte de dialogue de sélection des couleurs.
TFontNameProperty Les noms de fontes. La liste déroulante affiche toutes les fontes
actuellement installées.
TFontProperty Les fontes. Autorise le développement des propriétés d’une fonte
particulière et offre l’accès à la boîte de dialogue des fontes.

L’exemple suivant montre la déclaration d’un éditeur de propriétés simple


nommé TMyPropertyEditor :
type
TFloatProperty = class(TPropertyEditor)
public
function AllEqual: Boolean; override;
function GetValue: string; override;
procedure SetValue(const Value: string); override;
end;

Modification de la propriété sous une forme textuelle


Toutes les propriétés doivent fournir une représentation de type chaîne de leurs
valeurs en vue de leur affichage dans l’inspecteur d’objets. Dans le cas de la
plupart des propriétés, le développeur pourra saisir une nouvelle valeur lors de
la conception. TPropertyEditor et ses descendants fournissent des méthodes
virtuelles que vous pouvez redéfinir afin de convertir la représentation textuelle
de la propriété en sa valeur réelle.

8-8 Guide du concepteur de composants


Ajout d’éditeurs de propriétés

Les méthodes à redéfinir sont GetValue et SetValue. Votre éditeur de propriétés


hérite également de méthodes servant à affecter et à lire les différents types de
valeurs provenant de TPropertyEditor, comme indiqué dans le Tableau 8.2.

Tableau 8.2 Méthodes pour lire et écrire les valeurs des propriétés
Type de propriété Méthode Get Méthode Set
Virgule flottante GetFloatValue SetFloatValue
Pointeur de méthode (événement) GetMethodValue SetMethodValue
Type ordinal GetOrdValue SetOrdValue
Chaîne GetStrValue SetStrValue

Lorsque vous redéfinissez une méthode GetValue, appelez l’une des méthodes
“Get”. Lorsque vous redéfinissez SetValue, appelez l’une des méthodes “Set”.

Affichage de la valeur de la propriété


La méthode GetValue de l’éditeur de propriétés renvoie une chaîne représentant
la valeur en cours de la propriété. L’inspecteur d’objets utilise cette chaîne dans
la colonne des valeurs pour cette propriété. Par défaut, GetValue renvoie
“inconnu”.
Pour fournir une représentation sous une forme chaîne, vous devez redéfinir
la méthode GetValue de l’éditeur de propriétés.
Si la propriété n’est pas une valeur chaîne, votre méthode GetValue doit convertir
la valeur en une chaîne.

Définition de la valeur de la propriété


La méthode SetValue de l’éditeur de propriétés accepte la chaîne saisie dans
l’inspecteur d’objets, la convertit dans le type approprié, et définit la propriété. Si
la chaîne n’est pas une valeur convenant à la propriété, SetValue doit déclencher
et ignorer la valeur.
Pour lire des valeurs chaîne dans les propriétés, vous devez redéfinir la méthode
SetValue de l’éditeur de propriétés.
SetValue doit convertir la chaîne et la valider avant d’appeler une des méthodes.
L’exemple suivant propose les méthodes GetValue et SetValue du type Integer de
TIntegerProperty. Integer est de type ordinal, ainsi GetValue appelle GetOrdValue et
convertit le résultat en chaîne. SetValue convertit la chaîne en entier, effectue
certains calculs d’intervalle et appelle SetOrdValue.
function TIntegerProperty.GetValue: string;
begin
with GetTypeData(GetPropType)^ do
if OrdType = otULong then // non signé
Result := IntToStr(Cardinal(GetOrdValue))
else
Result := IntToStr(GetOrdValue);
end;

Accessibilité des composants au moment de la conception 8-9


Ajout d’éditeurs de propriétés

procedure TIntegerProperty.SetValue(const Value: string);


procedure Error(const Args: array of const);
begin
raise EPropertyError.CreateResFmt(@SOutOfRange, Args);
end;
var
L: Int64;
begin
L := StrToInt64(Value);
with GetTypeData(GetPropType)^ do
if OrdType = otULong then
begin // comparaison non signée et signalisation nécessaire
if (L < Cardinal(MinValue)) or (L > Cardinal(MaxValue)) then
// aller jusqu’à Int64 pour dépasser le %d dans la chaîne de format
Error([Int64(Cardinal(MinValue)), Int64(Cardinal(MaxValue))]);
end
else if (L < MinValue) or (L > MaxValue) then
Error([MinValue, MaxValue]);
SetOrdValue(L);
end;
Les spécificités des exemples particuliers sont moins importantes qu’en principe :
GetValue convertit la valeur en chaîne ; SetValue convertit la chaîne et valide la
valeur avant d’appeler une des méthodes “Set”.

Modification globale de la propriété


Si vous le souhaitez, vous pouvez fournir une boîte de dialogue pour la
définition de la propriété. L’utilisation la plus courante des éditeurs de propriétés
concerne les propriétés qui sont des classes. Un exemple est la propriété Font,
qui a une boîte de dialogue éditeur associée permettant au développeur de
choisir tous les attributs de fonte en même temps.
Pour fournir une boîte de dialogue de définition globale de la propriété,
redéfinissez la méthode Edit de la classe éditeur de propriétés.
Les méthodes Edit utilisent les mêmes méthodes “Get” et “Set” utilisées dans les
méthodes GetValue et SetValue. En fait, une méthode Edit appelle à la fois une
méthode “Get” et une méthode “Set”. Comme l’éditeur est spécifique du type,
il est habituellement inutile de convertir les valeurs des propriétés en chaînes.
L’éditeur traite généralement la valeur telle qu’elle a été récupérée.
Lorsque l’utilisateur clique sur le bouton ‘...’ à côté de la propriété, ou
double-clique sur la colonne des valeurs, l’inspecteur d’objets appelle la méthode
Edit de l’éditeur de propriétés.
Pour votre implémentation de la méthode Edit, suivez ces étapes :
1 Construisez l’éditeur que vous utilisez pour cette propriété.
2 Lisez la valeur en cours et attribuez-la à la propriété en utilisant une méthode
“Get”.

8-10 Guide du concepteur de composants


Ajout d’éditeurs de propriétés

3 Lorsque l’utilisateur sélectionne une nouvelle valeur, attribuez cette valeur à la


propriété en utilisant une méthode “Set”.
4 Détruisez l’éditeur.
Les propriétés Color trouvées dans la plupart des composants utilisent la boîte de
dialogue de couleur Windows standard comme éditeur de propriétés. Voici la
méthode Edit issue de TColorProperty, qui appelle la boîte de dialogue et utilise le
résultat :
procedure TColorProperty.Edit;
var
ColorDialog: TColorDialog;
begin
ColorDialog := TColorDialog.Create(Application); { construit l’éditeur }
try
ColorDialog.Color := GetOrdValue; { utilise la valeur existante }
if ColorDialog.Execute then { si l’utilisateur valide la boîte de dialogue par OK... }
SetOrdValue(ColorDialog.Color); { ...utilise le résultat pour définir la valeur }
finally
ColorDialog.Free; { détruit l’éditeur }
end;
end;

Spécification des attributs de l’éditeur


L’éditeur de propriétés doit fournir les informations permettant à l’inspecteur
d’objets de déterminer les outils à afficher. Par exemple, l’inspecteur d’objets a
besoin de savoir si la propriété a des sous-propriétés, ou s’il doit afficher la liste
des valeurs possibles de la propriété.
Pour spécifier les attributs de l’éditeur, vous devez redéfinir sa méthode
GetAttributes.
GetAttributes renvoie un ensemble de valeurs de type TPropertyAttributes qui peut
inclure une ou plusieurs des valeurs suivantes :

Tableau 8.3 Indicateurs des attributs des éditeurs de propriétés


Indicateur Méthode associée Signification si inclus
paValueList GetValues L’éditeur peut fournir une liste de valeurs énumérées.
paSubProperties GetProperties La propriété dispose de sous-propriétés qu’il est possible
d’afficher.
paDialog Edit L’éditeur peut afficher une boîte de dialogue permettant
de modifier globalement la propriété.
paMultiSelect N/D La propriété doit s’afficher lorsque l’utilisateur
sélectionne plusieurs composants.
paAutoUpdate SetValue Mise à jour du composant après chaque modification au
lieu d’attendre l’approbation de la valeur.
paSortList N/D L’inspecteur d’objets doit trier la liste de valeurs.
paReadOnly N/D La valeur de la propriété ne peut être modifiée lors de la
conception.

Accessibilité des composants au moment de la conception 8-11


Ajout d’éditeurs de propriétés

Tableau 8.3 Indicateurs des attributs des éditeurs de propriétés (suite)


Indicateur Méthode associée Signification si inclus
paRevertable N/D Active l’élément de menu Revenir à hérité dans le menu
contextuel de l’inspecteur d’objets. Cet élément de menu
demande à l’éditeur d’annuler la valeur en cours de la
propriété et de revenir à une valeur par défaut ou
standard préalablement établie.
paFullWidthName N/D La valeur n’a pas besoin d’être affichée. L’inspecteur
d’objets utilise toute sa largeur pour le nom de
propriété.
paVolatileSubProperties GetProperties L’inspecteur d’objets récupère les valeurs de toutes les
sous-propriétés à chaque modification de la valeur de la
propriété.
paReference GetComponentValue La valeur est une référence à quelque chose d’autre.
Utilisé conjointement avec paSubProperties, l’objet
référencé devrait être affiché comme sous-propriétés de
cette propriété.

Les propriétés Color sont plus polyvalentes que la plupart des autres propriétés,
l’utilisateur dispose de plusieurs moyens pour sélectionner une couleur dans
l’inspecteur d’objets : il peut taper une valeur, sélectionner dans une liste ou faire
appel à l’éditeur personnalisé. C’est pourquoi la méthode GetAttributes de
TColorProperty, inclut plusieurs attributs dans la valeur qu’elle renvoie :
function TColorProperty.GetAttributes: TPropertyAttributes;
begin
Result := [paMultiSelect, paDialog, paValueList, paRevertable];
end;

Recensement de l’éditeur de propriétés


Lorsque l’éditeur de propriétés est créé, vous devez le recenser dans Delphi. Le
recensement d’un éditeur de propriétés associe un type de propriété et un
éditeur spécifique. Vous pouvez recenser un éditeur pour toutes les propriétés
d’un type particulier ou juste pour une propriété particulière d’un type de
composant particulier.
Pour recenser un éditeur de propriétés, appelez une procédure
RegisterPropertyEditor.
RegisterPropertyEditor prend quatre paramètres :
• Un pointeur de type information décrivant le type de la propriété à modifier.
Il s’agit toujours d’un appel à la fonction intégrée TypeInfo, telle que
TypeInfo(TMyComponent).
• Le type du composant auquel s’applique cet éditeur. Si ce paramètre est nil,
l’éditeur s’applique à toutes les propriétés d’un type donné.
• Le nom de la propriété. Ce paramètre n’est significatif que si le paramètre qui
le précède spécifie un type particulier de composant. Dans ce cas, vous
pouvez spécifier une propriété de ce type auquel s’applique l’éditeur.

8-12 Guide du concepteur de composants


Catégories de propriétés

• Le type d’éditeur de propriétés à utiliser pour modifier la propriété spécifiée.


Voici un extrait de la procédure qui recense les éditeurs des composants
standard inclus dans la palette :
procedure Register;
begin
RegisterPropertyEditor(TypeInfo(TComponent), nil, ‘‘, TComponentProperty);
RegisterPropertyEditor(TypeInfo(TComponentName), TComponent, ‘Name’,
TComponentNameProperty);
RegisterPropertyEditor(TypeInfo(TMenuItem), TMenu, ‘‘, TMenuItemProperty);
end;
Les trois instructions de cette procédure couvrent les différentes utilisations de
RegisterPropertyEditor :
• La première instruction est la plus typique. Elle recense l’éditeur de propriétés
TComponentProperty pour toutes les propriétés de type TComponent (ou les
descendants de TComponent qui n’ont pas d’éditeur spécifique recensé).
Habituellement, vous créez un éditeur s’appliquant à un type particulier, puis
vous souhaitez l’utiliser pour l’ensemble des propriétés de ce type. C’est
pourquoi le deuxième et le troisième paramètres ont pour valeurs respectives
nil et une chaîne vide.
• La deuxième instruction est le type de recensement le plus spécifique. Elle
recense un éditeur pour une propriété spécifique et pour un type spécifique
de composant. Dans notre exemple, l’éditeur s’applique à la propriété Name
(de type TComponentName) de tous les composants.
• La troisième instruction est plus spécifique que la première, et moins que la
deuxième. Elle recense un éditeur pour toutes les propriétés de type
TMenuItem pour les composants de type TMenu.

Catégories de propriétés
Dans l’IDE, l’inspecteur d’objets vous permet de masquer et d’afficher
sélectivement des propriétés basées sur les catégories de propriété. Les propriétés
des nouveaux composants personnalisés peuvent rentrer dans ce schéma en
recensant des propriétés par catégories. Faites ceci lors du recensement du
composant en appelant RegisterPropertyInCategory ou RegisterPropertiesInCategory.
Utilisez RegisterPropertyInCategory pour recenser une seule propriété. Utilisez
RegisterPropertiesInCategory pour recenser plusieurs propriétés dans un seul appel
de fonction. Ces fonctions sont définies dans l’unité DesignIntf.
Notez qu’il n’est pas obligatoire de recenser des propriétés ni que toutes les
propriétés d’un composant personnalisé soient recensées lorsque quelques-unes le
sont. Toute propriété non explicitement associée à une catégorie est incluse dans
la catégorie TMiscellaneousCategory. De telles propriétés sont affichées ou
masquées dans l’inspecteur d’objets selon cette catégorisation par défaut.
En plus de ces deux fonctions de recensement de propriétés, il existe une
fonction IsPropertyInCategory. Cette fonction est utile pour la création d’utilitaires

Accessibilité des composants au moment de la conception 8-13


Catégories de propriétés

de localisation, dans laquelle vous devez déterminer si une propriété est recensée
dans une catégorie de propriété donnée.

Recensement d’une propriété à la fois


Vous pouvez recenser une propriété à la fois et l’associer à une catégorie de
propriété en utilisant la fonction RegisterPropertyInCategory.
RegisterPropertyInCategory est fournie dans quatre variations surchargées, chacune
proposant un ensemble différent de critères pour l’identification de la propriété
dans le composant personnalisé associé à la catégorie de propriété.
La première variation vous permet d’identifier la propriété selon son nom. La
ligne ci-après recense une propriété associée à l’affichage visuel du composant,
en identifiant la propriété par son nom, “AutoSize”.
RegisterPropertyInCategory(’Visual’, ’AutoSize’);
La deuxième variation identifie la propriété en utilisant le type de classe de
composant et le nom de propriété caractéristiques. L’exemple ci-après recense
(dans la catégorie THelpCategory) une propriété appelée “HelpContext” d’un
composant de la classe personnalisée TMyButton.
RegisterPropertyInCategory(’Help and Hints’, TMyButton, ’HelpContext’);
La troisième variation identifie la propriété en utilisant son type au lieu de son
nom. L’exemple ci-dessous recense une propriété en se basant sur son type,
Integer.
RegisterPropertyInCategory(’Visual’, TypeInfo(Integer));
La dernière variation utilise à la fois le type de la propriété et son nom pour
identifier la propriété. L’exemple ci-après recense une propriété basée sur une
combinaison de son type, TBitmap et de son nom, ”Pattern”.
RegisterPropertyInCategory(’Visual’, TypeInfo(TBitmap), ’Pattern’);
Consultez la section “Spécification de catégories de propriétés”, pour obtenir une
liste des catégories de propriété disponibles ainsi qu’une brève description de
leur utilisation.

Recensement de plusieurs propriétés en une seule fois


Vous pouvez recenser plusieurs propriétés en une seule fois et les associer à une
catégorie de propriété en utilisant la fonction RegisterPropertiesInCategory.
RegisterPropertiesInCategory est fournie dans trois variations surchargées, chacune
proposant un ensemble différent de critères pour l’identification de la propriété
dans le composant personnalisé associé à la catégorie de propriété.
La première variation vous permet d’identifier des propriétés en fonction du
nom ou du type de propriété. La liste est transmise sous la forme d’un tableau
de constantes. Dans l’exemple ci-après, toute propriété ayant pour nom “Text”
ou qui appartient à une classe de type TEdit est recensée dans la catégorie
‘Localizable’.

8-14 Guide du concepteur de composants


Catégories de propriétés

RegisterPropertiesInCategory(’Localizable’, [’Text’, TEdit]);


La deuxième variation vous permet de limiter les propriétés recensées à celles
qui appartiennent à un composant spécifique. La liste des propriétés à recenser
comprend seulement les noms, pas les types. Par exemple, le code suivant
recense un nombre de propriétés dans la catégorie ‘Help and Hints’ pour tous
les composants :
RegisterPropertiesInCategory(’Help and Hints’, TComponent, [’HelpContext’, ’Hint’,
’ParentShowHint’, ’ShowHint’]);
La troisième variation vous permet de limiter les propriétés recensées à celles
possédant un type spécifique. Comme avec la seconde variation, la liste des
propriétés à recenser peut n’inclure que les noms :
RegisterPropertiesInCategory(’Localizable’, TypeInfo(String), [’Text’, ’Caption’]);
Consultez la section “Spécification de catégories de propriétés”, pour obtenir une
liste des catégories de propriété disponibles ainsi qu’une brève description de
leur utilisation.

Spécification de catégories de propriétés


Lorsque vous recensez les propriétés dans une catégorie, vous pouvez utiliser la
chaîne de votre choix pour le nom de la catégorie. Si vous utilisez une chaîne
qui n’a pas encore été utilisée, l’inspecteur d’objets génère une nouvelle classe de
catégorie de propriétés avec ce nom. Vous pouvez néanmoins recenser des
propriétés dans des catégories intégrées. Les catégories de propriétés intégrées
sont décrites dans le Tableau 8.4 :

Tableau 8.4 Catégories de propriétés


Catégorie Utilisation
Action Propriétés relatives aux actions d’exécution ; les propriétés Enabled et Hint de
TEdit se trouvent dans cette catégorie.
Database Propriétés relatives aux opérations de bases de données ; les propriétés
DatabaseName et SQL de TQuery se trouvent dans cette catégorie.
Drag, Drop Propriétés relatives aux opérations de glisser-déplacer et d’ancrage ; les
and Docking propriétés DragCursor et DragKind de TImage se trouvent dans cette catégorie.
Help and Propriétés relatives à l’utilisation de l’aide en ligne ou des conseils ; les
Hints propriétés HelpContext et Hint de TMemo se trouvent dans cette catégorie.
Layout Propriétés relatives à l’affichage visuel d’un contrôle à la conception ; les
propriétés Top et Left de TDBEdit se trouvent dans cette catégorie.
Legacy Propriétés relatives aux opérations obsolètes ; les propriétés Ctl3D et
ParentCtl3D de TComboBox se trouvent dans cette catégorie.
Linkage Propriétés relatives à l’association ou à la liaison d’un composant à un autre ;
la propriété DataSet de TDataSource se trouve dans cette catégorie.
Locale Propriétés relatives aux localisations internationales ; les propriétés BiDiMode
et ParentBiDiMode de TMainMenu se trouvent dans cette catégorie.

Accessibilité des composants au moment de la conception 8-15


Ajout d’éditeurs de composants

Tableau 8.4 Catégories de propriétés (suite)


Catégorie Utilisation
Localizable Propriétés qui peuvent nécessiter des modifications dans les versions
localisées d’une application. De nombreuses propriétés chaîne (comme
Caption) font partie de cette catégorie ainsi que les propriétés qui déterminent
la taille et la position des contrôles.
Visual Propriétés relatives à l’affichage visuel d’un contrôle à l’exécution ; les
propriétés Align et Visible de TScrollBox se trouvent dans cette catégorie.
Input Propriétés relatives à la saisie de données (il n’est pas nécessaire qu’elles
soient relatives aux opérations de bases de données) ; les propriétés Enabled et
ReadOnly de TEdit se trouvent dans cette catégorie.
Miscellaneous Propriétés qui ne rentrent pas dans une catégorie ou n’ont pas besoin d’être
mises dans des catégories (et les propriétés non explicitement recensées dans
une catégorie spécifique) ; les propriétés AllowAllUp et Name de
TSpeedButton se trouvent dans cette catégorie.

Utilisation de la fonction IsPropertyInCategory


Une application peut rechercher les propriétés recensées existantes afin de
déterminer si une propriété donnée est toujours recensée dans une catégorie
indiquée. Ceci peut être particulièrement utile dans des situations telles qu’un
utilitaire de localisation qui vérifie la catégorisation des propriétés afin de
préparer ses opérations de localisation. Deux variations surchargées de la
fonction IsPropertyInCategory sont disponibles, autorisant différents critères afin
de déterminer si une propriété se trouve dans une catégorie.
La première variation vous permet de baser le critère de comparaison sur une
combinaison du type de classe du composant propriétaire et du nom de la
propriété. Dans la ligne de commande ci-après, pour que IsPropertyInCategory
renvoie True, la propriété doit appartenir à un descendant de TCustomEdit, avoir
le nom “Text”, et se trouver dans la catégorie de propriétés ’Localizable’.
IsItThere := IsPropertyInCategory(’Localizable’, TCustomEdit, ’Text’);
La deuxième variation vous permet de baser le critère de comparaison sur une
combinaison du nom de classe du composant propriétaire et du nom de la
propriété. Dans la ligne de commande ci-après, pour que IsPropertyInCategory
renvoie True, la propriété doit être un descendant de TCustomEdit, avoir le nom
“Text”, et se trouver dans la catégorie de propriétés ’Localizable’.
IsItThere := IsPropertyInCategory(’Localizable’, ‘TCustomEdit’, ’Text’);

Ajout d’éditeurs de composants


Les éditeurs de composants déterminent ce qui se passe lorsque vous
double-cliquez sur le composant dans le concepteur et ajoutent des commandes
au menu contextuel qui apparaît lorsque vous cliquez sur le composant avec le
bouton droit. Ils peuvent également copier votre composant dans le
Presse-papiers Windows dans des formats personnalisés.

8-16 Guide du concepteur de composants


Ajout d’éditeurs de composants

Si vous n’attribuez pas d’éditeur à vos composants, Delphi utilise l’éditeur de


composants par défaut. Ce dernier est implémenté par la classe TDefaultEditor.
TDefaultEditor n’ajoute aucun nouvel élément au menu contextuel d’un
composant. Lorsque vous double-cliquez sur le composant, TDefaultEditor
recherche ses propriétés et génère le premier gestionnaire d’événement trouvé
ou s’y rend.
Pour ajouter des éléments au menu contextuel, modifier le comportement du
composant lorsque vous double-cliquez dessus ou ajouter de nouveaux formats
de presse-papiers, dérivez une nouvelle classe à partir de TComponentEditor et
recensez-la pour qu’elle soit utilisée avec votre composant. Dans vos méthodes
redéfinies, vous pouvez utiliser la propriété Component de TComponentEditor pour
accéder au composant en cours de modification.
L’ajout d’un éditeur de composants personnalisé comprend plusieurs étapes :
• Ajout d’éléments au menu contextuel
• Modification du comportement suite à un double-clic
• Ajout de formats de presse-papiers
• Recensement d’un éditeur de composants

Ajout d’éléments au menu contextuel


Lorsque l’utilisateur clique avec le bouton droit sur le composant, les méthodes
GetVerbCount et GetVerb de l’éditeur de composants sont appelées pour
construire un menu contextuel. Vous pouvez redéfinir ces méthodes pour ajouter
des commandes (verbes) au menu contextuel.
L’ajout d’éléments au menu contextuel requiert ces étapes :
• Spécification d’éléments de menu
• Implémentation des commandes

Spécification d’éléments de menu


Redéfinissez la méthode GetVerbCount pour renvoyer le nombre de commandes
que vous ajoutez au menu contextuel. Redéfinissez la méthode GetVerb pour
renvoyer les chaînes qui doivent être ajoutées pour chacune de ces commandes.
Lorsque vous redéfinissez GetVerb, ajoutez un “et” commercial (&) dans une
chaîne afin que le caractère suivant apparaisse souligné dans le menu contextuel
et fasse office de touche de raccourci pour la sélection de l’élément du menu.
Veillez à ajouter des points de suspension (...) à la fin d’une commande si elle
fait apparaître une boîte de dialogue. GetVerb possède un paramètre unique pour
indiquer l’index de la commande.
Le code suivant redéfinit les méthodes GetVerbCount et GetVerb pour ajouter
deux commandes au menu contextuel.
function TMyEditor.GetVerbCount: Integer;
begin
Result := 2;
end;

Accessibilité des composants au moment de la conception 8-17


Ajout d’éditeurs de composants

function TMyEditor.GetVerb(Index: Integer): String;


begin
case Index of
0: Result := ‘&DoThis ...’;
1: Result := ‘Do&That’;
end;
end;
Remarque Veillez à ce que votre méthode GetVerb renvoie une valeur pour chaque index
possible indiqué par GetVerbCount.

Implémentation des commandes


Lorsque la commande fournie par GetVerb est sélectionnée dans le concepteur, la
méthode ExecuteVerb est appelée. Pour chaque commande que vous spécifiez
dans la méthode GetVerb, implémentez une action dans la méthode ExecuteVerb.
Vous pouvez accéder au composant en cours de modification à l’aide de la
propriété Component de l’éditeur.
Par exemple, la méthode ExecuteVerb suivante implémente les commandes de la
méthode GetVerb de l’exemple précédent.
procedure TMyEditor.ExecuteVerb(Index: Integer);
var
MySpecialDialog: TMyDialog;
begin
case Index of
0: begin
MyDialog := TMySpecialDialog.Create(Application); { instancie l’éditeur }
if MySpecialDialog.Execute then; { si l’utilisateur valide la boîte
de dialogue par OK... }
MyComponent.FThisProperty := MySpecialDialog.ReturnValue; { ...utilise la
valeur }
MySpecialDialog.Free; { détruit l’éditeur }
end;
1: That; { appelle la méthode That }
end;
end;

Modification du comportement suite à un double-clic


Lorsque vous double-cliquez sur le composant, la méthode Edit du composant
est appelée. Par défaut, la méthode Edit exécute la première commande ajoutée
au menu contextuel. Ainsi, dans l’exemple précédent, le fait de double-cliquer
sur le composant exécute la commande DoThis.
Même si l’exécution de la première commande est généralement une bonne idée,
vous pouvez modifier ce comportement par défaut. Par exemple, vous pouvez
définir un comportement différent si :
• vous n’ajoutez aucune commande au menu contextuel ;
• vous souhaitez afficher une boîte de dialogue qui combine plusieurs
commandes lorsque l’utilisateur double-clique sur le composant.

8-18 Guide du concepteur de composants


Ajout d’éditeurs de composants

Redéfinissez la méthode Edit pour spécifier un nouveau comportement lorsque


l’utilisateur double-clique sur le composant. Par exemple, la méthode Edit
suivante appelle une boîte de dialogue de fontes lorsque l’utilisateur
double-clique sur le composant :
procedure TMyEditor.Edit;
var
FontDlg: TFontDialog;
begin
FontDlg := TFontDialog.Create(Application);
try
if FontDlg.Execute then
MyComponent.FFont.Assign(FontDlg.Font);
finally
FontDlg.Free
end;
end;
Remarque Si vous souhaitez qu’un double-clic sur le composant affiche l’éditeur de code
d’un gestionnaire d’événement, utilisez TDefaultEditor comme classe de base pour
votre éditeur de composants au lieu de TComponentEditor. Puis, au lieu de
surcharger la méthode Edit, surchargez la méthode protégée
TDefaultEditor.EditProperty. EditProperty recherche les gestionnaires d’événement
du composant et affiche le premier qu’il trouve. Vous pouvez modifier ce
comportement pour visualiser un événement particulier. Par exemple :
procedure TMyEditor.EditProperty(PropertyEditor: TPropertyEditor;
Continue, FreeEditor: Boolean)
begin
if (PropertyEditor.ClassName = ‘TMethodProperty’) and
(PropertyEditor.GetName = ‘OnSpecialEvent’) then
// DefaultEditor.EditProperty(PropertyEditor, Continue, FreeEditor);
end;

Ajout de formats de presse-papiers


Par défaut, lorsque l’utilisateur choisit Copier lorsqu’un composant est
sélectionné dans l’EDI, le composant est copié dans le format interne de Delphi.
Il peut ensuite être collé dans une autre fiche ou module de données. Votre
composant peut copier d’autres formats dans le Presse-papiers en surchargeant la
méthode Copy.
Par exemple, la méthode Copy suivante permet à un composant TImage de copier
son image dans le Presse-papiers. L’image est ignorée par l’EDI de Delphi, mais
elle peut être collée dans d’autres applications.
procedure TMyComponent.Copy;
var
MyFormat : Word;
AData,APalette : THandle;
begin
TImage(Component).Picture.Bitmap.SaveToClipBoardFormat(MyFormat, AData, APalette);
ClipBoard.SetAsHandle(MyFormat, AData);
end;

Accessibilité des composants au moment de la conception 8-19


Compilation des composants en paquets

Recensement d’un éditeur de composants


Une fois l’éditeur de composants défini, il peut être enregistré pour fonctionner
avec une classe de composants spécifique. Un éditeur de composants enregistré
est créé pour chaque composant de cette classe lorsqu’il est sélectionné dans le
concepteur de fiche.
Pour associer un éditeur de composants à une classe composant, appelez
RegisterComponentEditor. RegisterComponentEditor adopte le nom de la classe
composant qui utilise l’éditeur et le nom de la classe éditeur de composants que
vous avez définie. Par exemple, l’instruction suivante recense une classe éditeur
de composants nommée TMyEditor en vue de son utilisation avec tous les
composants de type TMyComponent :
RegisterComponentEditor(TMyComponent, TMyEditor);
Placez l’appel à RegisterComponentEditor dans la procédure Register où vous
recensez votre composant. Par exemple, si un nouveau composant nommé
TMyComponent et son éditeur de composants TMyEditor sont tous les deux
implémentés dans la même unité, le code suivant recense le composant et son
association à l’éditeur de composants.
procedure Register;
begin
RegisterComponents(’Miscellaneous’, [TMyComponent);
RegisterComponentEditor(classes[0], TMyEditor);
end;

Compilation des composants en paquets


Une fois vos composants recensés, vous devez les compiler en paquets avant de
les installer dans l’EDI. Un paquet peut contenir un ou plusieurs composants
ainsi que des éditeurs de propriétés personnalisés. Pour plus d’informations sur
les paquets, voir Chapitre 16, “Utilisation des paquets et des composants,” du
Guide du développeur.
Pour créer et compiler un paquet, voir “Création et modification de paquets” au
Chapitre 16 du Guide du développeur. Placez les unités de code source de vos
composants personnalisés dans la liste Contains du paquet. Si vos composants
dépendent d’autres paquets, incluez ces derniers dans la liste Requires.
Pour installer vos composants dans l’EDI, voir “Installation de paquets de
composants” au Chapitre 16 du Guide du développeur.

8-20 Guide du concepteur de composants


Chapitre

Modification d’un composant


Chapitre9
9
existant
Le moyen le plus simple de créer un composant consiste à le dériver d’un
composant qui réalise la presque totalité des fonctions souhaitées et lui apporter
ensuite quelques modifications. L’exemple de ce chapitre modifie le composant
mémo standard pour créer un mémo qui ne fait pas de saut de ligne par défaut.
La valeur de la propriété WordWrap du composant mémo est initialisée à True.
Si vous utilisez fréquemment des mémos n’effectuant pas de saut de ligne, vous
pouvez créer un nouveau composant mémo qui ne fait pas de saut de ligne par
défaut.
Remarque Pour modifier les propriétés publiées ou enregistrer des gestionnaires
d’événements spécifiques pour un composant existant, il est souvent plus simple
d’utiliser un modèle de composant plutôt que de créer une classe.
La modification d’un composant existant se fait en deux étapes :
• Création et recensement du composant.
• Modification de la classe composant.

Création et recensement du composant


La création d’un composant se fait toujours de la même façon. vous créez une
unité, puis dérivez et recensez une classe composant avant de l’installer dans la
palette des composants. Ce processus est décrit dans “Création d’un nouveau
composant” à la page 1-9.
Remarque Les noms et les emplacements de certaines unités sont différents pour les
applications CLX. Par exemple, l’unité Controls est QControls dans les
applications CLX.

Modification d’un composant existant 9-1


Modification de la classe composant

Pour notre exemple, suivez la procédure générale de création d’un composant


en tenant compte des spécificités suivantes :
• Nommez l’unité du composant Memos.
• Dérivez un nouveau type de composant appelé TWrapMemo, descendant de
TMemo.
• Recensez TWrapMemo sur la page Exemples de la palette des composants.
• L’unité que vous obtenez doit ressembler à ceci :
unit Memos;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, StdCtrls;
type
TWrapMemo = class(TMemo)
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents(’Samples’, [TWrapMemo]);
end;
end.
Si vous compilez et installez maintenant le nouveau composant, il se comportera
exactement comme son ancêtre, TMemo. Dans la section suivante, vous
effectuerez une simple modification à votre composant.

Modification de la classe composant


Une fois la nouvelle classe composant créée, vous pouvez lui apporter presque
toutes les modifications que vous voulez. Dans notre exemple, vous allez
changer la valeur par défaut d’une propriété du composant mémo. Cela implique
deux changements mineurs dans la classe composant :
• Redéfinition du constructeur.
• Spécification de la nouvelle valeur par défaut de la propriété.
Le constructeur définit la valeur de la propriété. La valeur par défaut indique à
Delphi quelles valeurs stocker dans le fichier fiche (.dfm pour les applications
VCL et .xfm pour les applications CLX). Delphi ne stocke que les valeurs qui
diffèrent de la valeur par défaut, c’est pourquoi il est important d’effectuer les
deux étapes.

Redéfinition du constructeur
Lorsque vous placez un composant dans une fiche au moment de la conception
ou lorsqu’une application en cours d’exécution construit un composant, le

9-2 Guide du concepteur de composants


Modification de la classe composant

constructeur du composant définit les valeurs des propriétés. Quand un


composant est chargé depuis un fichier fiche, l’application définit toutes les
propriétés qui ont été modifiées lors de la conception.
Remarque Lorsque vous redéfinissez un constructeur, le nouveau constructeur doit appeler
le constructeur reçu en héritage avant toute autre action. Pour plus
d’informations, voir “Redéfinition des méthodes” à la page 2-9.
Dans notre exemple, votre nouveau composant doit surcharger le constructeur
transmis en héritage par TMemo en attribuant la valeur False à la propriété
WordWrap. Pour ce faire, ajoutez le constructeur redéfini à la prédéclaration, puis
écrivez le nouveau constructeur dans la partie implémentation de l’unité :
type
TWrapMemo = class(TMemo)
public { constructeurs toujours publics }
constructor Create(AOwner: TComponent); override; { cette syntaxe est toujours
identique }
end;
ƒ
constructor TWrapMemo.Create(AOwner: TComponent); { ceci va après l’implémentation }
begin
inherited Create(AOwner); { Faites TOUJOURS ceci en premier ! }
WordWrap := False; { définit la nouvelle valeur désirée }
end;
Vous pouvez maintenant installer le nouveau composant dans la palette des
composants puis l’ajouter dans une fiche. Remarquez que la propriété WordWrap
est dorénavant initialisée à False.
Si vous changez une valeur de propriété initiale, vous devez aussi désigner cette
valeur comme étant celle par défaut. Si vous échouez à faire correspondre la
valeur définie par le constructeur à celle spécifiée par défaut, Delphi ne peut pas
stocker, ni restaurer la valeur correcte.

Spécification de la nouvelle valeur par défaut de la propriété


Lorsque Delphi stocke la description d’une fiche dans un fichier fiche, il ne
stocke que les valeurs des propriétés différentes des valeurs par défaut. La taille
du fichier fiche reste minime et le chargement est plus rapide. Si vous créez une
propriété ou si vous changez la valeur par défaut d’une propriété existante, c’est
une bonne idée de mettre à jour la déclaration de la propriété en y incluant la
nouvelle valeur par défaut. Les fichiers fiche ainsi que le chargement et les
valeurs par défaut sont expliqués en détail dans le Chapitre 8, “Accessibilité des
composants au moment de la conception”.
Pour changer la valeur par défaut d’une propriété, redéclarez le nom de la
propriété, suivi de la directive default et de la nouvelle valeur par défaut. Il n’est
pas nécessaire de redéclarer la propriété entière mais uniquement le nom et la
valeur par défaut.

Modification d’un composant existant 9-3


Modification de la classe composant

Pour le composant mémo de saut à la ligne automatique, redéclarez la propriété


WordWrap dans la partie published de la déclaration d’objet, avec la valeur False
par défaut :
type
TWrapMemo = class(TMemo)
ƒ
published
property WordWrap default False;
end;
Spécifier la valeur par défaut de la propriété n’affecte en rien le fonctionnement
du composant. Vous devez toujours initialiser la valeur dans le constructeur du
composant. La redéclaration de la valeur par défaut assure que Delphi connaît
quand WordWrap doit être écrit dans le fichier fiche.

9-4 Guide du concepteur de composants


Chapitre

Création d’un contrôle graphique


Chapitre10
10
Un contrôle graphique est un composant simple. Un contrôle purement
graphique ne reçoit jamais la focalisation et n’a donc pas besoin de son propre
handle de fenêtre. Les utilisateurs peuvent quand même manipuler le contrôle
avec la souris, mais il n’y a pas d’interface clavier.
TShape, le contrôle graphique présenté dans ce chapitre, correspond au
composant forme inclus dans la page Supplément de la palette des composants.
Bien que le composant que nous allons créer soit identique au composant forme
standard, vous devrez lui donner un nom différent pour éviter des doublons
d’identificateur. Ce chapitre appelle son composant forme TSampleShape et
illustre toutes les étapes de sa création :
• Création et recensement du composant.
• Publication des propriétés héritées.
• Ajout de fonctionnalités graphiques.

Création et recensement du composant


La création d’un composant se fait toujours de la même façon. Vous créez une
unité et vous recensez le composant avant de l’installer dans la palette des
composants. Ce processus est décrit dans “Création d’un nouveau composant” à
la page 1-9.
Pour notre exemple, suivez la procédure générale de création d’un composant
en tenant compte des spécificités suivantes :
1 Appelez l’unité du composant Shapes.
2 Dérivez un nouveau type de composant appelé TSampleShape, descendant
de TGraphicControl.
3 Recensez TSampleShape sur la page Exemples (ou une autre page dans une
application CLX) de la palette des composants.

Création d’un contrôle graphique 10-1


Publication des propriétés héritées

L’unité que vous obtenez doit ressembler à ceci :


unit Shapes;
interface
uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms;
type
TSampleShape = class(TGraphicControl)
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponent(’Samples’, [TSampleShape]);
end;
end.
Remarque Les noms et les emplacements de certaines unités sont différents pour les
applications CLX. Par exemple, l’unité Controls est QControls dans les
applications CLX.

Publication des propriétés héritées


Lorsque vous dérivez un type de composant, vous choisissez parmi les
propriétés et les événements déclarés dans la partie protected de la classe ancêtre
ceux que vous voulez rendre disponibles aux utilisateurs du nouveau composant.
TGraphicControl publie toutes les propriétés qui permettent au composant de
fonctionner en tant que contrôle, donc les seules fonctionnalités que vous devez
publier sont celles dont vous avez besoin pour répondre aux événements de
souris et aux opérations glisser-déplacer.
La publication des propriétés et des événements reçus en héritage est expliquée
dans “Publication des propriétés héritées” à la page 3-3 et “Rendre visibles des
événements” à la page 4-6. Ces deux processus impliquent la redéclaration du
nom des propriétés dans la partie published de la déclaration de classe.
S’agissant de notre contrôle forme, vous devez publier les trois événements
associés à la souris ainsi que les deux propriétés associées aux opérations
glisser-déplacer :
type
TSampleShape = class(TGraphicControl)
published
property DragCursor; { propriétés glisser-déplacer }
property DragMode;
property OnDragDrop; { événements glisser-déplacer }
property OnDragOver;
property OnEndDrag;
property OnMouseDown; { événements de souris }
property OnMouseMove;
property OnMouseUp;
end;

10-2 Guide du concepteur de composants


Ajout de fonctionnalités graphiques

Le contrôle forme de notre exemple rend les interactions associées à la souris et


aux opérations glisser-déplacer accessibles à l’utilisateur.

Ajout de fonctionnalités graphiques


Lorsque votre composant graphique a été déclaré et qu’ont été publiées toutes
les propriétés reçues en héritage que vous voulez rendre disponibles, vous
pouvez ajouter les fonctionnalités graphiques qui caractériseront votre
composant. La création d’un contrôle graphique se compose de deux tâches :
1 Détermination de ce qui doit être dessiné.
2 Dessin de l’image du composant.
En outre, s’agissant du contrôle forme de notre exemple, vous allez ajouter
certaines propriétés qui serviront aux développeurs d’applications pour
personnaliser l’apparence du contrôle lors de la conception.

Détermination de ce qui doit être dessiné


Un contrôle graphique est capable de changer son apparence pour refléter un
changement de condition, y compris une intervention de l’utilisateur. Un contrôle
graphique qui aurait toujours le même aspect ne devrait pas être un composant.
Si vous voulez une image statique, importez une image au lieu d’utiliser un
contrôle.
Généralement, l’apparence d’un contrôle graphique dépend de plusieurs
propriétés. Par exemple, le contrôle jauge dispose de propriétés qui déterminent
sa forme et son orientation et si la représentation de la progression est
numérique ou graphique. De même, notre contrôle forme doit disposer d’une
propriété qui détermine le type de forme qu’il doit dessiner.
Pour donner à votre contrôle une propriété qui détermine la forme dessinée,
ajoutez une propriété intitulée Shape. Cela implique les tâches suivantes :
1 Déclaration du type de la propriété.
2 Déclaration de la propriété.
3 Ecriture de la méthode d’implémentation.
La création de propriété est expliquée en détail dans le Chapitre 3, “Création de
propriétés”.

Déclaration du type de la propriété


Lorsque vous déclarez une propriété dont le type est défini par l’utilisateur, le
type de la propriété doit être déclaré avant la classe qui inclut cette propriété.
Les types énumérés sont fréquemment employés par les propriétés.
S’agissant de notre contrôle forme, vous aurez besoin d’un type énuméré avec
un élément défini pour chaque forme que le contrôle est en mesure de dessiner.

Création d’un contrôle graphique 10-3


Ajout de fonctionnalités graphiques

Ajoutez la définition de type suivante avant la déclaration de classe du contrôle


forme.
type
TSampleShapeType = (sstRectangle, sstSquare, sstRoundRect, sstRoundSquare,
sstEllipse, sstCircle);
TSampleShape = class(TGraphicControl) { existe déjà }
Vous pouvez maintenant utiliser ce type pour déclarer une nouvelle propriété
dans la classe.

Déclaration de la propriété
Généralement, pour déclarer une propriété, vous déclarez un champ privé pour
stocker les données de la propriété puis vous spécifiez les méthodes pour lire et
écrire sa valeur. Souvent, la méthode pour lire la valeur n’est pas nécessaire car
un simple pointage sur la valeur stockée suffit.
S’agissant de notre contrôle forme, vous aurez à déclarer un champ contenant la
forme courante, puis à déclarer une propriété qui lit ce champ et l’écrit via un
appel de méthode.
Ajoutez les déclarations suivantes dans TSampleShape :
type
TSampleShape = class(TGraphicControl)
private
FShape: TSampleShapeType; { champ pour contenir la valeur de la propriété }
procedure SetShape(Value: TSampleShapeType);
published
property Shape: TSampleShapeType read FShape write SetShape;
end;
Il ne vous reste plus qu’à ajouter l’implémentation de SetShape.

Ecriture de la méthode d’implémentation


Lorsque la partie read ou write d’une définition de propriété utilise une
méthode plutôt qu’un accès direct aux données stockées de la propriété, vous
devez implémenter ces méthodes.
Ajoutez l’implémentation de la méthode SetShape à la partie implémentation
de l’unité :
procedure TSampleShape.SetShape(Value: TSampleShapeType);
begin
if FShape <> Value then { ignore s’il n’y a pas eu de changement }
begin
FShape := Value; { stocke la nouvelle valeur }
Invalidate; { force le dessin avec la nouvelle forme }
end;
end;

10-4 Guide du concepteur de composants


Ajout de fonctionnalités graphiques

Redéfinition du constructeur et du destructeur


Pour changer les valeurs par défaut des propriétés et initialiser les classes
appartenant à votre composant, vous devez redéfinir le constructeur et le
destructeur reçus en héritage. Dans les deux cas, n’oubliez pas d’appeler la
méthode reçue en héritage.

Modification des valeurs par défaut des propriétés


La taille par défaut d’un contrôle graphique étant réduite, vous pouvez modifier
sa largeur et sa hauteur dans le constructeur. La modification des valeurs par
défaut des propriétés est abordée plus en détail dans le Chapitre 9, “Modification
d’un composant existant”.
Dans notre exemple, le contrôle forme définit sa taille par un carré de 65 pixels
de côté.
Ajoutez le constructeur redéfini dans la déclaration de la classe composant :
type
TSampleShape = class(TGraphicControl)
public { constructeurs toujours publics }
constructor Create(AOwner: TComponent); override { ne pas oublier la directive
override }
end;
1 Redéclarez les propriétés Height et Width avec leurs nouvelles valeurs par
défaut :
type
TSampleShape = class(TGraphicControl)
ƒ
published
property Height default 65;
property Width default 65;
end;
2 Ecrivez le nouveau constructeur dans la partie implémentation de l’unité :
constructor TSampleShape.Create(AOwner: TComponent);
begin
inherited Create(AOwner); { appelez toujours le constructeur hérité }
Width := 65;
Height := 65;
end;

Publication du crayon et du pinceau


Par défaut, un canevas dispose d’un crayon fin et noir et d’un pinceau plein et
blanc. Pour permettre aux développeurs de changer le crayon et le pinceau, vous
devez leur fournir des classes pour les manipuler lors de la conception, puis
copier les classes dans le canevas lors des opérations de dessin. Des classes telles
qu’un crayon ou un pinceau auxiliaire sont appelées classes ayant un propriétaire

Création d’un contrôle graphique 10-5


Ajout de fonctionnalités graphiques

car elles appartiennent au composant qui est responsable de leur création et de


leur destruction.
La gestion des classes ayant un propriétaire se déroule en quatre étapes :
1 Déclaration des champs de la classe.
2 Déclaration des propriétés d’accès.
3 Initialisation des classes ayant un propriétaire.
4 Définition des propriétés des classes ayant un propriétaire.

Déclaration des champs de classe


Chaque classe appartenant à un composant doit avoir un champ déclaré dans ce
composant. Le champ de classe garantit que le composant dispose toujours d’un
pointeur sur l’objet qui lui appartient afin de lui permettre de le détruire avant
de se détruire lui-même. Généralement, un composant initialise les objets dont il
est propriétaire dans son constructeur et les détruit dans son destructeur.
Les champs de classe des objets ayant un propriétaire sont presque toujours
déclarés private. Si des applications (ou d’autres composants) ont besoin
d’accéder aux objets ayant un propriétaire, vous devez pour cela déclarer des
propriétés published ou public.
Ajoutez des champs de classe pour le crayon et le pinceau de votre contrôle
forme :
type
TSampleShape = class(TGraphicControl)
private { les données membres sont presque toujours privées }
FPen: TPen; { donnée membre pour l’objet crayon }
FBrush: TBrush; { donnée membre pour l’objet pinceau }
ƒ
end;

Déclaration des propriétés d’accès


Vous pouvez fournir les accès aux objets appartenant à un composant en
déclarant des propriétés de même type que ces objets. Cela offre aux
développeurs un moyen d’accéder aux objets lors de la conception ou de
l’exécution. Généralement, la partie read de la propriété ne fait que référencer le
champ de classe, alors que la partie write appelle une méthode qui permet au
composant de réagir aux changements apportés à l’objet.
Ajoutez des propriétés à votre contrôle forme pour fournir les accès aux champs
du crayon et du pinceau. Vous allez également déclarer les méthodes pour réagir
aux changements de crayon ou de pinceau.
type
TSampleShape = class(TGraphicControl)
ƒ
private { ces méthodes doivent être privées }
procedure SetBrush(Value: TBrush);
procedure SetPen(Value: TPen);

10-6 Guide du concepteur de composants


Ajout de fonctionnalités graphiques

published { les rend disponibles lors de la conception }


property Brush: TBrush read FBrush write SetBrush;
property Pen: TPen read FPen write SetPen;
end;
Vous devez ensuite écrire les méthodes SetBrush et SetPen dans la partie
implémentation de l’unité :
procedure TSampleShape.SetBrush(Value: TBrush);
begin
FBrush.Assign(Value); { remplace le pinceau existant par le paramètre }
end;
procedure TSampleShape.SetPen(Value: TPen);
begin
FPen.Assign(Value); { remplace le crayon existant par le paramètre }
end;
Affecter directement le contenu de Value à FBrush :
FBrush := Value;
écraserait le pointeur interne de FBrush, ferait perdre de la mémoire et créerait
une série de problèmes de propriété.

Initialisation des classes ayant un propriétaire


Si vous ajoutez des classes dans votre composant, le constructeur de ce dernier
doit les initialiser pour que l’utilisateur puisse interagir avec ces objets lors de
l’exécution. De même, le destructeur du composant doit également détruire les
objets appartenant au composant avant de détruire ce dernier.
Comme vous avez déclaré un crayon et un pinceau dans le contrôle forme, vous
devez les initialiser dans le constructeur du contrôle forme et les détruire dans
son destructeur :
1 Construisez le crayon et le pinceau dans le constructeur du contrôle forme :
constructor TSampleShape.Create(AOwner: TComponent);
begin
inherited Create(AOwner); { appelez toujours le constructeur hérité }
Width := 65;
Height := 65;
FPen := TPen.Create; { construit le crayon }
FBrush := TBrush.Create; { construit le pinceau }
end;
2 Ajoutez le destructeur redéfini dans la déclaration de la classe composant :
type
TSampleShape = class(TGraphicControl)
public { les destructeurs sont toujours publics}
constructor Create(AOwner: TComponent); override;
destructor Destroy; override; { ne pas oublier la directive override }
end;

Création d’un contrôle graphique 10-7


Ajout de fonctionnalités graphiques

3 Ecrivez le nouveau destructeur dans la partie implémentation de l’unité :


destructor TSampleShape.Destroy;
begin
FPen.Free; { détruit l’objet crayon }
FBrush.Free; { détruit l’objet pinceau }
inherited Destroy; { appelle aussi toujours le destructeur hérité }
end;

Définition des propriétés des classes ayant un propriétaire


L’une des dernières étapes de la gestion des classes crayon et pinceau consiste à
provoquer un nouveau dessin du contrôle forme si le crayon ou le pinceau est
modifié. Comme les classes crayon et pinceau disposent toutes les deux d’un
événement OnChange, vous pouvez créer une méthode dans le contrôle forme et
faire pointer les deux événements OnChange sur cette méthode.
Ajoutez la méthode suivante au contrôle forme et effectuez la mise à jour du
constructeur du composant pour affecter aux événements du crayon et du
pinceau cette nouvelle méthode :
type
TSampleShape = class(TGraphicControl)
published
procedure StyleChanged(Sender: TObject);
end;
ƒ
implementation
ƒ
constructor TSampleShape.Create(AOwner: TComponent);
begin
inherited Create(AOwner); { appelez toujours le constructeur hérité }
Width := 65;
Height := 65;
FPen := TPen.Create; { construit le crayon }
FPen.OnChange := StyleChanged; { affecte la méthode à l’événement OnChange }
FBrush := TBrush.Create; { construit le pinceau }
FBrush.OnChange := StyleChanged; { affecte la méthode à l’événement OnChange }
end;
procedure TSampleShape.StyleChanged(Sender: TObject);
begin
Invalidate; { efface et redessine le composant }
end;
Ces modifications faites, le composant se redessine pour refléter tout changement
du crayon ou du pinceau.

Dessin de l’image du composant


L’essentiel d’un contrôle graphique se résume à sa façon de dessiner son image à
l’écran. Le type abstrait TGraphicControl définit une méthode appelée Paint que
vous devez redéfinir pour peindre l’image voulue dans votre contrôle.

10-8 Guide du concepteur de composants


Ajout de fonctionnalités graphiques

La méthode Paint de votre contrôle forme doit accomplir plusieurs tâches :


• Utiliser le crayon et le pinceau sélectionnés par l’utilisateur.
• Utiliser la forme sélectionnée.
• Ajuster les coordonnées pour que les carrés et les cercles utilisent une largeur
et une hauteur identiques.
La redéfinition de la méthode Paint nécessite deux étapes :
1 Ajout de Paint dans la déclaration du composant.
2 Insertion de la méthode Paint dans la partie implémentation de l’unité.
S’agissant de notre contrôle forme, vous devez ajouter la déclaration suivante à
la déclaration de classe :
type
TSampleShape = class(TGraphicControl)
ƒ
protected
procedure Paint; override;
ƒ
end;
Vous devez ensuite écrire la méthode dans la partie implémentation de l’unité :
procedure TSampleShape.Paint;
begin
with Canvas do
begin
Pen := FPen; { copie le crayon du composant }
Brush := FBrush; { copie le pinceau du composant }
case FShape of
sstRectangle, sstSquare:
Rectangle(0, 0, Width, Height); { dessine les rectangles et carrés }
sstRoundRect, sstRoundSquare:
RoundRect(0, 0, Width, Height, Width div 4, Height div 4); { dessine des formes
arrondies }
sstCircle, sstEllipse:
Ellipse(0, 0, Width, Height); { dessine des formes arrondies }
end;
end;
end;
Paint est appelée à chaque fois que le contrôle doit mettre à jour son image. Les
contrôles sont dessinés lorsqu’ils s’affichent pour la première fois ou lorsqu’une
fenêtre qui se trouvait au-dessus disparaît. En outre, vous pouvez forcer le
dessin en appelant Invalidate, comme le fait la méthode StyleChanged.

Création d’un contrôle graphique 10-9


Ajout de fonctionnalités graphiques

Adaptation du dessin de la forme


Le contrôle forme standard effectue une tâche supplémentaire que ne fait pas
encore le contrôle forme de notre exemple : il gère les carrés et les cercles ainsi
que les rectangles et les ellipses. Pour ce faire, vous devez écrire le code qui
trouve le côté le plus petit et centre l’image.
Voici une méthode Paint parfaitement adaptée aux carrés et aux ellipses :
procedure TSampleShape.Paint;
var
X, Y, W, H, S: Integer;
begin
with Canvas do
begin
Pen := FPen; { copie le crayon du composant }
Brush := FBrush; { copie le pinceau du composant }
W := Width; { utilise la largeur du composant }
H := Height; { utilise la hauteur du composant }
if W < H then S := W else S := H; { enregistre du plus petit pour les cercles/carrés }
case FShape of { ajuste la hauteur, la largeur et la position }
sstRectangle, sstRoundRect, sstEllipse:
begin
X := 0; { l’origine est l’angle supérieur gauche de ces formes }
Y := 0;
end;
sstSquare, sstRoundSquare, sstCircle:
begin
X := (W - S) div 2; { centre horizontalement... }
Y := (H - S) div 2; { ...puis verticalement }
W := S; { utilise la dimension la plus petite pour la largeur... }
H := S; { ...et pour la hauteur }
end;
end;
case FShape of
sstRectangle, sstSquare:
Rectangle(X, Y, X + W, Y + H); { dessine les rectangles et les carrés }
sstRoundRect, sstRoundSquare:
RoundRect(X, Y, X + W, Y + H, S div 4, S div 4); { dessine des formes arrondies }
sstCircle, sstEllipse:
Ellipse(X, Y, X + W, Y + H); { dessine les formes arrondies }
end;
end;
end;

10-10 Guide du concepteur de composants


Chapitre

Personnalisation d’une grille


Chapitre11
11
La bibliothèque de composants fournit plusieurs composants abstraits que vous
pouvez utiliser comme points de départ pour personnaliser vos composants. Les
grilles et les boîtes liste sont les plus importants. Dans ce chapitre, nous allons
voir comment créer un petit calendrier en partant du composant grille de base
TCustomGrid.
La création du calendrier nécessite les étapes suivantes :
• Création et recensement du composant
• Publication des propriétés héritées
• Modification des valeurs initiales
• Redimensionnement des cellules
• Remplissage des cellules
• Navigation de mois en mois et d’année en année
• Navigation de jour en jour
Dans les applications VCL, le composant calendrier résultant est pratiquement
identique au composant TCalendar contenu dans la page Exemples de la palette
des composants. Dans les applications CLX, enregistrez le composant dans une
autre page ou créez une nouvelle page dans la palette. Voir “Spécification de la
page de palette” à la page 8-3 ou “palette des composants, ajout de pages” dans
l’aide en ligne.

Création et recensement du composant


La création d’un composant se fait toujours de la même façon. Vous créez une
unité et vous recensez le composant avant de l’installer dans la palette des
composants. Ce processus est décrit dans “Création d’un nouveau composant” à
la page 1-9.

Personnalisation d’une grille 11-1


Création et recensement du composant

Pour notre exemple, suivez la procédure générale de création d’un composant en


tenant compte des spécificités suivantes :
1 Appelez l’unité du composant CalSamp.
2 Dérivez un nouveau type de composant appelé TSampleCalendar, descendant
de TCustomGrid.
Recensez TSampleCalendar dans la page Exemples (ou une autre page dans une
application CLX) de la palette des composants.
L’unité résultante dérivée de TCustomGrid dans une application VCL doit
ressembler à ceci :
unit CalSamp;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Grids;
type
TSampleCalendar = class(TCustomGrid)
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents(’Samples’, [TSampleCalendar]);
end;
end.
Remarque Si la dérivation est effectuée à partir de la version CLX de TCustomGrid, la seule
différence réside dans la clause uses, qui affiche à la place les unités CLX. De
mêmes, les noms et les emplacements de certaines unités sont différents pour les
applications CLX. Par exemple, l’unité Controls est QControls dans les
applications CLX.
Si vous installez le composant calendrier maintenant, vous verrez qu’il apparaît
sur la page Exemples. Les seules propriétés disponibles sont les propriétés de
contrôle les plus basiques. L’étape suivante consiste à rendre disponible certaines
des propriétés plus spécialisées aux utilisateurs du calendrier.
Remarque Bien que vous puissiez installer le composant calendrier exemple que vous venez
de compiler, n’essayez pas de le placer tout de suite sur une fiche. Le composant
TCustomGrid contient une méthode DrawCell abstraite qui doit être redéclarée
avant que les objets d’instance puissent être créés. La redéfinition de la méthode
DrawCell est décrite dans “Remplissage des cellules” à la page 11-5.

11-2 Guide du concepteur de composants


Publication des propriétés héritées

Publication des propriétés héritées


Le composant grille abstrait, TCustomGrid, fournit de nombreuses propriétés
protected. Vous pouvez choisir parmi ces propriétés celles que vous voulez
rendre accessibles aux utilisateurs du contrôle calendrier.
Pour rendre accessibles aux utilisateurs de vos composants les propriétés
protégées qu’ils reçoivent en héritage, vous devez redéclarer ces propriétés dans
la partie published de la déclaration de vos composants.
S’agissant du contrôle calendrier, vous devez publier les propriétés et les
événements, comme ci-dessous :
type
TSampleCalendar = class(TCustomGrid)
published
property Align; { propriétés publiées }
property BorderStyle;
property Color;
property Font;
property GridLineWidth;
property ParentColor;
property ParentFont;
property OnClick; { événements publiés }
property OnDblClick;
property OnDragDrop;
property OnDragOver;
property OnEndDrag;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
end;
Il existe bien d’autres propriétés ne s’appliquant pas à un calendrier qui sont
publiables, par exemple la propriété Options qui permet à l’utilisateur de choisir
les lignes de la grille à dessiner.
Si vous installez le composant calendrier modifié dans la palette des composants
et l’utilisez dans une application, vous trouverez bien d’autres propriétés et
événements opérationnels. Nous allons maintenant commencer à ajouter de
nouvelles fonctionnalités au composant.

Modification des valeurs initiales


Un calendrier est essentiellement une grille avec un nombre fixe de lignes et de
colonnes, ne contenant pas nécessairement des dates. Les propriétés ColCount et
RowCount de la grille n’ont donc pas été publiées, car il est peu probable que les
utilisateurs du calendrier voudront afficher autre chose que les sept jours de la
semaine. Vous devez néanmoins définir les valeurs initiales de ces propriétés en
fonction des sept jours de la semaine.

Personnalisation d’une grille 11-3


Redimensionnement des cellules

Pour changer les valeurs initiales des propriétés du composant, vous devez
redéfinir le constructeur afin qu’il affecte les valeurs voulues. Le constructeur
doit être virtuel.
Souvenez-vous que vous devez ajouter le constructeur à la partie public de la
déclaration de la classe du composant, puis écrire le nouveau constructeur dans
la partie implémentation de l’unité du composant. La première instruction du
nouveau constructeur doit toujours être un appel au constructeur hérité. Ensuite,
ajoutez l’unité StdCtrls à la clause uses.
type
TSampleCalendar = class(TCustomGrid
public
constructor Create(AOwner: TComponent); override;
ƒ
end;
ƒ
constructor TSampleCalendar.Create(AOwner: TComponent);
begin
inherited Create(AOwner); { appelle le constructeur hérité }
ColCount := 7; { toujours 7 jours/semaine }
RowCount := 7; { toujours 6 semaines plus les titres }
FixedCols := 0; { aucun libellé de ligne }
FixedRows := 1; { une ligne pour les noms de jour }
ScrollBars := ssNone; { pas de défilement nécessaire }
Options := Options - [goRangeSelect] + [goDrawFocusSelected]; {désactive la sélection
d’intervalle }
end;
Le calendrier a dorénavant sept colonnes et sept lignes, avec la ligne de titre fixe
(ou qui ne défile pas).

Redimensionnement des cellules


Remarque Lorsqu’un utilisateur ou une application modifie la taille d’une fenêtre ou d’un
contrôle, Windows envoie le message WM_SIZE à la fenêtre ou au contrôle
concerné pour lui permettre d’ajuster les paramètres nécessaires afin de dessiner
ultérieurement son image dans la nouvelle taille. Votre composant VCL peut
répondre à ce message en modifiant la taille des cellules de façon à ce qu’elles
s’inscrivent dans les limites du contrôle. Pour répondre au message WM_SIZE,
vous devez ajouter au composant une méthode de gestion du message.
La création d’une méthode de gestion de message est décrite en détail dans
“Création de nouveaux gestionnaires de messages” à la page 7-6.
Dans notre exemple, le contrôle calendrier devant répondre au message
WM_SIZE, vous devez ajouter une méthode protégée appelée WMSize au
contrôle indexé sur le message WM_SIZE, puis écrire la méthode de calcul de la
taille des cellules qui permettra à toutes d’être visibles :
type
TSampleCalendar = class(TCustomGrid)
protected

11-4 Guide du concepteur de composants


Remplissage des cellules

procedure WMSize(var Message: TWMSize); message WM_SIZE;


ƒ
end;
ƒ
procedure TSampleCalendar.WMSize(var Message: TWMSize);
var
GridLines: Integer; { variable locale temporaire }
begin
GridLines := 6 * GridLineWidth; { calcule la taille combinée de toutes les lignes }
DefaultColWidth := (Message.Width - GridLines) div 7; { définit la nouvelle largeur
de cellule par défaut }
DefaultRowHeight := (Message.Height - GridLines) div 7; { ainsi que sa hauteur }
end;
Maintenant lorsque le calendrier est redimensionné, il affiche toutes les cellules
dans la taille maximum avec laquelle ils peuvent rentrer dans le contrôle.
Remarque Dans les applications CLX, les changements de taille d’une fenêtre ou d’un
contrôle sont notifiés automatiquement par un appel à la méthode protégée
BoundsChanged. Votre composant CLX peut répondre à cette notification en
modifiant la taille des cellules afin qu’elles tiennent toutes dans les limites du
contrôle.
Dans ce cas, le contrôle calendrier doit redéfinir BoundsChanged afin qu’elle
calcule la taille de cellule adéquate pour que toutes les cellules soient visibles
avec la nouvelle taille :
type
TSampleCalendar = class(TCustomGrid)
protected
procedure BoundsChanged; override;
ƒ
end;
ƒ
procedure TSampleCalendar.BoundsChanged;
var
GridLines: Integer; { variable locale temporaire }
begin
GridLines := 6 * GridLineWidth; { calcule la taille combinée de toutes les lignes }
DefaultColWidth := (Width - GridLines) div 7; { définit la nouvelle largeur
de cellule par défaut }
DefaultRowHeight := (Height - GridLines) div 7; { ainsi que sa hauteur }
inherited; { appelle maintenant la méthode dérivée }
end;

Remplissage des cellules


Un contrôle grille se remplit cellule par cellule. S’agissant du calendrier, cela
revient à calculer une date (si elle existe) pour chaque cellule. Le dessin par
défaut des cellules de la grille s’opère dans une méthode virtuelle intitulée
DrawCell.

Personnalisation d’une grille 11-5


Remplissage des cellules

Pour remplir le contenu des cellules de la grille, vous devez redéfinir la méthode
DrawCell.
Les cellules de titre de la ligne fixe sont ce qu’il y a de plus facile à remplir. La
bibliothèque d’exécution contient un tableau avec l’intitulé raccourci des jours et
il vous faut donc insérer l’intitulé approprié à chaque colonne :
type
TSampleCalendar = class(TCustomGrid)
protected
procedure DrawCell(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState);
override;
end;
ƒ
procedure TSampleCalendar.DrawCell(ACol, ARow: Longint; ARect: TRect;
AState: TGridDrawState);
begin
if ARow = 0 then
Canvas.TextOut(ARect.Left, ARect.Top, ShortDayNames[ACol + 1]);
{ utilise les chaînes RTL }
end;

Suivi de la date
Pour que le contrôle calendrier soit utile, les utilisateurs ainsi que les
applications doivent disposer d’un moyen de définir la date, le mois et l’année.
Delphi stocke les dates et les heures dans des variables de type TDateTime.
TDateTime est une représentation numérique encodée des dates et des heures
particulièrement pratique pour être manipulée par un programme mais peu
commode à interpréter par un utilisateur.
Vous pouvez donc stocker la date du calendrier sous une forme encodée et
fournir un accès direct à cette valeur lors de l’exécution, mais vous pouvez aussi
fournir les propriétés Day, Month et Year que l’utilisateur du composant peut
définir lors de la conception.
Le suivi de la date dans le calendrier comprend les traitements suivants :
• Stockage interne de la date
• Accès au jour, au mois et à l’année
• Génération des numéros de jours
• Sélection du jour en cours

Stockage interne de la date


Pour stocker la date du calendrier, vous devez avoir un champ contenant la
date, ainsi qu’une propriété accessible à l’exécution seulement qui fournit un
accès à cette date.
L’ajout de la date interne au calendrier requiert trois étapes :
1 Déclarez un champ privé pour contenir la date :
type

11-6 Guide du concepteur de composants


Remplissage des cellules

TSampleCalendar = class(TCustomGrid)
private
FDate: TDateTime;
ƒ
2 Initialisez le champ date dans le constructeur :
constructor TSampleCalendar.Create(AOwner: TComponent);
begin
inherited Create(AOwner); { existait déjà }
ƒ { d’autres initialisations ici }
FDate := Date; { prend la date active du RTL }
end;
3 Déclarez une propriété à l’exécution pour accéder à la date encodée.
Vous aurez besoin d’une méthode pour définir la date car sa définition
entraîne la mise à jour de l’image sur l’écran du contrôle :
type
TSampleCalendar = class(TCustomGrid)
private
procedure SetCalendarDate(Value: TDateTime);
public
property CalendarDate: TDateTime read FDate write SetCalendarDate;
ƒ
procedure TSampleCalendar.SetCalendarDate(Value: TDateTime);
begin
FDate := Value; { définit la nouvelle valeur date }
Refresh; { met à jour l’image à l’écran }
end;

Accès au jour, au mois et à l’année


Une date encodée numériquement est adaptée aux applications, mais l’utilisateur
préférera manipuler jour, mois et année. En créant des propriétés, vous offrez un
accès aux dates stockées et encodées numériquement.
Les éléments d’une date (jour, mois, année) sont des entiers. La modification de
chacun d’entre eux nécessite l’encodage de la date. Vous pouvez éviter la
duplication du code en partageant les méthodes d’implémentation entre les trois
propriétés. Autrement dit, vous pouvez écrire deux méthodes, l’une pour lire un
élément et l’autre pour l’écrire, et utiliser ces méthodes pour lire et écrire les
trois propriétés.
Pour fournir un accès lors de la conception aux éléments jour, mois et année,
procédez de la façon suivante :
1 Déclarez les trois propriétés, en attribuant à chacune un numéro unique
d’index :
type
TSampleCalendar = class(TCustomGrid)
public
property Day: Integer index 3 read GetDateElement write SetDateElement;
property Month: Integer index 2 read GetDateElement write SetDateElement;
property Year: Integer index 1 read GetDateElement write SetDateElement;

Personnalisation d’une grille 11-7


Remplissage des cellules

ƒ
2 Déclarez et écrivez les méthodes d’implémentation, définissant les différents
éléments pour chaque valeur d’index :
type
TSampleCalendar = class(TCustomGrid)
private
function GetDateElement(Index: Integer): Integer; { notez le paramètre Index }
procedure SetDateElement(Index: Integer; Value: Integer);
ƒ
function TSampleCalendar.GetDateElement(Index: Integer): Integer;
var
AYear, AMonth, ADay: Word;
begin
DecodeDate(FDate, AYear, AMonth, ADay); { éclate la date encodée en éléments }
case Index of
1: Result := AYear;
2: Result := AMonth;
3: Result := ADay;
else Result := -1;
end;
end;
procedure TSampleCalendar.SetDateElement(Index: Integer; Value: Integer);
var
AYear, AMonth, ADay: Word;
begin
if Value > 0 then { tous les éléments doivent être positifs }
begin
DecodeDate(FDate, AYear, AMonth, ADay);{ récupère les éléments courants de la date }
case Index of { définit le nouvel élément selon l’index }
1: AYear := Value;
2: AMonth := Value;
3: ADay := Value;
else Exit;
end;
FDate := EncodeDate(AYear, AMonth, ADay); { encode la date modifiée }
Refresh; { mise à jour du calendrier visible }
end;
end;
Vous pouvez maintenant définir le jour, le mois et l’année du calendrier lors de
la conception à partir de l’inspecteur d’objets, ou à l’exécution à partir du code.
Bien que vous n’ayez pas encore ajouté le code pour dessiner les dates dans les
cellules, vous disposez maintenant de toutes les données nécessaires.

Génération des numéros de jours


Insérer les numéros des jours dans le calendrier nécessite plusieurs
considérations. Le nombre de jours dans le mois dépend à la fois du mois et de
l’année. Le jour de la semaine qui débute le mois dépend aussi du mois et de
l’année. Utilisez la fonction IsLeapYear pour déterminer si l’année est bissextile.
Utilisez le tableau MonthDays dans l’unité SysUtils pour obtenir le nombre de
jours dans le mois.

11-8 Guide du concepteur de composants


Remplissage des cellules

Une fois récupérées les informations concernant les années bissextiles et le


nombre de jours par mois, vous pouvez calculer l’endroit de la grille où s’insère
chaque date. Le calcul dépend du premier jour du mois.
Comme vous devez considérer le décalage du premier jour du mois, par rapport
à l’origine de la grille, pour chaque cellule à remplir, le meilleur choix consiste à
calculer ce nombre après chaque changement de mois ou d’année, et de s’y
reporter à chaque fois. Vous pouvez stocker cette valeur dans un champ de
classe, puis mettre à jour ce champ à chaque modification de la date.
Pour remplir les cellules avec les numéros de jour appropriés, procédez de la
façon suivante :
1 Ajoutez à la classe un champ décalage du premier jour du mois, ainsi qu’une
méthode pour mettre à jour la valeur du champ :
type
TSampleCalendar = class(TCustomGrid)
private
FMonthOffset: Integer; { stocke le décalage }
ƒ
protected
procedure UpdateCalendar; virtual; { propriété pour l’accès au décalage }
end;
ƒ
procedure TSampleCalendar.UpdateCalendar;
var
AYear, AMonth, ADay: Word;
FirstDate: TDateTime; { date du premier jour du mois }
begin
if FDate <> 0 then { ne calcule le décalage que si la date est valide }
begin
DecodeDate(FDate, AYear, AMonth, ADay); { récupère les éléments de la date }
FirstDate := EncodeDate(AYear, AMonth, 1); { date du premier jour du mois }
FMonthOffset := 2 - DayOfWeek(FirstDate); { génère le décalage dans la grille }
end;
Refresh; { toujours repeindre le contrôle }
end;
2 Ajoutez les instructions au constructeur et aux méthodes SetCalendarDate et
SetDateElement qui appellent la nouvelle méthode de mise à jour à chaque
changement de date :
constructor TSampleCalendar.Create(AOwner: TComponent);
begin
inherited Create(AOwner); { existait déjà }
ƒ { d’autres initialisations ici }
UpdateCalendar; { définit le bon décalage }
end;
procedure TSampleCalendar.SetCalendarDate(Value: TDateTime);
begin
FDate := Value; { existait déjà }
UpdateCalendar; { appelait précédemment Refresh }
end;
procedure TSampleCalendar.SetDateElement(Index: Integer; Value: Integer);

Personnalisation d’une grille 11-9


Remplissage des cellules

begin
ƒ
FDate := EncodeDate(AYear, AMonth, ADay); { encode la date modifiée }
UpdateCalendar; { appelait précédemment Refresh }
end;
end;
3 Ajoutez une méthode au calendrier renvoyant le numéro du jour à partir des
coordonnées ligne/colonne d’une cellule qui lui sont transmises :
function TSampleCalendar.DayNum(ACol, ARow: Integer): Integer;
begin
Result := FMonthOffset + ACol + (ARow - 1) * 7; { calcule le jour pour cette cellule }
if (Result < 1) or (Result > MonthDays[IsLeapYear(Year), Month]) then
Result := -1; { renvoie -1 si incorrect }
end;
Pensez à ajouter la déclaration de DayNum à la déclaration de type du
composant.
4 Vous pouvez désormais calculer l’endroit où s’affichent les dates, et mettre à
jour DrawCell pour remplir les cellules :
procedure TCalendar.DrawCell(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState);
var
TheText: string;
TempDay: Integer;
begin
if ARow = 0 then { s’il s’agit de la ligne de titre ...}
TheText := ShortDayNames[ACol + 1] { utilise le nom du jour }
else begin
TheText := ’’; { cellule vide par défaut }
TempDay := DayNum(ACol, ARow); { récupère le numéro pour cette cellule }
if TempDay <> -1 then TheText := IntToStr(TempDay); { utilise ce numéro s’il est
valide }
end;
with ARect, Canvas do
TextRect(ARect, Left + (Right - Left - TextWidth(TheText)) div 2,
Top + (Bottom - Top - TextHeight(TheText)) div 2, TheText);
end;
Si maintenant vous réinstallez le composant calendrier et le placez dans une
fiche, les informations correspondant au mois en cours apparaîtront.

Sélection du jour en cours


Maintenant que les numéros des jours s’affichent dans les cellules du calendrier,
il devient intéressant de savoir positionner la surbrillance sur le jour en cours.
Comme la sélection se positionne implicitement sur la cellule située en haut et
à gauche, vous devez définir les propriétés Row et Column au moment de la
construction initiale du calendrier ainsi qu’à chaque changement de date.
Pour positionner la sélection dans la cellule du jour en cours, modifiez la
méthode UpdateCalendar pour définir Row et Column avant d’appeler Refresh :
procedure TSampleCalendar.UpdateCalendar;
begin

11-10 Guide du concepteur de composants


Navigation de mois en mois et d’année en année

if FDate <> 0 then


begin
ƒ { instructions définissant FMonthOffset }
Row := (ADay - FMonthOffset) div 7 + 1;
Col := (ADay - FMonthOffset) mod 7;
end;
Refresh; { déjà ici }
end;
Notez que vous réutilisez la variable ADay précédemment définie lors du
décodage de la date.

Navigation de mois en mois et d’année en année


Les propriétés sont particulièrement utiles pour manipuler les composants, en
particulier lors de la conception. Mais lorsque des manipulations fréquentes ou
instinctives font intervenir plusieurs propriétés, il paraît judicieux de fournir des
méthodes pour les gérer. Le passage au “mois suivant” dans notre calendrier est
un exemple de ce type. Le bouclage sur les douze mois avec l’incrémentation de
l’année est à la fois une caractéristique simple et commode pour le programmeur
qui utilise le composant.
Le seul inconvénient à l’encapsulation des manipulations les plus fréquentes sous
la forme de méthodes est le suivant : les méthodes ne sont accessibles qu’à
l’exécution. Néanmoins, de telles manipulations ne sont fastidieuses que
lorsqu’elles sont souvent répétées, ce qui est rarement le cas au moment de la
conception.
S’agissant du calendrier, ajoutez les quatre méthodes suivantes pour gérer le
passage de mois en mois et d’année en année. Chacune de ces méthodes utilise
la fonction IncMonth de façon légèrement différente pour incrémenter ou
décrémenter CalendarDate de mois en mois ou d’année en année.
procedure TCalendar.NextMonth;
begin
CalendarDate := IncMonth(CalendarDate, 1);
end;
procedure TCalendar.PrevMonth;
begin
CalendarDate := IncMonth(CalendarDate, -1);
end;
procedure TCalendar.NextYear;
begin
CalendarDate := IncMonth(CalendarDate, 12);
end;
procedure TCalendar.PrevYear;
begin
CalendarDate := DecodeDate(IncMonth(CalendarDate, -12);
end;

Personnalisation d’une grille 11-11


Navigation de jour en jour

N’oubliez pas d’ajouter les déclarations des nouvelles méthodes à la déclaration


de la classe.
Désormais, si vous créez une application qui utilise le composant calendrier,
vous pourrez facilement implémenter le passage de mois en mois ou d’année en
année.

Navigation de jour en jour


A l’intérieur d’un même mois, il existe deux moyens évidents pour naviguer
parmi les jours. Le premier consiste à utiliser les touches de direction et le
deuxième à répondre aux clics de la souris. Le composant grille standard les
gère tous les deux indistinctement en tant que clics de souris. Autrement dit, le
déplacement avec les touches de direction est pris en compte comme un clic sur
une cellule adjacente.
Le processus de navigation de jour en jour comprend :
• Déplacement de la sélection
• Fourniture d’un événement OnChange
• Exclusion des cellules vides

Déplacement de la sélection
Le comportement reçu en héritage d’une grille gère le déplacement de la
sélection en réponse aux touches de direction enfoncées ou aux clics de souris.
Pour modifier le jour sélectionné, vous devez modifier le comportement
implicite.
Pour gérer les déplacements à l’intérieur du calendrier, vous devez redéfinir la
méthode Click de la grille.
Lorsque vous redéfinissez une méthode telle que Click, en dépendance étroite
avec les interactions de l’utilisateur, vous devez pratiquement toujours inclure un
appel à la méthode reçue en héritage pour ne pas perdre le comportement
standard.
Le code suivant est une méthode Click surchargée pour la grille calendrier.
N’oubliez pas d’ajouter la déclaration de Click à TSampleCalendar, en incluant
après la directive override.
procedure TSampleCalendar.Click;
var
TempDay: Integer;
begin
inherited Click; { n’oubliez pas d’appeler la méthode héritée ! }
TempDay := DayNum(Col, Row); { récupère le numéro du jour de la cellule cliquée }
if TempDay <> -1 then Day := TempDay; { change le jour s’il est valide }
end;

11-12 Guide du concepteur de composants


Navigation de jour en jour

Fourniture d’un événement OnChange


Les utilisateurs de votre calendrier ont maintenant la possibilité de changer la
date. Il paraît donc judicieux de répondre à ces changements.
Ajoutez un événement OnChange à TSampleCalendar.
1 Déclarez l’événement ainsi qu’un champ pour le stocker et une méthode
virtuelle pour l’appeler :
type
TSampleCalendar = class(TCustomGrid)
private
FOnChange: TNotifyEvent;
protected
procedure Change; dynamic;
ƒ
published
property OnChange: TNotifyEvent read FOnChange write FOnChange;
ƒ
2 Ecrivez la méthode Change :
procedure TSampleCalendar.Change;
begin
if Assigned(FOnChange) then FOnChange(Self);
end;
3 Ajoutez les instructions appelant Change à la fin des méthodes SetCalendarDate
et SetDateElement :
procedure TSampleCalendar.SetCalendarDate(Value: TDateTime);
begin
FDate := Value;
UpdateCalendar;
Change; { seule nouvelle instruction }
end;
procedure TSampleCalendar.SetDateElement(Index: Integer; Value: Integer);
begin
ƒ { instructions définissant les valeurs des éléments }
FDate := EncodeDate(AYear, AMonth, ADay);
UpdateCalendar;
Change; { ceci est nouveau }
end;
end;
Les applications qui utilisent le composant calendrier peuvent maintenant
répondre aux changements en associant des gestionnaires à l’événement
OnChange.

Exclusion des cellules vides


Tel qu’il est actuellement, le calendrier déplace la sélection vers une cellule vide
sans changer la date. Il devient intéressant d’empêcher la sélection des cellules
vides.

Personnalisation d’une grille 11-13


Navigation de jour en jour

Pour déterminer si une cellule est sélectionnable, vous devez redéfinir la


méthode SelectCell de la grille.
SelectCell est une fonction qui accepte deux paramètres ligne et colonne et qui
renvoie une valeur booléenne indiquant si la cellule spécifiée est sélectionnable.
Vous pouvez surcharger SelectCell pour qu’elle renvoie False si la cellule ne
contient pas une date valide :
function TSampleCalendar.SelectCell(ACol, ARow: Longint): Boolean;
begin
if DayNum(ACol, ARow) = -1 then Result := False { -1 indique une date incorrecte }
else Result := inherited SelectCell(ACol, ARow); { sinon, utilise la valeur héritée }
end;
Désormais, si l’utilisateur clique sur une cellule vide ou tente de s’y déplacer
à l’aide des touches de direction, le calendrier ne modifie pas la sélection
en cours.

11-14 Guide du concepteur de composants


Chapitre

Contrôles orientés données


Chapitre12
12
Lorsque vous souhaitez vous connecter avec des bases de données, travaillez
avec les contrôles orientés données. C’est grâce à ces contrôles que l’application
établit un lien avec une partie spécifique d’une base de données. Parmi les
contrôles sensibles aux données Delphi, citons les libellés, les boîtes de saisie,
les boîtes liste, les boîtes à options, les contrôles de référence et les grilles.
Vous avez également la possibilité de construire vos propres contrôles orientés
données. Pour plus d’informations sur l’utilisation des contrôles orientés
données, voir Chapitre 20, “Utilisation de contrôles de données”, du Guide
du développeur.
Il existe différents niveaux d’orientation données. Le plus élémentaire fonctionne
en lecture seulement, permet de scruter des données et reflète l’état d’une base de
données. L’orientation données modifiables, permettant de modifier les données, est
plus complexe car l’utilisateur peut changer les valeurs stockées dans la base en
manipulant le contrôle. Notez également que le degré d’implication de la base de
données peut varier du cas le plus simple, un lien établi avec un seul champ,
aux cas plus complexes faisant intervenir des contrôles à enregistrements
multiples.
Ce chapitre illustre d’abord le cas le plus simple, en créant un contrôle en lecture
simple qui est lié à un seul champ d’un ensemble de données. Le contrôle
spécifique utilisé sera le calendrier TSampleCalendar créé dans le Chapitre 11,
“Personnalisation d’une grille”. Vous pouvez aussi utiliser le contrôle calendrier
standard de la page Exemples de la palette des composants, TCalendar (VCL
seulement).
Ce chapitre continue ensuite avec une explication sur la manière de faire
d’un nouveau contrôle pour scruter les données un contrôle de modification
des données.

Contrôles orientés données 12-1


Création d’un contrôle pour scruter les données

Création d’un contrôle pour scruter les données


La création d’un contrôle calendrier orienté données, que ce soit un contrôle
en lecture seulement ou un contrôle grâce auquel l’utilisateur peut changer les
données sous-jacentes, fait intervenir les étapes suivantes :
• Création et recensement du composant.
• Ajout du lien aux données.
• Réponse aux changements de données.

Création et recensement du composant


La création d’un composant se fait toujours de la même façon. Vous créez une
unité et vous recensez le composant avant de l’installer dans la palette des
composants. Ce processus est décrit dans “Création d’un nouveau composant” à
la page 1-9.
Pour notre exemple, suivez la procédure générale de création d’un composant en
tenant compte des spécificités suivantes :
• Appelez l’unité du composant DBCal.
• Dérivez une nouvelle classe composant appelée TDBCalendar, dérivée du
composant TSampleCalendar. Le Chapitre 11, “Personnalisation d’une grille”,
montre comment créer le composant TSampleCalendar.
• Recensez TDBCalendar dans la page Exemples (ou une autre page dans les
applications CLX) de la palette des composants.
L’unité résultante dérivée de TCustomGrid dans une application VCL doit
ressembler à ceci :
unit CalSamp;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Grids;
type
TSampleCalendar = class(TCustomGrid)
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents(’Samples’, [TSampleCalendar]);
end;
end.
Remarque Si la dérivation est effectuée à partir de la version CLX de TCustomGrid, la seule
différence réside dans la clause uses, qui affiche à la place les unités CLX.

12-2 Guide du concepteur de composants


Création d’un contrôle pour scruter les données

Si vous installez le composant calendrier maintenant, vous verrez qu’il apparaît


sur la page Exemples. Les seules propriétés disponibles sont les propriétés de
contrôle les plus basiques. L’étape suivante consiste à rendre disponible certaines
des propriétés plus spécialisées aux utilisateurs du calendrier.
Remarque Bien que vous puissiez installer le composant calendrier exemple que vous venez
de compiler, n’essayez pas de le placer tout de suite sur une fiche. Le composant
TCustomGrid contient une méthode DrawCell abstraite qui doit être redéclarée
avant que les objets d’instance puissent être créés. La redéfinition de la méthode
DrawCell est décrite dans “Remplissage des cellules” à la page 11-5.
Remarque Les noms et les emplacements de certaines unités sont différents pour les
applications CLX. Par exemple, l’unité Controls est QControls et il n’y a pas
d’unité Windows ou Messages dans les applications CLX.
L’unité que vous obtenez doit ressembler à ceci :
unit DBCal;
interface
uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Grids, Calendar;
type
TDBCalendar = class(TSampleCalendar)
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents(’Samples’, [TDBCalendar]);
end;
end.
Vous pouvez à présent commencer à transformer le nouveau calendrier en
scruteur de données.

Fonctionnement du contrôle en lecture seulement


Puisque votre calendrier scruteur de données ne fonctionnera qu’en lecture (par
rapport aux données), il est opportun de rendre le contrôle lui-même accessible
en lecture seulement. Ainsi, l’utilisateur ne s’attendra pas à voir répercuter dans
la base de données une modification qu’il aurait apportée au contrôle.
Rendre le calendrier accessible en lecture seulement fait intervenir deux étapes :
• Ajout de la propriété ReadOnly.
• Autorisation des mises à jour nécessaires.
Remarque Si vous démarrez avec le composant TCalendar de la page Exemples de Delphi
au lieu deTSampleCalendar, le contrôle a déjà une propriété ReadOnly. Vous
pouvez donc ignorer ces étapes.

Contrôles orientés données 12-3


Création d’un contrôle pour scruter les données

Ajout de la propriété ReadOnly


En ajoutant une propriété ReadOnly, vous fournissez le moyen de rendre le
contrôle accessible en lecture seulement au moment de la conception. Si la valeur
de cette propriété est True toutes les cellules du contrôle perdront la capacité à
être sélectionnées.
1 Ajoutez la déclaration de la propriété ainsi qu’une donnée membre private
pour contenir la valeur :
type
TDBCalendar = class(TSampleCalendar)
private
FReadOnly: Boolean; { champ de stockage interne }
public
constructor Create(AOwner: TComponent); override; { doit surcharger pour définir
les valeurs par défaut }
published
property ReadOnly: Boolean read FReadOnly write FReadOnly default True;
end;
ƒ
constructor TDBCalendar.Create(AOwner: TComponent);
begin
inherited Create(AOwner); { appelez toujours le constructeur hérité ! }
FReadOnly := True; { définit la valeur par défaut }
end;
2 Redéfinissez la méthode SelectCell pour inhiber la sélection si le contrôle est
accessible en lecture seulement. L’utilisation de SelectCell est expliquée dans la
section “Exclusion des cellules vides” à la page 11-13.
function TDBCalendar.SelectCell(ACol, ARow: Longint): Boolean;
begin
if FReadOnly then Result := False { sélection impossible si accès en lecture seule }
else Result := inherited SelectCell(ACol, ARow); { sinon, utilise la méthode héritée }
end;
N’oubliez pas d’ajouter la déclaration de SelectCell à la déclaration de la classe de
TDBCalendar, et ajoutez la directive override.
Si vous ajoutez maintenant le calendrier à une fiche, vous vous rendez compte
que le composant ignore les clics de souris et les frappes de touches. Et il ne met
plus à jour la position de la sélection lorsque vous changez la date.

Autorisation des mises à jour nécessaires


Le calendrier accessible en lecture seulement utilise la méthode SelectCell pour
effectuer toutes sortes de modifications, y compris l’affectation de valeurs aux
propriétés Row et Col. La méthode UpdateCalendar définit Row et Col à chaque
changement de date mais, dans la mesure où SelectCell n’autorise aucune
modification, la position de la sélection reste inchangée, même si la date est
modifiée.

12-4 Guide du concepteur de composants


Création d’un contrôle pour scruter les données

Pour outrepasser cette interdiction de toute modification, vous devez ajouter au


calendrier un indicateur booléen interne et n’autoriser les modifications que si la
valeur de cet indicateur est True :
type
TDBCalendar = class(TSampleCalendar)
private
FUpdating: Boolean; { indicateur privé à usage interne }
protected
function SelectCell(ACol, ARow: Longint): Boolean; override;
public
procedure UpdateCalendar; override; { notez la directive override }
end;
ƒ
function TDBCalendar.SelectCell(ACol, ARow: Longint): Boolean;
begin
if (not FUpdating) and FReadOnly then Result := False { sélection possible si mise
à jour }
else Result := inherited SelectCell(ACol, ARow); { sinon, utilise la méthode héritée }
end;
procedure TDBCalendar.UpdateCalendar;
begin
FUpdating := True; { définit l’indicateur pour permettre les mises à jour }
try
inherited UpdateCalendar; { mise à jour habituelle }
finally
FUpdating := False; { réinitialise toujours l’indicateur }
end;
end;
Le calendrier n’autorise toujours pas les modifications directes de l’utilisateur
mais les modifications de la date effectuées via les propriétés de date sont prises
en compte. Vous disposez maintenant d’un contrôle en lecture seulement tout à
fait opérationnel. Vous voilà prêt à ajouter les fonctionnalités servant à scruter
les données.

Ajout du lien aux données


La connexion entre un contrôle et une base de données est gérée par une classe
appelée lien de données. La classe lien de données, qui connecte un contrôle à un
seul champ d’une base de données, est TFieldDataLink. Il existe également des
liens de données vers des tables entières.
Un contrôle orienté données est propriétaire de sa classe lien de données.
Autrement dit, le contrôle est responsable de la construction et de la destruction
du lien de données. Pour des détails sur la gestion des classes ayant un
propriétaire, voir Chapitre 10, “Création d’un contrôle graphique”.
Pour établir un lien de données en tant que classe ayant un propriétaire,
respectez ces étapes :
1 Déclaration du champ de classe.
2 Déclaration des propriétés d’accès.

Contrôles orientés données 12-5


Création d’un contrôle pour scruter les données

3 Initialisation du lien de données.

Déclaration du champ de classe


Un composant utilise un champ pour chacune des classes dont il est le
propriétaire, comme cela est expliqué dans “Déclaration des champs de classe” à
la page 10-6. Dans ce cas, le calendrier a besoin d’un champ de type
TFieldDataLink pour son lien de données.
Déclarez un champ pour le lien de données du calendrier :
type
TDBCalendar = class(TSampleCalendar)
private
FDataLink: TFieldDataLink;
ƒ
end;
Avant de compiler l’application, vous devez ajouter DB et DBCtrls à la clause
uses de l’unité.

Déclaration des propriétés d’accès


Tout contrôle orienté données dispose d’une propriété DataSource indiquant la
classe source de données qui fournit les données au contrôle. En outre, un
contrôle qui accède à un champ unique a besoin d’une propriété DataField pour
spécifier ce champ dans la source de données.
Contrairement aux propriétés d’accès des classes ayant un propriétaire que nous
avons vues avec l’exemple du Chapitre 10, “Création d’un contrôle graphique”,
ces propriétés d’accès ne donnent pas accès aux classes ayant un propriétaire
elles-mêmes, mais plutôt aux propriétés correspondantes de la classe ayant un
propriétaire. Autrement dit, vous allez créer des propriétés qui autorisent le
contrôle et son lien de données à partager la même source et le même champ.
Déclarez les propriétés DataSource et DataField ainsi que leurs méthodes
d’implémentation, puis écrivez ces méthodes en tant que simples “boîtes à
lettres” vers les propriétés correspondantes de la classe lien de données :

Exemple de déclaration des propriétés d’accès


Déclarez les propriétés DataSource et DataField ainsi que leurs méthodes
d’implémentation, puis écrivez ces méthodes en tant que simples “boîtes à
lettres” vers les propriétés correspondantes de la classe lien de données :
type
TDBCalendar = class(TSampleCalendar)
private { les méthodes d’implémentation sont private }
ƒ
function GetDataField: string; { renvoie le nom du champ de données }
function GetDataSource: TDataSource; { renvoie une référence sur la source de données }
procedure SetDataField(const Value: string); { affecte le nom du champ de données }
procedure SetDataSource(Value: TDataSource); { affecte une nouvelle source de données }
published {rend les propriétés accessibles lors de la conception }
property DataField: string read GetDataField write SetDataField;

12-6 Guide du concepteur de composants


Création d’un contrôle pour scruter les données

property DataSource: TDataSource read GetDataSource write SetDataSource;


end;
ƒ
function TDBCalendar.GetDataField: string;
begin
Result := FDataLink.FieldName;
end;
function TDBCalendar.GetDataSource: TDataSource;
begin
Result := FDataLink.DataSource;
end;
procedure TDBCalendar.SetDataField(const Value: string);
begin
FDataLink.FieldName := Value;
end;
procedure TDBCalendar.SetDataSource(Value: TDataSource);
begin
FDataLink.DataSource := Value;
end;
Maintenant que sont établis les liens entre le calendrier et son lien de données,
il reste une étape importante à franchir. Vous devez construire la classe lien de
données au moment de la construction du contrôle calendrier et le détruire avant
de détruire ce même contrôle.

Initialisation du lien de données


Un contrôle orienté données doit avoir accès à son lien de données pendant
toute sa durée de vie, il doit donc construire l’objet lien de données dans son
propre constructeur et le détruire avant de se détruire lui-même.
Surchargez les méthodes Create et Destroy du calendrier pour construire et
détruire l’objet lien de données :
type
TDBCalendar = class(TSampleCalendar)
public { les constructeurs et destructeurs sont toujours publics }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
ƒ
end;
ƒ
constructor TDBCalendar.Create(AOwner: TComponent);
begin
inherited Create(AOwner); { appelle toujours d’abord le constructeur hérité }
FDataLink := TFieldDataLink.Create; { construit l’objet lien de données }
FDataLink.Control := self; { informe le lien de données sur le calendrier }
FReadOnly := True; { existe déjà }
end;
destructor TDBCalendar.Destroy;
begin
FDataLink.Free; { détruit toujours d’abord les objets ayant un propriétaire... }
inherited Destroy; { ...puis appelle le destructeur hérité }

Contrôles orientés données 12-7


Création d’un contrôle pour scruter les données

end;
Vous avez maintenant un lien de données complet. Il vous reste à indiquer au
contrôle les données qu’il doit lire dans le champ lié. La section suivante vous
explique comment procéder.

Réponse aux changements de données


Lorsqu’un contrôle a un lien de données et les propriétés précisant la source et
le champ des données, il doit répondre aux changements des données de ce
champ provoqués soit par un déplacement vers un autre enregistrement, soit par
une modification du champ.
Les classes lien de données ont toutes un événement intitulé OnDataChange.
Lorsque la source de données indique un changement dans ses données, l’objet
lien de données appelle le gestionnaire attaché à son événement OnDataChange.
Pour mettre à jour un contrôle en réponse à une modification des données, vous
devez attacher un gestionnaire à l’événement OnDataChange du lien de données.
Dans notre exemple, vous allez ajouter une méthode au calendrier, puis la
désigner comme gestionnaire de l’événement OnDataChange du lien de données.
Déclarez et implémentez la méthode DataChange, puis associez-la à l’événement
OnDataChange dans le constructeur. Dans le destructeur, détachez le gestionnaire
OnDataChange avant de détruire l’objet.
type
TDBCalendar = class(TSampleCalendar)
private { c’est un détail interne, donc le déclarer privé }
procedure DataChange(Sender: TObject); { doit avoir des paramètres corrects
pour l’événement }
end;
ƒ
constructor TDBCalendar.Create(AOwner: TComponent);
begin
inherited Create(AOwner); { appelle toujours d’abord le constructeur hérité }
FReadOnly := True; { existe déjà }
FDataLink := TFieldDataLink.Create; { construit l’objet lien de données }
FDataLink.OnDataChange := DataChange; { attache le gestionnaire à l’événement }
end;
destructor TDBCalendar.Destroy;
begin
FDataLink.OnDataChange := nil; { détache le gestionnaire avant de détruire l’objet }
FDataLink.Free; { détruit toujours d’abord les objets ayant un propriétaire... }
inherited Destroy; { ...puis appelle le destructeur hérité }
end;
procedure TDBCalendar.DataChange(Sender: TObject);
begin
if FDataLink.Field = nil then { s’il n’y a pas de champ attribué... }
CalendarDate := 0 { ...définit une date incorrecte }

12-8 Guide du concepteur de composants


Création d’un contrôle de modification de données

else CalendarDate := FDataLink.Field.AsDateTime; { sinon, définit le calendrier


par la date }
end;
Vous avez maintenant un contrôle de parcours des données.

Création d’un contrôle de modification de données


Lorsque vous créez un contrôle permettant de modifier les données, vous créez
et recensez le composant puis lui ajoutez un lien de données, comme pour les
contrôles permettant de scruter les données. Vous devez également répondre aux
changements de données dans le champ sous-jacent, mais vous devez prendre en
considération quelques points supplémentaires.
Par exemple, vous souhaitez sans doute que votre contrôle réponde aux
événements clavier et souris. Votre contrôle doit répondre lorsque l’utilisateur
change le contenu du contrôle. Lorsque l’utilisateur quitte le contrôle, les
changements effectués dans le contrôle doivent être répercutés dans l’ensemble
de données.
Le contrôle permettant la modification des données décrit ici est le même que le
contrôle calendrier décrit dans la première partie de ce chapitre. Le contrôle est
modifié de telle sorte qu’il permette l’édition en plus de la consultation des
données du champ lié.
Voici les étapes à suivre pour modifier un contrôle existant et en faire un
contrôle permettant la modification des données :
• Modification de la valeur par défaut de FReadOnly.
• Gestion des messages liés à la souris ou au clavier.
• Mise à jour de la classe lien de données sur un champ.
• Modification de la méthode Change.
• Mise à jour de l’ensemble de données.

Modification de la valeur par défaut de FReadOnly


Comme il s’agit d’un contrôle permettant la modification des données, la
propriété ReadOnly doit être False par défaut. Pour que la propriété ReadOnly soit
à False, modifiez la valeur de FReadOnly dans le constructeur :
constructor TDBCalendar.Create(AOwner: TComponent);
begin
ƒ
FReadOnly := False; { définit la valeur par défaut }
ƒ
end;

Contrôles orientés données 12-9


Création d’un contrôle de modification de données

Gestion des messages liés à la souris ou au clavier


Lorsque l’utilisateur commence à se servir du contrôle, celui-ci reçoit de
Windows les messages indiquant la manipulation de la souris
(WM_LBUTTONDOWN, WM_MBUTTONDOWN, ou WM_RBUTTONDOWN),
ou le message indiquant la manipulation du clavier (WM_KEYDOWN). Pour
permettre à un contrôle de répondre à ces messages, vous devez écrire les
gestionnaires des réponses à ces messages.
• Réponse aux messages indiquant la manipulation de la souris.
• Réponse aux messages indiquant la manipulation du clavier.
Remarque Si vous écrivez des applications CLX, la notification provient du système
d’exploitation sous la forme d’événements système. Pour plus d’informations sur
l’écriture de composants qui répondent aux événements système et widget, voir
“Réponse aux notifications du système à l’aide de CLX” à la page 7-10.

Réponse aux messages indiquant la manipulation de la souris


Une méthode MouseDown est une méthode protégée de l’événement
OnMouseDown d’un contrôle. Le contrôle lui-même appelle MouseDown en
réponse au message Windows indiquant la manipulation de la souris. Lorsque
vous redéfinissez la méthode MouseDown héritée, vous pouvez inclure du code
apportant d’autres réponses en plus de l’appel à l’événement OnMouseDown.
Pour redéfinir MouseDown, ajoutez la méthode MouseDown à la classe
TDBCalendar :
type
TDBCalendar = class(TSampleCalendar);
ƒ
protected
procedure MouseDown(Button: TButton, Shift: TShiftState, X: Integer, Y: Integer);
override;
ƒ
end;
procedure TDBCalendar.MouseDown(Button: TButton; Shift: TShiftState; X, Y: Integer);
var
MyMouseDown: TMouseEvent;
begin
if not ReadOnly and FDataLink.Edit then
inherited MouseDown(Button, Shift, X, Y)
else
begin
MyMouseDown := OnMouseDown;
if Assigned(MyMouseDown then MyMouseDown(Self, Button, Shift, X, Y);
end;
end;
Lorsque MouseDown répond à un message indiquant la manipulation de la
souris, la méthode MouseDown héritée est appelée uniquement si la propriété
ReadOnly du contrôle est False et si l’objet lien de données est en mode édition,
c’est-à-dire si le champ peut être modifié. Si le champ ne peut être modifié, le

12-10 Guide du concepteur de composants


Création d’un contrôle de modification de données

code mis par le programmeur dans le gestionnaire de l’événement


OnMouseDown, s’il en existe un, est exécuté.

Réponse aux messages indiquant la manipulation du clavier


Une méthode KeyDown est une méthode protégée de l’événement OnKeyDown
d’un contrôle. Le contrôle lui-même appelle KeyDown en réponse au message
Windows indiquant la manipulation du clavier. Lorsque vous redéfinissez la
méthode KeyDown héritée, vous pouvez inclure le code qui apporte d’autres
réponses en plus de l’appel à l’événement OnKeyDown.
Pour redéfinir KeyDown, suivez ces instructions :
1 Ajoutez une méthode KeyDown à la classe TDBCalendar :
type
TDBCalendar = class(TSampleCalendar);
ƒ
protected
procedure KeyDown(var Key: Word; Shift: TShiftState; X: Integer; Y: Integer);
override;
ƒ
end;
2 Implémentez la méthode KeyDown :
procedure KeyDown(var Key: Word; Shift: TShiftState);
var
MyKeyDown: TKeyEvent;
begin
if not ReadOnly and (Key in [VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, VK_END,
VK_HOME, VK_PRIOR, VK_NEXT]) and FDataLink.Edit then
inherited KeyDown(Key, Shift)
else
begin
MyKeyDown := OnKeyDown;
if Assigned(MyKeyDown) then MyKeyDown(Self, Key, Shift);
end;
end;
Lorsque KeyDown répond à un message indiquant la manipulation du clavier, la
méthode KeyDown héritée est appelée uniquement si la propriété ReadOnly du
contrôle vaut False, si la touche appuyée est une des touches de déplacement du
curseur et si l’objet lien de données est en mode édition, c’est-à-dire si le champ
peut être modifié. Si le champ ne peut être modifié ou si une autre touche a été
pressée, le code mis par le programmeur dans le gestionnaire de l’événement
OnKeyDown, s’il en existe un, est exécuté.

Contrôles orientés données 12-11


Création d’un contrôle de modification de données

Mise à jour de la classe lien de données sur un champ


Il existe deux types de modification des données :
• Le changement de la valeur d’un champ doit se répercuter dans le contrôle
orienté données
• Le changement dans le contrôle orienté données doit se répercuter dans la
valeur du champ
Le composant TDBCalendar a déjà une méthode DataChange qui gère les
modifications de la valeur du champ dans l’ensemble de données en assignant
cette valeur à la propriété CalendarDate. La méthode DataChange est le
gestionnaire de l’événement OnDataChange. Ainsi le composant calendrier est
capable de gérer le premier type de modification des données.
De manière semblable, la classe lien de données sur un champ a aussi un
événement OnUpdateData qui se produit lorsque l’utilisateur modifie le contenu
du contrôle orienté données. Le contrôle calendrier a une méthode UpdateData
qui devient le gestionnaire de l’événement OnUpdateData. UpdateData assigne au
champ lien de données la valeur modifiée dans le contrôle orienté données.
1 Pour répercuter dans la valeur du champ une modification effectuée sur la
valeur du calendrier, ajoutez une méthode UpdateData à la section private du
composant calendrier :
type
TDBCalendar = class(TSampleCalendar);
private
procedure UpdateData(Sender: TObject);
ƒ
end;
2 Implémentez la méthode UpdateData :
procedure UpdateData(Sender: TObject);
begin
FDataLink.Field.AsDateTime := CalendarDate; { définit le champ lien par la date
du calendrier }
end;
3 Dans le constructeur de TDBCalendar, affectez la méthode UpdateData à
l’événement OnUpdateData :
constructor TDBCalendar.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FReadOnly := True;
FDataLink := TFieldDataLink.Create;
FDataLink.OnDataChange := DataChange;
FDataLink.OnUpdateData := UpdateData;
end;

12-12 Guide du concepteur de composants


Création d’un contrôle de modification de données

Modification de la méthode Change


La méthode Change du TDBCalendar est appelée chaque fois qu’est définie une
nouvelle valeur de date. Change appelle le gestionnaire de l’événement OnChange,
s’il existe. L’utilisateur du composant peut écrire du code dans le gestionnaire de
l’événement OnChange afin de répondre aux modifications de la date.
Lorsque la date du calendrier change, l’ensemble de données sous-jacent doit
être averti de ce changement. Vous pouvez le faire en redéfinissant la méthode
Change et en ajoutant une ligne de code de plus. Voici les étapes à suivre :
1 Ajoutez une nouvelle méthode Change au composant TDBCalendar :
type
TDBCalendar = class(TSampleCalendar);
private
procedure Change; override;
ƒ
end;
2 Ecrivez la méthode Change, appelant la méthode Modified qui informe
l’ensemble de données que celles-ci ont changé, puis appelle la méthode
Change héritée :
procedure TDBCalendar.Change;
begin
FDataLink.Modified; { appelle la méthode Modified }
inherited Change; { appelle la méthode Change héritée }
end;

Mise à jour de l’ensemble de données


A ce point, une modification dans le contrôle orienté données a changé les
valeurs dans la classe du lien de données sur un champ. La dernière étape de la
création d’un contrôle permettant la modification des données consiste à mettre
à jour l’ensemble de données avec la nouvelle valeur. Cela doit se produire après
que la personne ayant changé la valeur du contrôle orienté données quitte le
contrôle en cliquant à l’extérieur de celui-ci ou en appuyant sur la touche
Tabulation. Ce processus n’est pas le même dans les applications VCL que dans
les applications CLX.
Remarque Les applications VCL définissent des ID de message pour les opérations sur les
contrôles. Par exemple, le message CM_EXIT est envoyé au contrôle lorsque
l’utilisateur quitte celui-ci. Vous pouvez écrire des gestionnaires de messages qui
répondent au message. Et ensuite, lorsque l’utilisateur quitte le contrôle, la
méthode CMExit, gestionnaire du message CM_EXIT, répondra en mettant à jour
l’enregistrement dans l’ensemble de données avec les valeurs modifiées dans
la classe lien de données sur un champ. Pour plus d’informations sur les
gestionnaires de messages, voir Chapitre 7, “Gestion des messages
et des notifications système”.

Contrôles orientés données 12-13


Création d’un contrôle de modification de données

Pour mettre à jour l’ensemble de données depuis un gestionnaire de message,


suivez ces instructions :
1 Ajoutez le gestionnaire de message au composant TDBCalendar :
type
TDBCalendar = class(TSampleCalendar);
private
procedure CMExit(var Message: TWMNoParams); message CM_EXIT;
ƒ
end;
2 Implémentez la méthode CMExit afin qu’elle ressemble à ceci :
procedure TDBCalendar.CMExit(var Message: TWMNoParams);
begin
try
FDataLink.UpdateRecord; { indique au lien de données de mettre à jour la base }
except
on Exception do SetFocus; { si échec, ne pas perdre la focalisation }
end;
inherited;
end;
Remarque Dans les applications CLX, TWidgetControl possède une méthode DoExit protégée
qui est appelée quand la focalisation en saisie quitte le contrôle. Cette méthode
appelle le gestionnaire de l’événement OnExit. Vous pouvez redéfinir cette
méthode pour actualiser l’enregistrement dans l’ensemble de données avant la
génération du gestionnaire d’événement OnExit.
Pour mettre à jour l’ensemble de données quand l’utilisateur quitte le contrôle,
suivez les étapes ci-après :
1 Ajoutez un override à la méthode DoExit du composant TDBCalendar :
type
TDBCalendar = class(TSampleCalendar);
private
procedure DoExit; override;
ƒ
end;
2 Implémentez la méthode DoExit afin qu’elle ressemble à ceci :
procedure TDBCalendar.CMExit(var Message: TWMNoParams);
begin
try
FDataLink.UpdateRecord; { indique au lien de données de mettre à jour la base }
except
on Exception do SetFocus; { si échec, ne pas perdre la focalisation }
end;
inherited; { laisse la méthode héritée générer un événement OnExit }
end;

12-14 Guide du concepteur de composants


Chapitre

Transformation d’une boîte


Chapitre13
13
de dialogue en composant
Il est pratique de transformer une boîte de dialogue fréquemment sollicitée en un
composant que vous pourrez ajouter dans la palette des composants. Ainsi, vos
composants boîte de dialogue fonctionneront exactement comme ceux des boîtes
de dialogue standard. L’objectif ici est de créer un composant simple qu’un
utilisateur peut ajouter à un projet et dont il peut définir les propriétés lors de la
conception.
La transformation d’une boîte de dialogue en composant nécessite les étapes
suivantes :
1 Définition de l’interface du composant
2 Création et recensement du composant
3 Création de l’interface du composant
4 Test du composant
A l’exécution, le composant “enveloppe” de Delphi, associé à la boîte de
dialogue crée et exécute celle-ci en lui transmettant les données spécifiées par
l’utilisateur. Le composant boîte de dialogue est donc à la fois réutilisable et
personnalisable.
Dans ce chapitre, vous allez voir comment créer un composant enveloppe autour
de la fiche générique A propos de... disponible dans le référentiel d’objets de
Delphi.
Remarque Copiez les fichiers ABOUT.PAS et ABOUT.DFM dans votre répertoire de travail.
Il n’y a pas grand chose à dire concernant la conception de la boîte de dialogue
enveloppée par un composant. Dans un tel contexte, n’importe quelle fiche peut
fonctionner comme une boîte de dialogue.

Transformation d’une boîte de dialogue en composant 13-1


Définition de l’interface du composant

Définition de l’interface du composant


Avant de créer le composant pour votre boîte de dialogue, vous devez décider
de la façon dont il sera utilisé par les développeurs. Vous devez créer une
interface entre votre boîte de dialogue et les applications qui l’utilisent.
Par exemple, considérons les propriétés des composants associés aux boîtes de
dialogue standard. Elles autorisent le développeur à définir l’état initial de la
boîte de dialogue, tel que le titre ou le paramétrage initial des contrôles, et
renvoient toutes les informations nécessaires lorsque la boîte de dialogue se
ferme. Les seules interactions directes ne se produisent qu’avec les propriétés du
composant enveloppe et pas avec les contrôles individuels de la boîte de
dialogue.
L’interface doit donc contenir suffisamment d’informations pour que la fiche
boîte de dialogue s’affiche selon les indications du développeur et qu’elle renvoie
les informations nécessaires à l’application. Les propriétés du composant
enveloppe peuvent être vues comme les données permanentes d’une boîte de
dialogue transitoire.
Dans le cas de votre boîte A propos de, aucune information n’est renvoyée.
Les propriétés de l’enveloppe contiennent donc uniquement les informations
nécessaires pour afficher correctement la boîte A propos de. Puisqu’il y a quatre
champs distincts dans cette boîte sur lesquels l’application peut agir, il vous faut
fournir quatre propriétés de type chaîne pour les paramétrer.

Création et recensement du composant


La création d’un composant débute toujours de la même façon. Vous créez une
unité et vous recensez le composant avant de l’installer dans la palette des
composants. Ce processus est décrit dans “Création d’un nouveau composant” à
la page 1-9.
Pour notre exemple, suivez la procédure générale de création d’un composant en
tenant compte des spécificités suivantes :
• Nommez l’unité du composant AboutDlg.
• Dérivez un nouveau type de composant appelé TAboutBoxDlg, descendant
de TComponent.
• Recensez TAboutBoxDlg sur la page Exemples de la palette des composants.
L’unité que vous obtenez doit ressembler à ceci :
unit AboutDlg;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms;
type
TAboutBoxDlg = class(TComponent)
end;

13-2 Guide du concepteur de composants


Création de l’interface du composant

procedure Register;
implementation
procedure Register;
begin
RegisterComponents(’Samples’, [TAboutBoxDlg]);
end;
end.
Remarque Les noms et les emplacements de certaines unités sont différents pour les
applications CLX. Par exemple, l’unité Controls est QControls dans les
applications CLX.
Pour l’instant, le nouveau composant possède uniquement les fonctionnalités
intégrées à TComponent. C’est le composant non visuel le plus simple. Dans la
section suivante, vous allez créer l’interface entre le composant et la boîte de
dialogue.

Création de l’interface du composant


Voici les étapes nécessaires à la création de l’interface du composant :
1 Inclusion de l’unité de la fiche.
2 Ajout des propriétés de l’interface.
3 Ajout de la méthode Execute.

Inclusion de l’unité de la fiche


Pour que votre composant enveloppe puisse initialiser et afficher la boîte de
dialogue enveloppée, vous devez ajouter les fichiers de l’unité de la fiche dans
la clause uses de l’unité du composant enveloppe.
Ajoutez About à la clause uses de l’unité AboutDlg.
La clause uses ressemble maintenant à ceci :
uses
Windows, SysUtils, Messages, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms,
About;
L’unité fiche déclare toujours une instance de la classe de la fiche. Dans le cas de
la boîte A propos de, la classe de la fiche est TAboutBox, et l’unité About doit
inclure la déclaration suivante :
var
AboutBox: TAboutBox;
Ainsi en ajoutant About à la clause uses, vous rendez disponible AboutBox au
composant enveloppe.

Transformation d’une boîte de dialogue en composant 13-3


Création de l’interface du composant

Ajout des propriétés de l’interface


Avant de poursuivre, vous devez déterminer les propriétés de votre composant
enveloppe nécessaires pour permettre aux développeurs d’utiliser votre boîte de
dialogue en tant que composant dans leurs applications. Puis, ajoutez les
déclarations de ces propriétés à la déclaration de classe du composant.
Les propriétés d’un composant enveloppe sont sensiblement plus simples à écrire
que celles d’un composant standard. Souvenez-vous que vous ne faites que créer
des données permanentes que l’enveloppe et la boîte de dialogue peuvent
échanger. En définissant ces données sous la forme de propriétés, vous donnez
aux développeurs la possibilité de définir des données au moment de la
conception qui, lors de l’exécution, seront transmises par l’enveloppe à la boîte
de dialogue.
La déclaration d’une propriété d’interface nécessite deux ajouts à la déclaration
de classe du composant :
• Un champ de classe privé qui est une variable utilisée par l’enveloppe pour
stocker la valeur de la propriété.
• La déclaration published de la propriété elle-même qui indique son nom et le
champ à utiliser pour le stockage.
De telles propriétés d’interface n’ont pas besoin de méthodes d’accès. Elles
accèdent directement aux données stockées. Par convention, le champ qui stocke
la valeur de la propriété porte le même nom que la propriété, mais précédé de la
lettre F. Le champ et la propriété doivent avoir le même type.
Par exemple, la déclaration d’une propriété d’interface de type entier appelée
Year, est la suivante :
type
TMyWrapper = class(TComponent)
private
FYear: Integer; { donnée membre pour les données de la propriété Year }
published
property Year: Integer read FYear write FYear; { la propriété et son stockage }
end;
S’agissant de votre boîte A propos de, vous devez disposer de quatre propriétés
de type string pour le nom du produit, les informations de version, les
informations de copyright et les commentaires éventuels.
type
TAboutBoxDlg = class(TComponent)
private
FProductName, FVersion, FCopyright, FComments: string; { déclare les données membres }
published
property ProductName: string read FProductName write FProductName;
property Version: string read FVersion write FVersion;
property Copyright: string read FCopyright write FCopyright;
property Comments: string read FComments write FComments;
end;

13-4 Guide du concepteur de composants


Création de l’interface du composant

Si vous installez votre composant dans la palette des composants et si vous


le placez dans une fiche, vous pourrez définir les propriétés, et ces valeurs
apparaîtront de façon permanente dans la fiche. Ainsi, lors de l’exécution de
la boîte de dialogue qu’il enveloppe, le composant pourra les utiliser.

Ajout de la méthode Execute


Il vous reste à définir dans l’interface du composant les moyens d’ouvrir la boîte
de dialogue et de récupérer un résultat lorsqu’elle se ferme. Comme pour les
composants des boîtes de dialogue standard, vous utiliserez une fonction
booléenne appelée Execute qui renvoie True si l’utilisateur clique sur OK, ou False
s’il annule la boîte de dialogue.
La déclaration de la méthode Execute ressemble toujours à ceci :
type
TMyWrapper = class(TComponent)
public
function Execute: Boolean;
end;
L’implémentation minimale de la méthode Execute doit construire la fiche de
la boîte de dialogue, puis afficher celle-ci en tant que boîte de dialogue modale
avant de renvoyer True ou False, selon la valeur renvoyée par ShowModal.
Voici l’implémentation minimale de la méthode Execute pour une fiche boîte
de dialogue de type TMyDialogBox :
function TMyWrapper.Execute: Boolean;
begin
DialogBox := TMyDialogBox.Create(Application); { construit la fiche }
try
Result := (DialogBox.ShowModal = IDOK); { exécute; définit le résultat selon
la façon de fermer }
finally
DialogBox.Free; { restitue la fiche }
end;
end;
Notez l’utilisation d’un bloc try..finally qui vérifie que l’application restitue
l’objet boîte de dialogue même si une exception se produit. En général, lorsque
vous construisez un objet de cette manière, vous devez utiliser un bloc
try..finally pour protéger le bloc de code et être sûr que l’application libère
toutes les ressources qu’elle alloue.
En pratique, il y a davantage de code dans le bloc try..finally. Plus
spécifiquement, avant l’appel à ShowModal, le composant enveloppe doit définir
certaines propriétés de la boîte de dialogue en fonction de ses propres propriétés
d’interface. A l’inverse, quand ShowModal rend la main, le composant enveloppe
définit certaines de ses propriétés d’interface en fonction du résultat de
l’exécution de la boîte de dialogue.
Dans le cas de la boîte A propos de, vous devez utiliser les quatre propriétés
d’interface du composant enveloppe pour définir le contenu des libellés de la

Transformation d’une boîte de dialogue en composant 13-5


Test du composant

boîte de dialogue. Comme cette dernière ne renvoie aucune information à


l’application, il n’y a rien de particulier à faire après l’appel à ShowModal.
Modifiez la méthode Execute du composant enveloppe de la boîte A propos de
pour qu’elle ressemble à ceci :
Dans la partie publique de la classe TAboutDlg, ajoutez la déclaration pour
la méthode Execute :
type
TAboutDlg = class(TComponent)
public
function Execute: Boolean;
end;
function TAboutBoxDlg.Execute: Boolean;
begin
AboutBox := TAboutBox.Create(Application); { construit la boîte A propos de }
try
if ProductName = ’’ then { si le nom du produit est vide... }
ProductName := Application.Title; { ...utilise à la place le titre de l’application }
AboutBox.ProductName.Caption := ProductName; { copie le nom du produit }
AboutBox.Version.Caption := Version; { copie les infos de version }
AboutBox.Copyright.Caption := Copyright; { copie les infos de copyright }
AboutBox.Comments.Caption := Comments; { copie les commentaires }
AboutBox.Caption := ’About ’ + ProductName; { définit le titre de la boîte }
with AboutBox do begin
ProgramIcon.Picture.Graphic := Application.Icon; { copie l’icône }
Result := (ShowModal = IDOK); { exécute et définit le résultat }
end;
finally
AboutBox.Free; { restitue la boîte de dialogue A propos de }
end;
end;

Test du composant
Une fois le composant boîte de dialogue installé, vous pouvez l’utiliser comme
n’importe quelle autre boîte de dialogue commune, en le plaçant sur une fiche
et en l’exécutant. Un moyen rapide de vérifier le fonctionnement de la boîte A
propos de consiste à ajouter un bouton de commande dans une fiche et à
exécuter la boîte de dialogue lorsque l’utilisateur clique sur ce bouton.
Par exemple, si vous avez créé une boîte de dialogue A propos de, et si vous
l’avez ajouté à la palette des composants, vous pouvez tester son fonctionnement
en suivant les étapes ci-dessous :
1 Créez un nouveau projet.
2 Placez un composant A propos de dans la fiche principale.
3 Placez un bouton de commande dans la fiche.
4 Double-cliquez sur le bouton de commande pour créer un gestionnaire
d’événements vide.

13-6 Guide du concepteur de composants


Test du composant

5 Dans le gestionnaire d’événements, entrez la ligne de code suivante :


AboutBoxDlg1.Execute;
6 Exécutez l’application.
Lorsque la fiche principale apparaît, cliquez sur le bouton de commande. La
boîte A propos de s’affiche avec l’icône projet par défaut et Project1 comme titre.
Choisissez OK pour fermer la boîte de dialogue.
Vous pouvez pousser plus loin le test du fonctionnement du composant en
définissant les différentes propriétés du composant A propos de et en exécutant
une nouvelle fois l’application.

Transformation d’une boîte de dialogue en composant 13-7


13-8 Guide du concepteur de composants
Chapitre

14
Extensions de l’EDI
Chapitre14

Vous pouvez étendre et personnaliser l’EDI avec vos propres éléments de menu,
boutons de barres d’outils, experts création de fiches dynamiques et davantage
en utilisant l’API Open Tools (souvent abrégée en API Tools). L’API Tools est un
ensemble d’une centaine d’interfaces qui interagissent et contrôlent l’EDI, y
compris le menu principal, les barres d’outils, les listes principales d’actions et
d’images, les tampons internes de l’éditeur de code source, les macros et liaisons
clavier, les fiches et leurs composants dans l’éditeur de fiches, le débogueur et le
processus en cours de débogage, l’achèvement de code, la vue des messages et la
liste de tâches.
Pour utiliser l’API Tools, il suffit d’écrire des classes qui implémentent certaines
interfaces et d’appeler les services proposés par d’autres interfaces. Votre code
API Tools doit être compilé et chargé dans l’EDI lors de la conception sous la
forme d’un paquet de conception ou d’une DLL. Ainsi l’écriture d’une extension
API Tools ressemble à la conception d’un éditeur de propriété ou de composant.
Avant d’aborder ce chapitre, vous devez vous être familiarisé avec l’utilisation
des paquets (Chapitre 16, “Utilisation des paquets et des composants”, du Guide
du développeur) et le recensement des composants (Chapitre 8, “Accessibilité des
composants au moment de la conception”).
Ce chapitre traite les sujets suivants :
• Présentation de l’API Tools
• Conception d’une classe expert
• Accès aux services de l’API Tools
• Utilisation des fichiers et des éditeurs
• Création de fiches et de projets
• Notification d’un expert des événements de l’EDI

Extensions de l’EDI 14-1


Présentation de l’API Tools

Présentation de l’API Tools


Toutes les déclarations de l’API Tools se trouvent dans une seule unité,
ToolsAPI. Pour utiliser l’API Tools, il faut généralement employer un paquet
de conception, c’est-à-dire que vous devez concevoir un complément API Tools
comme un paquet de conception ou comme une DLL utilisant des paquets
d’exécution. Pour plus d’informations sur les paquets et les bibliothèques, voir
“Installation du paquet de l’expert” à la page 14-5.
L’interface principale dans l’écriture d’une extension API est IOTAWizard,
la plupart des compléments de l’EDI sont donc appelés des experts. Pour
l’essentiel, les experts C++Builder et Delphi sont compatibles. Vous pouvez ainsi
écrire et compiler un expert avec Delphi puis l’utiliser dans C++Builder, et
inversement. L’interopérabilité fonctionne mieux entre des versions ayant le
même numéro, mais il est aussi possible d’écrire des experts utilisables dans les
versions futures des deux logiciels. Pour utiliser l’API Tools, vous devez écrire
des classes expert qui implémentent une ou plusieurs des interfaces définies dans
l’unité ToolsAPI. Un expert utilise les services proposés par l’API Tools. Chaque
service est une interface qui présente un ensemble de fonctions associées.
L’implémentation de l’interface est cachée dans l’EDI. L’API Tools ne publie que
l’interface que vous pouvez utiliser pour écrire vos experts sans avoir à vous
préoccuper de l’implémentation des interfaces. Les divers services donnent accès
à l’éditeur de code source, au concepteur de fiches, au débogueur, etc. La section
“Accès aux services de l’API Tools” à la page 14-5 aborde plus en détail ce sujet.
Les services et autres interfaces sont regroupées dans deux catégories de base.
Vous pouvez les distinguer par le préfixe utilisé dans le nom de type :
• Le préfixe NTA (native tools API, API outils natifs) correspond à un accès
direct à des objets réels de l’EDI, par exemple l’objet TMainMenu de l’EDI.
Pour utiliser ces interfaces, l’expert doit utiliser les paquets Borland, cela
signifie également que l’expert est lié à une version spécifique de l’EDI.
L’expert peut se trouver dans un paquet de conception ou dans une DLL
utilisant des paquets d’exécution.
• Le préfixe OTA (Open Tools API) ne nécessite pas de paquets et accède à
l’EDI uniquement via des interfaces. En théorie, vous pouvez concevoir un
expert dans tout langage gérant les interfaces de style COM, dans la mesure
où vous pouvez aussi utiliser les conventions d’appel de Delphi et les types
Delphi comme AnsiString. Les interfaces OTA ne donnent pas un accès
complet à l’EDI, mais presque toutes les fonctionnalités de l’API Tools sont
accessibles via des interfaces OTA. Si un expert n’utilise que des interfaces
OTA, il est possible d’écrire une DLL qui ne dépend pas d’une version
spécifique de l’EDI.
L’API Tools contient deux types d’interfaces : celles que le programmeur doit
implémenter et celles qui sont implémentées par l’EDI. La plupart des interfaces
rentrent dans cette dernière catégorie : ces interfaces définissent les possibilités de
l’EDI mais masquent l’implémentation réelle.

14-2 Guide du concepteur de composants


Conception d’une classe expert

Il existe trois types d’interfaces que vous devez implémenter : les experts, les
notificateurs et les créateurs :
• Comme indiqué plus haut dans cette section, une classe expert implémente
l’interface IOTAWizard et éventuellement des interfaces dérivées.
• Un notificateur est un autre type d’interface de l’API Tools. L’EDI utilise des
notificateurs pour prévenir votre expert quand il se produit quelque chose qui
le concerne. Si vous écrivez une classe qui implémente l’interface de
notification, recensez le notificateur dans l’API Tools et l’EDI appelle votre
objet notificateur quand l’utilisateur ouvre un fichier, modifie le code source,
change une fiche, démarre une session de débogage, etc. Les notificateurs sont
décrits en détail dans la section “Notification d’un expert des événements de
l’EDI” à la page 14-16.
• Un créateur est un autre type d’interface à implémenter. L’API Tools utilise
des créateurs pour créer de nouvelles unités, des fiches, des projets ou
d’autres fichiers ou pour ouvrir des fichiers existants. Pour plus
d’informations, voir la section “Création de fiches et de projets” à la
page 14-12.
Les modules et les éditeurs sont d’autres interfaces importantes. Une interface
module représente une unité ouverte ayant un ou plusieurs fichiers. Une
interface éditeur représente un fichier ouvert. Différentes interfaces éditeur vous
donnent accès aux différents aspects de l’EDI : l’éditeur de code source pour les
fichiers source, le concepteur de fiche pour les fichiers fiche et les ressources de
projet pour les fichiers ressource. Pour plus d’informations, voir la section
“Utilisation des fichiers et des éditeurs” à la page 14-11.
Les sections suivantes vous guident dans les étapes de la conception d’un expert.
Reportez-vous à l’aide en ligne pour des détails complets sur chaque interface.

Conception d’une classe expert


Il y a quatre types d’experts, le type d’un expert dépendant des interfaces
qu’implémente la classe de l’expert. Le Tableau 14.1 décrit les quatre types
d’experts.

Tableau 14.1 Les quatre types d’expert


Interface Description
IOTAFormWizard Crée généralement une nouvelle unité, une fiche ou un autre fichier
IOTAMenuWizard Automatiquement ajoutée au menu Aide
IOTAProjectWizard Crée généralement une nouvelle application ou un projet
IOTAWizard Autres experts ne rentrant pas dans les autres catégories

Extensions de l’EDI 14-3


Conception d’une classe expert

La seule différence entre ces experts est la manière dont l’utilisateur fait appel à
l’expert :
• Un expert menu est ajouté au menu Aide de l’EDI. Quand l’utilisateur
sélectionne l’élément de menu, l’EDI appelle la fonction Execute de l’expert.
Les experts normaux étant beaucoup plus flexibles, on n’utilise généralement
les experts menu uniquement pour le prototypage ou la mise au point.
• Les experts fiche et projet sont également appelés experts du référentiel car ils
sont placés dans le référentiel d’objets. L’utilisateur accède à ces experts dans
la boîte de dialogue Nouveaux éléments. L’utilisateur peut également voir ces
experts dans le référentiel d’objets (en choisissant la commande Outils|
Référentiel). L’utilisateur peut cocher la case Nouvelle fiche d’un expert fiche,
ce qui indique à l’EDI d’appeler l’expert fiche quand l’utilisateur choisit la
commande Fichier|Nouvelle fiche. L’utilisateur peut également cocher la case
Fiche principale. Celle-ci indique à l’EDI d’utiliser l’expert fiche pour la fiche
par défaut d’une nouvelle application. Pour un expert projet, l’utilisateur peut
cocher la case Nouveau projet. Dans ce cas, quand l’utilisateur choisit la
commande Fichier|Nouvelle application, l’EDI appelle l’expert projet
sélectionné.
• Le quatrième type d’expert correspond aux situations ne rentrant pas dans les
autres catégories. A la base, un expert ne fait rien automatiquement ou de
lui-même. Vous devez donc définir comment l’expert est appelé.
L’API Tools ne définit aucune restriction sur les experts, par exemple imposer
qu’un expert projet crée effectivement un projet. Vous pouvez tout aussi bien
concevoir un expert projet qui crée une fiche qu’un expert fiche qui crée un
projet (du moins si c’est vraiment ce que vous voulez).

Implémentation des interfaces de l’expert


Chaque classe expert doit implémenter au minimum IOTAWizard, ce qui
nécessite également d’implémenter ses ancêtres : IOTANotifier et IInterface. Les
experts fiche et projet doivent implémenter toutes leurs interfaces ancêtres, soit,
IOTARepositoryWizard, IOTAWizard, IOTANotifier et IInterface.
Votre implémentation de IInterface suit les mêmes règles que celles des interfaces
Delphi, qui sont les mêmes que celles des interfaces COM. C’est-à-dire que
QueryInterface effectue les transtypages et que _AddRef et _Release gèrent le
comptage de références. Vous pouvez utiliser une classe de base commune pour
simplifier l’écriture des classes d’experts et de notificateurs. A cette fin, l’unité
ToolsAPI définit une classe, TNotifierObject, qui implémente l’interface
IOTANotifier avec des corps de méthode vides.
Bien que les experts héritent de IOTANotifier et doivent donc implémenter toutes
ses fonctions, l’EDI n’utilise généralement pas ces fonctions et votre
implémentation peut rester vide (comme c’est le cas dans TNotifierObject). Par
conséquent, lorsque vous écrivez votre classe expert, vous devez uniquement
déclarer et implémenter les méthodes d’interface introduites par les interfaces
expert, en acceptant l’implémentation TNotifierObject de IOTANotifier.

14-4 Guide du concepteur de composants


Accès aux services de l’API Tools

Installation du paquet de l’expert


Comme pour tous les paquets de conception, le paquet d’un expert doit disposer
de la fonction Register. Pour davantage d’informations sur la fonction Register,
voir Chapitre 8, “Accessibilité des composants au moment de la conception”.
Dans la fonction Register vous pouvez recenser plusieurs experts en appelant
RegisterPackageWizard en lui transmettant comme seul argument l’objet expert,
comme indiqué ci-dessous :
procedure Register;
begin
RegisterPackageWizard(MyWizard.Create);
RegisterPackageWizard(MyOtherWizard.Create);
end;
Vous pouvez également recenser des éditeurs de propriété, des composants, etc.
dans le même paquet.
N’oubliez pas qu’un paquet de conception fait partie de l’application Delphi
principale, cela implique que tout nom de fiche doit être unique dans toute
l’application et tous les autres paquets de conception. C’est le principal
inconvénient de l’utilisation des paquets : vous ne pouvez pas prévoir comment
quelqu’un d’autre va appeler ses fiches.
Lors du développement, installez le paquet de l’expert de la même manière que
les autres paquets de conception : cliquez sur le bouton Installer dans le
gestionnaire de paquets. L’EDI compile et lie alors le paquet puis tente de le
charger. L’EDI affiche une boîte de dialogue vous indiquant si le chargement du
paquet s’est bien effectué.

Accès aux services de l’API Tools


Pour servir à quelque chose, un expert doit accéder à l’EDI : ses éditeurs,
fenêtres, menus, etc. C’est le rôle des interfaces de service. L’API Tools propose
de nombreux services, dont des services action pour effectuer des actions sur les
fichiers, des services d’édition pour accéder à l’éditeur de code source, des
services de débogage pour accéder au débogueur, etc. Le Tableau 14.2 résume
toutes les interfaces de service.

Tableau 14.2 Interfaces de service de l’API Tools


Interface Description
INTAServices Donne accès aux objets natifs de l’EDI : menu principal, liste
d’actions, liste d’images et les barres d’outils.
IOTAActionServices Effectue les actions de base sur les fichiers : ouverture,
fermeture, enregistrement et rechargement d’un fichier.
IOTACodeCompletionServices Donne accès à l’achèvement de code, ce qui permet à un
expert d’installer un gestionnaire personnalisé d’achèvement
de code.
IOTADebuggerServices Donne accès au débogueur.

Extensions de l’EDI 14-5


Accès aux services de l’API Tools

Tableau 14.2 Interfaces de service de l’API Tools (suite)


Interface Description
IOTAEditorServices Donne accès à l’éditeur de code source et aux tampons
internes.
IOTAKeyBindingServices Permet à un expert de recenser des liaisons de clavier
personnalisées.
IOTAKeyboardServices Donne accès aux macros et liaisons clavier.
IOTAKeyboardDiagnostics Inverse le débogage des frappes au clavier.
IOTAMessageServices Donne accès à la vue des messages.
IOTAModuleServices Donne accès aux fichiers ouverts.
IOTAPackageServices Obtient le nom de tous les paquets installés et de leurs
composants.
IOTAServices Services divers.
IOTAToDoServices Donne accès à la liste des tâches, ce qui permet à un expert
d’installer un gestionnaire personnalisé de la liste de tâches.
IOTAToolsFilter Recense les notificateurs d’outils de filtrage.
IOTAWizardServices Recense ou annule le recensement d’un expert.

Pour utiliser une interface de service, transtypez la variable BorlandIDEServices


dans le service souhaité à l’aide de la fonction globale Supports, définie dans
l’unité SysUtils. Par exemple,
procedure set_keystroke_debugging(debugging: Boolean);
var
diag: IOTAKeyboardDiagnostics
begin
if Supports(BorlandIDEServices, IOTAKeyboardDiagnostics, diag) then
diag.KeyTracing := debugging;
end;
Si votre expert a besoin d’utiliser fréquemment un service donné, vous pouvez
conserver un pointeur sur ce service comme donnée membre de la classe de
votre expert.

Utilisation d’objets natifs de l’EDI


Les experts ont un accès complet au menu principal, aux barres d’outils, à la
liste d’actions et la liste d’images de l’EDI. Par contre, les divers menus
contextuels de l’EDI ne sont pas accessibles via l’API Tools. Cette section
présente un exemple simple d’utilisation par un expert des objets natifs de l’EDI
pour interagir avec l’EDI.

Utilisation de l’interface INTAServices


L’interface INTAServices sert de point de départ à la manipulation des objets
natifs de l’EDI. Utilisez cette interface pour ajouter une image à la liste d’images,
une action à la liste d’actions, un élément de menu au menu principal ou un
bouton à une barre d’outils. Vous pouvez lier l’action à l’élément de menu ou un
bouton de barre d’outils. Quand l’expert est détruit, il doit nettoyer les objets

14-6 Guide du concepteur de composants


Accès aux services de l’API Tools

qu’il a créé mais il ne doit pas supprimer l’image qu’il a ajouté à la liste
d’images. La suppression d’une image perturberait l’indice de toutes les images
ajoutées après cet expert.
L’expert utilise réellement les objets TMainMenu, TActionList, TImageList et
TToolBar de l’EDI, vous pouvez donc écrire du code de la même manière que
dans une application normale. Cela signifie également que vous avez de
nombreuses opportunités de bloquer l’EDI ou de désactiver des caractéristiques
importantes, par exemple en supprimant le menu Fichier.

Ajout d’une image à la liste d’images


Supposons que vous vouliez ajouter un élément de menu pour appeler votre
expert. Vous voudrez également permettre à l’utilisateur d’ajouter un bouton de
barre d’outils qui appelle l’expert. La première étape est donc l’ajout d’une
image à la liste d’images de l’EDI. Vous pouvez ensuite utiliser l’indice de cette
image dans l’action qui est à son tour utilisée par l’élément de menu et un
bouton de barre d’outils. Utilisez l’éditeur d’images pour créer un fichier
ressource contenant une ressource bitmap 16 par 16. Ajoutez le code suivant au
constructeur de l’expert :
constructor MyWizard.Create;
var
Services: INTAServices;
Bmp: TBitmap;
ImageIndex: Integer;
begin
inherited;
Supports(BorlandIDEServices, INTAServices, Services);
{ Ajoute une image à la liste d’images. }
Bmp := TBitmap.Create;
Bmp.LoadFromResourceName(HInstance, ’Bitmap1’);
ImageIndex := Services.AddMasked(Bmp, Bmp.TransparentColor,
’Tempest Software.intro wizard image’);
Bmp.Free;
end;
Assurez-vous de charger la ressource en utilisant le nom ou l’identificateur
spécifié dans le fichier ressource. Vous devez choisir une couleur qui sera
interprétée comme la couleur de fond de l’image. Si vous ne voulez pas utiliser
de couleur de fond, choisissez une couleur n’existant pas dans le bitmap.

Ajout d’une action à la liste d’actions


L’indice de l’image est utilisé pour créer une action, comme indiqué ci-dessous.
L’expert utilise les événements OnExecute et OnUpdate. Généralement un expert
utilise l’événement OnUpdate pour activer ou désactiver l’action. Attention,
l’événement OnUpdate doit rendre la main rapidement, sinon l’utilisateur
remarquera que l’EDI devient plus lent une fois votre expert chargé.
L’événement OnExecute de l’action est similaire à la méthode Execute de l’expert.
Si vous utilisez un élément de menu pour appeler un expert fiche ou projet,
vous pouvez appeler directement Execute dans l’événement OnExecute.
NewAction := TAction.Create(nil);

Extensions de l’EDI 14-7


Accès aux services de l’API Tools

NewAction.ActionList := Services.ActionList;
NewAction.Caption := GetMenuText();
NewAction.Hint := ’Affiche une boîte de dialogue inutile’;
NewAction.ImageIndex := ImageIndex;
NewAction.OnUpdate := action_update;
NewAction.OnExecute := action_execute;
L’élément de menu initialise sa propriété Action avec l’action qui vient d’être
créée. Le problème dans la création d’un élément de menu, c’est de savoir où
l’insérer. L’exemple suivant recherche le menu Voir et insère le nouvel élément
de menu comme premier élément du menu Voir. En général, il n’est pas
judicieux de se baser sur une position absolue : en effet, vous ne savez jamais
quand un autre expert va s’ajouter au menu. De plus, les versions ultérieures de
Delphi peuvent également réorganiser les menus. Il est préférable de rechercher
dans le menu un élément de nom donné. Dans un souci de clarté l’approche
simpliste est indiqué ci-dessous.
for I := 0 to Services.MainMenu.Items.Count - 1 do
begin
with Services.MainMenu.Items[I] do
begin
if CompareText(Name, ’ViewsMenu’) = 0 then
begin
NewItem := TMenuItem.Create(nil);
NewItem.Action := NewAction;
Insert(0, NewItem);
end;
end;
end;
Une fois l’action ajoutée à la liste d’actions de l’EDI, l’utilisateur peut voir
l’action quand il personnalise les barres d’outils. L’utilisateur peut sélectionner
l’action et l’ajouter à une barre d’outils. Cela peut poser un problème quand
votre expert est déchargé : tous les boutons de barre d’outils se retrouvent avec
des pointeurs flottants sur une action et un gestionnaire d’événement OnClick
inexistants. Pour éviter des violations d’accès, votre expert doit trouver tous les
boutons d’outils faisant référence à ses actions et les supprimer.

Suppression de boutons de barres d’outils


Il n’y a pas de moyen simple de retirer un bouton d’une barre d’outils; vous
devez envoyer le message CM_CONTROLCHANGE, le premier paramètre
indique le contrôle à modifier et le second a la valeur zéro pour le retirer ou une
valeur non nulle pour l’ajouter à la barre d’outils. Après avoir retiré les boutons
de barre d’outils, le destructeur supprime l’action et l’élément de menu. La
suppression de ces éléments les retire automatiquement des objets ActionList et
MainMenu de l’EDI.
procedure remove_action (Action: TAction; ToolBar: TToolBar);
var
I: Integer;
Btn: TToolButton;
begin
for I := ToolBar.ButtonCount - 1 downto 0 do

14-8 Guide du concepteur de composants


Accès aux services de l’API Tools

begin
Btn := ToolBar.Buttons[I];
if Btn.Action = Action then
begin
{ Retire "Btn" de "ToolBar" }
ToolBar.Perform(CM_CONTROLCHANGE, WPARAM(Btn), 0);
Btn.Free;
end;
end;
end;
destructor MyWizard.Destroy;
var
Services: INTAServices;
Btn: TToolButton;
begin
Supports(BorlandIDEServices, INTAServices, Services);
{ Vérifier toutes les barres d’outils et retirer tous les boutons utilisant cette action.
}
remove_action(NewAction, Services.ToolBar[sCustomToolBar]);
remove_action(NewAction, Services.ToolBar[sDesktopToolBar]);
remove_action(NewAction, Services.ToolBar[sStandardToolBar]);
remove_action(NewAction, Services.ToolBar[sDebugToolBar]);
remove_action(NewAction, Services.ToolBar[sViewToolBar]);
remove_action(NewAction, Services.ToolBar[sInternetToolBar]);
NewItem.Free;
NewAction.Free;
end;
Comme vous pouvez le voir dans cet exemple simple, votre expert peut interagir
avec l’EDI de manière très flexible. Cependant, avec la flexibilité vient également
la responsabilité. Vous pouvez facilement produire des pointeurs flottants ou
d’autres violations d’accès. La section suivante donne quelques conseils pour
diagnostiquer ce type de problème.

Débogage d’un expert


Lors de l’écriture d’experts utilisant les outils natifs, vous pouvez écrire du code
provoquant le blocage de l’EDI. Vous pouvez également concevoir un expert qui
s’installe mais sans agir de la manière prévue. Le débogage est l’une des
difficultés de l’utilisation du code s’exécutant pendant la conception. C’est
néanmoins un problème facile à résoudre. Comme l’expert est installé dans
Delphi même, il vous suffit de définir l’exécutable Delphi comme Application
hôte du paquet (delphi32.exe) grâce à la commande Exécuter|Paramètres.
Quand vous voulez (ou avez besoin) de déboguer le paquet, ne l’installez pas.
Utilisez à la place la commande Exécuter|Exécuter de la barre de menus. Cela
démarre une nouvelle instance de Delphi. Dans la nouvelle instance, installez le
paquet déjà compilé en choisissant Composants|Installer des paquets dans la
barre de menus. Si vous revenez dans l’instance d’origine de Delphi, vous voyez
maintenant les points bleus vous indiquant que vous pouvez définir des points
d’arrêt dans le code source de l’expert. Si ce n’est pas le cas, vérifiez les options

Extensions de l’EDI 14-9


Accès aux services de l’API Tools

du compilateur pour vous assurer que vous avez activé le débogage ; vérifiez
que vous avez chargé le bon paquet ; et revérifiez les modules de processus pour
être sûr et certain que vous avez bien chargé le fichier .bpl souhaité.
En procédant ainsi, vous ne pouvez pas déboguer dans le code VCL, CLX ou
RTL mais pour l’expert même vous disposez de toutes les fonctions de
débogage, ce qui devrait suffire pour trouver le problème.

Numéros de version de l’interface


Si vous regardez attentivement la déclaration de certaines interfaces, comme
IOTAMessageServices, vous constaterez qu’elle hérite d’une autre interface de nom
similaire, comme IOTAMessageServices50, qui hérite à son tour de
IOTAMessageServices40. Cette utilisation des numéros de version isole votre code
des changements entre les versions de Delphi.
L’API Tools utilise le même principe de base que COM, à savoir qu’une interface
et son GUID ne changent jamais. Si une nouvelle version ajoute des
caractéristiques à une interface, l’API Tools déclare une nouvelle interface qui
hérite de l’ancienne. Le GUID ne change pas et reste attaché à l’ancienne
interface qui n’est pas modifiée. La nouvelle interface obtient un nouveau GUID.
Les anciens experts qui utilisent les anciens GUID continuent à fonctionner.
L’API Tools modifie également le nom des interfaces afin de préserver la
compatibilité du code source. Pour comprendre comment cela fonctionne, il est
important de faire la distinction entre les deux types d’interfaces de l’API Tools :
implémentées par Borland ou implémentées par l’utilisateur. Si l’EDI implémente
l’interface, le nom reste attaché à la version la plus récente de l’interface. La
nouvelle fonctionnalité n’affecte pas le code existant. L’ancien numéro de version
est alors ajouté au nom des anciennes interfaces.
Par contre, dans le cas d’une interface implémentée par l’utilisateur, les nouvelles
fonctions membre de l’interface de base nécessitent de nouvelles fonctions dans
votre code. Dans ce cas, le nom reste attaché à l’ancienne interface, et le numéro
de version est ajouté à la fin du nom de la nouvelle version.
Soit, par exemple, les services de message. Delphi 6 introduit une nouvelle
caractéristique : les groupes de messages. De ce fait, l’interface de base des
messages a besoin de nouvelles fonctions membre. Ces fonctions sont déclarées
dans une nouvelle classe d’interface, qui conserve le nom IOTAMessageServices.
L’ancienne interface des services de message est renommée en
IOTAMessageServices50 (pour la version 5). Le GUID de l’ancienne interface
IOTAMessageServices est le même que le GUID de la nouvelle interface
IOTAMessageServices50 car les fonctions membre sont les mêmes.
Prenons IOTAIDENotifier comme exemple d’une interface implémentée par
l’utilisateur. Delphi 5 ajoute de nouvelles fonctions : AfterCompile et BeforeCompile.
Le code existant qui utilisait IOTAIDENotifier n’a pas à être modifié, mais le
nouveau code utilisant les nouvelles fonctionnalités doit être modifié pour
redéfinir les nouvelles fonctions héritées de IOTAIDENotifier50. La version 6 n’a

14-10 Guide du concepteur de composants


Utilisation des fichiers et des éditeurs

pas ajouté de nouvelles fonctions, la version en cours à utiliser est donc


IOTAIDENotifier50.
Le principe de base consiste à utiliser la classe la plus dérivée lors de l’écriture
de nouveau code. Ne changez rien au code si vous recompilez simplement un
expert existant avec une nouvelle version de Delphi.

Utilisation des fichiers et des éditeurs


Avant d’aller plus loin, vous devez comprendre comment l’API Tools manipule
les fichiers. L’interface principale est IOTAModule. Un module représente un
ensemble de fichiers ouverts liés logiquement. Ainsi, une unité est représentée
par un seul module. A son tour, le module comprend un ou des éditeurs,
chaque éditeur représentant un fichier, par exemple le fichier source de l’unité
(.pas) ou le fichier fiche (.dfm ou .xfm). Comme les interfaces éditeur reflètent
l’état interne des éditeurs de l’EDI, un expert peut voir le code modifié et les
fiches que l’utilisateur voit, même si l’utilisateur n’a pas enregistré les
modifications.

Utilisation des interfaces de module


Pour obtenir une interface de module, démarrez le service de module
(IOTAModuleServices). Vous pouvez demander aux services de module tous les
modules qui sont ouverts, rechercher un module à partir d’un nom de fichier ou
de fiche ou ouvrir un fichier pour obtenir son interface de module.
Il existe différents types de modules correspondant aux différents types de
fichiers : projets, ressources ou bibliothèques de types. Transtypez une interface
de module vers une interface de module spécifique pour savoir si le module est
de ce type. Par exemple, voici un moyen de déterminer l’interface du groupe de
projets en cours :
{ Renvoie le groupe de projets en cours ou nil s’il n’y en a pas. }
function CurrentProjectGroup: IOTAProjectGroup;
var
I: Integer;
Svc: IOTAModuleServices;
Module: IOTAModule;
begin
Supports(BorlandIDEServices, IOTAModuleServices, Svc);
for I := 0 to Svc.ModuleCount - 1 do
begin
Module := Svc.Modules[I];
if Supports(Module, IOTAProjectGroup, Result) then
Exit;
end;
Result := nil;
end;

Extensions de l’EDI 14-11


Création de fiches et de projets

Utilisation des interfaces d’éditeur


Chaque module dispose d’au moins une interface d’éditeur. Certains modules
ont plusieurs éditeurs, tels qu’un fichier source (.pas) et un fichier description de
fiche (.dfm). Comme tous les éditeurs implémentent l’interface IOTAEditor ;
transtypez l’éditeur vers un type spécifique pour savoir quel est le type de
l’éditeur. Par exemple, le code suivant permet d’obtenir l’interface de l’éditeur de
fiche d’une unité :
{ Renvoie l’éditeur de fiche d’un module ou nil si l’unité n’a pas de fiche. }
function GetFormEditor(Module: IOTAModule): IOTAFormEditor;
var
I: Integer;
Editor: IOTAEditor;
begin
for I := 0 to Module.ModuleFileCount - 1 do
begin
Editor := Module.ModuleFileEditors[I];
if Supports(Editor, IOTAFormEditor, Result) then
Exit;
end;
Result := nil;
end;
Les interfaces d’éditeur donnent accès à l’état interne de l’éditeur. Vous pouvez
examiner le code source ou les composants que l’utilisateur est en train de
modifier, modifier le code source, les composants ou les propriétés, changer la
sélection dans les éditeurs de code source ou de fiche ou effectuer presque toute
opération d’édition que l’utilisateur final peut mettre en œuvre.
En utilisant une interface d’éditeur de fiche, un expert peut accéder à tous les
composants d’une fiche. Chaque composant, y compris la fiche racine ou le
module de données, a une interface IOTAComponent associée. Un expert peut
examiner ou modifier la plupart des propriétés des composants. Si vous avez
besoin d’un contrrôle complet du composant, vous pouvez transtyper l’interface
IOTAComponent vers INTAComponent. L’interface de composant natif donne à
l’expert un accès direct au pointeur TComponent. Cela est important si vous avez
besoin de lire ou de modifier une propriété de type classe, comme TFont, ce qui
ne peut se faire que via des interfaces de style NTA.

Création de fiches et de projets


Delphi propose de nombreux experts fiche et projet préinstallés et vous pouvez
également écrire les vôtres. Le référentiel d’objets vous permet de créer des
modèles statiques que vous pouvez utiliser dans un projet, mais un expert est
beaucoup plus puissant car il est dynamique. L’expert peut interroger
l’utilisateur et créer différents types de fichiers selon les réponses de l’utilisateur.
Cette section décrit la création d’un expert fiche ou projet.

14-12 Guide du concepteur de composants


Création de fiches et de projets

Création de modules
Généralement, un expert fiche ou projet crée un ou plusieurs nouveaux fichiers.
En fait, au lieu de fichiers réels, mieux vaut créer des modules sans nom et non
enregistrés. Lorsque l’utilisateur les enregistre, l’EDI lui demande le nom de
fichier. Un expert utilise un objet créateur pour créer de tels modules.
Une classe créateur implémente une interface de création qui hérite de
IOTACreator. L’expert transmet l’objet créateur à la méthode CreateModule du
service de module, et l’EDI rappelle l’objet créateur pour obtenir les paramètres
nécessaires à la création du module.
Ainsi, un expert fiche qui crée une nouvelle fiche implémente généralement
GetExisting pour renvoyer false et GetUnnamed pour renvoyer true. Cela crée un
module sans nom (l’utilisateur doit donc spécifier un nom avant de pouvoir
enregistrer le fichier) et sans fichier existant correspondant (l’utilisateur doit donc
enregistrer le fichier même s’il ne l’a pas modifié). D’autres méthodes du
créateur indiquent à l’EDI le type de fichier créé (un projet, une unité ou une
fiche), spécifient le contenu du fichier ou renvoient le nom de la fiche, le nom de
l’ancêtre ou d’autres informations importantes. D’autres rappels permettent à un
expert d’ajouter des modules à un projet qui vient d’être créé, ou d’ajouter des
composants à une fiche qui vient d’être créée.
Pour créer un nouveau fichier, ce qui est généralement nécessaire avec un expert
fiche ou projet, il faut spécifier le contenu du nouveau fichier. Pour ce faire,
écrivez une nouvelle classe implémentant l’interface IOTAFile. Si votre expert se
contente du contenu de fichier par défaut, vous pouvez renvoyer nil depuis
toute fonction qui renvoie IOTAFile.
Par exemple, votre société peut utiliser un bloc de commentaire standardisé
devant apparaître au début de chaque fichier source. Vous pouvez obtenir ceci
avec un modèle statique du référentiel d’objets, mais il faut alors modifier
manuellement le bloc de commentaire pour indiquer l’auteur et la date de
création. Vous pouvez utiliser à la place un créateur pour remplir
dynamiquement le bloc de commentaire lors de la création du fichier.
Il faut tout d’abord écrire un expert qui crée de nouvelles fiches et unités. La
plupart des fonctions d’un créateur renvoient zéro, des chaînes vides ou d’autres
valeurs par défaut, ce qui indique à l’API Tools d’utiliser le comportement par
défaut pour créer une nouvelle unité ou une fiche. Redéfinissez GetCreatorType
pour indiquer à l’API Tools le type de module à créer : une unité ou une fiche.
Pour créer une unité, renvoyez sUnit. Pour créer une fiche, renvoyez sForm.
Pour simplifier le code, utilisez une seule classe utilisant le type de créateur
comme argument du constructeur. Enregistrez le type du créateur dans une
donnée membre afin que GetCreatorType puisse renvoyer sa valeur. Implémentez
NewImplSource et NewIntfSource pour renvoyer le contenu souhaité dans le fichier.
TCreator = class(TInterfacedObject, IOTAModuleCreator)
public
constructor Create(const CreatorType: string);
{ IOTAModuleCreator }
function GetAncestorName: string;

Extensions de l’EDI 14-13


Création de fiches et de projets

function GetImplFileName: string;


function GetIntfFileName: string;
function GetFormName: string;
function GetMainForm: Boolean;
function GetShowForm: Boolean;
function GetShowSource: Boolean;
function NewFormFile(const FormIdent, AncestorIdent: string): IOTAFile;
function NewImplSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile;
function NewIntfSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile;
procedure FormCreated(const FormEditor: IOTAFormEditor);
{ IOTACreator }
function GetCreatorType: string;
function GetExisting: Boolean;
function GetFileSystem: string;
function GetOwner: IOTAModule;
function GetUnnamed: Boolean;
private
FCreatorType: string;
end;
La plupart des membres de TCreator renvoient zéro, nil ou des chaînes vides.
Les méthodes booléennes renvoient true, sauf GetExisting qui renvoie false. La
méthode la plus intéressante est GetOwner qui renvoie un pointeur sur le module
du projet en cours ou nil s’il n’y a pas de projet. Il n’y a pas de moyen simple
de déterminer le projet ou le groupe de projets en cours. Il faut à la place utiliser
GetOwner pour parcourir tous les modules ouverts. Si un groupe de projets est
trouvé, ce doit être le seul groupe de projets ouvert, donc GetOwner renvoie son
projet en cours. Sinon la fonction renvoie le premier module de projet trouvé ou
nil s’il n’y a pas de projet ouvert.
function TCreator.GetOwner: IOTAModule;
var
I: Integer;
Svc: IOTAModuleServices;
Module: IOTAModule;
Project: IOTAProject;
Group: IOTAProjectGroup;
begin
{ Renvoie le projet en cours. }
Supports(BorlandIDEServices, IOTAModuleServices, Svc);
Result := nil;
for I := 0 to Svc.ModuleCount - 1 do
begin
Module := Svc.Modules[I];
if Supports(Module, IOTAProject, Project) then
begin
{ Mémoriser le premier module de projet}
if Result = nil then
Result := Project;
end
else if Supports(Module, IOTAProjectGroup, Group) then
begin
{ Trouve le groupe de projets et renvoie son projet actif}

14-14 Guide du concepteur de composants


Création de fiches et de projets

Result := Group.ActiveProject;
Exit;
end;
end;
end;
Le créateur renvoie nil depuis NewFormSource pour générer un fichier fiche par
défaut. Les méthodes intéressantes sont NewImplSource et NewIntfSource qui
créent une instance de IOTAFile renvoyant le contenu du fichier.
La classe TFile implémente l’interface IOTAFile. Elle renvoie -1 comme âge du
fichier (cela signifie que le fichier n’existe pas) et renvoie le contenu du fichier
dans une chaîne. Pour garder la classe TFile simple, le créateur génère la chaîne,
la classe TFile se contenant de la transmettre.
TFile = class(TInterfacedObject, IOTAFile)
public
constructor Create(const Source: string);
function GetSource: string;
function GetAge: TDateTime;
private
FSource: string;
end;
constructor TFile.Create(const Source: string);
begin
FSource := Source;
end;
function TFile.GetSource: string;
begin
Result := FSource;
end;
function TFile.GetAge: TDateTime;
begin
Result := TDateTime(-1);
end;
Vous pouvez stocker le texte du contenu du fichier dans une ressource afin d’en
simplifier la modification, mais dans un souci de simplicité, dans cet exemple le
texte est codé directement dans l’expert. L’exemple suivant génère le code source
en supposant l’existence d’une fiche. Vous pouvez facilement gérer le cas encore
plus simple d’une unité de base. Testez FormIdent et, si elle est vide, créez une
unité de base, sinon créez une unité de fiche. Le squelette de base du code est le
même que celui par défaut de l’EDI (avec bien entendu l’ajout des commentaires
au début), mais vous pouvez le modifier à votre guise.
function TCreator.NewImplSource(
const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile;
var
FormSource: string;
begin
FormSource :=
’{ ----------------------------------------------------------------- ’ + #13#10 +
’%s - description’+ #13#10 +

Extensions de l’EDI 14-15


Notification d’un expert des événements de l’EDI

’Copyright © %y Your company, inc.’+ #13#10 +


’Created on %d’+ #13#10 +
’By %u’+ #13#10 +
’ ----------------------------------------------------------------- }’ + #13#10 +
#13#10;
return TFile.Create(Format(FormSource, ModuleIdent, FormIdent,
AncestorIdent));
}
La dernière étape consiste à créer deux experts fiche : un qui utilise sUnit comme
type de créateur et l’autre utilisant sForm. Pour proposer un avantage
supplémentaire à l’utilisateur, vous pouvez utiliser INTAServices pour ajouter un
élément au menu Fichier|Nouveau permettant d’appeler chaque expert. Le
gestionnaire d’événement OnClick de l’élément de menu peut appeler la fonction
Execute de l’expert.
Certains experts ont besoin d’activer ou de désactiver des éléments de menu en
fonction de ce qui se passe dans l’EDI. Ainsi, un expert qui archive un projet
dans un système de contrôle du code source doit désactiver sa commande
Archiver s’il n’y a pas de fichier ouvert dans l’EDI. Vous pouvez ajouter cette
fonctionnalité à votre expert en utilisant les notificateurs, décrits dans la section
suivante.

Notification d’un expert des événements de l’EDI


Un aspect important pour bien concevoir un expert consiste à le faire réagir aux
événements de l’EDI. En particulier, si un expert suit les interfaces de module,
il doit savoir quand l’utilisateur ferme le module, afin que l’expert puisse libérer
l’interface. Pour ce faire, l’expert a besoin d’un notificateur, ce qui signifie que
vous devez écrire une classe de notification.
Toutes les classes de notification implémentent une ou plusieurs interfaces de
notification. Une interface de notification définit des méthodes de rappel ;
l’expert recense un objet notificateur dans l’API Tools, et l’EDI rappelle le
notificateur quand il se produit quelque chose d’important.
Chaque interface de notification hérite de IOTANotifier même si toutes ses
méthodes ne sont pas utilisées dans un même notificateur. Le Tableau 14.3 liste
toutes les interfaces de notification en les décrivant brièvement.

Tableau 14.3 Interfaces de notification


Interface Description
IOTANotifier Classe de base abstraite de tous les notificateurs
IOTABreakpointNotifier Déclenchement ou modification d’un point d’arrêt dans le
débogueur
IOTADebuggerNotifier Exécution d’un programme dans le débogueur, ou l’ajout ou la
suppression d’un point d’arrêt
IOTAEditLineNotifier Suit le déplacement des lignes dans l’éditeur de code source

14-16 Guide du concepteur de composants


Notification d’un expert des événements de l’EDI

Tableau 14.3 Interfaces de notification (suite)


Interface Description
IOTAEditorNotifier Modification ou enregistrement d’un fichier source ou passage
d’un fichier à l’autre dans l’éditeur
IOTAFormNotifier Enregistrement d’une fiche ou modification de la fiche ou de ses
composants (ou d’un module de données)
IOTAIDENotifier Chargement de projets, installation de paquets et autres
événements de l’EDI de portée globale
IOTAMessageNotifier Ajout ou suppression d’onglets (groupes de messages) dans la
vue des messages
IOTAModuleNotifier Modification, enregistrement ou changement de nom d’un module
IOTAProcessModNotifier Chargement d’un module de processus dans le débogueur
IOTAProcessNotifier Création ou destruction de threads ou de processus dans le
débogueur
IOTAThreadNotifier Modification de l’état d’un thread dans le débogueur
IOTAToolsFilterNotifier Appel d’un outil de filtrage

Pour voir comment utiliser des notificateurs, reportez-vous à l’exemple


précédent. Grâce à des créateurs de module, l’exemple crée un expert qui ajoute
un commentaire à chaque fichier source. Le commentaire comprend le nom
initial du fichier de l’unité, mais l’utilisateur enregistre presque toujours le fichier
sous un nom différent. Dans ce cas, ce serait un avantage pour l’utilisateur que
l’expert actualise le commentaire pour correspondre au véritable nom du fichier.
Pour ce faire, vous devez utiliser un notificateur de module. L’expert enregistre
l’interface de module que renvoie CreateModule et l’utilise pour recenser un
notificateur de module. Le notificateur de module reçoit une notification quand
l’utilisateur modifie le fichier ou l’enregistre, mais comme ces événements ne
concernent pas l’expert, AfterSave et les fonctions associées sont laissées vides. La
fonction importante est ModuleRenamed, elle est appelée par l’EDI quand
l’utilisateur enregistre le fichier sous un nouveau nom. Voici la déclaration de la
classe du notificateur de module :
TModuleIdentifier = class(TNotifierObject, IOTAModuleNotifier)
public
constructor Create(const Module: IOTAModule);
destructor Destroy; override;
function CheckOverwrite: Boolean;
procedure ModuleRenamed(const NewName: string);
procedure Destroyed;
private
FModule: IOTAModule;
FName: string;
FIndex: Integer;
end;
L’un des moyens d’écrire un notificateur consiste à le faire se recenser lui-même
dans son constructeur. Le destructeur annule le recensement du notificateur.
Dans le cas d’un notificateur de module, l’EDI appelle la méthode Destroyed
quand l’utilisateur ferme le fichier. Dans ce cas, le notificateur doit lui-même

Extensions de l’EDI 14-17


Notification d’un expert des événements de l’EDI

annuler son recensement et libérer sa référence à l’interface de module. L’EDI


libère sa référence au notificateur, ce qui ramène à zéro son compteur de
références et libère l’objet. Il n’est donc pas nécessaire d’écrire le destructeur
défensivement : le notificateur n’est peut être déjà plus recensé.
constructor TModuleNotifier.Create( const Module: IOTAModule);
begin
FIndex := -1;
FModule := Module;
{ Recense ce notificateur. }
FIndex := Module.AddNotifier(self);
{ Stocke l’ancien nom du module. }
FName := ChangeFileExt(ExtractFileName(Module.FileName), ’’);
end;
destructor TModuleNotifier.Destroy;
begin
{ Annule le recensement du notificateur si ce n’est pas déjà fait. }
if Findex >= 0 then
FModule.RemoveNotifier(FIndex);
end;
procedure TModuleNotifier.Destroyed;
begin
{ L’interface du module est détruite, nettoyage du notificateur. }
if Findex >= 0 then
begin
{ Annule le recensement du notificateur. }
FModule.RemoveNotifier(FIndex);
FIndex := -1;
end;
FModule := nil;
end;
L’EDI rappelle la fonction ModuleRenamed du notificateur quand l’utilisateur
renomme le fichier. La fonction attend comme paramètre le nouveau nom qui est
utilisé par l’expert pour actualiser le commentaire dans le fichier. Pour modifier
le tampon source, l’expert utilise une interface de position d’édition. L’expert
recherche la position appropriée, vérifie si le texte trouvé est le bon et remplace
ce texte par le nouveau nom.
procedure TModuleNotifier.ModuleRenamed(const NewName: string);
var
ModuleName: string;
I: Integer;
Editor: IOTAEditor;
Buffer: IOTAEditBuffer;
Pos: IOTAEditPosition;
Check: string;
begin
{ Obtient le nom du module à partir du nouveau nom de fichier. }
ModuleName := ChangeFileExt(ExtractFileName(NewName), ’’);
for I := 0 to FModule.GetModuleFileCount - 1 do
begin
{ Actualise tous les tampons de l’éditeur de code source. }
Editor := FModule.GetModuleFileEditor(I);

14-18 Guide du concepteur de composants


Notification d’un expert des événements de l’EDI

if Supports(Editor, IOTAEditBuffer, Buffer) then


begin
Pos := Buffer.GetEditPosition;
{ Le nom du module se trouve dans la ligne 2 du commentaire.
Saute les espaces de début et copie l’ancien nom de module,
pour vérifier si c’est le bon emplacement. }
Pos.Move(2, 1);
Pos.MoveCursor(mmSkipWhite or mmSkipRight);
Check := Pos.RipText(’’, rfIncludeNumericChars or rfIncludeAlphaChars);
if Check = FName then
begin
Pos.Delete(Length(Check)); // Supprimer l’ancien nom.
Pos.InsertText(ModuleName); // Insérer le nouveau nom.
FName := ModuleName; // Mémoriser le nouveau nom.
end;
end;
end;
end;
Que se passe-t’il si l’utilisateur insère des commentaires au-dessus du nom de
module ? Dans ce cas, vous devez utiliser le notificateur de modification de ligne
pour suivre le numéro de la ligne dans laquelle se trouve le nom du module.
Pour ce faire, utilisez les interfaces IOTAEditLineNotifier et IOTAEditLineTracker
qui sont décrites dans l’aide en ligne.
Il faut être prudent dans l’écriture de notificateurs. Vous devez vous assurer
qu’aucun notificateur ne survit à son expert. Par exemple, si l’utilisateur utilise
l’expert pour créer une nouvelle unité, puis décharge l’expert, il y a toujours un
notificateur attaché à l’unité. Le résultat serait imprévisible mais provoquerait
probablement un blocage de l’EDI. L’expert doit donc garder la trace de tous ses
notificateurs et doit annuler le recensement de chaque notificateur avant sa
destruction. D’un autre côté, si l’utilisateur ferme le fichier d’abord, le
notificateur de module reçoit une notification Destroyed, ce qui signifie que le
notificateur doit annuler lui-même son recensement et libérer toutes ses
références au module. Ensuite, le notificateur doit également se retirer de la liste
des notificateurs de l’expert.
Voici la version définitive de la fonction Execute de l’expert. Elle crée le nouveau
module, utilise l’interface du module, crée un notificateur de module puis
enregistre le notificateur de module dans une liste d’interfaces (TInterfaceList).
procedure DocWizard.Execute;
var
Svc: IOTAModuleServices;
Module: IOTAModule;
Notifier: IOTAModuleNotifier;
begin
{ Renvoie le projet en cours. }
Supports(BorlandIDEServices, IOTAModuleServices, Svc);
Module := Svc.CreateModule(TCreator.Create(creator_type));
Notifier := TModuleNotifier.Create(Module);
list.Add(Notifier);
end

Extensions de l’EDI 14-19


Notification d’un expert des événements de l’EDI

Le destructeur de l’expert parcourt la liste d’interfaces et annule le recensement


de chaque notificateur de la liste. Il ne suffit pas de laisser la liste d’interfaces
libérer les interfaces qu’elle contient car l’EDI contient également les mêmes
interfaces. Vous devez demander à l’EDI de libérer les notificateurs d’interface
afin de libérer les objets notificateur. Dans ce cas, le destructeur fait croire aux
notificateurs que leurs modules ont été détruits. Dans une situation plus
compliquée, il peut s’avérer plus simple d’écrire une fonction Unregister distincte
pour la classe du notificateur.
destructor DocWizard.Destroy; override;
var
Notifier: IOTAModuleNotifier;
I: Integer;
begin
{ Annule le recensement de tous les notificateurs de la liste. }
for I := list.Count - 1 downto 0 do
begin
Supports(list.Items[I], IOTANotifier, Notifier);
{ Fait comme si l’objet associé a été détruit.
Ce qui oblige le notificateur à se libérer lui-même. }
Notifier.Destroyed;
list.Delete(I);
end;
list.Free;
FItem.Free;
end;
Le reste de l’expert gère les détails du recensement de l’expert, l’installation des
options de menu, etc.

14-20 Guide du concepteur de composants


Index
A boîtes de dialogue standard 13-1, 13-2
création 13-2
A propos de, boîte de dialogue 13-2, 13-3 exécution 13-5
ajout de propriétés 13-4 boîtes liste 11-1
exécution 13-6 BorlandIDEServices, variable 14-6
About, unité 13-3 BoundsChanged, méthode 7-15
AboutDlg, unité 13-2 Broadcast, méthode 7-9
abstraites, classes 1-3 Brush, propriété 6-3
affectation, instructions 3-2 BrushCopy, méthode 6-3, 6-7
Aide 8-4
aide en ligne 8-4 C
années bissextiles 11-8
API Open Tools Voir API Tools calendriers 11-1–11-14
API outils natifs 14-2 ajout de dates 11-5–11-11
API Tools 14-1–14-20 définition des propriétés et événements 11-3,
créateurs 14-3, 14-12–14-16 11-7, 11-12
création de fichiers 14-12–14-16 déplacement dans les 11-11–11-14
débogage 14-9–14-10 en lecture seule 12-3–12-5
éditeurs 14-3, 14-11–14-12 redimensionnement 11-4
experts 14-3, 14-3–14-5 sélection du jour en cours 11-10
modules 14-3, 14-11–14-12 canevas 1-8, 6-2, 6-3
notificateurs 14-3 outils de dessin par défaut 10-6
services 14-2, 14-5–14-12 palettes 6-5–6-6
applications Canvas, propriété 1-8
graphiques 1-8, 6-1 caractères 3-2
réalisation de palettes 6-5, 6-6 carrés, dessin 10-10
applications multithreads, envoi de messages 7-10 cercles, dessin 10-10
attributs, éditeurs de propriétés 8-11 chaînes 3-2, 3-9
renvoi 3-9
B champs
bases de données 12-6, 12-8
bases de données 12-1 enregistrements de message 7-2, 7-4, 7-7
propriétés d’accès 12-6–12-7 champs de classe 10-4
BEGIN_MESSAGE_MAP, macro 7-4, 7-7 attribution de nom 4-3
BeginAutoDrag, méthode 7-14 déclaration 10-6
bibliothèques de composants, ajout de Change, méthode 12-13
composants 1-16 classe lien de données sur un champ 12-12
bibliothèques, contrôles personnalisés 1-5 classes 1-2, 1-3, 2-1, 3-3
bitmap de palette, fichiers 1-14 abstraites 1-3
bitmaps 6-4 accès 2-4–2-7, 10-6
ajout aux composants 1-14 ancêtre 2-4
chargement 6-5 création 2-1
comparés aux contrôles graphiques 10-3 définition 1-13, 2-2
hors écran 6-6–6-7 méthodes statiques et 2-8
surfaces de dessin 6-4 méthodes virtuelles et 2-9
boîtes de dialogue 13-1–13-7 dérivation 2-2, 2-9
création 13-1 dérivées 2-4, 2-9
définition de l’état initial 13-2 éditeurs de propriété comme 8-7
éditeurs de propriété comme 8-10 héritage 2-8
standard Windows 13-2 hiérarchie 2-4
création 13-2 instanciation 2-2
exécution 13-5 par défaut 2-4

Index I-1
partie publiée 2-7 contrôles
partie publique 2-6 changement 1-3
propriétés comme 3-3 fenêtrés 1-4
transmission comme paramètres 2-10 forme 10-8
classes ancêtres 2-4 graphiques 6-4, 10-1–10-10
classes dérivées 2-4 création 1-4, 10-3
redéfinition des méthodes 2-9 dessin 10-3, 10-8–10-10
clic, événements 4-2, 4-8 événements 6-7
Click, méthode 4-2, 7-14 palettes et 6-5–6-6
redéfinition 4-7, 11-12 personnalisés 1-5
closures 4-9 pour modifier les données 12-9–12-14
CLX pour scruter les données 12-2–12-8
notifications système 7-10–7-16 réception de la focalisation 1-4
signaux 7-11–7-12 redessin 10-8, 10-9, 11-4, 11-5
CM_EXIT, message 12-13 redimensionnement 6-7, 11-4
CMExit, méthode 12-13 contrôles de redimensionnement
code 5-4 graphiques 6-7
ColorChanged, méthode 7-15 contrôles graphiques 1-4, 6-4, 10-1–10-10
composant, expert 1-10 comparés aux bitmaps 10-3
composants 1-1, 2-1, 3-3 création 1-4, 10-3
abstraites 1-3 dessin 10-3, 10-8–10-10
aide en ligne 8-4 enregistrement des ressources système 1-4
ajout à l’unité existante 1-13 événements 6-7
ajout à la palette de composants 8-1 contrôles mémo 3-9
ajout aux unités 1-13 modification 9-1
bitmaps 1-14 contrôles orientés données 12-1
bitmaps de palette 1-14 création 12-2–12-14
changement 9-1–9-4 destruction 12-7
classes dérivées 1-3, 1-13, 10-2 pour modifier les données 12-9–12-14
création 1-2, 1-9 pour scruter les données 12-2–12-8
dépendances 1-6 réponse aux changements 12-8
déplacement 1-16 contrôles personnalisés 1-5
double-clic 8-16, 8-18–8-19 bibliothèques 1-5
initialisation 3-14, 10-7, 12-7 contrôles préexistants 1-5
installation 8-20 conventions d’attribution de nom
interfaces 2-4, 13-2 champs 4-3
conception 2-7 événements 4-9
exécution 2-6 méthodes 5-2
menus contextuels 8-16, 8-17–8-18 propriétés 3-7
non visuels 1-5, 1-13, 13-3 types d’enregistrement de message 7-7
orientés données 12-1 CopyMode, propriété 6-3
paquets 8-20 CopyRect, méthode 6-3, 6-7
personnalisation 1-3, 3-2, 4-1 crayons 10-6
pour modifier les données 12-9–12-14 changement 10-8
pour scruter les données 12-2–12-8 créateurs 14-3, 14-12–14-16
recensement 1-13, 8-2 CursorChanged, méthode 7-15
répondre aux événements 4-6, 4-8, 4-10, 12-8
ressources, libération 13-5 D
test 1-17, 1-19, 13-6–13-7
DataChange, méthode 12-12
composants non visuels 1-5, 1-13, 13-3
DataField, propriété 12-6
constructeurs 1-18, 3-13, 5-3, 11-4, 11-5, 12-7
DataSource (propriété), contrôles orientés
objets ayant un propriétaire et 10-6, 10-7
données 12-6
redéfinition 9-2
Day, propriété 11-6
Contains, liste (paquets) 8-20
DblClick, méthode 7-14
contextes de périphériques 1-8, 6-2
.dcr, fichiers 1-14

I-2 Guide du concepteur de composants


débogage des experts 14-9–14-10 ajout d’images 14-7
déclarations personnalisation 14-1
classes 2-10, 10-6 suppression de boutons 14-8
public 2-6 Edit, méthode 8-10, 8-11
published 2-7 Editeur d’image, utilisation 1-14
gestionnaires d’événements 4-6, 4-9, 11-13 éditeur de code, affichage 8-19
gestionnaires de messages 7-4, 7-6, 7-8 éditeurs de composants 8-16–8-20
méthodes 5-4 par défaut 8-17
dynamiques 2-10 recensement 8-20
public 5-3 éditeurs de propriété 3-3
statiques 2-8 attributs 8-11
virtuelles 2-9 boîtes de dialogue comme 8-10
nouveaux types de composants 2-3 comme classes dérivées 8-7
propriétés 3-3, 3-4–3-7, 3-8, 3-14, 4-9, 10-4 éditeurs de propriétés 8-7–8-13
stored 3-14 recensement 8-12–8-13
types définis par l’utilisateur 10-3 éditeurs, API Tools 14-3, 14-11–14-12
déclarations de type, propriétés 10-3 Ellipse, méthode 6-3
default ellipses, dessin 10-10
directive 3-13, 9-3 EnabledChanged, méthode 7-15
mot réservé 3-8 END_MESSAGE_MAP, macro 7-4, 7-7
DefaultHandler, méthode 7-3 ensemble, types 3-2
délégation 4-1 ensembles 3-2
DelphiInterface, classe 14-6 enveloppes 1-5, 13-2
déploiement initialisation 13-3
extensions de l’EDI 14-20 Voir aussi enveloppes de composants
déploiement des extensions de l’EDI 14-5 enveloppes de composants 1-5, 13-2
déréférencement de pointeurs d’objet 2-10 initialisation 13-3
dérivation de classes 2-9 envoi des messages 7-8–7-10
destructeurs 5-3, 12-7 erreurs de compilation, directive override et 2-9
objets ayant un propriétaire et 10-6, 10-7 événements 1-8, 4-1–4-10
fichiers .dfm 3-12 accès 4-6
directives attribution de nom 4-9
default 3-13, 9-3 contrôles graphiques 6-7
dynamic 2-10 définition de nouveaux 4-7–4-10
override 2-9, 7-4 fourniture d’aide 8-4
protected 4-6 gestion de message 7-4, 7-6
public 4-6 hérités 4-5
published 3-3, 4-6, 13-4 implémentation 4-2, 4-5
stored 3-14 récupérer 4-4
virtual 2-9 réponse aux 4-6, 4-8, 4-10, 12-8
Dispatch, méthode 7-3, 7-5 standard 4-5, 4-5–4-7
DoExit, méthode 12-14 événements clavier 4-4, 4-10
DoMouseWheel, méthode 7-14 événements souris 10-2
données, accès 12-1 événements standard 4-5, 4-5–4-7
double-clics personnalisation 4-6
composants 8-16 événements système, personnalisation 7-16
réponse aux 8-18–8-19 EventFilter (méthode), événements système 7-15
DragOver, méthode 7-14 exceptions 5-2, 7-3, 13-5
Draw, méthode 6-3, 6-7 Execute (méthode), boîtes de dialogue 13-5
dynamic, directives 2-10 experts
API Tools 14-3
E Composant 1-10
création 14-2, 14-3–14-5
écriture seule, propriétés 3-7
débogage 14-9–14-10
EDI
installation 14-5, 14-20
ajout d’actions 14-7–14-8

Index I-3
réponse aux événements de l’EDI 14-16 GetAttributes, méthode 8-11
types 14-4 GetFloatValue, méthode 8-9
experts fiche 14-4 GetMethodValue, méthode 8-9
experts menu 14-4 GetOrdValue, méthode 8-9
experts projet 14-4 GetPalette, méthode 6-6
experts référentiel d’objets 14-4 GetProperties, méthode 8-11
GetStrValue, méthode 8-9
F GetValue, méthode 8-9
glisser-déplacer, événements 10-2
fenêtre
Graphic, propriété 6-4
classe 1-5
graphiques 6-1–6-8
contrôles 1-4
chargement 6-4, 6-5
gestion de message 11-4
complexes 6-6
handles 1-4, 1-6
conteneurs 6-4
procédures 7-3
enregistrement 6-4
fiches, en composants 13-1
fonctions, appel 6-2
fichiers, graphiques 6-4
indépendants 6-3
FillRect, méthode 6-3
méthodes 6-3, 6-5, 6-7
finally, mot réservé 6-7, 13-5
copie d’images 6-7
FloodFill, méthode 6-3
palettes 6-6
focalisation 1-4
outils de dessin 6-2, 6-8, 10-6
focalisation de saisie 1-4
changement 10-8
fonctions 1-7
redessiner les images 6-7
API Windows 1-4, 6-1
redimensionnement 6-7
attribution de nom 5-2
stockage 6-4
événements et 4-3
graphiques (méthodes), palettes 6-6
graphiques 6-2
grilles 11-1, 11-3, 11-5, 11-12
lecture des propriétés 3-7, 8-9, 8-11
fonctions membres
paramètres de propriété 3-7
H
Font, propriété 6-3 Handle, propriété 1-4, 1-6, 6-3
FontChanged, méthode 7-15 HandleException, méthode 7-3
formes géométriques, dessin 10-10 héritage de classe 2-8
FReadOnly 12-9 héritées
méthodes 4-7
G propriétés 10-2, 11-3
publication 3-3
GDI, applications 1-8, 6-1 hérités
gestion de message 7-4–7-5 événements 4-5
gestionnaires d’événements 1-8, 4-2, 4-9, 12-8 hiérarchie (classes) 2-4
affichage de l’éditeur de code 8-19 HookEvents, méthode 7-12
déclarations 4-6, 4-9, 11-13 hors écran, bitmaps 6-6–6-7
méthodes 4-3, 4-5, 4-6
par défaut, redéfinition 4-10 I
paramètres 4-3, 4-8, 4-9, 4-10
notification, événements 4-8 icônes 6-4
pointeurs 4-2, 4-3, 4-9 ajout aux composants 1-14
transmission de paramètres par référence 4-10 identificateurs
types 4-3–4-4, 4-8–4-9 champs de classe 4-3
vides 4-10 événements 4-9
gestionnaires de messages 7-2, 7-3, 11-4, 11-5 méthodes 5-2
création 7-6–7-8 paramètres de propriété 3-7
déclarations 7-6, 7-8 types d’enregistrement de message 7-7
méthodes, surcharge 7-7 images 6-3, 6-3–6-6
par défaut 7-3 dessin 10-8
redéfinition 7-4 redessiner 6-7
réduction du scintillement 6-6

I-4 Guide du concepteur de composants


indépendants du périphérique, graphiques 6-1 IOTAServices 14-6
index 3-9 IOTAThreadNotifier 14-17
index, mot réservé 11-7 IOTAToDoServices 14-6
indicateurs 12-5 IOTAToolsFilter 14-6
informations de types à l’exécution 2-7 IOTAToolsFilterNotifier 14-17
inspecteur d’objets 3-2, 8-7 IOTAWizard 14-2, 14-3
aide sur 8-4 IOTAWizardServices 14-6
modification des propriétés de tableau 3-3
Installation de composants, boîte de dialogue 1-16 K
instances 4-2
K, notes de bas de page (système d’aide) 8-5
INTAComponent 14-12
KeyDown, méthode 7-14, 12-11
INTAServices 14-5, 14-6–14-7, 14-16
KeyPress, méthode 7-14
interfaces 2-4, 13-2, 13-3
KeyString, méthode 7-14
API Tools 14-1, 14-4
KeyUp, méthode 7-14
numéros de version 14-10–14-11
conception 2-7
éléments de programme non visuels 1-5
L
exécution 2-6 lecture des paramètres de propriété 3-7
propriétés 3-11 lecture seule, propriétés 2-6, 2-7, 3-7, 12-3
propriétés, déclaration 13-4 libellés 1-4
interfaces d’exécution 2-6 libération des ressources 13-5
interfaces de conception 2-7 liens de données 12-5–12-8
interfaces du composant initialisation 12-7
création 13-3 Lines, propriété 3-9
propriétés, déclaration 13-4 LineTo, méthode 6-3
Invalidate, méthode 10-9 listes de recherche (système d’aide) 8-5
IOTAActionServices 14-5 Loaded, méthode 3-14
IOTABreakpointNotifier 14-16 LoadFromFile (méthode), graphiques 6-5
IOTACodeCompletionServices 14-5 LParam, paramètre 7-9
IOTAComponent 14-12 lParam, paramètre 7-2
IOTACreator 14-13
IOTADebuggerNotifier 14-16 M
IOTADebuggerServices 14-5
IOTAEditLineNotifier 14-16 macros 7-4
IOTAEditor 14-12 MainWndProc, méthode 7-3
IOTAEditorNotifier 14-17 mémoire (gestion), méthodes dynamiques ou
IOTAEditorServices 14-6 virtuelles 2-10
IOTAFile 14-13, 14-15 menus contextuels, ajout d’éléments 8-17–8-18
IOTAFormNotifier 14-17 MESSAGE_HANDLER, macro 7-4
IOTAFormWizard 14-3 MESSAGE_MAP, macro 7-7
IOTAIDENotifier 14-17 messages 7-1–7-8, 11-4
IOTAKeyBindingServices 14-6 applications multithreads 7-10
IOTAKeyboardDiagnostics 14-6 déclarations de gestionnaires 7-4
IOTAKeyboardServices 14-6 décomposeur 7-2
IOTAMenuWizard 14-3 définis par l’utilisateur 7-6, 7-8
IOTAMessageNotifier 14-17 définition 7-1, 7-2
IOTAMessageServices 14-6 enregistrement (types), déclaration 7-7
IOTAModule 14-11 enregistrements 7-2, 7-4
IOTAModuleNotifier 14-17 envoi 7-8–7-10
IOTAModuleServices 14-6, 14-11 identificateurs 7-6
IOTANotifier 14-16 interception 7-5
IOTAPackageServices 14-6 liés à la souris et au clavier 12-10
IOTAProcessModNotifier 14-17 Linux Voir notifications système
IOTAProcessNotifier 14-17 souris 12-10
IOTAProjectWizard 14-3 touche 12-10
Windows 7-1–7-10

Index I-5
messages de frappes de touches 4-6, 12-10 O
messages définis par l’utilisateur 7-6, 7-8
messages liés à la souris 12-10 objets
messages souris 7-2, 12-10 ayant un propriétaire 10-5–10-8
métafichiers 6-4 initialisation 10-7
méthodes 1-7, 5-1, 11-11 instanciation 4-2
appel 4-6, 5-3, 10-4 temporaires 6-7
attribution de nom 5-2 objets ayant un propriétaire 10-5–10-8
déclaration 5-4 initialisation 10-7
dynamiques 2-10 OnChange, événement 6-7, 10-8, 11-13, 12-13
public 5-3 OnClick, événement 4-2, 4-3, 4-5
statiques 2-8 OnCreate, événement 1-17
virtuelles 2-9 OnDataChange, événement 12-8, 12-12
dessin 10-9, 10-10 OnDblClick, événement 4-5
gestion des messages 7-2, 7-3, 7-5 OnDragDrop, événement 4-5
gestionnaires d’événements 4-3, 4-5, 4-6 OnDragOver, événement 4-5
redéfinition 4-6 OnEndDrag, événement 4-5
graphiques 6-3, 6-5, 6-7 OnEnter, événement 4-5
palettes 6-6 OnExit, événement 12-14
héritées 4-7 OnKeyDown, événement 4-5, 7-13, 12-11
initialisation 3-14 OnKeyPress, événement 4-5, 7-13
propriétés et 3-5–3-7, 5-1, 5-2, 10-4 OnKeyString, événement 7-13
protected 5-3 OnKeyUp, événement 4-5, 7-13
public 5-3 OnMouseDown, événement 4-5, 7-13, 12-10
redéfinition 2-9, 7-4, 7-5, 7-7, 11-12 OnMouseMove, événement 4-5, 7-13
répartition 2-8 OnMouseUp, événement 4-5, 7-13
virtuelles 2-9, 5-4 opérations avec effet 6-7
méthodes dynamiques 2-10 optimisation des ressources système 1-4
méthodes statiques 2-8 outils de dessin 6-2, 6-8, 10-6
modèles de composants 2-2 changement 10-8
Modified, méthode 12-13 outils natifs de l’API 14-6–14-10
modules 1-12 override, directive 2-9, 7-4
API Tools 14-3, 14-11–14-12 Owner, propriété 1-18
mois, renvoyer actuel 11-8
Month, propriété 11-6 P
mots clés 8-5
Paint, méthode 6-7, 10-9, 10-10
protected 4-6
PaintRequest, méthode 7-14
MouseDown, méthode 7-14, 12-10
palette des composants
MouseMove, méthode 7-14
ajout de bitmaps personnalisés 1-14
MouseUp, méthode 7-14
ajout de composants 8-1
MoveTo, méthode 6-3
déplacement des composants 1-16
Msg, paramètre 7-3
PaletteChanged, méthode 6-6, 7-15
MyEvent_ID, type 7-16
palettes 6-5–6-6
comportement par défaut 6-6
N spécification 6-6
nombres 3-2 paquets 8-20
valeurs de propriété 3-13 composants 8-20
notificateurs 14-3 liste Contains 8-20
API Tools 14-16–14-20 liste Requires 8-20
écriture 14-19 par défaut
notification, événements 4-8 classe ancêtre 2-4
notifications système 7-10–7-16 gestionnaires
notifications système Linux 7-10–7-16 événements 4-10
Nouveau, commande 1-12 message 7-3
Nouvelle unité, commande 1-12 redéfinition 4-10

I-6 Guide du concepteur de composants


valeurs de propriété 3-8 lecture seule 2-6, 2-7, 3-7, 12-3
changement 9-2, 9-3 modification sous forme textuelle 8-8
spécification 3-13 nodefault 3-8
paramètres présentation 1-6
classes comme 2-10 publiées 11-3
gestionnaires d’événements 4-3, 4-8, 4-9, 4-10 redéclaration 3-13, 4-6
messages 7-2, 7-3, 7-4, 7-7, 7-9, 7-10 sous-composants 3-10
paramètres de propriété 3-7 spécification des valeurs 3-13, 8-9
propriétés de tableaux 3-9 stockage 3-14
paramètres de propriété stockage de données interne 3-4, 3-7
écriture 3-9 stockage et chargement des propriétés non
lecture 3-9 publiées 3-15–3-17
Parent, propriété 1-18 tableau 3-3, 3-9
Pen, propriété 6-3 types 3-2, 3-9, 8-9, 10-3
Perform, méthode 7-9 valeurs par défaut 3-8, 3-13
personnalisation des composants 3-2 redéfinition 9-2, 9-3
picture, objets 6-4 visualisation 8-9
pinceaux 10-6 protected
changement 10-8 directive 4-6
Pixel, propriété 6-3 mot clé 3-3, 4-6
pointeurs protégés
classes 2-10 événements 4-6
méthode 4-2, 4-3, 4-9 public
valeurs de propriété par défaut 3-13 directive 4-6
pointeurs de classe 2-10 mot clé 4-6
pointeurs de méthode 4-2, 4-3, 4-9 partie de classes 2-6
PostMessage, méthode 7-10 propriétés 3-13
Presse-papiers (formats), ajout 8-16, 8-19 publiées
private, propriétés 3-5 propriétés
procédures 1-7, 4-3 exemple 10-2, 11-3
attribution de nom 5-2 published 3-3
paramètres de propriété 8-12 directive 3-3, 4-6, 13-4
programmation orientée objet 2-1–2-10 mot clé 4-6
déclarations 2-3, 2-10 partie de classes 2-7
classes 2-6, 2-7 propriétés 3-13, 3-14
méthodes 2-8, 2-9, 2-10
propriétés 3-1–3-14 Q
accès 3-5–3-7
QApplication_postEvent, méthode 7-16
actualisation 1-7
QCustomEvent_create, fonction 7-16
boîtes de dialogue standard 13-2
QEvent 7-13
changement 8-7–8-13, 9-2, 9-3
QKeyEvent 7-13
chargement 3-14
QMouseEvent 7-13
comme classes 3-3
Qt (événements), messages 7-16
composants enveloppe 13-4
déclaration 3-3, 3-4–3-7, 3-8, 3-14, 4-9, 10-4
stored 3-14
R
types définis par l’utilisateur 10-3 read, méthode 3-7
écriture de valeurs 3-7, 8-9 read, mot réservé 3-9, 10-4
écriture seule 3-7 ReadOnly, propriété 12-4, 12-9, 12-11
événements et 4-1, 4-2 réalisation de palettes 6-5, 6-6
fourniture d’aide 8-4 recensement
héritées 3-3, 10-2, 11-3 composants 1-13
interfaces 3-11 éditeurs de composants 8-20
lecture des valeurs 8-9 éditeurs de propriété 8-12–8-13
lecture et écriture 3-5 Rectangle, méthode 6-3

Index I-7
rectangles, dessin 10-10 TCustomControl 1-4
redéfinition des méthodes 2-9, 7-4, 7-5, 11-12 TCustomGrid 11-1, 11-3
redessin des contrôles 10-8, 10-9, 11-4, 11-5 TCustomListBox 1-4
redessiner les images 6-7 TDateTime, type 11-6
redimensionnement des contrôles 11-4 TDefaultEditor 8-17
Register, procédure 1-13, 8-2 temporaires, objets 6-7
RegisterComponents, procédure 1-14, 8-2 TEnumProperty, type 8-8
RegisterPropertyEditor, procédure 8-12 test
Requires, liste (paquets) 8-20 composants 1-17, 1-19, 13-6–13-7
.res, fichiers 1-16 valeurs 3-7
ressources 1-8, 6-1 TextChanged, méthode 7-15
en mémoire cache 6-2 TextHeight, méthode 6-3
libération 13-5 TextOut, méthode 6-3
système, optimisation 1-4 TextRect, méthode 6-3
ressources en mémoire cache 6-2 TextWidth, méthode 6-3
ressources système, préservation 1-4 TFieldDataLink 12-5
Result, paramètre 7-7 TFloatProperty, type 8-8
RTTI 2-7 TFontNameProperty, type 8-8
TFontProperty, type 8-8
S TGraphic 6-4
TGraphicControl 1-4, 10-2
SaveToFile (méthode), graphiques 6-5
THandleComponent 7-12
SelectCell, méthode 11-14, 12-4
TIcon 6-4
Self, paramètre 1-18
TiledDraw, méthode 6-7
SendMessage, méthode 7-10
TIntegerProperty, type 8-7, 8-9
services, API Tools 14-2, 14-5–14-12
TKeyPressEvent, type 4-4
SetFloatValue, méthode 8-9
TLabel 1-4
SetMethodValue, méthode 8-9
TListBox 1-4
SetOrdValue, méthode 8-9
TMessage 7-5, 7-7
SetStrValue, méthode 8-9
TMetafile 6-4
SetValue, méthode 8-9
TMethod, type 7-12
ShowHintChanged, méthode 7-15
TMethodProperty, type 8-8
signaux, réponse aux signaux (CLX) 7-11–7-12
TNotifyEvent 4-8
simple, types 3-2
TObject 2-4
sous-classement des contrôles Windows 1-5
ToolsAPI, unité 14-2
sous-composants, propriétés 3-10
TOrdinalProperty, type 8-7
stored, directive 3-14
TPicture, type 6-4
StretchDraw, méthode 6-3, 6-7
TPropertyAttributes 8-11
StyleChanged, méthode 7-15
TPropertyEditor, classe 8-7
systèmes d’aide 8-4
transfert d’enregistrements 13-2
fichiers 8-4
try, mot réservé 6-7, 13-5
mots clés 8-5
TSetElementProperty, type 8-8
TSetProperty, type 8-8
T TStringProperty, type 8-8
tableaux 3-3, 3-9 TWinControl 1-4, 4-5
TabStopChanged, méthode 7-15 types
Tapplication, événements système 7-13 définis par l’utilisateur 10-3
TBitmap 6-4 enregistrement de message 7-7
TCalendar 11-1 propriétés 3-2, 3-9, 8-9
TCharProperty, type 8-8 types définis par l’utilisateur 10-3
TClassProperty, type 8-8 types énumérés 3-2, 10-3
TColorProperty, type 8-8
TComponent 1-5 U
TComponentProperty, type 8-8
unités, ajout de composants 1-13
TControl 1-4, 4-5, 4-6
UpdateCalendar, méthode 12-4

I-8 Guide du concepteur de composants


V contextes de périphériques 1-8, 6-2
contrôles, sous-classement 1-5
valeurs 3-2 événements 4-5
booléennes 3-2, 3-13, 12-5 fonctions API 1-4, 6-1
propriété par défaut 3-8, 3-13 messages 7-1–7-10
redéfinition 9-2, 9-3 WM_APP, constante 7-6
test 3-7 WM_KEYDOWN, message 12-10
valeurs booléennes 3-2, 3-13, 12-5 WM_LBUTTONBUTTON, message 12-10
var (mot réservé), gestionnaires d’événements 4-4 WM_MBUTTONDOWN, message 12-10
VCL 1-1–1-2 WM_PAINT, message 7-4
virtual WM_RBUTTONDOWN, message 12-10
directive 2-9 WM_SIZE, message 11-4
virtuelle WndProc, méthode 7-3, 7-5
tables de méthode 2-9 WordWrap, propriété 9-1
virtuelles WParam, paramètre 7-9
méthodes 2-9, 5-4 wParam, paramètre 7-2
éditeurs de propriété 8-8–8-10 write, méthode 3-7
propriétés comme 3-2 write, mot réservé 3-9, 10-4
VisibleChanged, méthode 7-15
Y
W Year, propriété 11-6
WidgetDestroyed, propriété 7-14
Windows
boîtes de dialogue standard 13-2
création 13-2
exécution 13-5

Index I-9
I-10 Guide du concepteur de composants

You might also like