Guide Du Concepteur Des Composants en delphi

194
Guide du concepteur de composants Borland ® Delphi 7 pour Windows

Transcript of Guide Du Concepteur Des Composants en delphi

Page 1: Guide Du Concepteur Des Composants en delphi

Guide du concepteur de composants

Borland®

Delphi™ 7 pour Windows™

Page 2: Guide Du Concepteur Des Composants en delphi

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

Page 3: Guide Du Concepteur Des Composants en delphi

i

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

Bibliothèque de classes. . . . . . . . . . . . . . 1-1Composants et classes. . . . . . . . . . . . . . . 1-2Création de composants . . . . . . . . . . . . . 1-3

Modification de contrôles existants . . . . . 1-3Création de contrôles fenêtrés . . . . . . . . 1-4Création de contrôles graphiques . . . . . . 1-4Sous-classement de contrôles Windows. . . 1-5Création de composants non visuels . . . . 1-5

Contenu d’un composant ?. . . . . . . . . . . . 1-5Suppression des dépendances . . . . . . . . 1-6Définition des propriétés, méthodes

et événements . . . . . . . . . . . . . . . . . 1-6Propriétés . . . . . . . . . . . . . . . . . . 1-6Méthodes . . . . . . . . . . . . . . . . . . 1-7Evénements . . . . . . . . . . . . . . . . . 1-7

Encapsulation des graphiques . . . . . . . . 1-8Recensement des composants . . . . . . . . 1-9

Création d’un nouveau composant . . . . . . . 1-9Création d’un composant avec

l’expert composant . . . . . . . . . . . . . . 1-10Création manuelle d’un composant . . . . . 1-12

Création d’un fichier unité . . . . . . . . 1-12Dérivation du composant . . . . . . . . . 1-13Recensement du composant. . . . . . . . 1-13

Création de bitmaps pour les composants .1-14Installation d’un composant

dans la palette de composants . . . . . . . . . 1-16Emplacement des fichiers du composant . . 1-17

Test des composants non installés. . . . . . . . 1-17Test des composants installés . . . . . . . . . . 1-19

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

Définition de nouvelles classes . . . . . . . . . 2-1Dérivation de nouvelles classes . . . . . . . 2-2

Modification des valeurs par défaut d’une classe pour éviter les répétitions 2-2

Ajout de nouvelles capacités à une classe . . . . . . . . . . . . . . . . 2-3

Déclaration d’une nouvelle classe de composant . . . . . . . . . . . . . . . . . 2-3

Ancêtres, descendants et hiérarchies des classes . . . . . . . . . . . . . . . . . . . . . 2-3

Contrôle des accès . . . . . . . . . . . . . . . . . 2-4Masquer les détails d’implémentation . . . . 2-5Définition de l’interface avec le concepteur

des composants . . . . . . . . . . . . . . . . 2-6Définition de l’interface d’exécution . . . . . 2-6Définition de l’interface de conception. . . . 2-7

Répartition des méthodes . . . . . . . . . . . . . 2-8Méthodes statiques . . . . . . . . . . . . . . . 2-8

Exemple de méthodes statiques . . . . . . 2-8Méthodes virtuelles . . . . . . . . . . . . . . . 2-9

Redéfinition des méthodes . . . . . . . . . 2-9Méthodes dynamiques . . . . . . . . . . . . 2-10

Membres abstraits d’une classe . . . . . . . . . 2-10Classes et pointeurs . . . . . . . . . . . . . . . 2-10

Chapitre 3Création de propriétés 3-1Pourquoi créer des propriétés ?. . . . . . . . . . 3-1Types de propriétés. . . . . . . . . . . . . . . . . 3-2Publication des propriétés héritées . . . . . . . . 3-3Définition des propriétés . . . . . . . . . . . . . 3-4

Déclarations des propriétés . . . . . . . . . . 3-4Stockage interne des données . . . . . . . . . 3-4Accès direct . . . . . . . . . . . . . . . . . . . 3-5Méthodes d’accès . . . . . . . . . . . . . . . . 3-5

Méthode read . . . . . . . . . . . . . . . . 3-7Méthode write . . . . . . . . . . . . . . . . 3-7

Valeurs par défaut d’une propriété . . . . . . 3-8Spécification d’aucune valeur par défaut 3-8

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

pour sous-composants . . . . . . . . . . . . . 3-10Création des propriétés pour interfaces . . . . 3-11Stockage et chargement des propriétés . . . . 3-12

Utilisation du mécanisme de stockage et de chargement . . . . . . . . . . . . . . 3-12

Spécification des valeurs par défaut . . . . 3-13Détermination du stockage . . . . . . . . . 3-14Initialisation après chargement . . . . . . . 3-14Stockage et chargement

des propriétés non publiées . . . . . . . . . . . . . . . . . 3-15

Création de méthodes pour le stockage et le chargement de valeurs de propriétés . 3-15

Table des matières

Page 4: Guide Du Concepteur Des Composants en delphi

ii

Redéfinition de la méthode DefineProperties . . . . . . . . . . . . . 3-16

Chapitre 4Création d’événements 4-1Qu’est-ce qu’un événement ? . . . . . . . . . . 4-1

Les événements sont des pointeurs de méthodes. . . . . . . . . . . . . . . . . . 4-2

Les événements sont des propriétés . . . . . 4-2Les types d’événements sont des types

de pointeurs de méthodes. . . . . . . . . . 4-3Les types gestionnaire d’événement

sont des procédures . . . . . . . . . . . 4-3Les gestionnaires d’événements

sont facultatifs . . . . . . . . . . . . . . . . 4-4Implémentation des événements standard . . . 4-5

Identification des événements standard. . . 4-5Evénements standard

pour tous les contrôles . . . . . . . . . . 4-5Evénements standard pour les contrôles

standard . . . . . . . . . . . . . . . . . . 4-5Rendre visibles des événements . . . . . . . 4-6Changement de la gestion

des événements standard . . . . . . . . . . 4-6Définition de vos propres événements . . . . . 4-7

Déclenchement de l’événement . . . . . . . 4-7Deux sortes d’événements. . . . . . . . . 4-8

Définition du type de gestionnaire . . . . . 4-8Notifications simples . . . . . . . . . . . . 4-8Gestionnaires d’événements spécifiques. 4-8Renvoi d’informations

à partir du gestionnaire . . . . . . . . . 4-9Déclaration de l’événement . . . . . . . . . . 4-9

Les noms d’événement débutent par “On” . . . . . . . . . . . . . . . . . . 4-9

Appel de l’événement . . . . . . . . . . . . . 4-9

Chapitre 5Création de méthodes 5-1Eviter les interdépendances . . . . . . . . . . . 5-1Noms des méthodes. . . . . . . . . . . . . . . . 5-2Protection des méthodes . . . . . . . . . . . . . 5-3

Méthodes qui doivent être publiques . . . . 5-3Méthodes qui doivent être protégées . . . . 5-3Méthodes abstraites . . . . . . . . . . . . . . 5-4

Rendre virtuelles des méthodes . . . . . . . . . 5-4Déclaration des méthodes . . . . . . . . . . . . 5-4

Chapitre 6Graphiques et composants 6-1Présentation des graphiques . . . . . . . . . . . 6-1Utilisation du canevas . . . . . . . . . . . . . . . 6-3Travail sur les images . . . . . . . . . . . . . . . 6-3

Utilisation d’une image, d’un graphique ou d’un canevas . . . . . . . . . . . . . . . . 6-4

Chargement et stockage des graphiques . . . 6-4Gestion des palettes. . . . . . . . . . . . . . . 6-5

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

Bitmaps hors écran . . . . . . . . . . . . . . . . . 6-6Création et gestion des bitmaps hors écran . 6-6Copie des images bitmap . . . . . . . . . . . 6-7

Réponse aux changements . . . . . . . . . . . . 6-7

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

Compréhension du système de gestion des messages. . . . . . . . . . . . . . . . . . . . 7-1

Que contient un message Windows ? . . . . 7-2Répartition des messages . . . . . . . . . . . 7-3

Suivi du flux des messages . . . . . . . . 7-3Modification de la gestion des messages . . . . 7-4

Surcharge de la méthode du gestionnaire . . 7-4Utilisation des paramètres d’un message . . 7-4Interception des messages . . . . . . . . . . . 7-5

Création de nouveaux gestionnaires de messages . . . . . . . . . . . . . . . . . . . . 7-6

Définition de vos propres messages . . . . . 7-6Déclaration d’un identificateur

de message . . . . . . . . . . . . . . . . . 7-6Déclaration d’un type enregistrement

de message . . . . . . . . . . . . . . . . . 7-6Déclaration d’une nouvelle méthode

de gestion d’un message . . . . . . . . . . . 7-7Envoi des messages . . . . . . . . . . . . . . . 7-8

Diffusion d’un message à tous les contrôles d’une fiche . . . . . . . . . . 7-8

Appel direct du gestionnaire de message d’un contrôle. . . . . . . . . 7-9

Envoi d’un message à l’aide de la file d’attente des messages Windows . . . 7-10

Envoi d’un message qui ne s’exécute pas immédiatement . . . . . . . . . . . 7-10

Réponse aux notifications du système à l’aide de CLX . . . . . . . . . . . . . . . . . 7-10

Réponse aux signaux . . . . . . . . . . . . . 7-11

Page 5: Guide Du Concepteur Des Composants en delphi

iii

Affectation de gestionnaires de signaux personnalisés . . . . . . . . . . . . . . . 7-12

Réponse aux événements système . . . . . .7-12Evénements couramment utilisés . . . . 7-13Surcharge de la méthode EventFilter . . 7-15Génération des événements Qt . . . . . . 7-16

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

Recensement des composants . . . . . . . . . . 8-1Déclaration de la procédure Register . . . . 8-2Ecriture de la procédure Register . . . . . . 8-2

Spécification des composants . . . . . . . 8-3Spécification de la page de palette . . . . 8-3Utilisation de la fonction

RegisterComponents . . . . . . . . . . . 8-3Fournir l’aide pour vos composants . . . . . . 8-4

Création du fichier d’aide. . . . . . . . . . . 8-4Création des entrées . . . . . . . . . . . . 8-4Aide contextuelle des composants . . . . 8-6Ajout des fichiers d’aide

des composants . . . . . . . . . . . . . . 8-6Ajout d’éditeurs de propriétés . . . . . . . . . . 8-7

Dérivation d’une classe éditeur de propriétés . . . . . . . . . . . . . . . . . 8-7

Modification de la propriété sous une forme textuelle . . . . . . . . . . 8-8

Affichage de la valeur de la propriété . . 8-9Définition de la valeur de la propriété . 8-9

Modification globale de la propriété . . . . 8-10Spécification des attributs de l’éditeur . . . 8-11Recensement de l’éditeur de propriétés. . . 8-12

Catégories de propriétés . . . . . . . . . . . . . 8-13Recensement d’une propriété à la fois . . .8-14Recensement de plusieurs propriétés

en une seule fois . . . . . . . . . . . . . . . 8-14Spécification de catégories de propriétés . . 8-15Utilisation de la fonction

IsPropertyInCategory . . . . . . . . . . . . 8-16Ajout d’éditeurs de composants . . . . . . . . . 8-16

Ajout d’éléments au menu contextuel. . . . 8-17Spécification d’éléments de menu . . . . 8-17Implémentation des commandes . . . . . 8-18

Modification du comportement suite à un double-clic . . . . . . . . . . . . 8-18

Ajout de formats de presse-papiers . . . . . 8-19Recensement d’un éditeur de composants .8-20

Compilation des composants en paquets. . . . 8-20

Chapitre 9Modification d’un composant existant 9-1

Création et recensement du composant . . . . . 9-1Modification de la classe composant. . . . . . . 9-2

Redéfinition du constructeur . . . . . . . . . 9-2Spécification de la nouvelle valeur

par défaut de la propriété . . . . . . . . . . 9-3

Chapitre 10Création d’un contrôle graphique 10-1Création et recensement du composant . . . . 10-1Publication des propriétés héritées . . . . . . . 10-2Ajout de fonctionnalités graphiques . . . . . . 10-3

Détermination de ce qui doit être dessiné . 10-3Déclaration du type de la propriété. . . 10-3Déclaration de la propriété . . . . . . . . 10-4Ecriture de la méthode

d’implémentation . . . . . . . . . . . . 10-4Redéfinition du constructeur

et du destructeur . . . . . . . . . . . . . . 10-5Modification des valeurs par défaut

des propriétés . . . . . . . . . . . . . . 10-5Publication du crayon et du pinceau. . . . 10-5

Déclaration des champs de classe . . . . 10-6Déclaration des propriétés d’accès . . . 10-6Initialisation des classes ayant

un propriétaire . . . . . . . . . . . . . . 10-7Définition des propriétés des classes

ayant un propriétaire . . . . . . . . . . 10-8Dessin de l’image du composant . . . . . . 10-8Adaptation du dessin de la forme . . . . 10-10

Chapitre 11Personnalisation d’une grille 11-1Création et recensement du composant . . . . 11-1Publication des propriétés héritées . . . . . . . 11-3Modification des valeurs initiales . . . . . . . 11-3Redimensionnement des cellules . . . . . . . . 11-4Remplissage des cellules. . . . . . . . . . . . . 11-5

Suivi de la date . . . . . . . . . . . . . . . . 11-6Stockage interne de la date. . . . . . . . 11-6Accès au jour, au mois et à l’année . . . 11-7Génération des numéros de jours . . . . 11-8Sélection du jour en cours . . . . . . . .11-10

Navigation de mois en mois et d’année en année . . . . . . . . . . . . . .11-11

Navigation de jour en jour . . . . . . . . . . .11-12Déplacement de la sélection . . . . . . . . .11-12

Page 6: Guide Du Concepteur Des Composants en delphi

iv

Fourniture d’un événement OnChange . . 11-13Exclusion des cellules vides . . . . . . . . 11-13

Chapitre 12Contrôles orientés données 12-1Création d’un contrôle pour scruter

les données . . . . . . . . . . . . . . . . . . . . 12-2Création et recensement du composant . . . 12-2Fonctionnement du contrôle en lecture

seulement . . . . . . . . . . . . . . . . . . .12-3Ajout de la propriété ReadOnly . . . . . 12-4Autorisation des mises à jour

nécessaires . . . . . . . . . . . . . . . . . 12-4Ajout du lien aux données . . . . . . . . . . 12-5

Déclaration du champ de classe . . . . . 12-6Déclaration des propriétés d’accès . . . . 12-6Exemple de déclaration des propriétés

d’accès . . . . . . . . . . . . . . . . . . .12-6Initialisation du lien de données . . . . . 12-7

Réponse aux changements de données . . . 12-8Création d’un contrôle de modification

de données . . . . . . . . . . . . . . . . . . . .12-9Modification de la valeur par défaut

de FReadOnly . . . . . . . . . . . . . . . . .12-9Gestion des messages liés à la souris

ou au clavier. . . . . . . . . . . . . . . . . 12-10Réponse aux messages indiquant

la manipulation de la souris. . . . . . 12-10Réponse aux messages indiquant

la manipulation du clavier. . . . . . . 12-11Mise à jour de la classe lien de données

sur un champ . . . . . . . . . . . . . . . . 12-12Modification de la méthode Change . . . 12-13Mise à jour de l’ensemble de données . . 12-13

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

Définition de l’interface du composant . . . . 13-2Création et recensement du composant . . . . 13-2Création de l’interface du composant . . . . . 13-3

Inclusion de l’unité de la fiche . . . . . . . 13-3Ajout des propriétés de l’interface . . . . . 13-4Ajout de la méthode Execute . . . . . . . . 13-5

Test du composant . . . . . . . . . . . . . . . . 13-6

Chapitre 14Extensions de l’EDI 14-1Présentation de l’API Tools . . . . . . . . . . . 14-2Conception d’une classe expert . . . . . . . . . 14-3

Implémentation des interfaces de l’expert . 14-4Installation du paquet de l’expert. . . . . . 14-5

Accès aux services de l’API Tools . . . . . . . 14-5Utilisation d’objets natifs de l’EDI . . . . . 14-6

Utilisation de l’interface INTAServices . 14-6Ajout d’une image à la liste d’images . 14-7Ajout d’une action à la liste d’actions . 14-7Suppression de boutons

de barres d’outils . . . . . . . . . . . . 14-8Débogage d’un expert . . . . . . . . . . . . 14-9Numéros de version de l’interface . . . . 14-10

Utilisation des fichiers et des éditeurs . . . . .14-11Utilisation des interfaces de module . . . .14-11Utilisation des interfaces d’éditeur . . . . 14-12

Création de fiches et de projets . . . . . . . . 14-12Création de modules . . . . . . . . . . . . 14-13

Notification d’un expert des événements de l’EDI. . . . . . . . . . . 14-16

Index I-1

Page 7: Guide Du Concepteur Des Composants en delphi

P r é s e n t a t i o n g é n é r a l e d e l a c r é a t i o n d ’ u n c o m p o s a n t 1-1

C h a p i t r e

1Chapitre1Présentation générale de la création

d’un composantCe 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 classesLes 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

Page 8: Guide Du Concepteur Des Composants en delphi

1-2 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C o m p o s a n t s e t c l a s s e s

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 classesComme 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.

Page 9: Guide Du Concepteur Des Composants en delphi

P r é s e n t a t i o n g é n é r a l e d e l a c r é a t i o n d ’ u n c o m p o s a n t 1-3

C r é a t i o n d e c o m p o s a n t s

Création de composantsUn 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.

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

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 widget dans les applications CLX)

TWinControl (TWidgetControl 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

Page 10: Guide Du Concepteur Des Composants en delphi

1-4 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C r é a t i o n d e c o m p o s a n t s

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.

Page 11: Guide Du Concepteur Des Composants en delphi

P r é s e n t a t i o n g é n é r a l e d e l a c r é a t i o n d ’ u n c o m p o s a n t 1-5

C o n t e n u d ’ u n c o m p o s a n t ?

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

Page 12: Guide Du Concepteur Des Composants en delphi

1-6 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C o n t e n u d ’ u n c o m p o s a n t ?

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ésLes 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.

Page 13: Guide Du Concepteur Des Composants en delphi

P r é s e n t a t i o n g é n é r a l e d e l a c r é a t i o n d ’ u n c o m p o s a n t 1-7

C o n t e n u d ’ u n c o m p o s a n t ?

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éthodesLes 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énementsUn é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

Page 14: Guide Du Concepteur Des Composants en delphi

1-8 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C o n t e n u d ’ u n c o m p o s a n t ?

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”.

Page 15: Guide Du Concepteur Des Composants en delphi

P r é s e n t a t i o n g é n é r a l e d e l a c r é a t i o n d ’ u n c o m p o s a n t 1-9

C r é a t i o n d ’ u n n o u v e a u c o m p o s a n t

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 composantVous 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)

Page 16: Guide Du Concepteur Des Composants en delphi

1-10 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C r é a t i o n d ’ u n n o u v e a u c o m p o s a n t

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.

Page 17: Guide Du Concepteur Des Composants en delphi

P r é s e n t a t i o n g é n é r a l e d e l a c r é a t i o n d ’ u n c o m p o s a n t 1-11

C r é a t i o n d ’ u n n o u v e a u c o m p o s a n t

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

usesWindows, Messages, SysUtils, Types, Classes, Controls;

typeTMyControl = class(TCustomControl)private{ Déclarations privées }protected{ Déclarations protégées }public{ Déclarations publiques }published{ Déclarations publiées }

Page 18: Guide Du Concepteur Des Composants en delphi

1-12 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C r é a t i o n d ’ u n n o u v e a u c o m p o s a n t

end;

procedure Register;

implementation

procedure Register;begin

RegisterComponents(’Samples’, [TMyControl]); //Dans CLX, utilisez une autre pageend;

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 :

usesSysUtils, 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.

Page 19: Guide Du Concepteur Des Composants en delphi

P r é s e n t a t i o n g é n é r a l e d e l a c r é a t i o n d ’ u n c o m p o s a n t 1-13

C r é a t i o n d ’ u n n o u v e a u c o m p o s a n t

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 composantChaque 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 :

typeTNewComponent = 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 composantLe 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;

Page 20: Guide Du Concepteur Des Composants en delphi

1-14 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C r é a t i o n d ’ u n n o u v e a u c o m p o s a n t

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).

Page 21: Guide Du Concepteur Des Composants en delphi

P r é s e n t a t i o n g é n é r a l e d e l a c r é a t i o n d ’ u n c o m p o s a n t 1-15

C r é a t i o n d ’ u n n o u v e a u c o m p o s a n t

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

Page 22: Guide Du Concepteur Des Composants en delphi

1-16 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

I n s t a l l a t i o n d ’ u n c o m p o s a n t d a n s l a p a l e t t e d e c o m p o s a n t s

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 composantsPour 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.

Page 23: Guide Du Concepteur Des Composants en delphi

P r é s e n t a t i o n g é n é r a l e d e l a c r é a t i o n d ’ u n c o m p o s a n t 1-17

T e s t d e s c o m p o s a n t s n o n i n s t a l l é 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ésVous 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

Page 24: Guide Du Concepteur Des Composants en delphi

1-18 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

T e s t d e s c o m p o s a n t s n o n i n s t a l l é 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

usesSysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,Forms, Dialogs, MyControl; { 1. Ajouter NewTest à la clause uses. }

typeTForm1 = 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;

varForm1: 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.

Page 25: Guide Du Concepteur Des Composants en delphi

P r é s e n t a t i o n g é n é r a l e d e l a c r é a t i o n d ’ u n c o m p o s a n t 1-19

T e s t d e s c o m p o s a n t s i n s t a l l é s

Test des composants installésVous 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.

Page 26: Guide Du Concepteur Des Composants en delphi

1-20 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

Page 27: Guide Du Concepteur Des Composants en delphi

P r o g r a m m a t i o n o r i e n t é e o b j e t e t é c r i t u r e d e s c o m p o s a n t s 2-1

C h a p i t r e

2Chapitre2Programmation orientée objet

et écriture des composantsSi 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 classesLa 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.

Page 28: Guide Du Concepteur Des Composants en delphi

2-2 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

D é f i n i t i o n d e n o u v e l l e s c l a s s e s

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étitionsDans 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.

Page 29: Guide Du Concepteur Des Composants en delphi

P r o g r a m m a t i o n o r i e n t é e o b j e t e t é c r i t u r e d e s c o m p o s a n t s 2-3

A n c ê t r e s , d e s c e n d a n t s e t h i é r a r c h i e s d e s c l a s s e s

Ajout de nouvelles capacités à une classeUne 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 :

typeTSampleShape = 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 classesLes 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

Page 30: Guide Du Concepteur Des Composants en delphi

2-4 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C o n t r ô l e d e s a c c è 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èsIl 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ù est définie la classe.

Masquer les détails d’implémentation.

protected Accessible au code de ou des unités où sont définis la classe et ses descendants.

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

public Accessible à tout le code. Définition de l’interface d’exécution.

automated Accessible à tout le code. Les informations d’automatisation sont générées.

Automatisation OLE seulement.

published Accessible à tout le code et accessible depuis l’inspecteur d’objets. Enregistré dans un fichier fiche.

Définition de l’interface de conception.

Page 31: Guide Du Concepteur Des Composants en delphi

P r o g r a m m a t i o n o r i e n t é e o b j e t e t é c r i t u r e d e s c o m p o s a n t s 2-5

C o n t r ô l e d e s a c c è 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;

typeTSecretForm = 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;

varSecretForm: 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 }

interfaceuses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs,

HideInfo; { utilise l’unité avec TSecretForm }

typeTTestForm = class(TForm)

procedure FormCreate(Sender: TObject);end;

Page 32: Guide Du Concepteur Des Composants en delphi

2-6 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C o n t r ô l e d e s a c c è s

varTestForm: TTestForm;

implementationprocedure 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.

Page 33: Guide Du Concepteur Des Composants en delphi

P r o g r a m m a t i o n o r i e n t é e o b j e t e t é c r i t u r e d e s c o m p o s a n t s 2-7

C o n t r ô l e d e s a c c è 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 :

typeTSampleComponent = class(TComponent)private

FTempCelsius: Integer; { les détails de l’implémentation sont privés }function GetTempFahrenheit: Integer;

publicproperty 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.

typeTSampleComponent = 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;

Page 34: Guide Du Concepteur Des Composants en delphi

2-8 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

R é p a r t i t i o n d e s m é t h o d e s

Répartition des méthodesLe 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 statiquesDans 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.

typeTFirstComponent = 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;

Page 35: Guide Du Concepteur Des Composants en delphi

P r o g r a m m a t i o n o r i e n t é e o b j e t e t é c r i t u r e d e s c o m p o s a n t s 2-9

R é p a r t i t i o n d e s m é t h o d e s

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éthodesSurcharger 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.

typeTFirstComponent = 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;

Page 36: Guide Du Concepteur Des Composants en delphi

2-10 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

M e m b r e s a b s t r a i t s d ’ u n e c l a s s e

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 classeLorsqu’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 pointeursChaque 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.

Page 37: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d e p r o p r i é t é s 3-1

C h a p i t r e

3Chapitre3Création de propriétés

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

Page 38: Guide Du Concepteur Des Composants en delphi

3-2 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

T y p e s d e p r o p r i é 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ésUne 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).

Page 39: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d e p r o p r i é t é s 3-3

P u b l i c a t i o n d e s p r o p r i é t é s h é r i t é e s

Publication des propriétés héritéesTous 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.

typeTSampleComponent = 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.

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.

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

Type de propriété Traitement de l’inspecteur d’objets

Page 40: Guide Du Concepteur Des Composants en delphi

3-4 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

D é f i n i t i o n d e s p r o p r i é t é s

Définition des propriétésCette 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.

typeTYourComponent = class(TComponent)private

FCount: Integer; { utilisé pour le stockage interne }procedure SetCount (Value: Integer); { méthode d’écriture }

publicproperty 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.

Page 41: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d e p r o p r i é t é s 3-5

D é f i n i t i o n d e s p r o p r i é 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.

typeTSampleComponent = 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.

Page 42: Guide Du Concepteur Des Composants en delphi

3-6 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

D é f i n i t i o n d e s p r o p r i é 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 :

typeTSampleCalendar = 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;

privatefunction 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;

Page 43: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d e p r o p r i é t é s 3-7

D é f i n i t i o n d e s p r o p r i é t é s

Méthode readLa 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 writeLa 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 thenbegin

FCount := Value;Update;

end;end;

Page 44: Guide Du Concepteur Des Composants en delphi

3-8 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

D é f i n i t i o n d e s p r o p r i é 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éfautLorsque 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é.

typeTSampleComponent = 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;

Page 45: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d e p r o p r i é t é s 3-9

C r é a t i o n d e p r o p r i é t é s t a b l e a u

Création de propriétés tableauCertaines 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.

typeTDemoComponent = 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;

Page 46: Guide Du Concepteur Des Composants en delphi

3-10 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C r é a t i o n d e p r o p r i é t é s p o u r s o u s - c o m p o s a n t s

Création de propriétés pour sous-composantsPar 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 thenbegin

if Value <> nil thenbegin

if Assigned(FTimer) and (FTimer.Owner = Self) thenFTimer.Free;

FTimer := Value;FTimer.FreeNotification(self);

endelse //valeur nilbegin

if Assigned(FTimer) and (FTimer.Owner <> Self) then

Page 47: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d e p r o p r i é t é s 3-11

C r é a t i o n d e s p r o p r i é t é s p o u r i n t e r f a c e s

beginFTimer := TTimer.Create(self);FTimer.Name := ’Timer’; bit facultatif, rend le résultat plus agréableFTimer.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 interfacesVous 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

Page 48: Guide Du Concepteur Des Composants en delphi

3-12 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

S t o c k a g e e t c h a r g e m e n t d e s p r o p r i é 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ésDelphi 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.

Page 49: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d e p r o p r i é t é s 3-13

S t o c k a g e e t c h a r g e m e n t d e s p r o p r i é 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.

typeTStatusBar = class(TPanel)public

constructor Create(AOwner: TComponent); override; { surcharge pour définir lanouvelle valeur par défaut }

publishedproperty 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 }

Page 50: Guide Du Concepteur Des Composants en delphi

3-14 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

S t o c k a g e e t c h a r g e m e n t d e s p r o p r i é 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 :

typeTSampleComponent = 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.

Page 51: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d e p r o p r i é t é s 3-15

S t o c k a g e e t c h a r g e m e n t d e s p r o p r i é 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);

exceptif 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ésPour 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.

Page 52: Guide Du Concepteur Des Composants en delphi

3-16 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

S t o c k a g e e t c h a r g e m e n t d e s p r o p r i é 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 thenMyCompProperty := 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 DefinePropertiesAprè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 thenResult := MyCompProperty <> nil

Page 53: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d e p r o p r i é t é s 3-17

S t o c k a g e e t c h a r g e m e n t d e s p r o p r i é t é s

else if MyCompProperty = nil or TSampleComponent(Filer.Ancestor).MyCompProperty.Name <> MyCompProperty.Name then

Result := Trueelse Result := False;

endelse { aucune valeur héritée -- cherche la valeur par défaut (nil) }

Result := MyCompProperty <> nil;end;

begininherited; { autorise la définition des propriétés par les classes de base }Filer.DefineProperty(’MyCompProperty’, LoadCompProperty, StoreCompProperty, DoWrite);

end;

Page 54: Guide Du Concepteur Des Composants en delphi

3-18 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

Page 55: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d ’ é v é n e m e n t s 4-1

C h a p i t r e

4Chapitre4Création d’événements

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

Page 56: Guide Du Concepteur Des Composants en delphi

4-2 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

Q u ’ e s t - c e q u ’ u n é v é n e m e n t ?

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 :

• 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é.

L’utilisateur clique sur Button1 Button1.OnClick pointe surForm1.Button1Click

Form1.Button1Clicks’exécute

Occurrence Evénement Gestionnaire d’événement

Page 57: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d ’ é v é n e m e n t s 4-3

Q u ’ e s t - c e q u ’ u n é v é n e m e n t ?

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 :

typeTControl = class(TComponent)private

FOnClick: TNotifyEvent; { déclare un champ pour contenir le pointeur de méthode }ƒ

protectedproperty 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éduresBien 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

Page 58: Guide Du Concepteur Des Composants en delphi

4-4 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

Q u ’ e s t - c e q u ’ u n é v é n e m e n t ?

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 :

typeTKeyPressEvent = 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.

Page 59: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d ’ é v é n e m e n t s 4-5

I m p l é m e n t a t i o n d e s é v é n e m e n t s s t a n d a r d

Implémentation des événements standardLes 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ôlesLes é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 :

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 standardOutre 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 :

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.

OnClickOnDblClick

OnDragDropOnDragOver

OnEndDragOnMouseDown

OnMouseMoveOnMouseUp

OnEnterOnKeyUp

OnKeyDownOnExit

OnKeyPress

Page 60: Guide Du Concepteur Des Composants en delphi

4-6 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

I m p l é m e n t a t i o n d e s é v é n e m e n t s s t a n d a r d

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.

typeTMyControl = 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

Page 61: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d ’ é v é n e m e n t s 4-7

D é f i n i t i o n d e v o s p r o p r e s é v é n e m e n t s

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énementsIl 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.

Page 62: Guide Du Concepteur Des Composants en delphi

4-8 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

D é f i n i t i o n d e v o s p r o p r e s é v é n e m e n t s

Deux sortes d’événementsIl 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 simplesUn é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écifiquesDans 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.

Page 63: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d ’ é v é n e m e n t s 4-9

D é f i n i t i o n d e v o s p r o p r e s é v é n e m e n t s

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 gestionnaireComme 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.

Page 64: Guide Du Concepteur Des Composants en delphi

4-10 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

D é f i n i t i o n d e v o s p r o p r e s é v é n e m e n t s

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 validesVous 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éfautPour 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.

Page 65: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d e m é t h o d e s 5-1

C h a p i t r e

5Chapitre5Création de méthodes

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épendancesLorsque 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.

Page 66: Guide Du Concepteur Des Composants en delphi

5-2 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

N o m s d e s m é t h o d e s

• 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éthodesDelphi 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é.

Page 67: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d e m é t h o d e s 5-3

P r o t e c t i o n d e s m é t h o d e s

Protection des méthodesToutes 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.

Page 68: Guide Du Concepteur Des Composants en delphi

5-4 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

R e n d r e v i r t u e l l e s d e s m é t h o d e s

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éthodesVous 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éthodesLa 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.

typeTSampleComponent = class(TControl)protected

procedure MakeBigger; { déclare la méthode protected static }

Page 69: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d e m é t h o d e s 5-5

D é c l a r a t i o n d e s m é t h o d e s

publicfunction 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;

Page 70: Guide Du Concepteur Des Composants en delphi

5-6 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

Page 71: Guide Du Concepteur Des Composants en delphi

G r a p h i q u e s e t c o m p o s a n t s 6-1

C h a p i t r e

6Chapitre6Graphiques et composants

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 graphiquesDelphi 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

Page 72: Guide Du Concepteur Des Composants en delphi

6-2 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

P r é s e n t a t i o n d e s g r a p h i q u e s

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;

beginPenHandle := 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;

Page 73: Guide Du Concepteur Des Composants en delphi

G r a p h i q u e s e t c o m p o s a n t s 6-3

U t i l i s a t i o n d u c a n e v a s

procedure TForm1.FormPaint(Sender: TObject);begin

with Canvas dobegin

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 canevasLa 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.

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 imagesDans 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.

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 et des graphiques

Propriétés Pen, Brush et Font

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 de Windows

Propriété Handle

Page 74: Guide Du Concepteur Des Composants en delphi

6-4 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

T r a v a i l s u r l e s i m a g e s

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.

Page 75: Guide Du Concepteur Des Composants en delphi

G r a p h i q u e s e t c o m p o s a n t s 6-5

T r a v a i l s u r l e s i m a g e s

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.

Page 76: Guide Du Concepteur Des Composants en delphi

6-6 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

B i t m a p s h o r s é c r a n

Spécification d’une palette pour un contrôlePour 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 paletteSi 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 écranLorsque 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

Page 77: Guide Du Concepteur Des Composants en delphi

G r a p h i q u e s e t c o m p o s a n t s 6-7

R é p o n s e a u x c h a n g e m e n t s

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 :

typeTFancyControl = 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 }

finallyBitmap.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.

Réponse aux changementsTous 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.

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 une zone bitmap avec effets.

TiledDraw (CLX)

Page 78: Guide Du Concepteur Des Composants en delphi

6-8 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

R é p o n s e a u x c h a n g e m e n t s

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é :

typeTShape = 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;

Page 79: Guide Du Concepteur Des Composants en delphi

G e s t i o n d e s m e s s a g e s e t d e s n o t i f i c a t i o n s s y s t è m e 7-1

C h a p i t r e

7Chapitre7Gestion des messages

et des notifications systèmeLes 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 messagesToutes 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

Page 80: Guide Du Concepteur Des Composants en delphi

7-2 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C o m p r é h e n s i o n d u s y s t è m e d e g e s t i o n d e s m e s s a g e s

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 :

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.

Evénement MainWndProc WndProc Dispatch Gestionnaire

Page 81: Guide Du Concepteur Des Composants en delphi

G e s t i o n d e s m e s s a g e s e t d e s n o t i f i c a t i o n s s y s t è m e 7-3

C o m p r é h e n s i o n d u s y s t è m e d e g e s t i o n d e s m e s s a g e s

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 messagesLa 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.

Page 82: Guide Du Concepteur Des Composants en delphi

7-4 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

M o d i f i c a t i o n d e l a g e s t i o n d e s m e s s a g e s

Modification de la gestion des messagesAvant 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 :

typeTMyComponent = 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

Page 83: Guide Du Concepteur Des Composants en delphi

G e s t i o n d e s m e s s a g e s e t d e s n o t i f i c a t i o n s s y s t è m e 7-5

M o d i f i c a t i o n d e l a g e s t i o n d e s m e s s a g e s

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;

Page 84: Guide Du Concepteur Des Composants en delphi

7-6 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C r é a t i o n d e n o u v e a u x g e s t i o n n a i r e s d e m e s s a g e s

Création de nouveaux gestionnaires de messagesPuisque 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 messageUn 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 :

constMY_MYFIRSTMESSAGE = WM_APP + 400;MY_MYSECONDMESSAGE = WM_APP + 401;

Déclaration d’un type enregistrement de messageSi 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

Page 85: Guide Du Concepteur Des Composants en delphi

G e s t i o n d e s m e s s a g e s e t d e s n o t i f i c a t i o n s s y s t è m e 7-7

C r é a t i o n d e n o u v e a u x g e s t i o n n a i r e s d e m e s s a g e s

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.

typeTWMMouse = 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.

Page 86: Guide Du Concepteur Des Composants en delphi

7-8 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C r é a t i o n d e n o u v e a u x g e s t i o n n a i r e s d e m e s s a g e s

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.

constCM_CHANGECOLOR = WM_APP + 400;

typeTMyComponent = class(TControl)ƒ

protectedprocedure 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 ficheQuand 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.

Page 87: Guide Du Concepteur Des Composants en delphi

G e s t i o n d e s m e s s a g e s e t d e s n o t i f i c a t i o n s s y s t è m e 7-9

C r é a t i o n d e n o u v e a u x g e s t i o n n a i r e s d e m e s s a g e s

(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 doScreen.Forms[I].Broadcast(Msg);

Appel direct du gestionnaire de message d’un contrôleParfois, 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.

Page 88: Guide Du Concepteur Des Composants en delphi

7-10 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

R é p o n s e a u x n o t i f i c a t i o n s d u s y s t è m e à l ’ a i d e d e C L X

Envoi d’un message à l’aide de la file d’attente des messages WindowsDans 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édiatementDans 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 CLXSous 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

Page 89: Guide Du Concepteur Des Composants en delphi

G e s t i o n d e s m e s s a g e s e t d e s n o t i f i c a t i o n s s y s t è m e 7-11

R é p o n s e a u x n o t i f i c a t i o n s d u s y s t è m e à l ’ a i d e d e C L X

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++.

Page 90: Guide Du Concepteur Des Composants en delphi

7-12 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

R é p o n s e a u x n o t i f i c a t i o n s d u s y s t è m e à l ’ a i d e d e C L X

Affectation de gestionnaires de signaux personnalisésDe 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

Page 91: Guide Du Concepteur Des Composants en delphi

G e s t i o n d e s m e s s a g e s e t d e s n o t i f i c a t i o n s s y s t è m e 7-13

R é p o n s e a u x n o t i f i c a t i o n s d u s y s t è m e à l ’ a i d e d e C L X

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ésLa 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

Page 92: Guide Du Concepteur Des Composants en delphi

7-14 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

R é p o n s e a u x n o t i f i c a t i o n s d u s y s t è m e à l ’ a i d e d e C L X

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.

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

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.

Page 93: Guide Du Concepteur Des Composants en delphi

G e s t i o n d e s m e s s a g e s e t d e s n o t i f i c a t i o n s s y s t è m e 7-15

R é p o n s e a u x n o t i f i c a t i o n s d u s y s t è m e à l ’ a i d e d e C L X

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

Surcharge de la méthode EventFilterSi 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;

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.

Page 94: Guide Du Concepteur Des Composants en delphi

7-16 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

R é p o n s e a u x n o t i f i c a t i o n s d u s y s t è m e à l ’ a i d e d e C L X

Génération des événements QtDe 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é) :

constMyEvent_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 :

varMyEvent: QCustomEventH;

beginMyEvent := 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.

Page 95: Guide Du Concepteur Des Composants en delphi

A c c e s s i b i l i t é d e s c o m p o s a n t s a u m o m e n t d e l a c o n c e p t i o n 8-1

C h a p i t r e

8Chapitre8Accessibilité des composants

au moment de la conceptionCe 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 composantsLe 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.

Page 96: Guide Du Concepteur Des Composants en delphi

8-2 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

R e c e n s e m e n t d e s c o m p o s a n t s

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;interfacetype

ƒ { 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.

Page 97: Guide Du Concepteur Des Composants en delphi

A c c e s s i b i l i t é d e s c o m p o s a n t s a u m o m e n t d e l a c o n c e p t i o n 8-3

R e c e n s e m e n t d e s c o m p o s a n t s

2 Spécification de la page de palette. 3 Utilisation de la fonction RegisterComponents.

Spécification des composantsDans 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 paletteLe 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 RegisterComponentsDans 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;

Page 98: Guide Du Concepteur Des Composants en delphi

8-4 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

F o u r n i r l ’ a i d e p o u r v o s c o m p o s a n t s

Fournir l’aide pour vos composantsLorsque 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éesPour 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.

Page 99: Guide Du Concepteur Des Composants en delphi

A c c e s s i b i l i t é d e s c o m p o s a n t s a u m o m e n t d e l a c o n c e p t i o n 8-5

F o u r n i r l ’ a i d e p o u r v o s c o m p o s a n t s

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.

Page 100: Guide Du Concepteur Des Composants en delphi

8-6 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

F o u r n i r l ’ a i d e p o u r v o s c o m p o s a n t s

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 composantsChaque 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 composantsPour 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.

Page 101: Guide Du Concepteur Des Composants en delphi

A c c e s s i b i l i t é d e s c o m p o s a n t s a u m o m e n t d e l a c o n c e p t i o n 8-7

A j o u t d ’ é d i t e u r s d e p r o p r i é t é s

Ajout d’éditeurs de propriétésL’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.

Page 102: Guide Du Concepteur Des Composants en delphi

8-8 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

A j o u t d ’ é d i t e u r s d e p r o p r i é t é s

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

typeTFloatProperty = 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.

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.

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

Type Propriétés modifiables

Page 103: Guide Du Concepteur Des Composants en delphi

A c c e s s i b i l i t é d e s c o m p o s a n t s a u m o m e n t d e l a c o n c e p t i o n 8-9

A j o u t d ’ é d i t e u r s d e p r o p r i é 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.

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)^ doif OrdType = otULong then // non signé

Result := IntToStr(Cardinal(GetOrdValue))else

Result := IntToStr(GetOrdValue);end;

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

Page 104: Guide Du Concepteur Des Composants en delphi

8-10 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

A j o u t d ’ é d i t e u r s d e p r o p r i é t é s

procedure TIntegerProperty.SetValue(const Value: string);procedure Error(const Args: array of const);begin

raise EPropertyError.CreateResFmt(@SOutOfRange, Args);end;

varL: Int64;

beginL := StrToInt64(Value);with GetTypeData(GetPropType)^ do

if OrdType = otULong thenbegin // 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 formatError([Int64(Cardinal(MinValue)), Int64(Cardinal(MaxValue))]);

endelse 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”.

Page 105: Guide Du Concepteur Des Composants en delphi

A c c e s s i b i l i t é d e s c o m p o s a n t s a u m o m e n t d e l a c o n c e p t i o n 8-11

A j o u t d ’ é d i t e u r s d e p r o p r i é 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.

Page 106: Guide Du Concepteur Des Composants en delphi

8-12 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

A j o u t d ’ é d i t e u r s d e p r o p r i é t é s

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.

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é.

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

Indicateur Méthode associée Signification si inclus

Page 107: Guide Du Concepteur Des Composants en delphi

A c c e s s i b i l i t é d e s c o m p o s a n t s a u m o m e n t d e l a c o n c e p t i o n 8-13

C a t é g o r i e s d e p r o p r i é 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ésDans 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

Page 108: Guide Du Concepteur Des Composants en delphi

8-14 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C a t é g o r i e s d e p r o p r i é 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’.

Page 109: Guide Du Concepteur Des Composants en delphi

A c c e s s i b i l i t é d e s c o m p o s a n t s a u m o m e n t d e l a c o n c e p t i o n 8-15

C a t é g o r i e s d e p r o p r i é 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 and Docking

Propriétés relatives aux opérations de glisser-déplacer et d’ancrage ; les propriétés DragCursor et DragKind de TImage se trouvent dans cette catégorie.

Help and Hints

Propriétés relatives à l’utilisation de l’aide en ligne ou des conseils ; les 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.

Page 110: Guide Du Concepteur Des Composants en delphi

8-16 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

A j o u t d ’ é d i t e u r s d e c o m p o s a n t s

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 composantsLes é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.

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.

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

Catégorie Utilisation

Page 111: Guide Du Concepteur Des Composants en delphi

A c c e s s i b i l i t é d e s c o m p o s a n t s a u m o m e n t d e l a c o n c e p t i o n 8-17

A j o u t d ’ é d i t e u r s d e c o m p o s a n t s

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 menuRedé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;

Page 112: Guide Du Concepteur Des Composants en delphi

8-18 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

A j o u t d ’ é d i t e u r s d e c o m p o s a n t s

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

case Index of0: 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 commandesLorsque 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 of0: 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.

Page 113: Guide Du Concepteur Des Composants en delphi

A c c e s s i b i l i t é d e s c o m p o s a n t s a u m o m e n t d e l a c o n c e p t i o n 8-19

A j o u t d ’ é d i t e u r s d e c o m p o s a n t s

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 thenMyComponent.FFont.Assign(FontDlg.Font);

finallyFontDlg.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)

beginif (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;

beginTImage(Component).Picture.Bitmap.SaveToClipBoardFormat(MyFormat, AData, APalette);ClipBoard.SetAsHandle(MyFormat, AData);

end;

Page 114: Guide Du Concepteur Des Composants en delphi

8-20 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C o m p i l a t i o n d e s c o m p o s a n t s e n p a q u e t s

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 paquetsUne 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.

Page 115: Guide Du Concepteur Des Composants en delphi

M o d i f i c a t i o n d ’ u n c o m p o s a n t e x i s t a n t 9-1

C h a p i t r e

9Chapitre9Modification d’un composant

existantLe 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 composantLa 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.

Page 116: Guide Du Concepteur Des Composants en delphi

9-2 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

M o d i f i c a t i o n d e l a c l a s s e c o m p o s a n t

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;interfaceuses

SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,Forms, StdCtrls;

typeTWrapMemo = class(TMemo)end;

procedure Register;implementationprocedure 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 composantUne 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

Page 117: Guide Du Concepteur Des Composants en delphi

M o d i f i c a t i o n d ’ u n c o m p o s a n t e x i s t a n t 9-3

M o d i f i c a t i o n d e l a c l a s s e c o m p o s a n t

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é :

typeTWrapMemo = class(TMemo)public { constructeurs toujours publics }

constructor Create(AOwner: TComponent); override; { cette syntaxe est toujoursidentique }

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.

Page 118: Guide Du Concepteur Des Composants en delphi

9-4 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

M o d i f i c a t i o n d e l a c l a s s e c o m p o s a n t

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 :

typeTWrapMemo = 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.

Page 119: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d ’ u n c o n t r ô l e g r a p h i q u e 10-1

C h a p i t r e

10Chapitre10Création d’un contrôle graphique

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 composantLa 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.

Page 120: Guide Du Concepteur Des Composants en delphi

10-2 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

P u b l i c a t i o n d e s p r o p r i é t é s h é r i t é e s

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

unit Shapes;interfaceuses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms;type

TSampleShape = class(TGraphicControl)end;

procedure Register;implementationprocedure 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éesLorsque 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 :

typeTSampleShape = 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;

Page 121: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d ’ u n c o n t r ô l e g r a p h i q u e 10-3

A j o u t d e f o n c t i o n n a l i t é s g r a p h i q u e s

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 graphiquesLorsque 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.

Page 122: Guide Du Concepteur Des Composants en delphi

10-4 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

A j o u t d e f o n c t i o n n a l i t é s g r a p h i q u e s

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

typeTSampleShapeType = (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 :

typeTSampleShape = class(TGraphicControl)private

FShape: TSampleShapeType; { champ pour contenir la valeur de la propriété }procedure SetShape(Value: TSampleShapeType);

publishedproperty 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émentationLorsque 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;

Page 123: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d ’ u n c o n t r ô l e g r a p h i q u e 10-5

A j o u t d e f o n c t i o n n a l i t é s g r a p h i q u e s

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ésLa 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 :

typeTSampleShape = class(TGraphicControl)public { constructeurs toujours publics }

constructor Create(AOwner: TComponent); override { ne pas oublier la directiveoverride }

end;

1 Redéclarez les propriétés Height et Width avec leurs nouvelles valeurs par défaut :

typeTSampleShape = 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

Page 124: Guide Du Concepteur Des Composants en delphi

10-6 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

A j o u t d e f o n c t i o n n a l i t é s g r a p h i q u e s

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 classeChaque 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 :

typeTSampleShape = 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èsVous 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.

typeTSampleShape = class(TGraphicControl)ƒprivate { ces méthodes doivent être privées }

procedure SetBrush(Value: TBrush);procedure SetPen(Value: TPen);

Page 125: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d ’ u n c o n t r ô l e g r a p h i q u e 10-7

A j o u t d e f o n c t i o n n a l i t é s g r a p h i q u e s

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étaireSi 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 :

typeTSampleShape = class(TGraphicControl)public { les destructeurs sont toujours publics}

constructor Create(AOwner: TComponent); override;destructor Destroy; override; { ne pas oublier la directive override }

end;

Page 126: Guide Du Concepteur Des Composants en delphi

10-8 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

A j o u t d e f o n c t i o n n a l i t é s g r a p h i q u e s

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étaireL’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 :

typeTSampleShape = 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.

Page 127: Guide Du Concepteur Des Composants en delphi

C r é a t i o n d ’ u n c o n t r ô l e g r a p h i q u e 10-9

A j o u t d e f o n c t i o n n a l i t é s g r a p h i q u e s

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 :

typeTSampleShape = 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 dobegin

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.

Page 128: Guide Du Concepteur Des Composants en delphi

10-10 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

A j o u t d e f o n c t i o n n a l i t é s g r a p h i q u e s

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 dobegin

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 ofsstRectangle, 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;

Page 129: Guide Du Concepteur Des Composants en delphi

P e r s o n n a l i s a t i o n d ’ u n e g r i l l e 11-1

C h a p i t r e

11Chapitre11Personnalisation d’une grille

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 composantLa 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.

Page 130: Guide Du Concepteur Des Composants en delphi

11-2 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C r é a t i o n e t r e c e n s e m e n t d u c o m p o s a n t

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

usesWindows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Grids;

typeTSampleCalendar = 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.

Page 131: Guide Du Concepteur Des Composants en delphi

P e r s o n n a l i s a t i o n d ’ u n e g r i l l e 11-3

P u b l i c a t i o n d e s p r o p r i é t é s h é r i t é e s

Publication des propriétés héritéesLe 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 :

typeTSampleCalendar = 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 initialesUn 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.

Page 132: Guide Du Concepteur Des Composants en delphi

11-4 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

R e d i m e n s i o n n e m e n t d e s c e l l u l e s

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.

typeTSampleCalendar = class(TCustomGridpublic

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 cellulesRemarque 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 :

typeTSampleCalendar = class(TCustomGrid)protected

Page 133: Guide Du Concepteur Des Composants en delphi

P e r s o n n a l i s a t i o n d ’ u n e g r i l l e 11-5

R e m p l i s s a g e d e s c e l l u l e s

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 :

typeTSampleCalendar = 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 cellulesUn 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.

Page 134: Guide Du Concepteur Des Composants en delphi

11-6 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

R e m p l i s s a g e d e s c e l l u l e s

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 :

typeTSampleCalendar = 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 datePour 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

Page 135: Guide Du Concepteur Des Composants en delphi

P e r s o n n a l i s a t i o n d ’ u n e g r i l l e 11-7

R e m p l i s s a g e d e s c e l l u l e s

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 :

typeTSampleCalendar = 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éeUne 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 :

typeTSampleCalendar = 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;

Page 136: Guide Du Concepteur Des Composants en delphi

11-8 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

R e m p l i s s a g e d e s c e l l u l e s

ƒ

2 Déclarez et écrivez les méthodes d’implémentation, définissant les différents éléments pour chaque valeur d’index :

typeTSampleCalendar = 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 joursInsé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.

Page 137: Guide Du Concepteur Des Composants en delphi

P e r s o n n a l i s a t i o n d ’ u n e g r i l l e 11-9

R e m p l i s s a g e d e s c e l l u l e s

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 :

typeTSampleCalendar = 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 }

beginif 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);

Page 138: Guide Du Concepteur Des Composants en delphi

11-10 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

R e m p l i s s a g e d e s c e l l u l e s

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;

beginif 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 coursMaintenant 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

Page 139: Guide Du Concepteur Des Composants en delphi

P e r s o n n a l i s a t i o n d ’ u n e g r i l l e 11-11

N a v i g a t i o n d e m o i s e n m o i s e t d ’ a n n é e e n a n n é e

if FDate <> 0 thenbegin

ƒ { 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éeLes 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;

Page 140: Guide Du Concepteur Des Composants en delphi

11-12 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

N a v i g a t i o n d e j o u r e n j o u r

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 jourA 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;

Page 141: Guide Du Concepteur Des Composants en delphi

P e r s o n n a l i s a t i o n d ’ u n e g r i l l e 11-13

N a v i g a t i o n d e j o u r e n j o u r

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 :

typeTSampleCalendar = 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.

Page 142: Guide Du Concepteur Des Composants en delphi

11-14 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

N a v i g a t i o n d e j o u r e n j o u r

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.

Page 143: Guide Du Concepteur Des Composants en delphi

C o n t r ô l e s o r i e n t é s d o n n é e s 12-1

C h a p i t r e

12Chapitre12Contrôles orientés données

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.

Page 144: Guide Du Concepteur Des Composants en delphi

12-2 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C r é a t i o n d ’ u n c o n t r ô l e p o u r s c r u t e r l e s d o n n é e s

Création d’un contrôle pour scruter les donnéesLa 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

usesWindows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Grids;

typeTSampleCalendar = 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.

Page 145: Guide Du Concepteur Des Composants en delphi

C o n t r ô l e s o r i e n t é s d o n n é e s 12-3

C r é a t i o n d ’ u n c o n t r ô l e p o u r s c r u t e r l e s d o n n é e s

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;

typeTDBCalendar = 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.

Page 146: Guide Du Concepteur Des Composants en delphi

12-4 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C r é a t i o n d ’ u n c o n t r ô l e p o u r s c r u t e r l e s d o n n é e s

Ajout de la propriété ReadOnlyEn 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 :

typeTDBCalendar = class(TSampleCalendar)private

FReadOnly: Boolean; { champ de stockage interne }public

constructor Create(AOwner: TComponent); override; { doit surcharger pour définirles valeurs par défaut }

publishedproperty 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écessairesLe 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.

Page 147: Guide Du Concepteur Des Composants en delphi

C o n t r ô l e s o r i e n t é s d o n n é e s 12-5

C r é a t i o n d ’ u n c o n t r ô l e p o u r s c r u t e r l e s d o n n é e s

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 :

typeTDBCalendar = 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.

Page 148: Guide Du Concepteur Des Composants en delphi

12-6 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C r é a t i o n d ’ u n c o n t r ô l e p o u r s c r u t e r l e s d o n n é e s

3 Initialisation du lien de données.

Déclaration du champ de classeUn 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 :

typeTDBCalendar = 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èsTout 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èsDé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 :

typeTDBCalendar = 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;

Page 149: Guide Du Concepteur Des Composants en delphi

C o n t r ô l e s o r i e n t é s d o n n é e s 12-7

C r é a t i o n d ’ u n c o n t r ô l e p o u r s c r u t e r l e s d o n n é e s

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éesUn 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 :

typeTDBCalendar = 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é }

Page 150: Guide Du Concepteur Des Composants en delphi

12-8 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C r é a t i o n d ’ u n c o n t r ô l e p o u r s c r u t e r l e s d o n n é e s

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.

typeTDBCalendar = class(TSampleCalendar)private { c’est un détail interne, donc le déclarer privé }

procedure DataChange(Sender: TObject); { doit avoir des paramètres correctspour 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 }

Page 151: Guide Du Concepteur Des Composants en delphi

C o n t r ô l e s o r i e n t é s d o n n é e s 12-9

C r é a t i o n d ’ u n c o n t r ô l e d e m o d i f i c a t i o n d e d o n n é e s

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éesLorsque 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;

Page 152: Guide Du Concepteur Des Composants en delphi

12-10 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C r é a t i o n d ’ u n c o n t r ô l e d e m o d i f i c a t i o n d e d o n n é e s

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 sourisUne 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 :

typeTDBCalendar = class(TSampleCalendar);ƒ

protectedprocedure 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 theninherited MouseDown(Button, Shift, X, Y)

elsebegin

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

Page 153: Guide Du Concepteur Des Composants en delphi

C o n t r ô l e s o r i e n t é s d o n n é e s 12-11

C r é a t i o n d ’ u n c o n t r ô l e d e m o d i f i c a t i o n d e d o n n é e s

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 clavierUne 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 :

typeTDBCalendar = class(TSampleCalendar);ƒ

protectedprocedure 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 theninherited KeyDown(Key, Shift)

elsebegin

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é.

Page 154: Guide Du Concepteur Des Composants en delphi

12-12 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C r é a t i o n d ’ u n c o n t r ô l e d e m o d i f i c a t i o n d e d o n n é e s

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 :

typeTDBCalendar = 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 datedu 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;

Page 155: Guide Du Concepteur Des Composants en delphi

C o n t r ô l e s o r i e n t é s d o n n é e s 12-13

C r é a t i o n d ’ u n c o n t r ô l e d e m o d i f i c a t i o n d e d o n n é e s

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 :

typeTDBCalendar = 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”.

Page 156: Guide Du Concepteur Des Composants en delphi

12-14 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C r é a t i o n d ’ u n c o n t r ô l e d e m o d i f i c a t i o n d e d o n n é e s

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 :

typeTDBCalendar = 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

tryFDataLink.UpdateRecord; { indique au lien de données de mettre à jour la base }

excepton 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 :

typeTDBCalendar = 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

tryFDataLink.UpdateRecord; { indique au lien de données de mettre à jour la base }

excepton 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;

Page 157: Guide Du Concepteur Des Composants en delphi

T r a n s f o r m a t i o n d ’ u n e b o î t e d e d i a l o g u e e n c o m p o s a n t 13-1

C h a p i t r e

13Chapitre13Transformation d’une boîte

de dialogue en composantIl 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.

Page 158: Guide Du Concepteur Des Composants en delphi

13-2 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

D é f i n i t i o n d e l ’ i n t e r f a c e d u c o m p o s a n t

Définition de l’interface du composantAvant 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 composantLa 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;interfaceuses

SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms;type

TAboutBoxDlg = class(TComponent)end;

Page 159: Guide Du Concepteur Des Composants en delphi

T r a n s f o r m a t i o n d ’ u n e b o î t e d e d i a l o g u e e n c o m p o s a n t 13-3

C r é a t i o n d e l ’ i n t e r f a c e d u c o m p o s a n t

procedure Register;implementationprocedure 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 composantVoici 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 :

usesWindows, 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 :

varAboutBox: TAboutBox;

Ainsi en ajoutant About à la clause uses, vous rendez disponible AboutBox au composant enveloppe.

Page 160: Guide Du Concepteur Des Composants en delphi

13-4 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C r é a t i o n d e l ’ i n t e r f a c e d u c o m p o s a n t

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 :

typeTMyWrapper = 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.

typeTAboutBoxDlg = class(TComponent)private

FProductName, FVersion, FCopyright, FComments: string; { déclare les données membres }

publishedproperty 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;

Page 161: Guide Du Concepteur Des Composants en delphi

T r a n s f o r m a t i o n d ’ u n e b o î t e d e d i a l o g u e e n c o m p o s a n t 13-5

C r é a t i o n d e l ’ i n t e r f a c e d u c o m p o s a n t

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 :

typeTMyWrapper = 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 selonla façon de fermer }

finallyDialogBox.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

Page 162: Guide Du Concepteur Des Composants en delphi

13-6 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

T e s t d u c o m p o s a n t

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)

publicfunction 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 composantUne 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.

Page 163: Guide Du Concepteur Des Composants en delphi

T r a n s f o r m a t i o n d ’ u n e b o î t e d e d i a l o g u e e n c o m p o s a n t 13-7

T e s t d u c o m p o s a n t

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.

Page 164: Guide Du Concepteur Des Composants en delphi

13-8 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

Page 165: Guide Du Concepteur Des Composants en delphi

E x t e n s i o n s d e l ’ E D I 14-1

C h a p i t r e

14Chapitre14Extensions de l’EDI

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

Page 166: Guide Du Concepteur Des Composants en delphi

14-2 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

P r é s e n t a t i o n d e l ’ A P I T o o l s

Présentation de l’API ToolsToutes 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.

Page 167: Guide Du Concepteur Des Composants en delphi

E x t e n s i o n s d e l ’ E D I 14-3

C o n c e p t i o n d ’ u n e c l a s s e e x p e r t

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 expertIl 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

Page 168: Guide Du Concepteur Des Composants en delphi

14-4 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C o n c e p t i o n d ’ u n e c l a s s e e x p e r t

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.

Page 169: Guide Du Concepteur Des Composants en delphi

E x t e n s i o n s d e l ’ E D I 14-5

A c c è s a u x s e r v i c e s d e l ’ A P I T o o l s

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 ToolsPour 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.

Page 170: Guide Du Concepteur Des Composants en delphi

14-6 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

A c c è s a u x s e r v i c e s d e l ’ A P I T o o l s

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: IOTAKeyboardDiagnosticsbegin 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 INTAServicesL’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

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.

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

Interface Description

Page 171: Guide Du Concepteur Des Composants en delphi

E x t e n s i o n s d e l ’ E D I 14-7

A c c è s a u x s e r v i c e s d e l ’ A P I T o o l s

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’imagesSupposons 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;

begininherited;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’actionsL’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);

Page 172: Guide Du Concepteur Des Composants en delphi

14-8 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

A c c è s a u x s e r v i c e s d e l ’ A P I T o o l s

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 dobegin

with Services.MainMenu.Items[I] do begin

if CompareText(Name, ’ViewsMenu’) = 0 thenbegin

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’outilsIl 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;

beginfor I := ToolBar.ButtonCount - 1 downto 0 do

Page 173: Guide Du Concepteur Des Composants en delphi

E x t e n s i o n s d e l ’ E D I 14-9

A c c è s a u x s e r v i c e s d e l ’ A P I T o o l s

beginBtn := ToolBar.Buttons[I];if Btn.Action = Action thenbegin

{ Retire "Btn" de "ToolBar" }ToolBar.Perform(CM_CONTROLCHANGE, WPARAM(Btn), 0);Btn.Free;

end;end;

end;

destructor MyWizard.Destroy;var

Services: INTAServices;Btn: TToolButton;

beginSupports(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

Page 174: Guide Du Concepteur Des Composants en delphi

14-10 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

A c c è s a u x s e r v i c e s d e l ’ A P I T o o l s

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

Page 175: Guide Du Concepteur Des Composants en delphi

E x t e n s i o n s d e l ’ E D I 14-11

U t i l i s a t i o n d e s f i c h i e r s e t d e s é d i t e u r s

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 éditeursAvant 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;

beginSupports(BorlandIDEServices, IOTAModuleServices, Svc);for I := 0 to Svc.ModuleCount - 1 dobegin

Module := Svc.Modules[I];if Supports(Module, IOTAProjectGroup, Result) then

Exit;end;Result := nil;

end;

Page 176: Guide Du Concepteur Des Composants en delphi

14-12 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C r é a t i o n d e f i c h e s e t d e p r o j e t s

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;

beginfor I := 0 to Module.ModuleFileCount - 1 dobegin

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 projetsDelphi 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.

Page 177: Guide Du Concepteur Des Composants en delphi

E x t e n s i o n s d e l ’ E D I 14-13

C r é a t i o n d e f i c h e s e t d e p r o j e t s

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;

Page 178: Guide Du Concepteur Des Composants en delphi

14-14 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

C r é a t i o n d e f i c h e s e t d e p r o j e t s

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;

privateFCreatorType: 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 dobegin

Module := Svc.Modules[I];if Supports(Module, IOTAProject, Project) thenbegin

{ Mémoriser le premier module de projet}if Result = nil then Result := Project;

endelse if Supports(Module, IOTAProjectGroup, Group) thenbegin

{ Trouve le groupe de projets et renvoie son projet actif}

Page 179: Guide Du Concepteur Des Composants en delphi

E x t e n s i o n s d e l ’ E D I 14-15

C r é a t i o n d e f i c h e s e t d e p r o j e t s

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;

privateFSource: 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;

varFormSource: string;

beginFormSource := ’{ ----------------------------------------------------------------- ’ + #13#10 + ’%s - description’+ #13#10 +

Page 180: Guide Du Concepteur Des Composants en delphi

14-16 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

N o t i f i c a t i o n d ’ u n e x p e r t d e s é v é n e m e n t s d e l ’ E D I

’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’EDIUn 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

Page 181: Guide Du Concepteur Des Composants en delphi

E x t e n s i o n s d e l ’ E D I 14-17

N o t i f i c a t i o n d ’ u n e x p e r t d e s é v é n e m e n t s d e l ’ E D I

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;

privateFModule: 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

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

Tableau 14.3 Interfaces de notification (suite)

Interface Description

Page 182: Guide Du Concepteur Des Composants en delphi

14-18 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

N o t i f i c a t i o n d ’ u n e x p e r t d e s é v é n e m e n t s d e l ’ E D I

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 thenbegin

{ 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 dobegin { Actualise tous les tampons de l’éditeur de code source. }

Editor := FModule.GetModuleFileEditor(I);

Page 183: Guide Du Concepteur Des Composants en delphi

E x t e n s i o n s d e l ’ E D I 14-19

N o t i f i c a t i o n d ’ u n e x p e r t d e s é v é n e m e n t s d e l ’ E D I

if Supports(Editor, IOTAEditBuffer, Buffer) thenbegin

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

beginPos.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

Page 184: Guide Du Concepteur Des Composants en delphi

14-20 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

N o t i f i c a t i o n d ’ u n e x p e r t d e s é v é n e m e n t s d e l ’ E D I

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 dobegin

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.

Page 185: Guide Du Concepteur Des Composants en delphi

I n d e x I-1

AA propos de, boîte de dialogue 13-2, 13-3

ajout de propriétés 13-4exécution 13-6

About, unité 13-3AboutDlg, unité 13-2abstraites, classes 1-3affectation, instructions 3-2Aide 8-4aide en ligne 8-4années bissextiles 11-8API Open Tools Voir API ToolsAPI outils natifs 14-2API Tools 14-1–14-20

créateurs 14-3, 14-12–14-16création de fichiers 14-12–14-16débogage 14-9–14-10éditeurs 14-3, 14-11–14-12experts 14-3, 14-3–14-5modules 14-3, 14-11–14-12notificateurs 14-3services 14-2, 14-5–14-12

applicationsgraphiques 1-8, 6-1réalisation de palettes 6-5, 6-6

applications multithreads, envoi de messages 7-10attributs, éditeurs de propriétés 8-11

Bbases de données 12-1

propriétés d’accès 12-6–12-7BEGIN_MESSAGE_MAP, macro 7-4, 7-7BeginAutoDrag, méthode 7-14bibliothèques de composants, ajout de

composants 1-16bibliothèques, contrôles personnalisés 1-5bitmap de palette, fichiers 1-14bitmaps 6-4

ajout aux composants 1-14chargement 6-5comparés aux contrôles graphiques 10-3hors écran 6-6–6-7surfaces de dessin 6-4

boîtes de dialogue 13-1–13-7création 13-1définition de l’état initial 13-2éditeurs de propriété comme 8-10standard Windows 13-2

création 13-2exécution 13-5

boîtes de dialogue standard 13-1, 13-2création 13-2exécution 13-5

boîtes liste 11-1BorlandIDEServices, variable 14-6BoundsChanged, méthode 7-15Broadcast, méthode 7-9Brush, propriété 6-3BrushCopy, méthode 6-3, 6-7

Ccalendriers 11-1–11-14

ajout de dates 11-5–11-11définition des propriétés et événements 11-3,

11-7, 11-12déplacement dans les 11-11–11-14en lecture seule 12-3–12-5redimensionnement 11-4sélection du jour en cours 11-10

canevas 1-8, 6-2, 6-3outils de dessin par défaut 10-6palettes 6-5–6-6

Canvas, propriété 1-8caractères 3-2carrés, dessin 10-10cercles, dessin 10-10chaînes 3-2, 3-9

renvoi 3-9champs

bases de données 12-6, 12-8enregistrements de message 7-2, 7-4, 7-7

champs de classe 10-4attribution de nom 4-3déclaration 10-6

Change, méthode 12-13classe lien de données sur un champ 12-12classes 1-2, 1-3, 2-1, 3-3

abstraites 1-3accès 2-4–2-7, 10-6ancêtre 2-4création 2-1définition 1-13, 2-2

méthodes statiques et 2-8méthodes virtuelles et 2-9

dérivation 2-2, 2-9dérivées 2-4, 2-9éditeurs de propriété comme 8-7héritage 2-8hiérarchie 2-4instanciation 2-2par défaut 2-4

Index

Page 186: Guide Du Concepteur Des Composants en delphi

I-2 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

partie publiée 2-7partie publique 2-6propriétés comme 3-3transmission comme paramètres 2-10

classes ancêtres 2-4classes dérivées 2-4

redéfinition des méthodes 2-9clic, événements 4-2, 4-8Click, méthode 4-2, 7-14

redéfinition 4-7, 11-12closures 4-9CLX

notifications système 7-10–7-16signaux 7-11–7-12

CM_EXIT, message 12-13CMExit, méthode 12-13code 5-4ColorChanged, méthode 7-15composant, expert 1-10composants 1-1, 2-1, 3-3

abstraites 1-3aide en ligne 8-4ajout à l’unité existante 1-13ajout à la palette de composants 8-1ajout aux unités 1-13bitmaps 1-14bitmaps de palette 1-14changement 9-1–9-4classes dérivées 1-3, 1-13, 10-2création 1-2, 1-9dépendances 1-6déplacement 1-16double-clic 8-16, 8-18–8-19initialisation 3-14, 10-7, 12-7installation 8-20interfaces 2-4, 13-2

conception 2-7exécution 2-6

menus contextuels 8-16, 8-17–8-18non visuels 1-5, 1-13, 13-3orientés données 12-1paquets 8-20personnalisation 1-3, 3-2, 4-1pour modifier les données 12-9–12-14pour scruter les données 12-2–12-8recensement 1-13, 8-2répondre aux événements 4-6, 4-8, 4-10, 12-8ressources, libération 13-5test 1-17, 1-19, 13-6–13-7

composants non visuels 1-5, 1-13, 13-3constructeurs 1-18, 3-13, 5-3, 11-4, 11-5, 12-7

objets ayant un propriétaire et 10-6, 10-7redéfinition 9-2

Contains, liste (paquets) 8-20contextes de périphériques 1-8, 6-2

contrôleschangement 1-3fenêtrés 1-4forme 10-8graphiques 6-4, 10-1–10-10

création 1-4, 10-3dessin 10-3, 10-8–10-10événements 6-7

palettes et 6-5–6-6personnalisés 1-5pour modifier les données 12-9–12-14pour scruter les données 12-2–12-8réception de la focalisation 1-4redessin 10-8, 10-9, 11-4, 11-5redimensionnement 6-7, 11-4

contrôles de redimensionnementgraphiques 6-7

contrôles graphiques 1-4, 6-4, 10-1–10-10comparés aux bitmaps 10-3création 1-4, 10-3dessin 10-3, 10-8–10-10enregistrement des ressources système 1-4événements 6-7

contrôles mémo 3-9modification 9-1

contrôles orientés données 12-1création 12-2–12-14destruction 12-7pour modifier les données 12-9–12-14pour scruter les données 12-2–12-8réponse aux changements 12-8

contrôles personnalisés 1-5bibliothèques 1-5

contrôles préexistants 1-5conventions d’attribution de nom

champs 4-3événements 4-9méthodes 5-2propriétés 3-7types d’enregistrement de message 7-7

CopyMode, propriété 6-3CopyRect, méthode 6-3, 6-7crayons 10-6

changement 10-8créateurs 14-3, 14-12–14-16CursorChanged, méthode 7-15

DDataChange, méthode 12-12DataField, propriété 12-6DataSource (propriété), contrôles orientés

données 12-6Day, propriété 11-6DblClick, méthode 7-14.dcr, fichiers 1-14

Page 187: Guide Du Concepteur Des Composants en delphi

I n d e x I-3

débogage des experts 14-9–14-10déclarations

classes 2-10, 10-6public 2-6published 2-7

gestionnaires d’événements 4-6, 4-9, 11-13gestionnaires de messages 7-4, 7-6, 7-8méthodes 5-4

dynamiques 2-10public 5-3statiques 2-8virtuelles 2-9

nouveaux types de composants 2-3propriétés 3-3, 3-4–3-7, 3-8, 3-14, 4-9, 10-4

stored 3-14types définis par l’utilisateur 10-3

déclarations de type, propriétés 10-3default

directive 3-13, 9-3mot réservé 3-8

DefaultHandler, méthode 7-3délégation 4-1DelphiInterface, classe 14-6déploiement

extensions de l’EDI 14-20déploiement des extensions de l’EDI 14-5déréférencement de pointeurs d’objet 2-10dérivation de classes 2-9destructeurs 5-3, 12-7

objets ayant un propriétaire et 10-6, 10-7fichiers .dfm 3-12directives

default 3-13, 9-3dynamic 2-10override 2-9, 7-4protected 4-6public 4-6published 3-3, 4-6, 13-4stored 3-14virtual 2-9

Dispatch, méthode 7-3, 7-5DoExit, méthode 12-14DoMouseWheel, méthode 7-14données, accès 12-1double-clics

composants 8-16réponse aux 8-18–8-19

DragOver, méthode 7-14Draw, méthode 6-3, 6-7dynamic, directives 2-10

Eécriture seule, propriétés 3-7EDI

ajout d’actions 14-7–14-8

ajout d’images 14-7personnalisation 14-1suppression de boutons 14-8

Edit, méthode 8-10, 8-11Editeur d’image, utilisation 1-14éditeur de code, affichage 8-19éditeurs de composants 8-16–8-20

par défaut 8-17recensement 8-20

éditeurs de propriété 3-3attributs 8-11boîtes de dialogue comme 8-10comme classes dérivées 8-7

éditeurs de propriétés 8-7–8-13recensement 8-12–8-13

éditeurs, API Tools 14-3, 14-11–14-12Ellipse, méthode 6-3ellipses, dessin 10-10EnabledChanged, méthode 7-15END_MESSAGE_MAP, macro 7-4, 7-7ensemble, types 3-2ensembles 3-2enveloppes 1-5, 13-2

initialisation 13-3 Voir aussi enveloppes de composants

enveloppes de composants 1-5, 13-2initialisation 13-3

envoi des messages 7-8–7-10erreurs de compilation, directive override et 2-9événements 1-8, 4-1–4-10

accès 4-6attribution de nom 4-9contrôles graphiques 6-7définition de nouveaux 4-7–4-10fourniture d’aide 8-4gestion de message 7-4, 7-6hérités 4-5implémentation 4-2, 4-5récupérer 4-4réponse aux 4-6, 4-8, 4-10, 12-8standard 4-5, 4-5–4-7

événements clavier 4-4, 4-10événements souris 10-2événements standard 4-5, 4-5–4-7

personnalisation 4-6événements système, personnalisation 7-16EventFilter (méthode), événements système 7-15exceptions 5-2, 7-3, 13-5Execute (méthode), boîtes de dialogue 13-5experts

API Tools 14-3Composant 1-10création 14-2, 14-3–14-5débogage 14-9–14-10installation 14-5, 14-20

Page 188: Guide Du Concepteur Des Composants en delphi

I-4 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

réponse aux événements de l’EDI 14-16types 14-4

experts fiche 14-4experts menu 14-4experts projet 14-4experts référentiel d’objets 14-4

Ffenêtre

classe 1-5contrôles 1-4gestion de message 11-4handles 1-4, 1-6procédures 7-3

fiches, en composants 13-1fichiers, graphiques 6-4FillRect, méthode 6-3finally, mot réservé 6-7, 13-5FloodFill, méthode 6-3focalisation 1-4focalisation de saisie 1-4fonctions 1-7

API Windows 1-4, 6-1attribution de nom 5-2événements et 4-3graphiques 6-2lecture des propriétés 3-7, 8-9, 8-11

fonctions membresparamètres de propriété 3-7

Font, propriété 6-3FontChanged, méthode 7-15formes géométriques, dessin 10-10FReadOnly 12-9

GGDI, applications 1-8, 6-1gestion de message 7-4–7-5gestionnaires d’événements 1-8, 4-2, 4-9, 12-8

affichage de l’éditeur de code 8-19déclarations 4-6, 4-9, 11-13méthodes 4-3, 4-5, 4-6par défaut, redéfinition 4-10paramètres 4-3, 4-8, 4-9, 4-10

notification, événements 4-8pointeurs 4-2, 4-3, 4-9transmission de paramètres par référence 4-10types 4-3–4-4, 4-8–4-9vides 4-10

gestionnaires de messages 7-2, 7-3, 11-4, 11-5création 7-6–7-8déclarations 7-6, 7-8méthodes, surcharge 7-7par défaut 7-3redéfinition 7-4

GetAttributes, méthode 8-11GetFloatValue, méthode 8-9GetMethodValue, méthode 8-9GetOrdValue, méthode 8-9GetPalette, méthode 6-6GetProperties, méthode 8-11GetStrValue, méthode 8-9GetValue, méthode 8-9glisser-déplacer, événements 10-2Graphic, propriété 6-4graphiques 6-1–6-8

chargement 6-4, 6-5complexes 6-6conteneurs 6-4enregistrement 6-4fonctions, appel 6-2indépendants 6-3méthodes 6-3, 6-5, 6-7

copie d’images 6-7palettes 6-6

outils de dessin 6-2, 6-8, 10-6changement 10-8

redessiner les images 6-7redimensionnement 6-7stockage 6-4

graphiques (méthodes), palettes 6-6grilles 11-1, 11-3, 11-5, 11-12

HHandle, propriété 1-4, 1-6, 6-3HandleException, méthode 7-3héritage de classe 2-8héritées

méthodes 4-7propriétés 10-2, 11-3

publication 3-3hérités

événements 4-5hiérarchie (classes) 2-4HookEvents, méthode 7-12hors écran, bitmaps 6-6–6-7

Iicônes 6-4

ajout aux composants 1-14identificateurs

champs de classe 4-3événements 4-9méthodes 5-2paramètres de propriété 3-7types d’enregistrement de message 7-7

images 6-3, 6-3–6-6dessin 10-8redessiner 6-7réduction du scintillement 6-6

Page 189: Guide Du Concepteur Des Composants en delphi

I n d e x I-5

indépendants du périphérique, graphiques 6-1index 3-9index, mot réservé 11-7indicateurs 12-5informations de types à l’exécution 2-7inspecteur d’objets 3-2, 8-7

aide sur 8-4modification des propriétés de tableau 3-3

Installation de composants, boîte de dialogue 1-16instances 4-2INTAComponent 14-12INTAServices 14-5, 14-6–14-7, 14-16interfaces 2-4, 13-2, 13-3

API Tools 14-1, 14-4numéros de version 14-10–14-11

conception 2-7éléments de programme non visuels 1-5exécution 2-6propriétés 3-11propriétés, déclaration 13-4

interfaces d’exécution 2-6interfaces de conception 2-7interfaces du composant

création 13-3propriétés, déclaration 13-4

Invalidate, méthode 10-9IOTAActionServices 14-5IOTABreakpointNotifier 14-16IOTACodeCompletionServices 14-5IOTAComponent 14-12IOTACreator 14-13IOTADebuggerNotifier 14-16IOTADebuggerServices 14-5IOTAEditLineNotifier 14-16IOTAEditor 14-12IOTAEditorNotifier 14-17IOTAEditorServices 14-6IOTAFile 14-13, 14-15IOTAFormNotifier 14-17IOTAFormWizard 14-3IOTAIDENotifier 14-17IOTAKeyBindingServices 14-6IOTAKeyboardDiagnostics 14-6IOTAKeyboardServices 14-6IOTAMenuWizard 14-3IOTAMessageNotifier 14-17IOTAMessageServices 14-6IOTAModule 14-11IOTAModuleNotifier 14-17IOTAModuleServices 14-6, 14-11IOTANotifier 14-16IOTAPackageServices 14-6IOTAProcessModNotifier 14-17IOTAProcessNotifier 14-17IOTAProjectWizard 14-3

IOTAServices 14-6IOTAThreadNotifier 14-17IOTAToDoServices 14-6IOTAToolsFilter 14-6IOTAToolsFilterNotifier 14-17IOTAWizard 14-2, 14-3IOTAWizardServices 14-6

KK, notes de bas de page (système d’aide) 8-5KeyDown, méthode 7-14, 12-11KeyPress, méthode 7-14KeyString, méthode 7-14KeyUp, méthode 7-14

Llecture des paramètres de propriété 3-7lecture seule, propriétés 2-6, 2-7, 3-7, 12-3libellés 1-4libération des ressources 13-5liens de données 12-5–12-8

initialisation 12-7Lines, propriété 3-9LineTo, méthode 6-3listes de recherche (système d’aide) 8-5Loaded, méthode 3-14LoadFromFile (méthode), graphiques 6-5LParam, paramètre 7-9lParam, paramètre 7-2

Mmacros 7-4MainWndProc, méthode 7-3mémoire (gestion), méthodes dynamiques ou

virtuelles 2-10menus contextuels, ajout d’éléments 8-17–8-18MESSAGE_HANDLER, macro 7-4MESSAGE_MAP, macro 7-7messages 7-1–7-8, 11-4

applications multithreads 7-10déclarations de gestionnaires 7-4décomposeur 7-2définis par l’utilisateur 7-6, 7-8définition 7-1, 7-2enregistrement (types), déclaration 7-7enregistrements 7-2, 7-4envoi 7-8–7-10identificateurs 7-6interception 7-5liés à la souris et au clavier 12-10Linux Voir notifications systèmesouris 12-10touche 12-10Windows 7-1–7-10

Page 190: Guide Du Concepteur Des Composants en delphi

I-6 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

messages de frappes de touches 4-6, 12-10messages définis par l’utilisateur 7-6, 7-8messages liés à la souris 12-10messages souris 7-2, 12-10métafichiers 6-4méthodes 1-7, 5-1, 11-11

appel 4-6, 5-3, 10-4attribution de nom 5-2déclaration 5-4

dynamiques 2-10public 5-3statiques 2-8virtuelles 2-9

dessin 10-9, 10-10gestion des messages 7-2, 7-3, 7-5gestionnaires d’événements 4-3, 4-5, 4-6

redéfinition 4-6graphiques 6-3, 6-5, 6-7

palettes 6-6héritées 4-7initialisation 3-14propriétés et 3-5–3-7, 5-1, 5-2, 10-4protected 5-3public 5-3redéfinition 2-9, 7-4, 7-5, 7-7, 11-12répartition 2-8virtuelles 2-9, 5-4

méthodes dynamiques 2-10méthodes statiques 2-8modèles de composants 2-2Modified, méthode 12-13modules 1-12

API Tools 14-3, 14-11–14-12mois, renvoyer actuel 11-8Month, propriété 11-6mots clés 8-5

protected 4-6MouseDown, méthode 7-14, 12-10MouseMove, méthode 7-14MouseUp, méthode 7-14MoveTo, méthode 6-3Msg, paramètre 7-3MyEvent_ID, type 7-16

Nnombres 3-2

valeurs de propriété 3-13notificateurs 14-3

API Tools 14-16–14-20écriture 14-19

notification, événements 4-8notifications système 7-10–7-16notifications système Linux 7-10–7-16Nouveau, commande 1-12Nouvelle unité, commande 1-12

Oobjets

ayant un propriétaire 10-5–10-8initialisation 10-7

instanciation 4-2temporaires 6-7

objets ayant un propriétaire 10-5–10-8initialisation 10-7

OnChange, événement 6-7, 10-8, 11-13, 12-13OnClick, événement 4-2, 4-3, 4-5OnCreate, événement 1-17OnDataChange, événement 12-8, 12-12OnDblClick, événement 4-5OnDragDrop, événement 4-5OnDragOver, événement 4-5OnEndDrag, événement 4-5OnEnter, événement 4-5OnExit, événement 12-14OnKeyDown, événement 4-5, 7-13, 12-11OnKeyPress, événement 4-5, 7-13OnKeyString, événement 7-13OnKeyUp, événement 4-5, 7-13OnMouseDown, événement 4-5, 7-13, 12-10OnMouseMove, événement 4-5, 7-13OnMouseUp, événement 4-5, 7-13opérations avec effet 6-7optimisation des ressources système 1-4outils de dessin 6-2, 6-8, 10-6

changement 10-8outils natifs de l’API 14-6–14-10override, directive 2-9, 7-4Owner, propriété 1-18

PPaint, méthode 6-7, 10-9, 10-10PaintRequest, méthode 7-14palette des composants

ajout de bitmaps personnalisés 1-14ajout de composants 8-1déplacement des composants 1-16

PaletteChanged, méthode 6-6, 7-15palettes 6-5–6-6

comportement par défaut 6-6spécification 6-6

paquets 8-20composants 8-20liste Contains 8-20liste Requires 8-20

par défautclasse ancêtre 2-4gestionnaires

événements 4-10message 7-3redéfinition 4-10

Page 191: Guide Du Concepteur Des Composants en delphi

I n d e x I-7

valeurs de propriété 3-8changement 9-2, 9-3spécification 3-13

paramètresclasses comme 2-10gestionnaires d’événements 4-3, 4-8, 4-9, 4-10messages 7-2, 7-3, 7-4, 7-7, 7-9, 7-10paramètres de propriété 3-7

propriétés de tableaux 3-9paramètres de propriété

écriture 3-9lecture 3-9

Parent, propriété 1-18Pen, propriété 6-3Perform, méthode 7-9personnalisation des composants 3-2picture, objets 6-4pinceaux 10-6

changement 10-8Pixel, propriété 6-3pointeurs

classes 2-10méthode 4-2, 4-3, 4-9valeurs de propriété par défaut 3-13

pointeurs de classe 2-10pointeurs de méthode 4-2, 4-3, 4-9PostMessage, méthode 7-10Presse-papiers (formats), ajout 8-16, 8-19private, propriétés 3-5procédures 1-7, 4-3

attribution de nom 5-2paramètres de propriété 8-12

programmation orientée objet 2-1–2-10déclarations 2-3, 2-10

classes 2-6, 2-7méthodes 2-8, 2-9, 2-10

propriétés 3-1–3-14accès 3-5–3-7actualisation 1-7boîtes de dialogue standard 13-2changement 8-7–8-13, 9-2, 9-3chargement 3-14comme classes 3-3composants enveloppe 13-4déclaration 3-3, 3-4–3-7, 3-8, 3-14, 4-9, 10-4

stored 3-14types définis par l’utilisateur 10-3

écriture de valeurs 3-7, 8-9écriture seule 3-7événements et 4-1, 4-2fourniture d’aide 8-4héritées 3-3, 10-2, 11-3interfaces 3-11lecture des valeurs 8-9lecture et écriture 3-5

lecture seule 2-6, 2-7, 3-7, 12-3modification sous forme textuelle 8-8nodefault 3-8présentation 1-6publiées 11-3redéclaration 3-13, 4-6sous-composants 3-10spécification des valeurs 3-13, 8-9stockage 3-14stockage de données interne 3-4, 3-7stockage et chargement des propriétés non

publiées 3-15–3-17tableau 3-3, 3-9types 3-2, 3-9, 8-9, 10-3valeurs par défaut 3-8, 3-13

redéfinition 9-2, 9-3visualisation 8-9

protecteddirective 4-6mot clé 3-3, 4-6

protégésévénements 4-6

publicdirective 4-6mot clé 4-6partie de classes 2-6propriétés 3-13

publiéespropriétés

exemple 10-2, 11-3published 3-3

directive 3-3, 4-6, 13-4mot clé 4-6partie de classes 2-7propriétés 3-13, 3-14

QQApplication_postEvent, méthode 7-16QCustomEvent_create, fonction 7-16QEvent 7-13QKeyEvent 7-13QMouseEvent 7-13Qt (événements), messages 7-16

Rread, méthode 3-7read, mot réservé 3-9, 10-4ReadOnly, propriété 12-4, 12-9, 12-11réalisation de palettes 6-5, 6-6recensement

composants 1-13éditeurs de composants 8-20éditeurs de propriété 8-12–8-13

Rectangle, méthode 6-3

Page 192: Guide Du Concepteur Des Composants en delphi

I-8 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s

rectangles, dessin 10-10redéfinition des méthodes 2-9, 7-4, 7-5, 11-12redessin des contrôles 10-8, 10-9, 11-4, 11-5redessiner les images 6-7redimensionnement des contrôles 11-4Register, procédure 1-13, 8-2RegisterComponents, procédure 1-14, 8-2RegisterPropertyEditor, procédure 8-12Requires, liste (paquets) 8-20.res, fichiers 1-16ressources 1-8, 6-1

en mémoire cache 6-2libération 13-5système, optimisation 1-4

ressources en mémoire cache 6-2ressources système, préservation 1-4Result, paramètre 7-7RTTI 2-7

SSaveToFile (méthode), graphiques 6-5SelectCell, méthode 11-14, 12-4Self, paramètre 1-18SendMessage, méthode 7-10services, API Tools 14-2, 14-5–14-12SetFloatValue, méthode 8-9SetMethodValue, méthode 8-9SetOrdValue, méthode 8-9SetStrValue, méthode 8-9SetValue, méthode 8-9ShowHintChanged, méthode 7-15signaux, réponse aux signaux (CLX) 7-11–7-12simple, types 3-2sous-classement des contrôles Windows 1-5sous-composants, propriétés 3-10stored, directive 3-14StretchDraw, méthode 6-3, 6-7StyleChanged, méthode 7-15systèmes d’aide 8-4

fichiers 8-4mots clés 8-5

Ttableaux 3-3, 3-9TabStopChanged, méthode 7-15Tapplication, événements système 7-13TBitmap 6-4TCalendar 11-1TCharProperty, type 8-8TClassProperty, type 8-8TColorProperty, type 8-8TComponent 1-5TComponentProperty, type 8-8TControl 1-4, 4-5, 4-6

TCustomControl 1-4TCustomGrid 11-1, 11-3TCustomListBox 1-4TDateTime, type 11-6TDefaultEditor 8-17temporaires, objets 6-7TEnumProperty, type 8-8test

composants 1-17, 1-19, 13-6–13-7valeurs 3-7

TextChanged, méthode 7-15TextHeight, méthode 6-3TextOut, méthode 6-3TextRect, méthode 6-3TextWidth, méthode 6-3TFieldDataLink 12-5TFloatProperty, type 8-8TFontNameProperty, type 8-8TFontProperty, type 8-8TGraphic 6-4TGraphicControl 1-4, 10-2THandleComponent 7-12TIcon 6-4TiledDraw, méthode 6-7TIntegerProperty, type 8-7, 8-9TKeyPressEvent, type 4-4TLabel 1-4TListBox 1-4TMessage 7-5, 7-7TMetafile 6-4TMethod, type 7-12TMethodProperty, type 8-8TNotifyEvent 4-8TObject 2-4ToolsAPI, unité 14-2TOrdinalProperty, type 8-7TPicture, type 6-4TPropertyAttributes 8-11TPropertyEditor, classe 8-7transfert d’enregistrements 13-2try, mot réservé 6-7, 13-5TSetElementProperty, type 8-8TSetProperty, type 8-8TStringProperty, type 8-8TWinControl 1-4, 4-5types

définis par l’utilisateur 10-3enregistrement de message 7-7propriétés 3-2, 3-9, 8-9

types définis par l’utilisateur 10-3types énumérés 3-2, 10-3

Uunités, ajout de composants 1-13UpdateCalendar, méthode 12-4

Page 193: Guide Du Concepteur Des Composants en delphi

I n d e x I-9

Vvaleurs 3-2

booléennes 3-2, 3-13, 12-5propriété par défaut 3-8, 3-13

redéfinition 9-2, 9-3test 3-7

valeurs booléennes 3-2, 3-13, 12-5var (mot réservé), gestionnaires d’événements 4-4VCL 1-1–1-2virtual

directive 2-9virtuelle

tables de méthode 2-9virtuelles

méthodes 2-9, 5-4éditeurs de propriété 8-8–8-10propriétés comme 3-2

VisibleChanged, méthode 7-15

WWidgetDestroyed, propriété 7-14Windows

boîtes de dialogue standard 13-2création 13-2exécution 13-5

contextes de périphériques 1-8, 6-2contrôles, sous-classement 1-5événements 4-5fonctions API 1-4, 6-1messages 7-1–7-10

WM_APP, constante 7-6WM_KEYDOWN, message 12-10WM_LBUTTONBUTTON, message 12-10WM_MBUTTONDOWN, message 12-10WM_PAINT, message 7-4WM_RBUTTONDOWN, message 12-10WM_SIZE, message 11-4WndProc, méthode 7-3, 7-5WordWrap, propriété 9-1WParam, paramètre 7-9wParam, paramètre 7-2write, méthode 3-7write, mot réservé 3-9, 10-4

YYear, propriété 11-6

Page 194: Guide Du Concepteur Des Composants en delphi

I-10 G u i d e d u c o n c e p t e u r d e c o m p o s a n t s