Guide Du Concepteur Des Composants en delphi

September 15, 2017 | Author: Elkhalif | Category: Application Software, Library (Computing), Software Development, Computing, Technology
Share Embed Donate


Short Description

Download Guide Du Concepteur Des Composants en delphi...

Description

Guide du concepteur de composants

Borland® ™

Delphi 7 pour Windows™

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

Table des matières Chapitre 1

Ancêtres, descendants et hiérarchies des classes . . . . . . . . . . . . . . . . . . . . . 2-3 Contrôle des accès . . . . . . . . . . . . . . . . . 2-4 Masquer les détails d’implémentation . . . . 2-5 Définition de l’interface avec le concepteur des composants . . . . . . . . . . . . . . . . 2-6 Définition de l’interface d’exécution . . . . . 2-6 Définition de l’interface de conception. . . . 2-7 Répartition des méthodes . . . . . . . . . . . . . 2-8 Méthodes statiques . . . . . . . . . . . . . . . 2-8 Exemple de méthodes statiques . . . . . . 2-8 Méthodes virtuelles . . . . . . . . . . . . . . . 2-9 Redéfinition des méthodes . . . . . . . . . 2-9 Méthodes dynamiques . . . . . . . . . . . . 2-10 Membres abstraits d’une classe . . . . . . . . . 2-10 Classes et pointeurs . . . . . . . . . . . . . . . 2-10

Présentation générale de la création d’un composant 1-1 Bibliothèque de classes. . . . . . . . . . . . . Composants et classes. . . . . . . . . . . . . . Création de composants . . . . . . . . . . . . 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 . . . Contenu d’un composant ?. . . . . . . . . . . Suppression des dépendances . . . . . . . Définition des propriétés, méthodes et événements . . . . . . . . . . . . . . . . Propriétés . . . . . . . . . . . . . . . . . Méthodes . . . . . . . . . . . . . . . . . Evénements . . . . . . . . . . . . . . . . Encapsulation des graphiques . . . . . . . Recensement des composants . . . . . . . Création d’un nouveau composant . . . . . . Création d’un composant avec l’expert composant . . . . . . . . . . . . . Création manuelle d’un composant . . . . Création d’un fichier unité . . . . . . . Dérivation du composant . . . . . . . . Recensement du composant. . . . . . . Création de bitmaps pour les composants Installation d’un composant dans la palette de composants . . . . . . . . Emplacement des fichiers du composant . Test des composants non installés. . . . . . . Test des composants installés . . . . . . . . .

. . . . . . . . . .

1-1 1-2 1-3 1-3 1-4 1-4 1-5 1-5 1-5 1-6

. . . . . . .

1-6 1-6 1-7 1-7 1-8 1-9 1-9

Chapitre 3

Création de propriétés

. 1-10 . 1-12 . 1-12 . 1-13 . 1-13 . 1-14 . 1-16 . 1-17 . 1-17 . 1-19

Chapitre 2

Programmation orientée objet et écriture des composants Définition de nouvelles classes . . . . . . . . . Dérivation de nouvelles classes . . . . . . . Modification des valeurs par défaut d’une classe pour éviter les répétitions Ajout de nouvelles capacités à une classe . . . . . . . . . . . . . . . . Déclaration d’une nouvelle classe de composant . . . . . . . . . . . . . . . . .

3-1

Pourquoi créer des propriétés ?. . . . . . . . . . 3-1 Types de propriétés. . . . . . . . . . . . . . . . . 3-2 Publication des propriétés héritées . . . . . . . . 3-3 Définition des propriétés . . . . . . . . . . . . . 3-4 Déclarations des propriétés . . . . . . . . . . 3-4 Stockage interne des données . . . . . . . . . 3-4 Accès direct . . . . . . . . . . . . . . . . . . . 3-5 Méthodes d’accès . . . . . . . . . . . . . . . . 3-5 Méthode read . . . . . . . . . . . . . . . . 3-7 Méthode write . . . . . . . . . . . . . . . . 3-7 Valeurs par défaut d’une propriété . . . . . . 3-8 Spécification d’aucune valeur par défaut 3-8 Création de propriétés tableau . . . . . . . . . . 3-9 Création de propriétés pour sous-composants . . . . . . . . . . . . . 3-10 Création des propriétés pour interfaces . . . . 3-11 Stockage 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-13 Détermination du stockage . . . . . . . . . 3-14 Initialisation après chargement . . . . . . . 3-14 Stockage 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

2-1 2-1 2-2 2-2 2-3 2-3 i

Chapitre 6

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

Graphiques et composants

Chapitre 4

Création d’événements

4-1

Qu’est-ce qu’un événement ? . . . . . . . . . . Les événements sont des pointeurs de méthodes. . . . . . . . . . . . . . . . . . Les événements sont des propriétés . . . . . Les types d’événements sont des types de pointeurs de méthodes. . . . . . . . . . Les types gestionnaire d’événement sont des procédures . . . . . . . . . . . Les gestionnaires d’événements sont facultatifs . . . . . . . . . . . . . . . . Implémentation des événements standard . . . Identification des événements standard. . . Evénements standard pour tous les contrôles . . . . . . . . . . Evénements standard pour les contrôles standard . . . . . . . . . . . . . . . . . . Rendre visibles des événements . . . . . . . Changement de la gestion des événements standard . . . . . . . . . . Définition de vos propres événements . . . . . Déclenchement de l’événement . . . . . . . Deux sortes d’événements. . . . . . . . . Définition du type de gestionnaire . . . . . Notifications simples . . . . . . . . . . . . Gestionnaires d’événements spécifiques. Renvoi d’informations à partir du gestionnaire . . . . . . . . . Déclaration de l’événement . . . . . . . . . . Les noms d’événement débutent par “On” . . . . . . . . . . . . . . . . . . Appel de l’événement . . . . . . . . . . . . .

4-1 4-2 4-2 4-3 4-3 4-4 4-5 4-5

Chapitre 7

Gestion des messages et des notifications système

4-5

Compréhension du système de gestion des messages. . . . . . . . . . . . . . . . . . . Que contient un message Windows ? . . . Répartition des messages . . . . . . . . . . Suivi du flux des messages . . . . . . . Modification de la gestion des messages . . . Surcharge de la méthode du gestionnaire . Utilisation des paramètres d’un message . Interception des messages . . . . . . . . . . Création de nouveaux gestionnaires de messages . . . . . . . . . . . . . . . . . . . Définition de vos propres messages . . . . Déclaration d’un identificateur de message . . . . . . . . . . . . . . . . Déclaration d’un type enregistrement de message . . . . . . . . . . . . . . . . Déclaration d’une nouvelle méthode de gestion d’un message . . . . . . . . . . Envoi des messages . . . . . . . . . . . . . . Diffusion d’un message à tous les contrôles d’une fiche . . . . . . . . . Appel direct du gestionnaire de message d’un contrôle. . . . . . . . Envoi d’un message à l’aide de la file d’attente des messages Windows . . . Envoi d’un message qui ne s’exécute pas immédiatement . . . . . . . . . . . Réponse aux notifications du système à l’aide de CLX . . . . . . . . . . . . . . . . . Réponse aux signaux . . . . . . . . . . . . .

4-5 4-6 4-6 4-7 4-7 4-8 4-8 4-8 4-8 4-9 4-9 4-9 4-9

Chapitre 5

Création de méthodes Eviter les interdépendances . . . . . . . . Noms des méthodes. . . . . . . . . . . . . Protection des méthodes . . . . . . . . . . Méthodes qui doivent être publiques . Méthodes qui doivent être protégées . Méthodes abstraites . . . . . . . . . . . Rendre virtuelles des méthodes . . . . . . Déclaration des méthodes . . . . . . . . .

5-1 . . . . . . . .

. . . . . . . .

. . . . . . . .

6-1

Présentation des graphiques . . . . . . . . . . . 6-1 Utilisation du canevas . . . . . . . . . . . . . . . 6-3 Travail sur les images . . . . . . . . . . . . . . . 6-3 Utilisation d’une image, d’un graphique ou d’un canevas . . . . . . . . . . . . . . . . 6-4 Chargement et stockage des graphiques . . . 6-4 Gestion des palettes. . . . . . . . . . . . . . . 6-5 Spécification d’une palette pour un contrôle . . . . . . . . . . . . . . 6-6 Bitmaps hors écran . . . . . . . . . . . . . . . . . 6-6 Création et gestion des bitmaps hors écran . 6-6 Copie des images bitmap . . . . . . . . . . . 6-7 Réponse aux changements . . . . . . . . . . . . 6-7

5-1 5-2 5-3 5-3 5-3 5-4 5-4 5-4

ii

7-1 . 7-1 . 7-2 . 7-3 . 7-3 . 7-4 . 7-4 . 7-4 . 7-5 . 7-6 . 7-6 . 7-6 . 7-6 . 7-7 . 7-8 . 7-8 . 7-9 7-10 7-10 7-10 7-11

Chapitre 9

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

Modification d’un composant existant Création et recensement du composant Modification de la classe composant. . Redéfinition du constructeur . . . . Spécification de la nouvelle valeur par défaut de la propriété . . . . .

Chapitre 8

Accessibilité des composants au moment de la conception Recensement des composants . . . . . . . . . Déclaration de la procédure Register . . . Ecriture de la procédure Register . . . . . Spécification des composants . . . . . . Spécification de la page de palette . . . Utilisation de la fonction RegisterComponents . . . . . . . . . . Fournir l’aide pour vos composants . . . . . Création du fichier d’aide. . . . . . . . . . Création des entrées . . . . . . . . . . . Aide contextuelle des composants . . . Ajout des fichiers d’aide des composants . . . . . . . . . . . . . Ajout d’éditeurs de propriétés . . . . . . . . . Dérivation d’une classe éditeur de propriétés . . . . . . . . . . . . . . . . Modification de la propriété sous une forme textuelle . . . . . . . . . Affichage de la valeur de la propriété . Définition de la valeur de la propriété Modification globale de la propriété . . . Spécification des attributs de l’éditeur . . Recensement de l’éditeur de propriétés. . Catégories de propriétés . . . . . . . . . . . . Recensement d’une propriété à la fois . . Recensement de plusieurs propriétés en une seule fois . . . . . . . . . . . . . . Spécification de catégories de propriétés . Utilisation de la fonction IsPropertyInCategory . . . . . . . . . . . Ajout d’éditeurs de composants . . . . . . . . Ajout d’éléments au menu contextuel. . . Spécification d’éléments de menu . . . Implémentation des commandes . . . . Modification du comportement suite à un double-clic . . . . . . . . . . . Ajout de formats de presse-papiers . . . . Recensement d’un éditeur de composants Compilation des composants en paquets. . .

8-1 . . . . .

8-1 8-2 8-2 8-3 8-3

. . . . .

8-3 8-4 8-4 8-4 8-6

9-1 . . . . . 9-1 . . . . . 9-2 . . . . . 9-2 . . . . . 9-3

Chapitre 10

Création d’un contrôle graphique

10-1

Création et recensement du composant . . . . 10-1 Publication des propriétés héritées . . . . . . . 10-2 Ajout de fonctionnalités graphiques . . . . . . 10-3 Détermination de ce qui doit être dessiné . 10-3 Déclaration du type de la propriété. . . 10-3 Déclaration de la propriété . . . . . . . . 10-4 Ecriture de la méthode d’implémentation . . . . . . . . . . . . 10-4 Redéfinition du constructeur et du destructeur . . . . . . . . . . . . . . 10-5 Modification des valeurs par défaut des propriétés . . . . . . . . . . . . . . 10-5 Publication du crayon et du pinceau . . . . 10-5 Déclaration des champs de classe . . . . 10-6 Déclaration des propriétés d’accès . . . 10-6 Initialisation des classes ayant un propriétaire . . . . . . . . . . . . . . 10-7 Définition des propriétés des classes ayant un propriétaire . . . . . . . . . . 10-8 Dessin de l’image du composant . . . . . . 10-8 Adaptation du dessin de la forme . . . . 10-10

. 8-6 . 8-7 . 8-7 . 8-8 . 8-9 . 8-9 . 8-10 . 8-11 . 8-12 . 8-13 . 8-14

Chapitre 11

Personnalisation d’une grille Création et recensement du composant . . Publication des propriétés héritées . . . . . Modification des valeurs initiales . . . . . Redimensionnement des cellules . . . . . . Remplissage des cellules. . . . . . . . . . . Suivi de la date . . . . . . . . . . . . . . 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 . . . . . . Navigation de mois en mois et d’année en année . . . . . . . . . . . . Navigation de jour en jour . . . . . . . . . Déplacement de la sélection . . . . . . .

. 8-14 . 8-15 . 8-16 . 8-16 . 8-17 . 8-17 . 8-18 . 8-18 . 8-19 . 8-20 . 8-20

iii

11-1 . . . . . . . . . .

. 11-1 . 11-3 . 11-3 . 11-4 . 11-5 . 11-6 . 11-6 . 11-7 . 11-8 .11-10

. .11-11 . .11-12 . .11-12

Chapitre 13

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

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

Chapitre 12

Contrôles orientés données Création d’un contrôle pour scruter les données . . . . . . . . . . . . . . . . . . . Création et recensement du composant . . Fonctionnement du contrôle en lecture seulement . . . . . . . . . . . . . . . . . . Ajout de la propriété ReadOnly . . . . Autorisation des mises à jour nécessaires . . . . . . . . . . . . . . . . Ajout du lien aux données . . . . . . . . . Déclaration du champ de classe . . . . Déclaration des propriétés d’accès . . . Exemple de déclaration des propriétés d’accès . . . . . . . . . . . . . . . . . . Initialisation du lien de données . . . . Réponse aux changements de données . . Création d’un contrôle de modification de données . . . . . . . . . . . . . . . . . . . Modification de la valeur par défaut de FReadOnly . . . . . . . . . . . . . . . . Gestion des messages liés à la souris ou au clavier. . . . . . . . . . . . . . . . . Réponse aux messages indiquant la manipulation de la souris. . . . . . Réponse aux messages indiquant la manipulation du 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 . .

Définition de l’interface du composant Création et recensement du composant Création de l’interface du composant . Inclusion de l’unité de la fiche . . . Ajout des propriétés de l’interface . Ajout de la méthode Execute . . . . Test du composant . . . . . . . . . . . .

12-1 . 12-2 . 12-2 . 12-3 . 12-4

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

13-2 13-2 13-3 13-3 13-4 13-5 13-6

Chapitre 14

Extensions de l’EDI

. 12-4 . 12-5 . 12-6 . 12-6

14-1

Présentation de l’API Tools . . . . . . . . . . . 14-2 Conception d’une classe expert . . . . . . . . . 14-3 Implémentation des interfaces de l’expert . 14-4 Installation du paquet de l’expert. . . . . . 14-5 Accès aux services de l’API Tools . . . . . . . 14-5 Utilisation d’objets natifs de l’EDI . . . . . 14-6 Utilisation de l’interface INTAServices . 14-6 Ajout d’une image à la liste d’images . 14-7 Ajout d’une action à la liste d’actions . 14-7 Suppression de boutons de barres d’outils . . . . . . . . . . . . 14-8 Débogage d’un expert . . . . . . . . . . . . 14-9 Numéros de version de l’interface . . . . 14-10 Utilisation des fichiers et des éditeurs . . . . .14-11 Utilisation des interfaces de module . . . .14-11 Utilisation des interfaces d’éditeur . . . . 14-12 Création de fiches et de projets . . . . . . . . 14-12 Création de modules . . . . . . . . . . . . 14-13 Notification d’un expert des événements de l’EDI. . . . . . . . . . . 14-16

. 12-6 . 12-7 . 12-8 . 12-9 . 12-9 12-10 12-10 12-11 12-12 12-13 12-13

Index

iv

I-1

Chapitre

1

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

Chapitre1

Ce chapitre est une présentation générale de la conception des composants et du processus d’écriture des composants pour les applications Delphi. Vous devez toutefois être familier de Delphi et de ses composants standard. • • • • • • • •

Bibliothèque de classes Composants et classes Création de composants Contenu d’un composant ? Création d’un nouveau composant Test des composants non installés Test des composants installés Installation d’un composant dans la palette de composants

Pour des informations sur l’installation de nouveaux composants, voir “Installation de paquets de composants” au Chapitre 16 du Guide du développeur.

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

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

1-1

Composants et classes

reportez-vous à la référence en ligne de la CLX pour plus de détails sur les composants. La classe TComponent est l’ancêtre partagé de chaque composant de la bibliothèque de composants. TComponent fournit les propriétés et les événements de base nécessaires au fonctionnement d’un composant dans l’EDI. Les différentes branches de la bibliothèque offrent d’autres possibilités plus spécialisées. Figure 1.1

Hiérarchie des classes de la bibliothèque de composants visuels

Lorsque vous créez un composant, vous l’ajoutez à la bibliothèque de composants en dérivant une nouvelle classe de l’un des types de classes existant dans la hiérarchie.

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

1-2

Guide du concepteur de composants

Création de composants

Création de composants Un composant peut quasiment constituer tout élément de programme manipulable à la conception. La création d’un composant consiste à dériver une nouvelle classe à partir d’une classe existante. Vous pouvez dériver un nouveau composant de plusieurs façons : • • • • •

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

Le Tableau 1.1 présente les différents types de composants et les classes que vous utiliserez comme point de départ pour chacun d’eux. Tableau 1.1

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

Pour

Démarrez avec le type suivant

Modifier un composant existant

N’importe quel composant existant tel que TButton ou TListBox, ou un type de composant abstrait tel que TCustomListBox

Créer un contrôle fenêtré (ou 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

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

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

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

1-3

Création de composants

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

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

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

1-4

Guide du concepteur de composants

Contenu d’un composant ?

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

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

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

Contenu d’un composant ? Pour que vos composants s’intègrent de manière sûre à l’environnement de Delphi, vous devez suivre certaines conventions. Dans cette section, vous allez apprendre : • • • •

Suppression des dépendances Définition des propriétés, méthodes et événements Encapsulation des graphiques Recensement des composants

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

1-5

Contenu d’un composant ?

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

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

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

1-6

Guide du concepteur de composants

Contenu d’un composant ?

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

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

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

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

1-7

Contenu d’un composant ?

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

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

1-8

Guide du concepteur de composants

Création d’un nouveau composant

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

Création d’un nouveau composant Vous pouvez créer un nouveau composant de deux façons : • Création d’un composant avec l’expert composant • Création manuelle d’un composant Vous pouvez utiliser l’une ou l’autre de ces méthodes pour créer un composant aux fonctions minimales, prêt à être installé dans la palette de composants. Après l’installation, vous pouvez placer votre nouveau composant sur une fiche et le tester à la fois en mode conception et en mode exécution. Vous pouvez ensuite ajouter d’autres fonctionnalités au composant, mettre à jour la palette de composants et poursuivre les tests. Il y a quelques étapes de base à suivre chaque fois que vous créez un nouveau composant. Elles sont décrites ci-après ; pour les autres exemples présentés dans ce document, nous supposerons que vous savez effectuer ces étapes.

1 Création d’une unité pour le nouveau composant. 2 Dérivation du composant à partir d’un type de composant existant. 3 Ajout de propriétés, méthodes et événements. 4 Recensement de votre composant dans l’EDI. 5 Création d’un bitmap pour le composant 6 Création d’un paquet (bibliothèque dynamiquement liée spéciale) pour pouvoir installer le composant dans l’EDI. 7 Création d’un fichier d’aide pour le composant et ses propriétés, méthodes et événements. Créer un fichier d’aide pour indiquer aux utilisateurs d’un composant comment utiliser celui-ci n’est pas obligatoire.

Remarque

Lorsque vous avez terminé, le composant complet est constitué des fichiers suivants : • • • • •

Un Un Un Un Un

fichier fichier fichier fichier fichier

paquet (.BPL) ou un fichier collection de paquets (.DPC) paquet compilé (.DCP) unité compilée (.DCU) bitmap pour la palette (.DCR) d’aide (.HLP)

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

1-9

Création d’un nouveau composant

Vous pouvez également créer un bitmap pour représenter votre nouveau composant. Voir “Création de bitmaps pour les composants” à la page 1-14. Les chapitres du reste de la partie V expliquent tous les aspects de la construction des composants et offrent des exemples complets d’écriture de plusieurs sortes de composants.

Création d’un composant avec l’expert composant L’expert composant simplifie les premières étapes de création d’un composant. Lorsque vous utilisez l’expert composant, vous devez spécifier : • • • • • •

La Le La Le Le Le

classe à partir de laquelle le composant est dérivé. nom de classe du nouveau composant. page de la palette de composants où vous voulez qu’il apparaisse. nom de l’unité dans laquelle le composant est créé. chemin d’accès à cette unité. nom du paquet dans lequel vous voulez placer le composant.

L’expert composant exécute les opérations que vous exécuteriez pour créer manuellement un composant : • Création d’une unité. • Dérivation du composant. • Recensement du composant. L’expert composant ne peut pas ajouter de composant à une unité existante. Cela ne peut se faire que manuellement.

1 Pour ouvrir l’expert composant, choisissez l’une de ces deux méthodes : • Choisissez Composant|Nouveau composant. • Choisissez Fichier|Nouveau|Autre et double-cliquez sur Composant

2 Remplissez les champs de l’expert composant : • Dans Type ancêtre, spécifiez la classe à partir de laquelle vous dérivez le nouveau composant. Remarque

Dans la liste déroulante, de nombreux composants sont présentés deux fois avec des noms d’unité différents, un pour les applications VCL et l’autre pour les applications CLX. Les unités spécifiques à la CLX commencent par Q (telles que QGraphics au lieu de Graphics). Veillez à dériver à partir du bon composant. • Dans Nom de classe, spécifiez le nom de classe de votre nouveau composant. • Dans Page de palette, spécifiez la page de la palette dans laquelle vous voulez installer le composant. • Dans Nom de fichier unité, spécifiez le nom de l’unité dans laquelle vous voulez déclarer la classe du composant. Si l’unité n’est pas dans le chemin de recherche, modifiez ce dernier.

1-10

Guide du concepteur de composants

Création d’un nouveau composant

Figure 1.2

L’expert composant

3 Lorsque vous avez rempli les champs de l’expert composant, effectuez l’une des opérations suivantes : • Cliquez sur Installer. Pour placer un composant dans un paquet nouveau ou non, cliquez sur Composant|Installer et spécifiez le paquet dans la boîte de dialogue qui apparaît. Voir “Test des composants non installés” à la page 1-17.

4 Cliquez sur OK. L’EDI crée une nouvelle unité. Attention

Si vous dérivez un composant d’une classe dont le nom commence par “custom” (comme TCustomControl), ne tentez pas de le placer sur une fiche avant d’avoir surchargé toute méthode abstraite du composant initial. Delphi ne peut pas créer d’instance d’une classe ayant des propriétés ou des méthodes abstraites. Pour voir le code source de votre unité, cliquez sur Voir l’unité. Si l’expert composant est déjà fermé, ouvrez le fichier unité dans l’éditeur de code en sélectionnant Fichier|Ouvrir. Delphi crée une nouvelle unité contenant la déclaration de classe et la procédure Register, et ajoute une clause uses qui comprend toutes les unités Delphi standard. L’unité ressemble à cela : unit MyControl; interface uses Windows, Messages, SysUtils, Types, Classes, Controls; type TMyControl = class(TCustomControl) private { Déclarations privées } protected { Déclarations protégées } public { Déclarations publiques } published { Déclarations publiées }

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

1-11

Création d’un nouveau composant

end; procedure Register; implementation procedure Register; begin RegisterComponents(’Samples’, [TMyControl]); //Dans CLX, utilisez une autre page end; end. Remarque

Aux endroits où les applications CLX utilisent des unités distinctes, celles-ci sont remplacées par des unités de même nom précédées d’un Q ; par exemple, QControls remplace Controls. Dans le cas d’une dérivation à partir de TCustomControl dans l’unité QControls, la seule différence est la clause uses qui ressemble à ceci : uses SysUtils, Types, Classes, QGraphics, QControls, QForms, QDialogs, QStdCtrls;

Création manuelle d’un composant Le moyen le plus simple de créer un composant est d’utiliser l’expert composant. Cependant, vous pouvez effectuer manuellement les mêmes étapes. Pour créer un composant manuellement, suivez ces étapes :

1 Création d’un fichier unité 2 Dérivation du composant 3 Recensement du composant

Création d’un fichier unité Une unité est un module de code Delphi compilé séparément. Delphi emploie les unités pour plusieurs raisons. Chaque fiche possède sa propre unité et la plupart des composants (ou des groupes logiques de composants) possèdent aussi leurs propres unités Lorsque vous créez un composant, vous créez une nouvelle unité pour ce composant ou bien vous l’ajoutez à une unité existante. Pour créer une nouvelle unité pour un composant :

1 Choisissez l’une des commandes suivantes : • Fichier|Nouveau|Unité. • Fichier|Nouveau|Autre pour afficher la boîte de dialogue Nouveaux éléments, sélectionnez Unité puis OK. L’EDI crée un nouveau fichier unité et l’ouvre dans l’éditeur de code.

2 Enregistrez ce fichier sous un nom significatif. 3 Dérivez la classe composant.

1-12

Guide du concepteur de composants

Création d’un nouveau composant

Pour ouvrir une unité existante :

1 Choisissez Fichier|Ouvrir et sélectionnez l’unité de code source dans laquelle vous voulez ajouter vos composants. Remarque

Lorsque vous ajoutez un composant à une unité, vérifiez que cette unité ne contient que du code de composant. L’ajout d’un code de composant à une unité qui contient, par exemple, une fiche, provoquera des erreurs dans la palette de composants.

2 Dérivez la classe composant.

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

Pour l’instant, le nouveau composant ne fait rien de plus que TComponent. C’est juste un squelette sur lequel vous allez bâtir votre nouveau composant. La dérivation des classes est expliquée plus en détail dans “Définition de nouvelles classes” à la page 2-1.

Recensement du composant Le recensement est une opération simple qui indique à l’EDI les composants à ajouter à la bibliothèque des composants et les pages de la palette sur lesquelles ils doivent apparaître. Pour une présentation plus détaillée du processus de recensement, voir Chapitre 8, “Accessibilité des composants au moment de la conception”. Pour recenser un composant :

1 Ajoutez une procédure nommée Register à la partie interface de l’unité du composant. Register n’a pas de paramètres, la déclaration est donc très simple : procedure Register;

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

1-13

Création d’un nouveau composant

Si vous ajoutez un composant à une unité qui contient déjà des composants, elle doit déjà avoir la procédure Register déclarée, afin que vous ne soyez pas obligé de changer la déclaration. Remarque

Bien que Delphi soit un langage qui ne tient pas compte de la distinction minuscules/majuscules, la procédure Register en tient compte et doit être orthographiée avec un R majuscule.

2 Ecrivez la procédure Register dans la partie implémentation de l’unité, en appelant RegisterComponents pour chaque composant que vous voulez recenser. RegisterComponents est une procédure qui prend deux paramètres : le nom d’une page de palette de composants et un ensemble de types de composants. Si vous ajoutez un composant à un recensement existant, vous pouvez soit ajouter le nouveau composant à l’ensemble dans l’instruction existante, soit ajouter une nouvelle instruction qui appelle RegisterComponents. Pour recenser un composant appelé TMyControl et le placer sur la page Exemples de la palette, vous devrez ajouter la procédure Register suivante à l’unité contenant la déclaration de TMyControl : procedure Register; begin RegisterComponents(’Samples’, [TNewControl]); end;

Cette procédure Register place TMyControl sur la page Exemples de la palette des composants. Une fois le composant recensé, vous pouvez le compiler dans un paquet (voir Chapitre 8, “Accessibilité des composants au moment de la conception”) et l’installer sur la palette des composants.

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

1 Choisissez Outils|Editeur d’images. 2 Dans la boîte de dialogue Editeur d’images, choisissez Fichier|Nouveau| Ressources composants (.dcr).

1-14

Guide du concepteur de composants

Création d’un nouveau composant

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

Vous devez mettre toutes les lettres en majuscules, quelle que soit la façon dont vous avez saisi le nom de la classe dans la boîte de dialogue Nouveau composant.

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

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

1-15

Installation d’un composant dans la palette de composants

répertoire LIB. Cliquez sur le bouton Parcourir pour désigner un autre emplacement pour l’unité générée pour le composant. Remarque

Si vous utilisez un fichier .res pour le bitmap plutôt qu’un fichier .dcr, ajoutez au source du composant une référence qui lie la ressource. Par exemple, si votre fichier .res s’appelle MyNewButton.res, après vous être assuré que le .pas et le .res se trouvent dans le même répertoire, ajoutez ce qui suit à MyNewButton.pas sous la section type : {*R *.res}

10 Choisissez Composant|Installer un composant pour installer votre composant dans un nouveau paquet ou dans un paquet existant. Cliquez sur OK. Votre nouveau paquet est construit puis installé. Le bitmap représentant votre nouveau composant apparaît sur la page de la palette de composants choisie dans l’expert Composant.

Installation d’un composant dans la palette de composants Pour installer des composants dans un paquet et sur la palette des composants :

1 Choisissez Composant|Installer un composant. La boîte de dialogue Installation de composant apparaît.

2 Installez le nouveau composant dans un paquet existant ou nouveau en choisissant la page appropriée. 3 Entrez le nom du fichier .pas contenant le nouveau composant ou choisissez Parcourir pour rechercher l’unité. 4 Ajustez le chemin de recherche si le fichier .pas du nouveau composant ne se trouve pas dans l’emplacement par défaut indiqué. 5 Spécifiez le nom du paquet dans lequel installer le composant ou choisissez Parcourir pour localiser le paquet. 6 Si le composant est installé dans un nouveau paquet, vous pouvez également spécifier une description du nouveau paquet. 7 Choisissez OK pour fermer la boîte de dialogue Installation de composants. Cela compile/reconstruit le paquet et installe le composant dans la palette des composants. Remarque

Les composants nouvellement installés apparaissent initialement dans la page de la palette de composants qui a été spécifiée par le concepteur du composant. Vous pouvez déplacer le composant dans une page différente après son installation dans la palette en utilisant la boîte de dialogue Composant| Configurer la palette. Pour plus d’informations sur les concepteurs de composants qui doivent distribuer leurs composants aux utilisateurs en vue de leur installation sur la palette des composants, voir “Emplacement des fichiers du composant” à la page 1-17.

1-16

Guide du concepteur de composants

Test des composants non installés

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

Test des composants non installés Vous pouvez tester le comportement d’un composant à l’exécution avant de l’installer sur la palette de composants. Cette technique est particulièrement utile pour le débogage des composants nouvellement créés, mais vous pouvez l’utiliser pour tester n’importe quel composant, que celui-ci apparaisse ou non sur la palette de composants. Pour plus d’informations sur le test des composants déjà installés, voir “Test des composants installés” à la page 1-19. Vous pouvez tester un composant non installé en émulant les actions exécutées par Delphi quand le composant est sélectionné dans la palette et placé dans une fiche. Pour tester un composant non installé,

1 Ajoutez le nom de l’unité du composant à la clause uses de l’unité fiche. 2 Ajoutez un champ objet à la fiche pour représenter le composant. Il s’agit là d’une des différences principales entre votre façon d’ajouter des composants et celle utilisée par Delphi. Vous ajoutez le champ objet à la partie publique à la fin de la déclaration de type de fiche. Delphi l’ajouterait au-dessus, dans la partie de la déclaration de type qu’il gère. Il ne faut jamais ajouter de champs à la partie gérée par Delphi de la déclaration de type de fiche. Les éléments de cette partie correspondent à ceux stockés dans le fichier fiche. L’ajout des noms de composants qui n’existent pas sur la fiche peut rendre incorrect le fichier de la fiche.

3 Attachez un gestionnaire à l’événement OnCreate de la fiche. 4 Construisez le composant dans le gestionnaire OnCreate de la fiche. Lors de l’appel au constructeur du composant, vous devez transmettre un paramètre spécifiant le propriétaire du composant (le composant chargé de la destruction du composant au moment opportun). Il faut pratiquement toujours transmettre Self comme propriétaire. Dans une méthode, Self représente une

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

1-17

Test des composants non installés

référence sur l’objet contenant la méthode. Dans ce cas, dans le gestionnaire OnCreate de la fiche, Self représente la fiche.

5 Initialisez la propriété Parent. L’initialisation de la propriété Parent est toujours la première opération à effectuer après la construction d’un contrôle. Le parent est le composant qui contient visuellement le contrôle ; le plus souvent c’est sur la fiche que le contrôle apparaît, mais il peut aussi s’agir d’une boîte groupe ou d’un volet. Normalement, il faut donner à Parent la valeur Self, c’est-à dire la fiche. Initialisez toujours Parent avant les autres propriétés du contrôle. Attention

Si votre composant n’est pas un contrôle (c’est-à-dire si TControl n’est pas un de ses ancêtres), passez cette étape. Si vous définissez accidentellement la propriété Parent de la fiche (à la place de celle du composant) à la valeur Self, vous pouvez provoquer un problème du système d’exploitation.

6 Si vous le souhaitez, initialisez d’autres propriétés du composant. Supposons que vous souhaitiez tester un nouveau composant de type TMyControl dans une unité appelée MyControl. Créez un nouveau projet, puis effectuez les étapes nécessaires pour avoir une unité fiche qui ressemble à ceci : unit Unit1; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, MyControl; { 1. Ajouter NewTest à la clause uses. } type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); { 2 Attacher un gestionnaire à OnCreate } private { Déclarations privées } public { Déclarations publiques } MyControl1: TMyControl1; { 3. Ajouter un champ objet } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); begin MyControl1 := TMyControl.Create(Self); { 4. Construire le composant } MyControl1.Parent := Self; { 5. Si le composant est un contrôle, initialiser la propriété Parent. } MyControl1.Left := 12; { 6. Définir les autres propriétés... ƒ ...continuer si nécessaire } end; end.

1-18

Guide du concepteur de composants

Test des composants installés

Test des composants installés Vous pouvez tester le comportement d’un composant à la conception après son installation sur la palette de composants. Cette technique est particulièrement utile pour le débogage des composants nouvellement créés, mais vous pouvez l’utiliser pour tester n’importe quel composant, que celui-ci apparaisse ou non sur la palette de composants. Pour plus d’informations sur le test des composants qui n’ont pas encore été installés, voir “Test des composants non installés” à la page 1-17. Le test de vos composants après l’installation vous permet de déboguer le composant qui génère seulement des exceptions à la conception lorsqu’il est déposé sur une fiche. Testez un composant installé en exécutant une seconde instance de l’EDI :

1 Choisissez Projet|Options et sur la page Répertoires/Conditions, affectez à la zone Sources débogage le fichier source du composant. 2 Sélectionnez ensuite Outils|Options du débogueur. Sur la page Exceptions du langage, activez les exceptions à suivre. 3 Ouvrez le fichier source du composant et définissez des points d’arrêt. 4 Sélectionnez Exécuter|Paramètres et affectez au champ Application hôte le nom et l’emplacement du fichier exécutable de Delphi. 5 Dans la boîte de dialogue Paramètres d’exécution, cliquez sur le bouton Charger pour démarrer une seconde instance de Delphi. 6 Déposez ensuite les composants à tester sur la fiche, ce qui devrait provoquer l’arrêt sur les points d’arrêt définis dans le source.

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

1-19

1-20

Guide du concepteur de composants

Chapitre

2

Programmation orientée objet et écriture des composants

Chapitre2

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

Définition de nouvelles classes Ancêtres, descendants et hiérarchies des classes Contrôle des accès Répartition des méthodes Membres abstraits d’une classe Classes et pointeurs

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

Programmation orientée objet et écriture des composants

2-1

Définition de nouvelles classes

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

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

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

2-2

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.

Guide du concepteur de composants

Ancêtres, descendants et hiérarchies des classes

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

Déclaration d’une nouvelle classe de composant Outre les composants standard, Delphi fournit de nombreuses classes abstraites qui serviront de base pour dériver de nouveaux composants. Le Tableau 1.1 à la page 1-3 montre les classes que vous pouvez utiliser pour créer vos propres composants. Pour déclarer une nouvelle classe de composant, ajoutez une déclaration de classe au fichier unité du composant. Voici la déclaration d’un composant graphique simple : type TSampleShape = class(TGraphicControl) end;

Une déclaration de composant achevée comprend généralement la déclaration des propriétés, des événements et des méthodes avant le end. Mais une déclaration comme celle ci-dessus est aussi admise et représente un point de départ pour l’ajout de fonctionnalités à votre composant.

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

Programmation orientée objet et écriture des composants

2-3

Contrôle des accès

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

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

2-4

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.

Guide du concepteur de composants

Contrôle des accès

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

Masquer les détails d’implémentation Déclarer private une partie d’une classe rend cette partie invisible au code situé hors du fichier unité de la classe. Dans l’unité qui contient la déclaration, le code peut accéder à cette partie comme si elle était publique. L’exemple Delphi suivant montre comment le fait de déclarer une donnée membre private empêche les utilisateurs d’accéder aux informations. La liste montre les deux unités de fiche VCL. Chaque fiche a un gestionnaire pour son événement OnCreate qui affecte une valeur à la donnée membre private. Le compilateur autorise l’affectation à la donnée membre uniquement dans la fiche où elle a été déclarée. unit HideInfo; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs; type TSecretForm = class(TForm) procedure FormCreate(Sender: TObject); private FSecretCode: Integer; end;

{ déclarer une nouvelle fiche } { déclare la partie privée } { déclare une donnée membre private }

var SecretForm: TSecretForm; implementation {$R *.dfm} procedure TSecretForm.FormCreate(Sender: TObject); begin FSecretCode := 42; end; end. unit TestHide;

{ ceci se compile correctement } { fin de l’unité } { il s’agit du fichier fiche principal }

interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, HideInfo; { utilise l’unité avec TSecretForm } type TTestForm = class(TForm) procedure FormCreate(Sender: TObject); end;

Programmation orientée objet et écriture des composants

2-5

Contrôle des accès

var TestForm: TTestForm; implementation procedure TTestForm.FormCreate(Sender: TObject); begin SecretForm.FSecretCode := 13; end; end.

{ le compilateur s’arrête avec "Identificateur de champ attendu" } { fin de l’unité }

Bien qu’un programme utilisant l’unité HideInfo puisse utiliser les classes de type TSecretForm, il ne peut accéder au champ FSecretCode dans aucune de ces classes. Remarque

Les noms et les emplacements de certaines unités sont différents pour les applications CLX. Par exemple, l’unité Controls est QControls dans une application CLX.

Définition de l’interface avec le concepteur des composants Déclarer protected une partie d’une classe rend cette partie uniquement visible par cette classe et par ses descendants (et par les autres classes qui partagent leurs fichiers unité). Vous pouvez utiliser les déclarations protected pour définir l’interface de conception des composants d’une classe. Les unités de l’application ne peuvent pas accéder aux parties protected, mais les classes dérivées le peuvent. Cela signifie que les concepteurs des composants peuvent modifier la façon dont fonctionne une classe sans rendre apparents ces détails aux développeurs d’applications. Remarque

Une erreur commune consiste à essayer d’accéder aux méthodes protégées d’un gestionnaire d’événement. Les gestionnaires d’événements sont généralement des méthodes de la fiche, et non du composant qui reçoit l’événement. Par conséquent, ils n’accèdent pas aux méthodes protégées du composant (à moins que le composant ne soit déclaré dans la même unité que la fiche).

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

2-6

Guide du concepteur de composants

Contrôle des accès

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

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

Programmation orientée objet et écriture des composants

2-7

Répartition des méthodes

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

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

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

2-8

Guide du concepteur de composants

Répartition des méthodes

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

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

Programmation orientée objet et écriture des composants

2-9

Membres abstraits d’une classe

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

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

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

2-10

Guide du concepteur de composants

Chapitre

3

Création de propriétés

Chapitre3

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

Pourquoi créer des propriétés ? Types de propriétés Publication des propriétés héritées Définition des propriétés Création de propriétés tableau Stockage et chargement des propriétés

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

Création de propriétés

3-1

Types de propriétés

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

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

3-2

Affichage des propriétés dans l’inspecteur d’objets 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).

Guide du concepteur de composants

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

Tableau 3.1 Type de propriété

Affichage des propriétés dans l’inspecteur d’objets (suite) Traitement de l’inspecteur d’objets

Objet

Les propriétés qui sont elles-mêmes des classes ont souvent leur propre éditeur de propriétés, qui est spécifié dans la procédure de recensement du composant. Si la classe d’une propriété a ses propres propriétés publiées (published), l’inspecteur d’objets permet au développeur d’étendre la liste (en double-cliquant) afin d’inclure ces propriétés et de les modifier individuellement. Les propriétés doivent descendre de TPersistent.

Interface

Les propriétés qui sont des interfaces peuvent apparaître dans l’inspecteur d’objets tant que la valeur est une interface implémentée par un composant (un descendant de TComponent). Les propriétés interface ont souvent leur propre éditeur de propriétés.

Tableau

Les propriétés tableau doivent disposer d’un éditeur de propriétés spécifique. L’inspecteur d’objets ne dispose d’aucune fonction intégrée permettant de modifier les propriétés de ce type. Vous pouvez spécifier un éditeur de propriétés lorsque vous recensez vos composants.

Publication des propriétés héritées Tous les composants héritent des propriétés de leurs classes ancêtre. Lorsque vous dérivez un composant à partir d’un composant existant, le nouveau composant hérite de toutes les propriétés de l’ancêtre immédiat. Si vous effectuez la dérivation à partir d’une des classes abstraites, aucune des propriétés héritées n’est published, la plupart sont protected ou public. Pour rendre disponible dans l’inspecteur d’objets une propriété protected ou private au moment de la conception, vous devez redéclarer published la propriété. Redéclarer une propriété signifie ajouter la déclaration d’une propriété héritée à la déclaration de la classe descendante. Si vous dérivez un composant de TWinControl, par exemple, il hérite de la propriété protégée DockSite. En redéclarant DockSite dans votre nouveau composant, vous pouvez changer le niveau de protection en public ou publié. Le code suivant montre une redéclaration de DockSite publié, le rendant disponible lors de la conception. type TSampleComponent = class(TWinControl) published property DockSite; end;

Lorsque vous redéclarez une propriété, vous spécifiez uniquement le nom de la propriété, non le type ni les autres informations décrites plus loin dans “Définition des propriétés”. Vous pouvez aussi déclarer de nouvelles valeurs par défaut et spécifier si la propriété est ou non stockée. Les redéclarations peuvent augmenter la visibilité d’une propriété, mais pas la réduire. Vous pouvez ainsi rendre une propriété protégée publique, mais vous ne pouvez pas masquer une propriété publique en la redéclarant protégée.

Création de propriétés

3-3

Définition des propriétés

Définition des propriétés Cette section montre comment déclarer de nouvelles propriétés et explique certaines conventions respectées par les composants standard. Ces rubriques comprennent : • • • • •

Déclarations des propriétés Stockage interne des données Accès direct Méthodes d’accès Valeurs par défaut d’une propriété

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

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

3-4

Guide du concepteur de composants

Définition des propriétés

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

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

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

Création de propriétés

3-5

Définition des propriétés

Voici une classe qui déclare trois propriétés en utilisant le spécificateur d’index, qui autorise aux trois propriétés d’avoir les mêmes méthodes d’accès en lecture et en écriture : type TSampleCalendar = class(TCustomGrid) public property Day: Integer index 3 read GetDateElement write SetDateElement; property Month: Integer index 2 read GetDateElement write SetDateElement; property Year: Integer index 1 read GetDateElement write SetDateElement; private function GetDateElement(Index: Integer): Integer; { notez le paramètre Index } procedure SetDateElement(Index: Integer; Value: Integer); ƒ

Comme chaque élément de la date (day, month et year) est un int et comme la définition de chacun requiert le codage de la date lorsqu’elle est définie, le code évite la duplication en partageant les méthodes de lecture et d’écriture pour les trois propriétés. Vous n’avez besoin que d’une seule méthode pour lire un élément date et une autre pour écrire l’élément date. Voici la méthode read qui obtient l’élément date : function TSampleCalendar.GetDateElement(Index: Integer): Integer; var AYear, AMonth, ADay: Word; begin DecodeDate(FDate, AYear, AMonth, ADay); { éclate la date encodée en éléments } case Index of 1: Result := AYear; 2: Result := AMonth; 3: Result := ADay; else Result := -1; end; end;

Voici la méthode write qui définit l’élément date approprié : procedure TSampleCalendar.SetDateElement(Index: var AYear, AMonth, ADay: Word; begin if Value > 0 then begin DecodeDate(FDate, AYear, AMonth, ADay); { case Index of 1: AYear := Value; 2: AMonth := Value; 3: ADay := Value; else Exit; end; FDate := EncodeDate(AYear, AMonth, ADay); Refresh; end; end;

3-6

Guide du concepteur de composants

Integer; Value: Integer);

{ tous les éléments doivent être positifs } récupère les éléments courants de la date } { définit le nouvel élément selon l’index }

{ encode la date modifiée } { mise à jour du calendrier visible }

Définition des propriétés

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

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

Création de propriétés

3-7

Définition des propriétés

Valeurs par défaut d’une propriété Lorsque vous déclarez une propriété, vous pouvez déclarer une valeur par défaut. Delphi utilise cette valeur par défaut pour déterminer si une propriété doit être stockée dans un fichier fiche. Si vous ne donnez pas de valeur par défaut à une propriété, Delphi stocke toujours cette propriété. Pour spécifier une valeur par défaut pour une propriété, ajoutez la directive default à la déclaration (ou à la redéclaration) de la propriété, suivie par la valeur par défaut. Par exemple, property Cool Boolean read GetCool write SetCool default True; Remarque

Déclarer une valeur par défaut pour une propriété n’a pas pour effet de définir cette propriété par cette valeur. La méthode constructeur du composant doit initialiser la valeur des propriétés lorsque c’est nécessaire. Toutefois, comme les objets initialisent toujours leurs champs à 0, il n’est pas nécessaire que le constructeur initialise les propriétés entières à 0, les propriétés chaînes à null ni les propriétés booléennes à False.

Spécification d’aucune valeur par défaut Lorsque vous redéclarez une propriété, vous pouvez indiquer que la propriété ne possède pas de valeur par défaut, même si une telle valeur est définie pour la propriété reçue en héritage. Pour indiquer qu’une propriété n’a pas de valeur par défaut, ajoutez la directive nodefault à la déclaration de la propriété. Par exemple, property FavoriteFlavor string nodefault;

Lorsque vous déclarez une propriété pour la première fois, vous n’êtes pas obligé de spécifier nodefault. L’absence d’une valeur par défaut déclarée indique l’inexistence d’une valeur par défaut. Voici la déclaration d’un composant qui comprend une propriété booléenne unique appelée IsTrue dont la valeur par défaut est True. La déclaration ci-dessous (dans la section implémentation de l’unité) représente le constructeur qui initialise la propriété. type TSampleComponent = class(TComponent) private FIsTrue: Boolean; public constructor Create(AOwner: TComponent); override; published property IsTrue: Boolean read FIsTrue write FIsTrue default True; end; ƒ constructor TSampleComponent.Create(AOwner: TComponent); begin inherited Create(AOwner); { appelle le constructeur hérité } FIsTrue := True; { définit la valeur par défaut } end;

3-8

Guide du concepteur de composants

Création de propriétés tableau

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

Création de propriétés

3-9

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

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

3-10

Guide du concepteur de composants

Création des propriétés pour interfaces

begin FTimer := TTimer.Create(self); FTimer.Name := ’Timer’; bit facultatif, rend le résultat plus agréable FTimer.SetSubComponent(True); FTimer.FreeNotification(self); end; end; end; end;

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

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

Appeler ReferenceInterface avec une interface est équivalent à appeler la méthode FreeNotification d’un autre composant. Donc, après l’appel de ReferenceInterface

Création de propriétés

3-11

Stockage et chargement des propriétés

depuis le setter de la propriété, vous pouvez surcharger la méthode Notification pour gérer les notifications depuis l’implémenteur de l’interface : procedure TDemoComponent.Notification(AComponent: TComponent; Operation: TOperation); begin inherited Notification(AComponent, Operation); if (Assigned(MyIntfProp)) and (AComponent.IsImplementorOf(MyInftProp)) then MyIntfProp := nil; end;

Notez que le code de Notification assigne la valeur nil à la propriété MyIntfProp, et non au champ privé (FIntfField). Cela garantit que Notification appelle le setter de la propriété, qui appelle ReferenceInterface pour annuler la demande de notification établie lorsque la valeur de la propriété a été définie au préalable. Toutes les assignations de la propriété interface doivent se faire via le setter de la propriété.

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

Utilisation du mécanisme de stockage et de chargement Spécification des valeurs par défaut Détermination du stockage Initialisation après chargement Stockage et chargement des propriétés non publiées

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

3-12

Guide du concepteur de composants

Stockage et chargement des propriétés

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

Spécification des valeurs par défaut Les composants Delphi ne stockent la valeur des propriétés que si elles diffèrent des valeurs par défaut. Sauf indication contraire, Delphi suppose qu’une propriété n’a pas de valeur par défaut, ce qui a pour conséquence que le composant stocke toujours la propriété, quelle que soit sa valeur. Pour spécifier une valeur par défaut pour une propriété, ajoutez la directive default et la nouvelle valeur par défaut à la fin de la déclaration de la propriété. Vous pouvez également spécifier une valeur par défaut en redéclarant une propriété. De fait, l’attribution d’une autre valeur par défaut est l’une des raisons qui peut vous amener à redéclarer une propriété. Remarque

La spécification d’une valeur par défaut n’a pas pour effet d’attribuer cette valeur à la propriété lorsque l’objet est créé. Le constructeur du composant doit s’en charger. Une propriété dont la valeur n’est pas définie par le constructeur du composant, a la valeur zéro, ou la valeur affichée par la propriété quand son stockage en mémoire est 0. Par défaut, les nombres valent donc 0, les booléens False, les pointeurs nil, etc. En cas de doute, affectez une valeur dans la méthode du constructeur. Le code suivant montre la déclaration d’un composant qui attribue une valeur par défaut à la propriété Align, ainsi que l’implémentation du constructeur du composant qui affecte cette valeur. Dans notre exemple, le nouveau composant est un cas particulier du composant volet standard utilisé en tant que barre d’état dans une fenêtre. Il doit donc implicitement s’aligner sur la bordure inférieure de son propriétaire. type TStatusBar = class(TPanel) public constructor Create(AOwner: TComponent); override; published property Align default alBottom; end;

{ surcharge pour définir la nouvelle valeur par défaut }

{ redéclare avec la nouvelle valeur par défaut }

ƒ constructor TStatusBar.Create(AOwner: TComponent); begin inherited Create(AOwner); { effectue une initialisation héritée }

Création de propriétés

3-13

Stockage et chargement des propriétés

Align := alBottom; end;

{ affecte une nouvelle valeur par défaut à Align }

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

{ toujours stockée } { jamais stockée } { dépend de la valeur de la fonction }

Initialisation après chargement Après qu’un composant a lu les valeurs de toutes ses propriétés dans sa description stockée, il appelle une méthode virtuelle appelée Loaded, qui effectue toutes les initialisations nécessaires. L’appel de Loaded s’exécute avant que ne s’affichent la fiche et ses contrôles, ainsi vous n’avez pas à vous soucier du scintillement de l’écran provoqué par ces initialisations. Pour initialiser un composant après le chargement des valeurs des propriétés, vous devez surcharger la méthode Loaded. Remarque

La première opération à accomplir dans une méthode Loaded consiste à appeler la méthode Loaded reçue en héritage. Ceci afin d’être sûr que toutes les propriétés reçues en héritage sont correctement initialisées avant d’effectuer l’initialisation de votre propre composant. Le code suivant provient du composant TDatabase. Après chargement, la base de données essaie de rétablir toutes les connexions ouvertes au moment du stockage, et spécifie comment gérer toutes les exceptions qui se produisent pendant la connexion.

3-14

Guide du concepteur de composants

Stockage et chargement des propriétés

procedure TDatabase.Loaded; begin inherited Loaded; try if FStreamedConnected then Open else CheckSessionName(False); except if csDesigning in ComponentState then Application.HandleException(Self) else raise; end; end;

{ appelle d’abord la méthode héritée } { rétablit les connexions }

{ lors de la conception... } { permet à Delphi de gérer l’exception } { sinon, redéclenche }

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

1 Création de méthodes pour le stockage et le chargement de valeurs de propriétés. 2 Redéfinition de la méthode DefineProperties, en transmettant ces méthodes à un objet filer.

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

Création de propriétés

3-15

Stockage et chargement des propriétés

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

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

3-16

Guide du concepteur de composants

Stockage et chargement des propriétés

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

Création de propriétés

3-17

3-18

Guide du concepteur de composants

Chapitre

4

Création d’événements

Chapitre4

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

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

Création d’événements

4-1

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

la fiche contenant le bouton et l’associe à l’événement OnClick. Lorsqu’un événement clic de souris se produit sur le bouton, ce dernier appelle la méthode associée à OnClick qui est pour notre exemple Button1Click. Pour écrire un événement, vous devez comprendre ceci : L’utilisateur clique sur Button1

Button1.OnClick pointe sur Form1.Button1Click

Occurrence

Evénement

• • • • •

Les Les Les Les Les

Form1.Button1Click s’exécute Gestionnaire d’événement

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

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

L’implémentation de Click appelle le gestionnaire utilisateur des événements liés aux clics de la souris, s’il existe. Si l’utilisateur a affecté un gestionnaire à l’événement OnClick d’un contrôle, le fait de cliquer sur ce dernier génère l’appel de la méthode. Si aucun gestionnaire n’est affecté, rien ne se produit.

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

4-2

Guide du concepteur de composants

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

Par convention, le nom de la donnée membre est le même que celui de la propriété précédé de la lettre F. Par exemple, le pointeur de la méthode OnClick est stocké dans une donnée membre intitulée FOnClick de type TNotifyEvent ; la déclaration de la propriété de l’événement OnClick ressemble à ceci : type TControl = class(TComponent) private FOnClick: TNotifyEvent; { déclare un champ pour contenir le pointeur de méthode } ƒ protected property OnClick: TNotifyEvent read FOnClick write FOnClick; end;

Pour en savoir davantage sur TNotifyEvent et sur les autres types d’événements, voir la prochaine section, “Les types d’événements sont des types de pointeurs de méthodes”. Comme pour toute autre propriété, vous pouvez définir ou modifier la valeur d’un événement au moment de l’exécution. L’avantage principal des événements implémentés sous la forme de propriétés est que l’utilisateur de composant peut lui associer un gestionnaire au moment de la conception à l’aide de l’inspecteur d’objets.

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

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

Création d’événements

4-3

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

gestionnaire afin de ne pas rendre obligatoire la modification de la valeur par le code de l’utilisateur. Un exemple de transmission des paramètres var à un gestionnaire d’événement est fourni par l’événement OnKeyPress, de type TKeyPressEvent. TKeyPressEvent définit deux paramètres, l’un indiquant l’objet ayant généré l’événement et l’autre la touche enfoncée : type TKeyPressEvent = procedure(Sender: TObject; var Key: Char) of object;

Normalement, le paramètre Key contient le caractère enfoncé par l’utilisateur. Toutefois dans certaines circonstances, l’utilisateur de composant peut souhaiter changer ce caractère. Par exemple, pour forcer tous les caractères en majuscules dans un éditeur. Dans un cas comme celui-là, l’utilisateur doit définir le gestionnaire suivant pour gérer les frappes de touche : procedure TForm1.Edit1KeyPressed(Sender: TObject; var Key: Char); begin Key := UpCase(Key); end;

Vous pouvez également utiliser les paramètres var pour permettre à l’utilisateur de redéfinir la gestion par défaut.

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

4-4

Guide du concepteur de composants

Implémentation des événements standard

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

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

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

OnDragDrop OnDragOver

OnEndDrag OnMouseDown

OnMouseMove OnMouseUp

Les événements standard disposent de méthodes virtuelles protégées, déclarées dans TControl, dont les noms correspondent aux noms des événements. Par exemple, les événements OnClick appellent une méthode nommée Click, et les événements OnEndDrag appellent une méthode nommée DoEndDrag.

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

OnKeyDown OnExit

OnKeyPress

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

Création d’événements

4-5

Implémentation des événements standard

Remarque

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

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

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

4-6

Guide du concepteur de composants

Définition de vos propres événements

de composant et si votre code agit sur cet état, vous devez d’abord effectuer ces changements d’état avant d’autoriser le code de l’utilisateur à y répondre. Supposons que vous écrivez un composant et que vous souhaitez modifier la façon dont il répond aux clics de souris. Au lieu d’associer un gestionnaire à l’événement OnClick, comme le ferait le développeur d’applications, redéfinissez la méthode protégée Click : procedure click override ƒ

{ déclaration forward }

procedure TMyControl.Click; begin inherited Click; { exécute la gestion standard, y compris l’appel au gestionnaire } ƒ { vos modifications s’insèrent ici } end;

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

Déclenchement de l’événement Définition du type de gestionnaire Déclaration de l’événement Appel de l’événement

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

Pour les applications CLX, voir “Réponse aux notifications du système à l’aide de CLX” à la page 7-10.

Création d’événements

4-7

Définition de vos propres événements

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

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

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

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

4-8

Guide du concepteur de composants

Définition de vos propres événements

Si votre événement a été généré en réponse à un message, les paramètres transmis au gestionnaire d’événement seront vraisemblablement issus des paramètres du message.

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

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

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

Exception principale à cette règle : les nombreux événements qui se produisent avant ou après certaines occurrences commencent par “Before” ou “After”.

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

Création d’événements

4-9

Définition de vos propres événements

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

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

Il ne doit en aucun cas ressembler à ceci : if Assigned(OnClick) then OnClick(Self) else { exécute la gestion par défaut};

Les utilisateurs peuvent redéfinir la gestion par défaut Pour certains types d’événements, les développeurs peuvent vouloir remplacer la gestion par défaut ou même supprimer l’ensemble des réponses. Pour permettre cela, vous devez transmettre au gestionnaire un argument par référence et vérifier si le gestionnaire renvoie une certaine valeur. Cela reste dans la lignée de l’affirmation qu’un gestionnaire vide doit produire le même effet qu’un gestionnaire absent : puisqu’un gestionnaire vide ne modifie en rien la valeur des arguments passés par référence, la gestion par défaut se déroule toujours après l’appel du gestionnaire vide. Par exemple, lors de la gestion des événements frappe de touches, le développeur d’applications peut omettre la gestion par défaut de la frappe de touches du composant en attribuant le caractère null (#0) au paramètre var Key. La logique de programmation sous-jacente est la suivante : if Assigned(OnKeyPress) then OnKeyPress(Self, Key); if Key #0 then ... { exécute la gestion par défaut}

Le code réel est légèrement différent car il prend également en compte les messages Windows mais la logique reste la même. Par défaut, le composant appelle le gestionnaire défini par l’utilisateur avant d’exécuter la gestion standard. Si le gestionnaire défini par l’utilisateur attribue le caractère null à Key, le composant omet l’exécution de la gestion par défaut.

4-10

Guide du concepteur de composants

Chapitre

5

Création de méthodes

Chapitre5

Les méthodes des composants sont des procédures et des fonctions intégrées dans la structure d’une classe. Il n’existe pratiquement aucune restriction sur ce que peuvent réaliser les méthodes d’un composant, mais Delphi n’en respecte pas moins un certain nombre de standards qu’il est préférable de suivre. Ce sont : • • • • •

Eviter les interdépendances Noms des méthodes Protection des méthodes Rendre virtuelles des méthodes Déclaration des méthodes

En général, les composants ne doivent pas contenir beaucoup de méthodes et vous devez chercher à minimiser le nombre des méthodes appelées par une application. Il est préférable d’encapsuler sous la forme de propriétés des caractéristiques qu’il serait tentant d’implémenter sous forme de méthodes. Les propriétés fournissent une interface qui s’inscrit parfaitement dans Delphi et sont accessibles au moment de la conception.

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

Création de méthodes

5-1

Noms des méthodes

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

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

5-2

Guide du concepteur de composants

Protection des méthodes

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

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

Les constructeurs et destructeurs sont toujours déclarés public.

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

Création de méthodes

5-3

Rendre virtuelles des méthodes

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

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

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

5-4

Guide du concepteur de composants

{ déclare la méthode protected static }

Déclaration des méthodes

public function CalculateArea: Integer; virtual; end;

{ déclare la méthode public virtual }

ƒ implementation ƒ procedure TSampleComponent.MakeBigger; begin Height := Height + 5; Width := Width + 5; end; function TSampleComponent.CalculateArea: Integer; begin Result := Width * Height; end;

{ implémente la première méthode }

{ implémente la deuxième méthode }

Création de méthodes

5-5

5-6

Guide du concepteur de composants

Chapitre

6

Graphiques et composants

Chapitre6

Windows fournit une puissante interface GDI (Graphics Device Interface) servant à dessiner des graphiques indépendamment des périphériques. Malheureusement, GDI impose au programmeur des contraintes supplémentaires telles que la gestion des ressources graphiques. Delphi prend en charge toutes ces tâches GDI ingrates, vous laisse vous concentrer sur le travail productif, vous épargnant les recherches de handles perdus ou de ressources non restituées. De même que toute partie de l’API Windows, vous pouvez appeler les fonctions GDI directement depuis votre application Delphi Toutefois, vous vous rendrez vite compte que l’utilisation de l’encapsulation Delphi des fonctions graphiques est un moyen plus efficace et plus rapide de créer des graphiques. Remarque

Les fonctions GDI sont particulières à Windows et ne s’appliquent pas aux applications CLX. Mais, des composants CLX utilisent la bibliothèque Qt. Les rubriques de cette section comprennent : • • • • •

Présentation des graphiques Utilisation du canevas Travail sur les images Bitmaps hors écran Réponse aux changements

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

Graphiques et composants

6-1

Présentation des graphiques

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

6-2

Guide du concepteur de composants

Utilisation du canevas

procedure TForm1.FormPaint(Sender: TObject); begin with Canvas do begin Pen.Color := clBlue; Brush.Color := clYellow; Ellipse(10, 10, 50, 50); end; end;

{ attribue la couleur bleu au crayon } { attribue la couleur jaune au pinceau } { dessine l’ellipse }

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

Résumé des possibilités du canevas

Niveau

Opération

Outils

Haut

Dessin de lignes et de formes

Méthodes comme MoveTo, LineTo, Rectangle et Ellipse

Affichage et mesure de texte

Méthodes TextOut, TextHeight, TextWidth et TextRect

Remplissage de zones

Méthodes FillRect et FloodFill

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

Appel des fonctions GDI de Windows

Propriété Handle

Intermédiaire

Bas

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

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

Graphiques et composants

6-3

Travail sur les images

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

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

Chargement et stockage des graphiques Toutes les images et tous les graphiques de Delphi peuvent charger leurs images à partir d’un fichier et les stocker en retour dans ce même fichier (ou dans un autre). Ceci peut s’effectuer à tout moment. Remarque

6-4

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.

Guide du concepteur de composants

Travail sur les images

Pour charger une image dans un objet image à partir d’un fichier, appelez la méthode LoadFromFile de l’objet image. Pour enregistrer une image dans un fichier à partir d’un objet image, appelez la méthode SaveToFile de l’objet image. LoadFromFile et SaveToFile acceptent le nom d’un fichier comme seul paramètre. LoadFromFile utilise l’extension du fichier pour déterminer le type d’objet graphique créé ou chargé. SaveToFile utilise le type de fichier approprié au type d’objet graphique enregistré. Pour charger un bitmap dans l’objet image d’un contrôle image, par exemple, vous devez transmettre le nom du fichier bitmap à la méthode LoadFromFile de l’objet image : procedure TForm1.LoadBitmapClick(Sender: TObject); begin Image1.Picture.LoadFromFile(’RANDOM.BMP’); end;

L’objet image reconnaît .bmp comme extension par défaut des fichiers bitmap, elle crée donc le graphique en tant que TBitmap, avant d’appeler la méthode LoadFromFile du graphique. Puisque le graphique est un bitmap, l’image est chargée depuis le fichier en tant que bitmap.

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

Delphi n’assure ni la création, ni la maintenance des palettes autres que celles des bitmaps. Toutefois, si vous disposez d’un handle de palette, les contrôles Delphi peuvent se charger de sa gestion.

Graphiques et composants

6-5

Bitmaps hors écran

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

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

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

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

6-6

Guide du concepteur de composants

Réponse aux changements

plutôt construire un objet bitmap puis dessiner sur son canevas avant de copier la totalité de l’image sur le canevas affiché. La méthode Paint d’un contrôle graphique est un exemple d’utilisation typique d’un bitmap hors écran. Comme avec tout objet temporaire, l’objet bitmap doit être protégé par un bloc try..finally : type TFancyControl = class(TGraphicControl) protected procedure Paint; override; end;

{ surcharge la méthode Paint }

procedure TFancyControl.Paint; var Bitmap: TBitmap; { variable temporaire pour le bitmap hors écran } begin Bitmap := TBitmap.Create; { construit l’objet bitmap } try { dessine sur le bitmap } { copie le résultat dans le canevas du contrôle } finally Bitmap.Free; { détruit l’objet bitmap } end; end;

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

Méthodes de copie des images

Pour créer cet effet

Appelez cette méthode

Copie d’un graphique entier.

Draw

Copie et redimensionnement d’un graphique.

StretchDraw

Copie d’une partie d’un canevas.

CopyRect

Copie d’un bitmap avec effets.

BrushCopy (VCL)

Copie d’un graphique de manière répétitive afin de remplir une zone bitmap avec effets.

TiledDraw (CLX)

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

Graphiques et composants

6-7

Réponse aux changements

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

6-8

Guide du concepteur de composants

}

} } } }

{ détruit et redessine le composant }

Chapitre

7

Gestion des messages et des notifications système Chapitre7

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

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

Gestion des messages et des notifications système

7-1

Compréhension du système de gestion des messages

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

MainWndProc

WndProc

Dispatch

Gestionnaire

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

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

7-2

Guide du concepteur de composants

Compréhension du système de gestion des messages

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

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

Gestion des messages et des notifications système

7-3

Modification de la gestion des messages

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

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

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

7-4

Guide du concepteur de composants

Modification de la gestion des messages

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

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

Le composant TControl définit des plages entières de messages liés à la souris qu’il filtre lorsqu’un utilisateur fait glisser puis lâche un contrôle. Une méthode WndProc surchargée peut agir par deux moyens : • Elle peut filtrer des plages entières de messages au lieu de spécifier un gestionnaire pour chacun d’eux. • Elle peut inhiber totalement la répartition des messages de façon à ce que les gestionnaires ne soient jamais appelés. Voici une partie de la méthode WndProc pour TControl, par exemple : procedure TControl.WndProc(var Message: TMessage); begin ƒ if (Message.Msg >= WM_MOUSEFIRST) and (Message.Msg Cardinal(MaxValue)) then // aller jusqu’à Int64 pour dépasser le %d dans la chaîne de format Error([Int64(Cardinal(MinValue)), Int64(Cardinal(MaxValue))]); end else if (L < MinValue) or (L > MaxValue) then Error([MinValue, MaxValue]); SetOrdValue(L); end;

Les spécificités des exemples particuliers sont moins importantes qu’en principe : GetValue convertit la valeur en chaîne ; SetValue convertit la chaîne et valide la valeur avant d’appeler une des méthodes “Set”.

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

1 Construisez l’éditeur que vous utilisez pour cette propriété. 2 Lisez la valeur en cours et attribuez-la à la propriété en utilisant une méthode “Get”.

8-10

Guide du concepteur de composants

Ajout d’éditeurs de propriétés

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

l’éditeur } existante } par OK... } la valeur } l’éditeur }

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

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

Indicateur

Méthode associée

Signification si inclus

paValueList

GetValues

L’éditeur peut fournir une liste de valeurs énumérées.

paSubProperties

GetProperties

La propriété dispose de sous-propriétés qu’il est possible d’afficher.

paDialog

Edit

L’éditeur peut afficher une boîte de dialogue permettant de modifier globalement la propriété.

paMultiSelect

N/D

La propriété doit s’afficher lorsque l’utilisateur sélectionne plusieurs composants.

paAutoUpdate

SetValue

Mise à jour du composant après chaque modification au lieu d’attendre l’approbation de la valeur.

paSortList

N/D

L’inspecteur d’objets doit trier la liste de valeurs.

paReadOnly

N/D

La valeur de la propriété ne peut être modifiée lors de la conception.

Accessibilité des composants au moment de la conception

8-11

Ajout d’éditeurs de propriétés

Tableau 8.3

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

Indicateur

Méthode associée

Signification si inclus

paRevertable

N/D

Active l’élément de menu Revenir à hérité dans le menu contextuel de l’inspecteur d’objets. Cet élément de menu demande à l’éditeur d’annuler la valeur en cours de la propriété et de revenir à une valeur par défaut ou standard préalablement établie.

paFullWidthName

N/D

La valeur n’a pas besoin d’être affichée. L’inspecteur d’objets utilise toute sa largeur pour le nom de propriété.

paVolatileSubProperties

GetProperties

L’inspecteur d’objets récupère les valeurs de toutes les sous-propriétés à chaque modification de la valeur de la propriété.

paReference

GetComponentValue

La valeur est une référence à quelque chose d’autre. Utilisé conjointement avec paSubProperties, l’objet référencé devrait être affiché comme sous-propriétés de cette propriété.

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

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

8-12

Guide du concepteur de composants

Catégories de propriétés

• Le type d’éditeur de propriétés à utiliser pour modifier la propriété spécifiée. Voici un extrait de la procédure qui recense les éditeurs des composants standard inclus dans la palette : procedure Register; begin RegisterPropertyEditor(TypeInfo(TComponent), nil, ‘‘, TComponentProperty); RegisterPropertyEditor(TypeInfo(TComponentName), TComponent, ‘Name’, TComponentNameProperty); RegisterPropertyEditor(TypeInfo(TMenuItem), TMenu, ‘‘, TMenuItemProperty); end;

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

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

Accessibilité des composants au moment de la conception

8-13

Catégories de propriétés

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

Recensement d’une propriété à la fois Vous pouvez recenser une propriété à la fois et l’associer à une catégorie de propriété en utilisant la fonction RegisterPropertyInCategory. RegisterPropertyInCategory est fournie dans quatre variations surchargées, chacune proposant un ensemble différent de critères pour l’identification de la propriété dans le composant personnalisé associé à la catégorie de propriété. La première variation vous permet d’identifier la propriété selon son nom. La ligne ci-après recense une propriété associée à l’affichage visuel du composant, en identifiant la propriété par son nom, “AutoSize”. RegisterPropertyInCategory(’Visual’, ’AutoSize’);

La deuxième variation identifie la propriété en utilisant le type de classe de composant et le nom de propriété caractéristiques. L’exemple ci-après recense (dans la catégorie THelpCategory) une propriété appelée “HelpContext” d’un composant de la classe personnalisée TMyButton. RegisterPropertyInCategory(’Help and Hints’, TMyButton, ’HelpContext’);

La troisième variation identifie la propriété en utilisant son type au lieu de son nom. L’exemple ci-dessous recense une propriété en se basant sur son type, Integer. RegisterPropertyInCategory(’Visual’, TypeInfo(Integer));

La dernière variation utilise à la fois le type de la propriété et son nom pour identifier la propriété. L’exemple ci-après recense une propriété basée sur une combinaison de son type, TBitmap et de son nom, ”Pattern”. RegisterPropertyInCategory(’Visual’, TypeInfo(TBitmap), ’Pattern’);

Consultez la section “Spécification de catégories de propriétés”, pour obtenir une liste des catégories de propriété disponibles ainsi qu’une brève description de leur utilisation.

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

8-14

Guide du concepteur de composants

Catégories de propriétés

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

La deuxième variation vous permet de limiter les propriétés recensées à celles qui appartiennent à un composant spécifique. La liste des propriétés à recenser comprend seulement les noms, pas les types. Par exemple, le code suivant recense un nombre de propriétés dans la catégorie ‘Help and Hints’ pour tous les composants : RegisterPropertiesInCategory(’Help and Hints’, TComponent, [’HelpContext’, ’Hint’, ’ParentShowHint’, ’ShowHint’]);

La troisième variation vous permet de limiter les propriétés recensées à celles possédant un type spécifique. Comme avec la seconde variation, la liste des propriétés à recenser peut n’inclure que les noms : RegisterPropertiesInCategory(’Localizable’, TypeInfo(String), [’Text’, ’Caption’]);

Consultez la section “Spécification de catégories de propriétés”, pour obtenir une liste des catégories de propriété disponibles ainsi qu’une brève description de leur utilisation.

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

Catégories de propriétés

Catégorie

Utilisation

Action

Propriétés relatives aux actions d’exécution ; les propriétés Enabled et Hint de TEdit se trouvent dans cette catégorie.

Database

Propriétés relatives aux opérations de bases de données ; les propriétés DatabaseName et SQL de TQuery se trouvent dans cette catégorie.

Drag, Drop 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.

Accessibilité des composants au moment de la conception

8-15

Ajout d’éditeurs de composants

Tableau 8.4

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

Catégorie

Utilisation

Localizable

Propriétés qui peuvent nécessiter des modifications dans les versions localisées d’une application. De nombreuses propriétés chaîne (comme Caption) font partie de cette catégorie ainsi que les propriétés qui déterminent la taille et la position des contrôles.

Visual

Propriétés relatives à l’affichage visuel d’un contrôle à l’exécution ; les propriétés Align et Visible de TScrollBox se trouvent dans cette catégorie.

Input

Propriétés relatives à la saisie de données (il n’est pas nécessaire qu’elles soient relatives aux opérations de bases de données) ; les propriétés Enabled et ReadOnly de TEdit se trouvent dans cette catégorie.

Miscellaneous

Propriétés qui ne rentrent mises dans des catégories une catégorie spécifique) ; TSpeedButton se trouvent

pas dans une catégorie ou n’ont pas besoin d’être (et les propriétés non explicitement recensées dans les propriétés AllowAllUp et Name de dans cette catégorie.

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

La deuxième variation vous permet de baser le critère de comparaison sur une combinaison du nom de classe du composant propriétaire et du nom de la propriété. Dans la ligne de commande ci-après, pour que IsPropertyInCategory renvoie True, la propriété doit être un descendant de TCustomEdit, avoir le nom “Text”, et se trouver dans la catégorie de propriétés ’Localizable’. IsItThere := IsPropertyInCategory(’Localizable’, ‘TCustomEdit’, ’Text’);

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

8-16

Guide du concepteur de composants

Ajout d’éditeurs de composants

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

Ajout d’éléments au menu contextuel Modification du comportement suite à un double-clic Ajout de formats de presse-papiers Recensement d’un éditeur de composants

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

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

Accessibilité des composants au moment de la conception

8-17

Ajout d’éditeurs de composants

function TMyEditor.GetVerb(Index: Integer): String; begin case Index of 0: Result := ‘&DoThis ...’; 1: Result := ‘Do&That’; end; end; Remarque

Veillez à ce que votre méthode GetVerb renvoie une valeur pour chaque index possible indiqué par GetVerbCount.

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

} } } } }

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

8-18

Guide du concepteur de composants

Ajout d’éditeurs de composants

Redéfinissez la méthode Edit pour spécifier un nouveau comportement lorsque l’utilisateur double-clique sur le composant. Par exemple, la méthode Edit suivante appelle une boîte de dialogue de fontes lorsque l’utilisateur double-clique sur le composant : procedure TMyEditor.Edit; var FontDlg: TFontDialog; begin FontDlg := TFontDialog.Create(Application); try if FontDlg.Execute then MyComponent.FFont.Assign(FontDlg.Font); finally FontDlg.Free end; end; Remarque

Si vous souhaitez qu’un double-clic sur le composant affiche l’éditeur de code d’un gestionnaire d’événement, utilisez TDefaultEditor comme classe de base pour votre éditeur de composants au lieu de TComponentEditor. Puis, au lieu de surcharger la méthode Edit, surchargez la méthode protégée TDefaultEditor.EditProperty. EditProperty recherche les gestionnaires d’événement du composant et affiche le premier qu’il trouve. Vous pouvez modifier ce comportement pour visualiser un événement particulier. Par exemple : procedure TMyEditor.EditProperty(PropertyEditor: TPropertyEditor; Continue, FreeEditor: Boolean) begin if (PropertyEditor.ClassName = ‘TMethodProperty’) and (PropertyEditor.GetName = ‘OnSpecialEvent’) then // DefaultEditor.EditProperty(PropertyEditor, Continue, FreeEditor); end;

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

Accessibilité des composants au moment de la conception

8-19

Compilation des composants en paquets

Recensement d’un éditeur de composants Une fois l’éditeur de composants défini, il peut être enregistré pour fonctionner avec une classe de composants spécifique. Un éditeur de composants enregistré est créé pour chaque composant de cette classe lorsqu’il est sélectionné dans le concepteur de fiche. Pour associer un éditeur de composants à une classe composant, appelez RegisterComponentEditor. RegisterComponentEditor adopte le nom de la classe composant qui utilise l’éditeur et le nom de la classe éditeur de composants que vous avez définie. Par exemple, l’instruction suivante recense une classe éditeur de composants nommée TMyEditor en vue de son utilisation avec tous les composants de type TMyComponent : RegisterComponentEditor(TMyComponent, TMyEditor);

Placez l’appel à RegisterComponentEditor dans la procédure Register où vous recensez votre composant. Par exemple, si un nouveau composant nommé TMyComponent et son éditeur de composants TMyEditor sont tous les deux implémentés dans la même unité, le code suivant recense le composant et son association à l’éditeur de composants. procedure Register; begin RegisterComponents(’Miscellaneous’, [TMyComponent); RegisterComponentEditor(classes[0], TMyEditor); end;

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

8-20

Guide du concepteur de composants

Chapitre

9

Modification d’un composant existant

Chapitre9

Le moyen le plus simple de créer un composant consiste à le dériver d’un composant qui réalise la presque totalité des fonctions souhaitées et lui apporter ensuite quelques modifications. L’exemple de ce chapitre modifie le composant mémo standard pour créer un mémo qui ne fait pas de saut de ligne par défaut. La valeur de la propriété WordWrap du composant mémo est initialisée à True. Si vous utilisez fréquemment des mémos n’effectuant pas de saut de ligne, vous pouvez créer un nouveau composant mémo qui ne fait pas de saut de ligne par défaut. Remarque

Pour modifier les propriétés publiées ou enregistrer des gestionnaires d’événements spécifiques pour un composant existant, il est souvent plus simple d’utiliser un modèle de composant plutôt que de créer une classe. La modification d’un composant existant se fait en deux étapes : • Création et recensement du composant. • Modification de la classe composant.

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

Les noms et les emplacements de certaines unités sont différents pour les applications CLX. Par exemple, l’unité Controls est QControls dans les applications CLX.

Modification d’un composant existant

9-1

Modification de la classe composant

Pour notre exemple, suivez la procédure générale de création d’un composant en tenant compte des spécificités suivantes : • Nommez l’unité du composant Memos. • Dérivez un nouveau type de composant appelé TWrapMemo, descendant de TMemo. • Recensez TWrapMemo sur la page Exemples de la palette des composants. • L’unité que vous obtenez doit ressembler à ceci : unit Memos; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, StdCtrls; type TWrapMemo = class(TMemo) end; procedure Register; implementation procedure Register; begin RegisterComponents(’Samples’, [TWrapMemo]); end; end.

Si vous compilez et installez maintenant le nouveau composant, il se comportera exactement comme son ancêtre, TMemo. Dans la section suivante, vous effectuerez une simple modification à votre composant.

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

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

9-2

Guide du concepteur de composants

Modification de la classe composant

constructeur du composant définit les valeurs des propriétés. Quand un composant est chargé depuis un fichier fiche, l’application définit toutes les propriétés qui ont été modifiées lors de la conception. Remarque

Lorsque vous redéfinissez un constructeur, le nouveau constructeur doit appeler le constructeur reçu en héritage avant toute autre action. Pour plus d’informations, voir “Redéfinition des méthodes” à la page 2-9. Dans notre exemple, votre nouveau composant doit surcharger le constructeur transmis en héritage par TMemo en attribuant la valeur False à la propriété WordWrap. Pour ce faire, ajoutez le constructeur redéfini à la prédéclaration, puis écrivez le nouveau constructeur dans la partie implémentation de l’unité : type TWrapMemo = class(TMemo) public constructor Create(AOwner: TComponent); override; end; ƒ constructor TWrapMemo.Create(AOwner: TComponent); begin inherited Create(AOwner); WordWrap := False; end;

{ constructeurs toujours publics } { cette syntaxe est toujours identique }

{ ceci va après l’implémentation } { Faites TOUJOURS ceci en premier ! } { définit la nouvelle valeur désirée }

Vous pouvez maintenant installer le nouveau composant dans la palette des composants puis l’ajouter dans une fiche. Remarquez que la propriété WordWrap est dorénavant initialisée à False. Si vous changez une valeur de propriété initiale, vous devez aussi désigner cette valeur comme étant celle par défaut. Si vous échouez à faire correspondre la valeur définie par le constructeur à celle spécifiée par défaut, Delphi ne peut pas stocker, ni restaurer la valeur correcte.

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

Modification d’un composant existant

9-3

Modification de la classe composant

Pour le composant mémo de saut à la ligne automatique, redéclarez la propriété WordWrap dans la partie published de la déclaration d’objet, avec la valeur False par défaut : type TWrapMemo = class(TMemo) ƒ published property WordWrap default False; end;

Spécifier la valeur par défaut de la propriété n’affecte en rien le fonctionnement du composant. Vous devez toujours initialiser la valeur dans le constructeur du composant. La redéclaration de la valeur par défaut assure que Delphi connaît quand WordWrap doit être écrit dans le fichier fiche.

9-4

Guide du concepteur de composants

Chapitre

10 Création d’un contrôle graphique

Chapitre10

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

Création et recensement du composant La création d’un composant se fait toujours de la même façon. Vous créez une unité et vous recensez le composant avant de l’installer dans la palette des composants. Ce processus est décrit dans “Création d’un nouveau composant” à la page 1-9. Pour notre exemple, suivez la procédure générale de création d’un composant en tenant compte des spécificités suivantes :

1 Appelez l’unité du composant Shapes. 2 Dérivez un nouveau type de composant appelé TSampleShape, descendant de TGraphicControl. 3 Recensez TSampleShape sur la page Exemples (ou une autre page dans une application CLX) de la palette des composants.

Création d’un contrôle graphique

10-1

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

L’unité que vous obtenez doit ressembler à ceci : unit Shapes; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms; type TSampleShape = class(TGraphicControl) end; procedure Register; implementation procedure Register; begin RegisterComponent(’Samples’, [TSampleShape]); end; end. Remarque

Les noms et les emplacements de certaines unités sont différents pour les applications CLX. Par exemple, l’unité Controls est QControls dans les applications CLX.

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

10-2

Guide du concepteur de composants

Ajout de fonctionnalités graphiques

Le contrôle forme de notre exemple rend les interactions associées à la souris et aux opérations glisser-déplacer accessibles à l’utilisateur.

Ajout de fonctionnalités graphiques Lorsque votre composant graphique a été déclaré et qu’ont été publiées toutes les propriétés reçues en héritage que vous voulez rendre disponibles, vous pouvez ajouter les fonctionnalités graphiques qui caractériseront votre composant. La création d’un contrôle graphique se compose de deux tâches :

1 Détermination de ce qui doit être dessiné. 2 Dessin de l’image du composant. En outre, s’agissant du contrôle forme de notre exemple, vous allez ajouter certaines propriétés qui serviront aux développeurs d’applications pour personnaliser l’apparence du contrôle lors de la conception.

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

1 Déclaration du type de la propriété. 2 Déclaration de la propriété. 3 Ecriture de la méthode d’implémentation. La création de propriété est expliquée en détail dans le Chapitre 3, “Création de propriétés”.

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

Création d’un contrôle graphique

10-3

Ajout de fonctionnalités graphiques

Ajoutez la définition de type suivante avant la déclaration de classe du contrôle forme. type TSampleShapeType = (sstRectangle, sstSquare, sstRoundRect, sstRoundSquare, sstEllipse, sstCircle); TSampleShape = class(TGraphicControl) { existe déjà }

Vous pouvez maintenant utiliser ce type pour déclarer une nouvelle propriété dans la classe.

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

Il ne vous reste plus qu’à ajouter l’implémentation de SetShape.

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

10-4

Guide du concepteur de composants

Ajout de fonctionnalités graphiques

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

Modification des valeurs par défaut des propriétés La taille par défaut d’un contrôle graphique étant réduite, vous pouvez modifier sa largeur et sa hauteur dans le constructeur. La modification des valeurs par défaut des propriétés est abordée plus en détail dans le Chapitre 9, “Modification d’un composant existant”. Dans notre exemple, le contrôle forme définit sa taille par un carré de 65 pixels de côté. Ajoutez le constructeur redéfini dans la déclaration de la classe composant : type TSampleShape = class(TGraphicControl) public constructor Create(AOwner: TComponent); override

{ constructeurs toujours publics } { ne pas oublier la directive override }

end;

1 Redéclarez les propriétés Height et Width avec leurs nouvelles valeurs par défaut : type TSampleShape = class(TGraphicControl) ƒ published property Height default 65; property Width default 65; end;

2 Ecrivez le nouveau constructeur dans la partie implémentation de l’unité : constructor TSampleShape.Create(AOwner: TComponent); begin inherited Create(AOwner); { appelez toujours le constructeur hérité } Width := 65; Height := 65; end;

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

Création d’un contrôle graphique

10-5

Ajout de fonctionnalités graphiques

car elles appartiennent au composant qui est responsable de leur création et de leur destruction. La gestion des classes ayant un propriétaire se déroule en quatre étapes :

1 2 3 4

Déclaration des champs de la classe. Déclaration des propriétés d’accès. Initialisation des classes ayant un propriétaire. Définition des propriétés des classes ayant un propriétaire.

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

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

10-6

Guide du concepteur de composants

{ ces méthodes doivent être privées }

Ajout de fonctionnalités graphiques

published { les rend disponibles lors de la conception } property Brush: TBrush read FBrush write SetBrush; property Pen: TPen read FPen write SetPen; end;

Vous devez ensuite écrire les méthodes SetBrush et SetPen dans la partie implémentation de l’unité : procedure TSampleShape.SetBrush(Value: TBrush); begin FBrush.Assign(Value); { remplace le pinceau existant par le paramètre } end; procedure TSampleShape.SetPen(Value: TPen); begin FPen.Assign(Value); { remplace le crayon existant par le paramètre } end;

Affecter directement le contenu de Value à FBrush : FBrush := Value;

écraserait le pointeur interne de FBrush, ferait perdre de la mémoire et créerait une série de problèmes de propriété.

Initialisation des classes ayant un propriétaire Si vous ajoutez des classes dans votre composant, le constructeur de ce dernier doit les initialiser pour que l’utilisateur puisse interagir avec ces objets lors de l’exécution. De même, le destructeur du composant doit également détruire les objets appartenant au composant avant de détruire ce dernier. Comme vous avez déclaré un crayon et un pinceau dans le contrôle forme, vous devez les initialiser dans le constructeur du contrôle forme et les détruire dans son destructeur :

1 Construisez le crayon et le pinceau dans le constructeur du contrôle forme : constructor TSampleShape.Create(AOwner: TComponent); begin inherited Create(AOwner); { appelez toujours le constructeur hérité } Width := 65; Height := 65; FPen := TPen.Create; { construit le crayon } FBrush := TBrush.Create; { construit le pinceau } end;

2 Ajoutez le destructeur redéfini dans la déclaration de la classe composant : type TSampleShape = class(TGraphicControl) public { les destructeurs sont toujours publics} constructor Create(AOwner: TComponent); override; destructor Destroy; override; { ne pas oublier la directive override } end;

Création d’un contrôle graphique

10-7

Ajout de fonctionnalités graphiques

3 Ecrivez le nouveau destructeur dans la partie implémentation de l’unité : destructor TSampleShape.Destroy; begin FPen.Free; FBrush.Free; inherited Destroy; end;

{ détruit l’objet crayon } { détruit l’objet pinceau } { appelle aussi toujours le destructeur hérité }

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

le constructeur hérité }

{ construit le crayon } à l’événement OnChange } { construit le pinceau } à l’événement OnChange }

procedure TSampleShape.StyleChanged(Sender: TObject); begin Invalidate; { efface et redessine le composant } end;

Ces modifications faites, le composant se redessine pour refléter tout changement du crayon ou du pinceau.

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

10-8

Guide du concepteur de composants

Ajout de fonctionnalités graphiques

La méthode Paint de votre contrôle forme doit accomplir plusieurs tâches : • Utiliser le crayon et le pinceau sélectionnés par l’utilisateur. • Utiliser la forme sélectionnée. • Ajuster les coordonnées pour que les carrés et les cercles utilisent une largeur et une hauteur identiques. La redéfinition de la méthode Paint nécessite deux étapes :

1 Ajout de Paint dans la déclaration du composant. 2 Insertion de la méthode Paint dans la partie implémentation de l’unité. S’agissant de notre contrôle forme, vous devez ajouter la déclaration suivante à la déclaration de classe : type TSampleShape = class(TGraphicControl) ƒ protected procedure Paint; override; ƒ end;

Vous devez ensuite écrire la méthode dans la partie implémentation de l’unité : procedure TSampleShape.Paint; begin with Canvas do begin Pen := FPen; { copie le crayon du composant Brush := FBrush; { copie le pinceau du composant case FShape of sstRectangle, sstSquare: Rectangle(0, 0, Width, Height); { dessine les rectangles et carrés sstRoundRect, sstRoundSquare: RoundRect(0, 0, Width, Height, Width div 4, Height div 4); { dessine des formes arrondies sstCircle, sstEllipse: Ellipse(0, 0, Width, Height); { dessine des formes arrondies end; end; end;

} }

}

} }

Paint est appelée à chaque fois que le contrôle doit mettre à jour son image. Les contrôles sont dessinés lorsqu’ils s’affichent pour la première fois ou lorsqu’une fenêtre qui se trouvait au-dessus disparaît. En outre, vous pouvez forcer le dessin en appelant Invalidate, comme le fait la méthode StyleChanged.

Création d’un contrôle graphique

10-9

Ajout de fonctionnalités graphiques

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

} } } } } }

}

} } } }

case FShape of sstRectangle, sstSquare: Rectangle(X, Y, X + W, Y + H); { dessine les rectangles et les carrés } sstRoundRect, sstRoundSquare: RoundRect(X, Y, X + W, Y + H, S div 4, S div 4); { dessine des formes arrondies } sstCircle, sstEllipse: Ellipse(X, Y, X + W, Y + H); { dessine les formes arrondies } end; end; end;

10-10

Guide du concepteur de composants

Chapitre

11 Personnalisation d’une grille

Chapitre11

La bibliothèque de composants fournit plusieurs composants abstraits que vous pouvez utiliser comme points de départ pour personnaliser vos composants. Les grilles et les boîtes liste sont les plus importants. Dans ce chapitre, nous allons voir comment créer un petit calendrier en partant du composant grille de base TCustomGrid. La création du calendrier nécessite les étapes suivantes : • • • • • • •

Création et recensement du composant Publication des propriétés héritées Modification des valeurs initiales Redimensionnement des cellules Remplissage des cellules Navigation de mois en mois et d’année en année Navigation de jour en jour

Dans les applications VCL, le composant calendrier résultant est pratiquement identique au composant TCalendar contenu dans la page Exemples de la palette des composants. Dans les applications CLX, enregistrez le composant dans une autre page ou créez une nouvelle page dans la palette. Voir “Spécification de la page de palette” à la page 8-3 ou “palette des composants, ajout de pages” dans l’aide en ligne.

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

Personnalisation d’une grille

11-1

Création et recensement du composant

Pour notre exemple, suivez la procédure générale de création d’un composant en tenant compte des spécificités suivantes :

1 Appelez l’unité du composant CalSamp. 2 Dérivez un nouveau type de composant appelé TSampleCalendar, descendant de TCustomGrid. Recensez TSampleCalendar dans la page Exemples (ou une autre page dans une application CLX) de la palette des composants. L’unité résultante dérivée de TCustomGrid dans une application VCL doit ressembler à ceci : unit CalSamp; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Grids; type TSampleCalendar = class(TCustomGrid) end; procedure Register; implementation procedure Register; begin RegisterComponents(’Samples’, [TSampleCalendar]); end; end. Remarque

Si la dérivation est effectuée à partir de la version CLX de TCustomGrid, la seule différence réside dans la clause uses, qui affiche à la place les unités CLX. De mêmes, les noms et les emplacements de certaines unités sont différents pour les applications CLX. Par exemple, l’unité Controls est QControls dans les applications CLX. Si vous installez le composant calendrier maintenant, vous verrez qu’il apparaît sur la page Exemples. Les seules propriétés disponibles sont les propriétés de contrôle les plus basiques. L’étape suivante consiste à rendre disponible certaines des propriétés plus spécialisées aux utilisateurs du calendrier.

Remarque

11-2

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.

Guide du concepteur de composants

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

Publication des propriétés héritées Le composant grille abstrait, TCustomGrid, fournit de nombreuses propriétés protected. Vous pouvez choisir parmi ces propriétés celles que vous voulez rendre accessibles aux utilisateurs du contrôle calendrier. Pour rendre accessibles aux utilisateurs de vos composants les propriétés protégées qu’ils reçoivent en héritage, vous devez redéclarer ces propriétés dans la partie published de la déclaration de vos composants. S’agissant du contrôle calendrier, vous devez publier les propriétés et les événements, comme ci-dessous : type TSampleCalendar = class(TCustomGrid) published property Align; { propriétés publiées } property BorderStyle; property Color; property Font; property GridLineWidth; property ParentColor; property ParentFont; property OnClick; { événements publiés } property OnDblClick; property OnDragDrop; property OnDragOver; property OnEndDrag; property OnKeyDown; property OnKeyPress; property OnKeyUp; end;

Il existe bien d’autres propriétés ne s’appliquant pas à un calendrier qui sont publiables, par exemple la propriété Options qui permet à l’utilisateur de choisir les lignes de la grille à dessiner. Si vous installez le composant calendrier modifié dans la palette des composants et l’utilisez dans une application, vous trouverez bien d’autres propriétés et événements opérationnels. Nous allons maintenant commencer à ajouter de nouvelles fonctionnalités au composant.

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

Personnalisation d’une grille

11-3

Redimensionnement des cellules

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

} } } } } } }

Le calendrier a dorénavant sept colonnes et sept lignes, avec la ligne de titre fixe (ou qui ne défile pas).

Redimensionnement des cellules Remarque

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

11-4

Guide du concepteur de composants

Remplissage des cellules

procedure WMSize(var Message: TWMSize); message WM_SIZE; ƒ end; ƒ procedure TSampleCalendar.WMSize(var Message: TWMSize); var GridLines: Integer; { variable locale temporaire begin GridLines := 6 * GridLineWidth; { calcule la taille combinée de toutes les lignes DefaultColWidth := (Message.Width - GridLines) div 7; { définit la nouvelle largeur de cellule par défaut DefaultRowHeight := (Message.Height - GridLines) div 7; { ainsi que sa hauteur end;

} } } }

Maintenant lorsque le calendrier est redimensionné, il affiche toutes les cellules dans la taille maximum avec laquelle ils peuvent rentrer dans le contrôle. Remarque

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

} } } } }

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

Personnalisation d’une grille

11-5

Remplissage des cellules

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

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

Stockage interne de la date Accès au jour, au mois et à l’année Génération des numéros de jours Sélection du jour en cours

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

1 Déclarez un champ privé pour contenir la date : type

11-6

Guide du concepteur de composants

Remplissage des cellules

TSampleCalendar = class(TCustomGrid) private FDate: TDateTime; ƒ

2 Initialisez le champ date dans le constructeur : constructor TSampleCalendar.Create(AOwner: TComponent); begin inherited Create(AOwner); { existait déjà } ƒ { d’autres initialisations ici } FDate := Date; { prend la date active du RTL } end;

3 Déclarez une propriété à l’exécution pour accéder à la date encodée. Vous aurez besoin d’une méthode pour définir la date car sa définition entraîne la mise à jour de l’image sur l’écran du contrôle : type TSampleCalendar = class(TCustomGrid) private procedure SetCalendarDate(Value: TDateTime); public property CalendarDate: TDateTime read FDate write SetCalendarDate; ƒ procedure TSampleCalendar.SetCalendarDate(Value: TDateTime); begin FDate := Value; { définit la nouvelle valeur date } Refresh; { met à jour l’image à l’écran } end;

Accès au jour, au mois et à l’année Une date encodée numériquement est adaptée aux applications, mais l’utilisateur préférera manipuler jour, mois et année. En créant des propriétés, vous offrez un accès aux dates stockées et encodées numériquement. Les éléments d’une date (jour, mois, année) sont des entiers. La modification de chacun d’entre eux nécessite l’encodage de la date. Vous pouvez éviter la duplication du code en partageant les méthodes d’implémentation entre les trois propriétés. Autrement dit, vous pouvez écrire deux méthodes, l’une pour lire un élément et l’autre pour l’écrire, et utiliser ces méthodes pour lire et écrire les trois propriétés. Pour fournir un accès lors de la conception aux éléments jour, mois et année, procédez de la façon suivante :

1 Déclarez les trois propriétés, en attribuant à chacune un numéro unique d’index : type TSampleCalendar = class(TCustomGrid) public property Day: Integer index 3 read GetDateElement write SetDateElement; property Month: Integer index 2 read GetDateElement write SetDateElement; property Year: Integer index 1 read GetDateElement write SetDateElement;

Personnalisation d’une grille

11-7

Remplissage des cellules

ƒ

2 Déclarez et écrivez les méthodes d’implémentation, définissant les différents éléments pour chaque valeur d’index : type TSampleCalendar = class(TCustomGrid) private function GetDateElement(Index: Integer): Integer; { notez le paramètre Index procedure SetDateElement(Index: Integer; Value: Integer); ƒ function TSampleCalendar.GetDateElement(Index: Integer): Integer; var AYear, AMonth, ADay: Word; begin DecodeDate(FDate, AYear, AMonth, ADay); { éclate la date encodée en éléments case Index of 1: Result := AYear; 2: Result := AMonth; 3: Result := ADay; else Result := -1; end; end; procedure TSampleCalendar.SetDateElement(Index: Integer; Value: Integer); var AYear, AMonth, ADay: Word; begin if Value > 0 then { tous les éléments doivent être positifs begin DecodeDate(FDate, AYear, AMonth, ADay);{ récupère les éléments courants de la date case Index of { définit le nouvel élément selon l’index 1: AYear := Value; 2: AMonth := Value; 3: ADay := Value; else Exit; end; FDate := EncodeDate(AYear, AMonth, ADay); { encode la date modifiée Refresh; { mise à jour du calendrier visible end; end;

}

}

} } }

} }

Vous pouvez maintenant définir le jour, le mois et l’année du calendrier lors de la conception à partir de l’inspecteur d’objets, ou à l’exécution à partir du code. Bien que vous n’ayez pas encore ajouté le code pour dessiner les dates dans les cellules, vous disposez maintenant de toutes les données nécessaires.

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

11-8

Guide du concepteur de composants

Remplissage des cellules

Une fois récupérées les informations concernant les années bissextiles et le nombre de jours par mois, vous pouvez calculer l’endroit de la grille où s’insère chaque date. Le calcul dépend du premier jour du mois. Comme vous devez considérer le décalage du premier jour du mois, par rapport à l’origine de la grille, pour chaque cellule à remplir, le meilleur choix consiste à calculer ce nombre après chaque changement de mois ou d’année, et de s’y reporter à chaque fois. Vous pouvez stocker cette valeur dans un champ de classe, puis mettre à jour ce champ à chaque modification de la date. Pour remplir les cellules avec les numéros de jour appropriés, procédez de la façon suivante :

1 Ajoutez à la classe un champ décalage du premier jour du mois, ainsi qu’une méthode pour mettre à jour la valeur du champ : type TSampleCalendar = class(TCustomGrid) private FMonthOffset: Integer; { stocke le décalage ƒ protected procedure UpdateCalendar; virtual; { propriété pour l’accès au décalage end; ƒ procedure TSampleCalendar.UpdateCalendar; var AYear, AMonth, ADay: Word; FirstDate: TDateTime; { date du premier jour du mois begin if FDate 0 then { ne calcule le décalage que si la date est valide begin DecodeDate(FDate, AYear, AMonth, ADay); { récupère les éléments de la date FirstDate := EncodeDate(AYear, AMonth, 1); { date du premier jour du mois FMonthOffset := 2 - DayOfWeek(FirstDate); { génère le décalage dans la grille end; Refresh; { toujours repeindre le contrôle end;

}

}

} } } } } }

2 Ajoutez les instructions au constructeur et aux méthodes SetCalendarDate et SetDateElement qui appellent la nouvelle méthode de mise à jour à chaque changement de date : constructor TSampleCalendar.Create(AOwner: TComponent); begin inherited Create(AOwner); { existait déjà ƒ { d’autres initialisations ici UpdateCalendar; { définit le bon décalage end; procedure TSampleCalendar.SetCalendarDate(Value: TDateTime); begin FDate := Value; { existait déjà UpdateCalendar; { appelait précédemment Refresh end; procedure TSampleCalendar.SetDateElement(Index: Integer; Value: Integer);

Personnalisation d’une grille

} } }

} }

11-9

Remplissage des cellules

begin ƒ FDate := EncodeDate(AYear, AMonth, ADay); UpdateCalendar; end; end;

{ encode la date modifiée } { appelait précédemment Refresh }

3 Ajoutez une méthode au calendrier renvoyant le numéro du jour à partir des coordonnées ligne/colonne d’une cellule qui lui sont transmises : function TSampleCalendar.DayNum(ACol, ARow: Integer): Integer; begin Result := FMonthOffset + ACol + (ARow - 1) * 7; { calcule le jour pour cette cellule } if (Result < 1) or (Result > MonthDays[IsLeapYear(Year), Month]) then Result := -1; { renvoie -1 si incorrect } end;

Pensez à ajouter la déclaration de DayNum à la déclaration de type du composant.

4 Vous pouvez désormais calculer l’endroit où s’affichent les dates, et mettre à jour DrawCell pour remplir les cellules : procedure TCalendar.DrawCell(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState); var TheText: string; TempDay: Integer; begin if ARow = 0 then { s’il s’agit de la ligne de titre ...} TheText := ShortDayNames[ACol + 1] { utilise le nom du jour } else begin TheText := ’’; { cellule vide par défaut } TempDay := DayNum(ACol, ARow); { récupère le numéro pour cette cellule } if TempDay -1 then TheText := IntToStr(TempDay); { utilise ce numéro s’il est valide } end; with ARect, Canvas do TextRect(ARect, Left + (Right - Left - TextWidth(TheText)) div 2, Top + (Bottom - Top - TextHeight(TheText)) div 2, TheText); end;

Si maintenant vous réinstallez le composant calendrier et le placez dans une fiche, les informations correspondant au mois en cours apparaîtront.

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

11-10

Guide du concepteur de composants

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

if FDate 0 then begin ƒ { instructions définissant FMonthOffset } Row := (ADay - FMonthOffset) div 7 + 1; Col := (ADay - FMonthOffset) mod 7; end; Refresh; { déjà ici } end;

Notez que vous réutilisez la variable ADay précédemment définie lors du décodage de la date.

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

Personnalisation d’une grille

11-11

Navigation de jour en jour

N’oubliez pas d’ajouter les déclarations des nouvelles méthodes à la déclaration de la classe. Désormais, si vous créez une application qui utilise le composant calendrier, vous pourrez facilement implémenter le passage de mois en mois ou d’année en année.

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

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

11-12

Guide du concepteur de composants

Navigation de jour en jour

Fourniture d’un événement OnChange Les utilisateurs de votre calendrier ont maintenant la possibilité de changer la date. Il paraît donc judicieux de répondre à ces changements. Ajoutez un événement OnChange à TSampleCalendar.

1 Déclarez l’événement ainsi qu’un champ pour le stocker et une méthode virtuelle pour l’appeler : type TSampleCalendar = class(TCustomGrid) private FOnChange: TNotifyEvent; protected procedure Change; dynamic; ƒ published property OnChange: TNotifyEvent read FOnChange write FOnChange; ƒ

2 Ecrivez la méthode Change : procedure TSampleCalendar.Change; begin if Assigned(FOnChange) then FOnChange(Self); end;

3 Ajoutez les instructions appelant Change à la fin des méthodes SetCalendarDate et SetDateElement : procedure TSampleCalendar.SetCalendarDate(Value: TDateTime); begin FDate := Value; UpdateCalendar; Change; { seule nouvelle instruction } end; procedure TSampleCalendar.SetDateElement(Index: Integer; Value: Integer); begin ƒ { instructions définissant les valeurs des éléments } FDate := EncodeDate(AYear, AMonth, ADay); UpdateCalendar; Change; { ceci est nouveau } end; end;

Les applications qui utilisent le composant calendrier peuvent maintenant répondre aux changements en associant des gestionnaires à l’événement OnChange.

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

Personnalisation d’une grille

11-13

Navigation de jour en jour

Pour déterminer si une cellule est sélectionnable, vous devez redéfinir la méthode SelectCell de la grille. SelectCell est une fonction qui accepte deux paramètres ligne et colonne et qui renvoie une valeur booléenne indiquant si la cellule spécifiée est sélectionnable. Vous pouvez surcharger SelectCell pour qu’elle renvoie False si la cellule ne contient pas une date valide : function TSampleCalendar.SelectCell(ACol, ARow: Longint): Boolean; begin if DayNum(ACol, ARow) = -1 then Result := False { -1 indique une date incorrecte } else Result := inherited SelectCell(ACol, ARow); { sinon, utilise la valeur héritée } end;

Désormais, si l’utilisateur clique sur une cellule vide ou tente de s’y déplacer à l’aide des touches de direction, le calendrier ne modifie pas la sélection en cours.

11-14

Guide du concepteur de composants

Chapitre

12 Contrôles orientés données

Chapitre12

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

Contrôles orientés données

12-1

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

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

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

12-2

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.

Guide du concepteur de composants

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

Si vous installez le composant calendrier maintenant, vous verrez qu’il apparaît sur la page Exemples. Les seules propriétés disponibles sont les propriétés de contrôle les plus basiques. L’étape suivante consiste à rendre disponible certaines des propriétés plus spécialisées aux utilisateurs du calendrier. Remarque

Bien que vous puissiez installer le composant calendrier exemple que vous venez de compiler, n’essayez pas de le placer tout de suite sur une fiche. Le composant TCustomGrid contient une méthode DrawCell abstraite qui doit être redéclarée avant que les objets d’instance puissent être créés. La redéfinition de la méthode DrawCell est décrite dans “Remplissage des cellules” à la page 11-5.

Remarque

Les noms et les emplacements de certaines unités sont différents pour les applications CLX. Par exemple, l’unité Controls est QControls et il n’y a pas d’unité Windows ou Messages dans les applications CLX. L’unité que vous obtenez doit ressembler à ceci : unit DBCal; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Grids, Calendar; type TDBCalendar = class(TSampleCalendar) end; procedure Register; implementation procedure Register; begin RegisterComponents(’Samples’, [TDBCalendar]); end; end.

Vous pouvez à présent commencer à transformer le nouveau calendrier en scruteur de données.

Fonctionnement du contrôle en lecture seulement Puisque votre calendrier scruteur de données ne fonctionnera qu’en lecture (par rapport aux données), il est opportun de rendre le contrôle lui-même accessible en lecture seulement. Ainsi, l’utilisateur ne s’attendra pas à voir répercuter dans la base de données une modification qu’il aurait apportée au contrôle. Rendre le calendrier accessible en lecture seulement fait intervenir deux étapes : • Ajout de la propriété ReadOnly. • Autorisation des mises à jour nécessaires. Remarque

Si vous démarrez avec le composant TCalendar de la page Exemples de Delphi au lieu deTSampleCalendar, le contrôle a déjà une propriété ReadOnly. Vous pouvez donc ignorer ces étapes.

Contrôles orientés données

12-3

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

Ajout de la propriété ReadOnly En ajoutant une propriété ReadOnly, vous fournissez le moyen de rendre le contrôle accessible en lecture seulement au moment de la conception. Si la valeur de cette propriété est True toutes les cellules du contrôle perdront la capacité à être sélectionnées.

1 Ajoutez la déclaration de la propriété ainsi qu’une donnée membre private pour contenir la valeur : type TDBCalendar = class(TSampleCalendar) private FReadOnly: Boolean; public constructor Create(AOwner: TComponent); override;

{ champ de stockage interne } { doit surcharger pour définir les valeurs par défaut }

published property ReadOnly: Boolean read FReadOnly write FReadOnly default True; end; ƒ constructor TDBCalendar.Create(AOwner: TComponent); begin inherited Create(AOwner); { appelez toujours le constructeur hérité ! } FReadOnly := True; { définit la valeur par défaut } end;

2 Redéfinissez la méthode SelectCell pour inhiber la sélection si le contrôle est accessible en lecture seulement. L’utilisation de SelectCell est expliquée dans la section “Exclusion des cellules vides” à la page 11-13. function TDBCalendar.SelectCell(ACol, ARow: Longint): Boolean; begin if FReadOnly then Result := False { sélection impossible si accès en lecture seule } else Result := inherited SelectCell(ACol, ARow); { sinon, utilise la méthode héritée } end;

N’oubliez pas d’ajouter la déclaration de SelectCell à la déclaration de la classe de TDBCalendar, et ajoutez la directive override. Si vous ajoutez maintenant le calendrier à une fiche, vous vous rendez compte que le composant ignore les clics de souris et les frappes de touches. Et il ne met plus à jour la position de la sélection lorsque vous changez la date.

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

12-4

Guide du concepteur de composants

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

Pour outrepasser cette interdiction de toute modification, vous devez ajouter au calendrier un indicateur booléen interne et n’autoriser les modifications que si la valeur de cet indicateur est True : type TDBCalendar = class(TSampleCalendar) private FUpdating: Boolean; { indicateur privé à usage interne protected function SelectCell(ACol, ARow: Longint): Boolean; override; public procedure UpdateCalendar; override; { notez la directive override end; ƒ function TDBCalendar.SelectCell(ACol, ARow: Longint): Boolean; begin if (not FUpdating) and FReadOnly then Result := False { sélection possible si mise à jour else Result := inherited SelectCell(ACol, ARow); { sinon, utilise la méthode héritée end;

}

}

} }

procedure TDBCalendar.UpdateCalendar; begin FUpdating := True; { définit l’indicateur pour permettre les mises à jour } try inherited UpdateCalendar; { mise à jour habituelle } finally FUpdating := False; { réinitialise toujours l’indicateur } end; end;

Le calendrier n’autorise toujours pas les modifications directes de l’utilisateur mais les modifications de la date effectuées via les propriétés de date sont prises en compte. Vous disposez maintenant d’un contrôle en lecture seulement tout à fait opérationnel. Vous voilà prêt à ajouter les fonctionnalités servant à scruter les données.

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

1 Déclaration du champ de classe. 2 Déclaration des propriétés d’accès.

Contrôles orientés données

12-5

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

3 Initialisation du lien de données.

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

Avant de compiler l’application, vous devez ajouter DB et DBCtrls à la clause uses de l’unité.

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

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

12-6

Guide du concepteur de composants

} } } } } }

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

property DataSource: TDataSource read GetDataSource write SetDataSource; end; ƒ function TDBCalendar.GetDataField: string; begin Result := FDataLink.FieldName; end; function TDBCalendar.GetDataSource: TDataSource; begin Result := FDataLink.DataSource; end; procedure TDBCalendar.SetDataField(const Value: string); begin FDataLink.FieldName := Value; end; procedure TDBCalendar.SetDataSource(Value: TDataSource); begin FDataLink.DataSource := Value; end;

Maintenant que sont établis les liens entre le calendrier et son lien de données, il reste une étape importante à franchir. Vous devez construire la classe lien de données au moment de la construction du contrôle calendrier et le détruire avant de détruire ce même contrôle.

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

}

} } } }

destructor TDBCalendar.Destroy; begin FDataLink.Free; { détruit toujours d’abord les objets ayant un propriétaire... } inherited Destroy; { ...puis appelle le destructeur hérité }

Contrôles orientés données

12-7

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

end;

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

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

} } } }

destructor TDBCalendar.Destroy; begin FDataLink.OnDataChange := nil; { détache le gestionnaire avant de détruire l’objet } FDataLink.Free; { détruit toujours d’abord les objets ayant un propriétaire... } inherited Destroy; { ...puis appelle le destructeur hérité } end; procedure TDBCalendar.DataChange(Sender: TObject); begin if FDataLink.Field = nil then { s’il n’y a pas de champ attribué... } CalendarDate := 0 { ...définit une date incorrecte }

12-8

Guide du concepteur de composants

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

else CalendarDate := FDataLink.Field.AsDateTime;

{ sinon, définit le calendrier par la date }

end;

Vous avez maintenant un contrôle de parcours des données.

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

Modification de la valeur par défaut de FReadOnly. Gestion des messages liés à la souris ou au clavier. Mise à jour de la classe lien de données sur un champ. Modification de la méthode Change. Mise à jour de l’ensemble de données.

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

{ définit la valeur par défaut }

Contrôles orientés données

12-9

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

Gestion des messages liés à la souris ou au clavier Lorsque l’utilisateur commence à se servir du contrôle, celui-ci reçoit de Windows les messages indiquant la manipulation de la souris (WM_LBUTTONDOWN, WM_MBUTTONDOWN, ou WM_RBUTTONDOWN), ou le message indiquant la manipulation du clavier (WM_KEYDOWN). Pour permettre à un contrôle de répondre à ces messages, vous devez écrire les gestionnaires des réponses à ces messages. • Réponse aux messages indiquant la manipulation de la souris. • Réponse aux messages indiquant la manipulation du clavier. Remarque

Si vous écrivez des applications CLX, la notification provient du système d’exploitation sous la forme d’événements système. Pour plus d’informations sur l’écriture de composants qui répondent aux événements système et widget, voir “Réponse aux notifications du système à l’aide de CLX” à la page 7-10.

Réponse aux messages indiquant la manipulation de la souris Une méthode MouseDown est une méthode protégée de l’événement OnMouseDown d’un contrôle. Le contrôle lui-même appelle MouseDown en réponse au message Windows indiquant la manipulation de la souris. Lorsque vous redéfinissez la méthode MouseDown héritée, vous pouvez inclure du code apportant d’autres réponses en plus de l’appel à l’événement OnMouseDown. Pour redéfinir MouseDown, ajoutez la méthode MouseDown à la classe TDBCalendar : type TDBCalendar = class(TSampleCalendar); ƒ protected procedure MouseDown(Button: TButton, Shift: TShiftState, X: Integer, Y: Integer); override; ƒ end; procedure TDBCalendar.MouseDown(Button: TButton; Shift: TShiftState; X, Y: Integer); var MyMouseDown: TMouseEvent; begin if not ReadOnly and FDataLink.Edit then inherited MouseDown(Button, Shift, X, Y) else begin MyMouseDown := OnMouseDown; if Assigned(MyMouseDown then MyMouseDown(Self, Button, Shift, X, Y); end; end;

Lorsque MouseDown répond à un message indiquant la manipulation de la souris, la méthode MouseDown héritée est appelée uniquement si la propriété ReadOnly du contrôle est False et si l’objet lien de données est en mode édition, c’est-à-dire si le champ peut être modifié. Si le champ ne peut être modifié, le

12-10

Guide du concepteur de composants

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

code mis par le programmeur dans le gestionnaire de l’événement OnMouseDown, s’il en existe un, est exécuté.

Réponse aux messages indiquant la manipulation du clavier Une méthode KeyDown est une méthode protégée de l’événement OnKeyDown d’un contrôle. Le contrôle lui-même appelle KeyDown en réponse au message Windows indiquant la manipulation du clavier. Lorsque vous redéfinissez la méthode KeyDown héritée, vous pouvez inclure le code qui apporte d’autres réponses en plus de l’appel à l’événement OnKeyDown. Pour redéfinir KeyDown, suivez ces instructions :

1 Ajoutez une méthode KeyDown à la classe TDBCalendar : type TDBCalendar = class(TSampleCalendar); ƒ protected procedure KeyDown(var Key: Word; Shift: TShiftState; X: Integer; Y: Integer); override; ƒ end;

2 Implémentez la méthode KeyDown : procedure KeyDown(var Key: Word; Shift: TShiftState); var MyKeyDown: TKeyEvent; begin if not ReadOnly and (Key in [VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, VK_END, VK_HOME, VK_PRIOR, VK_NEXT]) and FDataLink.Edit then inherited KeyDown(Key, Shift) else begin MyKeyDown := OnKeyDown; if Assigned(MyKeyDown) then MyKeyDown(Self, Key, Shift); end; end;

Lorsque KeyDown répond à un message indiquant la manipulation du clavier, la méthode KeyDown héritée est appelée uniquement si la propriété ReadOnly du contrôle vaut False, si la touche appuyée est une des touches de déplacement du curseur et si l’objet lien de données est en mode édition, c’est-à-dire si le champ peut être modifié. Si le champ ne peut être modifié ou si une autre touche a été pressée, le code mis par le programmeur dans le gestionnaire de l’événement OnKeyDown, s’il en existe un, est exécuté.

Contrôles orientés données

12-11

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

Mise à jour de la classe lien de données sur un champ Il existe deux types de modification des données : • Le changement de la valeur d’un champ doit se répercuter dans le contrôle orienté données • Le changement dans le contrôle orienté données doit se répercuter dans la valeur du champ Le composant TDBCalendar a déjà une méthode DataChange qui gère les modifications de la valeur du champ dans l’ensemble de données en assignant cette valeur à la propriété CalendarDate. La méthode DataChange est le gestionnaire de l’événement OnDataChange. Ainsi le composant calendrier est capable de gérer le premier type de modification des données. De manière semblable, la classe lien de données sur un champ a aussi un événement OnUpdateData qui se produit lorsque l’utilisateur modifie le contenu du contrôle orienté données. Le contrôle calendrier a une méthode UpdateData qui devient le gestionnaire de l’événement OnUpdateData. UpdateData assigne au champ lien de données la valeur modifiée dans le contrôle orienté données.

1 Pour répercuter dans la valeur du champ une modification effectuée sur la valeur du calendrier, ajoutez une méthode UpdateData à la section private du composant calendrier : type TDBCalendar = class(TSampleCalendar); private procedure UpdateData(Sender: TObject); ƒ end;

2 Implémentez la méthode UpdateData : procedure UpdateData(Sender: TObject); begin FDataLink.Field.AsDateTime := CalendarDate;

{ définit le champ lien par la date du calendrier }

end;

3 Dans le constructeur de TDBCalendar, affectez la méthode UpdateData à l’événement OnUpdateData : constructor TDBCalendar.Create(AOwner: TComponent); begin inherited Create(AOwner); FReadOnly := True; FDataLink := TFieldDataLink.Create; FDataLink.OnDataChange := DataChange; FDataLink.OnUpdateData := UpdateData; end;

12-12

Guide du concepteur de composants

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

Modification de la méthode Change La méthode Change du TDBCalendar est appelée chaque fois qu’est définie une nouvelle valeur de date. Change appelle le gestionnaire de l’événement OnChange, s’il existe. L’utilisateur du composant peut écrire du code dans le gestionnaire de l’événement OnChange afin de répondre aux modifications de la date. Lorsque la date du calendrier change, l’ensemble de données sous-jacent doit être averti de ce changement. Vous pouvez le faire en redéfinissant la méthode Change et en ajoutant une ligne de code de plus. Voici les étapes à suivre :

1 Ajoutez une nouvelle méthode Change au composant TDBCalendar : type TDBCalendar = class(TSampleCalendar); private procedure Change; override; ƒ end;

2 Ecrivez la méthode Change, appelant la méthode Modified qui informe l’ensemble de données que celles-ci ont changé, puis appelle la méthode Change héritée : procedure TDBCalendar.Change; begin FDataLink.Modified; inherited Change; end;

{ appelle la méthode Modified } { appelle la méthode Change héritée }

Mise à jour de l’ensemble de données A ce point, une modification dans le contrôle orienté données a changé les valeurs dans la classe du lien de données sur un champ. La dernière étape de la création d’un contrôle permettant la modification des données consiste à mettre à jour l’ensemble de données avec la nouvelle valeur. Cela doit se produire après que la personne ayant changé la valeur du contrôle orienté données quitte le contrôle en cliquant à l’extérieur de celui-ci ou en appuyant sur la touche Tabulation. Ce processus n’est pas le même dans les applications VCL que dans les applications CLX. Remarque

Les applications VCL définissent des ID de message pour les opérations sur les contrôles. Par exemple, le message CM_EXIT est envoyé au contrôle lorsque l’utilisateur quitte celui-ci. Vous pouvez écrire des gestionnaires de messages qui répondent au message. Et ensuite, lorsque l’utilisateur quitte le contrôle, la méthode CMExit, gestionnaire du message CM_EXIT, répondra en mettant à jour l’enregistrement dans l’ensemble de données avec les valeurs modifiées dans la classe lien de données sur un champ. Pour plus d’informations sur les gestionnaires de messages, voir Chapitre 7, “Gestion des messages et des notifications système”.

Contrôles orientés données

12-13

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

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

1 Ajoutez le gestionnaire de message au composant TDBCalendar : type TDBCalendar = class(TSampleCalendar); private procedure CMExit(var Message: TWMNoParams); message CM_EXIT; ƒ end;

2 Implémentez la méthode CMExit afin qu’elle ressemble à ceci : procedure TDBCalendar.CMExit(var Message: TWMNoParams); begin try FDataLink.UpdateRecord; { indique au lien de données de mettre à jour la base } except on Exception do SetFocus; { si échec, ne pas perdre la focalisation } end; inherited; end; Remarque

Dans les applications CLX, TWidgetControl possède une méthode DoExit protégée qui est appelée quand la focalisation en saisie quitte le contrôle. Cette méthode appelle le gestionnaire de l’événement OnExit. Vous pouvez redéfinir cette méthode pour actualiser l’enregistrement dans l’ensemble de données avant la génération du gestionnaire d’événement OnExit. Pour mettre à jour l’ensemble de données quand l’utilisateur quitte le contrôle, suivez les étapes ci-après :

1 Ajoutez un override à la méthode DoExit du composant TDBCalendar : type TDBCalendar = class(TSampleCalendar); private procedure DoExit; override; ƒ end;

2 Implémentez la méthode DoExit afin qu’elle ressemble à ceci : procedure TDBCalendar.CMExit(var Message: TWMNoParams); begin try FDataLink.UpdateRecord; { indique au lien de données de mettre à jour la base } except on Exception do SetFocus; { si échec, ne pas perdre la focalisation } end; inherited; { laisse la méthode héritée générer un événement OnExit } end;

12-14

Guide du concepteur de composants

Chapitre

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

Chapitre13

Il est pratique de transformer une boîte de dialogue fréquemment sollicitée en un composant que vous pourrez ajouter dans la palette des composants. Ainsi, vos composants boîte de dialogue fonctionneront exactement comme ceux des boîtes de dialogue standard. L’objectif ici est de créer un composant simple qu’un utilisateur peut ajouter à un projet et dont il peut définir les propriétés lors de la conception. La transformation d’une boîte de dialogue en composant nécessite les étapes suivantes :

1 Définition de l’interface du composant 2 Création et recensement du composant 3 Création de l’interface du composant 4 Test du composant A l’exécution, le composant “enveloppe” de Delphi, associé à la boîte de dialogue crée et exécute celle-ci en lui transmettant les données spécifiées par l’utilisateur. Le composant boîte de dialogue est donc à la fois réutilisable et personnalisable. Dans ce chapitre, vous allez voir comment créer un composant enveloppe autour de la fiche générique A propos de... disponible dans le référentiel d’objets de Delphi. Remarque

Copiez les fichiers ABOUT.PAS et ABOUT.DFM dans votre répertoire de travail. Il n’y a pas grand chose à dire concernant la conception de la boîte de dialogue enveloppée par un composant. Dans un tel contexte, n’importe quelle fiche peut fonctionner comme une boîte de dialogue.

Transformation d’une boîte de dialogue en composant

13-1

Définition de l’interface du composant

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

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

13-2

Guide du concepteur de composants

Création de l’interface du composant

procedure Register; implementation procedure Register; begin RegisterComponents(’Samples’, [TAboutBoxDlg]); end; end. Remarque

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

Création de l’interface du composant Voici les étapes nécessaires à la création de l’interface du composant :

1 Inclusion de l’unité de la fiche. 2 Ajout des propriétés de l’interface. 3 Ajout de la méthode Execute.

Inclusion de l’unité de la fiche Pour que votre composant enveloppe puisse initialiser et afficher la boîte de dialogue enveloppée, vous devez ajouter les fichiers de l’unité de la fiche dans la clause uses de l’unité du composant enveloppe. Ajoutez About à la clause uses de l’unité AboutDlg. La clause uses ressemble maintenant à ceci : uses Windows, SysUtils, Messages, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, About;

L’unité fiche déclare toujours une instance de la classe de la fiche. Dans le cas de la boîte A propos de, la classe de la fiche est TAboutBox, et l’unité About doit inclure la déclaration suivante : var AboutBox: TAboutBox;

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

Transformation d’une boîte de dialogue en composant

13-3

Création de l’interface du composant

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

S’agissant de votre boîte A propos de, vous devez disposer de quatre propriétés de type string pour le nom du produit, les informations de version, les informations de copyright et les commentaires éventuels. type TAboutBoxDlg = class(TComponent) private FProductName, FVersion, FCopyright, FComments: string; { déclare les données membres } published property property property property end;

13-4

ProductName: string read FProductName write FProductName; Version: string read FVersion write FVersion; Copyright: string read FCopyright write FCopyright; Comments: string read FComments write FComments;

Guide du concepteur de composants

Création de l’interface du composant

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

Ajout de la méthode Execute Il vous reste à définir dans l’interface du composant les moyens d’ouvrir la boîte de dialogue et de récupérer un résultat lorsqu’elle se ferme. Comme pour les composants des boîtes de dialogue standard, vous utiliserez une fonction booléenne appelée Execute qui renvoie True si l’utilisateur clique sur OK, ou False s’il annule la boîte de dialogue. La déclaration de la méthode Execute ressemble toujours à ceci : type TMyWrapper = class(TComponent) public function Execute: Boolean; end;

L’implémentation minimale de la méthode Execute doit construire la fiche de la boîte de dialogue, puis afficher celle-ci en tant que boîte de dialogue modale avant de renvoyer True ou False, selon la valeur renvoyée par ShowModal. Voici l’implémentation minimale de la méthode Execute pour une fiche boîte de dialogue de type TMyDialogBox : function TMyWrapper.Execute: Boolean; begin DialogBox := TMyDialogBox.Create(Application); try Result := (DialogBox.ShowModal = IDOK); finally DialogBox.Free; end; end;

{ construit la fiche } { exécute; définit le résultat selon la façon de fermer } { restitue la fiche }

Notez l’utilisation d’un bloc try..finally qui vérifie que l’application restitue l’objet boîte de dialogue même si une exception se produit. En général, lorsque vous construisez un objet de cette manière, vous devez utiliser un bloc try..finally pour protéger le bloc de code et être sûr que l’application libère toutes les ressources qu’elle alloue. En pratique, il y a davantage de code dans le bloc try..finally. Plus spécifiquement, avant l’appel à ShowModal, le composant enveloppe doit définir certaines propriétés de la boîte de dialogue en fonction de ses propres propriétés d’interface. A l’inverse, quand ShowModal rend la main, le composant enveloppe définit certaines de ses propriétés d’interface en fonction du résultat de l’exécution de la boîte de dialogue. Dans le cas de la boîte A propos de, vous devez utiliser les quatre propriétés d’interface du composant enveloppe pour définir le contenu des libellés de la

Transformation d’une boîte de dialogue en composant

13-5

Test du composant

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

} } } } } } } } } }

}

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

1 Créez un nouveau projet. 2 Placez un composant A propos de dans la fiche principale. 3 Placez un bouton de commande dans la fiche. 4 Double-cliquez sur le bouton de commande pour créer un gestionnaire d’événements vide.

13-6

Guide du concepteur de composants

Test du composant

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

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

Transformation d’une boîte de dialogue en composant

13-7

13-8

Guide du concepteur de composants

Chapitre

14 Extensions de l’EDI

Chapitre14

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

Présentation de l’API Tools Conception d’une classe expert Accès aux services de l’API Tools Utilisation des fichiers et des éditeurs Création de fiches et de projets Notification d’un expert des événements de l’EDI

Extensions de l’EDI

14-1

Présentation de l’API Tools

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

14-2

Guide du concepteur de composants

Conception d’une classe expert

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

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

Description

IOTAFormWizard

Crée généralement une nouvelle unité, une fiche ou un autre fichier

IOTAMenuWizard

Automatiquement ajoutée au menu Aide

IOTAProjectWizard

Crée généralement une nouvelle application ou un projet

IOTAWizard

Autres experts ne rentrant pas dans les autres catégories

Extensions de l’EDI

14-3

Conception d’une classe expert

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

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

14-4

Guide du concepteur de composants

Accès aux services de l’API Tools

Installation du paquet de l’expert Comme pour tous les paquets de conception, le paquet d’un expert doit disposer de la fonction Register. Pour davantage d’informations sur la fonction Register, voir Chapitre 8, “Accessibilité des composants au moment de la conception”. Dans la fonction Register vous pouvez recenser plusieurs experts en appelant RegisterPackageWizard en lui transmettant comme seul argument l’objet expert, comme indiqué ci-dessous : procedure Register; begin RegisterPackageWizard(MyWizard.Create); RegisterPackageWizard(MyOtherWizard.Create); end;

Vous pouvez également recenser des éditeurs de propriété, des composants, etc. dans le même paquet. N’oubliez pas qu’un paquet de conception fait partie de l’application Delphi principale, cela implique que tout nom de fiche doit être unique dans toute l’application et tous les autres paquets de conception. C’est le principal inconvénient de l’utilisation des paquets : vous ne pouvez pas prévoir comment quelqu’un d’autre va appeler ses fiches. Lors du développement, installez le paquet de l’expert de la même manière que les autres paquets de conception : cliquez sur le bouton Installer dans le gestionnaire de paquets. L’EDI compile et lie alors le paquet puis tente de le charger. L’EDI affiche une boîte de dialogue vous indiquant si le chargement du paquet s’est bien effectué.

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

Description

INTAServices

Donne accès aux objets natifs de l’EDI : menu principal, liste d’actions, liste d’images et les barres d’outils.

IOTAActionServices

Effectue les actions de base sur les fichiers : ouverture, fermeture, enregistrement et rechargement d’un fichier.

IOTACodeCompletionServices

Donne accès à l’achèvement de code, ce qui permet à un expert d’installer un gestionnaire personnalisé d’achèvement de code.

IOTADebuggerServices

Donne accès au débogueur.

Extensions de l’EDI

14-5

Accès aux services de l’API Tools

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

Description

IOTAEditorServices

Donne accès à l’éditeur de code source et aux tampons internes.

IOTAKeyBindingServices

Permet à un expert de recenser des liaisons de clavier personnalisées.

IOTAKeyboardServices

Donne accès aux macros et liaisons clavier.

IOTAKeyboardDiagnostics

Inverse le débogage des frappes au clavier.

IOTAMessageServices

Donne accès à la vue des messages.

IOTAModuleServices

Donne accès aux fichiers ouverts.

IOTAPackageServices

Obtient le nom de tous les paquets installés et de leurs composants.

IOTAServices

Services divers.

IOTAToDoServices

Donne accès à la liste des tâches, ce qui permet à un expert d’installer un gestionnaire personnalisé de la liste de tâches.

IOTAToolsFilter

Recense les notificateurs d’outils de filtrage.

IOTAWizardServices

Recense ou annule le recensement d’un expert.

Pour utiliser une interface de service, transtypez la variable BorlandIDEServices dans le service souhaité à l’aide de la fonction globale Supports, définie dans l’unité SysUtils. Par exemple, procedure set_keystroke_debugging(debugging: Boolean); var diag: IOTAKeyboardDiagnostics begin if Supports(BorlandIDEServices, IOTAKeyboardDiagnostics, diag) then diag.KeyTracing := debugging; end;

Si votre expert a besoin d’utiliser fréquemment un service donné, vous pouvez conserver un pointeur sur ce service comme donnée membre de la classe de votre expert.

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

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

14-6

Guide du concepteur de composants

Accès aux services de l’API Tools

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

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

Assurez-vous de charger la ressource en utilisant le nom ou l’identificateur spécifié dans le fichier ressource. Vous devez choisir une couleur qui sera interprétée comme la couleur de fond de l’image. Si vous ne voulez pas utiliser de couleur de fond, choisissez une couleur n’existant pas dans le bitmap.

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

Extensions de l’EDI

14-7

Accès aux services de l’API Tools

NewAction.ActionList NewAction.Caption NewAction.Hint NewAction.ImageIndex NewAction.OnUpdate NewAction.OnExecute

:= := := := := :=

Services.ActionList; GetMenuText(); ’Affiche une boîte de dialogue inutile’; ImageIndex; action_update; action_execute;

L’élément de menu initialise sa propriété Action avec l’action qui vient d’être créée. Le problème dans la création d’un élément de menu, c’est de savoir où l’insérer. L’exemple suivant recherche le menu Voir et insère le nouvel élément de menu comme premier élément du menu Voir. En général, il n’est pas judicieux de se baser sur une position absolue : en effet, vous ne savez jamais quand un autre expert va s’ajouter au menu. De plus, les versions ultérieures de Delphi peuvent également réorganiser les menus. Il est préférable de rechercher dans le menu un élément de nom donné. Dans un souci de clarté l’approche simpliste est indiqué ci-dessous. for I := 0 to Services.MainMenu.Items.Count - 1 do begin with Services.MainMenu.Items[I] do begin if CompareText(Name, ’ViewsMenu’) = 0 then begin NewItem := TMenuItem.Create(nil); NewItem.Action := NewAction; Insert(0, NewItem); end; end; end;

Une fois l’action ajoutée à la liste d’actions de l’EDI, l’utilisateur peut voir l’action quand il personnalise les barres d’outils. L’utilisateur peut sélectionner l’action et l’ajouter à une barre d’outils. Cela peut poser un problème quand votre expert est déchargé : tous les boutons de barre d’outils se retrouvent avec des pointeurs flottants sur une action et un gestionnaire d’événement OnClick inexistants. Pour éviter des violations d’accès, votre expert doit trouver tous les boutons d’outils faisant référence à ses actions et les supprimer.

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

14-8

Guide du concepteur de composants

Accès aux services de l’API Tools

begin Btn := ToolBar.Buttons[I]; if Btn.Action = Action then begin { Retire "Btn" de "ToolBar" } ToolBar.Perform(CM_CONTROLCHANGE, WPARAM(Btn), 0); Btn.Free; end; end; end; destructor MyWizard.Destroy; var Services: INTAServices; Btn: TToolButton; begin Supports(BorlandIDEServices, INTAServices, Services); { Vérifier toutes les barres d’outils et retirer tous les boutons utilisant cette action. } remove_action(NewAction, Services.ToolBar[sCustomToolBar]); remove_action(NewAction, Services.ToolBar[sDesktopToolBar]); remove_action(NewAction, Services.ToolBar[sStandardToolBar]); remove_action(NewAction, Services.ToolBar[sDebugToolBar]); remove_action(NewAction, Services.ToolBar[sViewToolBar]); remove_action(NewAction, Services.ToolBar[sInternetToolBar]); NewItem.Free; NewAction.Free; end;

Comme vous pouvez le voir dans cet exemple simple, votre expert peut interagir avec l’EDI de manière très flexible. Cependant, avec la flexibilité vient également la responsabilité. Vous pouvez facilement produire des pointeurs flottants ou d’autres violations d’accès. La section suivante donne quelques conseils pour diagnostiquer ce type de problème.

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

Extensions de l’EDI

14-9

Accès aux services de l’API Tools

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

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

14-10

Guide du concepteur de composants

Utilisation des fichiers et des éditeurs

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

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

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

Extensions de l’EDI

14-11

Création de fiches et de projets

Utilisation des interfaces d’éditeur Chaque module dispose d’au moins une interface d’éditeur. Certains modules ont plusieurs éditeurs, tels qu’un fichier source (.pas) et un fichier description de fiche (.dfm). Comme tous les éditeurs implémentent l’interface IOTAEditor ; transtypez l’éditeur vers un type spécifique pour savoir quel est le type de l’éditeur. Par exemple, le code suivant permet d’obtenir l’interface de l’éditeur de fiche d’une unité : { Renvoie l’éditeur de fiche d’un module ou nil si l’unité n’a pas de fiche. } function GetFormEditor(Module: IOTAModule): IOTAFormEditor; var I: Integer; Editor: IOTAEditor; begin for I := 0 to Module.ModuleFileCount - 1 do begin Editor := Module.ModuleFileEditors[I]; if Supports(Editor, IOTAFormEditor, Result) then Exit; end; Result := nil; end;

Les interfaces d’éditeur donnent accès à l’état interne de l’éditeur. Vous pouvez examiner le code source ou les composants que l’utilisateur est en train de modifier, modifier le code source, les composants ou les propriétés, changer la sélection dans les éditeurs de code source ou de fiche ou effectuer presque toute opération d’édition que l’utilisateur final peut mettre en œuvre. En utilisant une interface d’éditeur de fiche, un expert peut accéder à tous les composants d’une fiche. Chaque composant, y compris la fiche racine ou le module de données, a une interface IOTAComponent associée. Un expert peut examiner ou modifier la plupart des propriétés des composants. Si vous avez besoin d’un contrrôle complet du composant, vous pouvez transtyper l’interface IOTAComponent vers INTAComponent. L’interface de composant natif donne à l’expert un accès direct au pointeur TComponent. Cela est important si vous avez besoin de lire ou de modifier une propriété de type classe, comme TFont, ce qui ne peut se faire que via des interfaces de style NTA.

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

14-12

Guide du concepteur de composants

Création de fiches et de projets

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

Extensions de l’EDI

14-13

Création de fiches et de projets

function GetImplFileName: string; function GetIntfFileName: string; function GetFormName: string; function GetMainForm: Boolean; function GetShowForm: Boolean; function GetShowSource: Boolean; function NewFormFile(const FormIdent, AncestorIdent: string): IOTAFile; function NewImplSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile; function NewIntfSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile; procedure FormCreated(const FormEditor: IOTAFormEditor); { IOTACreator } function GetCreatorType: string; function GetExisting: Boolean; function GetFileSystem: string; function GetOwner: IOTAModule; function GetUnnamed: Boolean; private FCreatorType: string; end;

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

14-14

Guide du concepteur de composants

Création de fiches et de projets

Result := Group.ActiveProject; Exit; end; end; end;

Le créateur renvoie nil depuis NewFormSource pour générer un fichier fiche par défaut. Les méthodes intéressantes sont NewImplSource et NewIntfSource qui créent une instance de IOTAFile renvoyant le contenu du fichier. La classe TFile implémente l’interface IOTAFile. Elle renvoie -1 comme âge du fichier (cela signifie que le fichier n’existe pas) et renvoie le contenu du fichier dans une chaîne. Pour garder la classe TFile simple, le créateur génère la chaîne, la classe TFile se contenant de la transmettre. TFile = class(TInterfacedObject, IOTAFile) public constructor Create(const Source: string); function GetSource: string; function GetAge: TDateTime; private FSource: string; end; constructor TFile.Create(const Source: string); begin FSource := Source; end; function TFile.GetSource: string; begin Result := FSource; end; function TFile.GetAge: TDateTime; begin Result := TDateTime(-1); end;

Vous pouvez stocker le texte du contenu du fichier dans une ressource afin d’en simplifier la modification, mais dans un souci de simplicité, dans cet exemple le texte est codé directement dans l’expert. L’exemple suivant génère le code source en supposant l’existence d’une fiche. Vous pouvez facilement gérer le cas encore plus simple d’une unité de base. Testez FormIdent et, si elle est vide, créez une unité de base, sinon créez une unité de fiche. Le squelette de base du code est le même que celui par défaut de l’EDI (avec bien entendu l’ajout des commentaires au début), mais vous pouvez le modifier à votre guise. function TCreator.NewImplSource( const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile; var FormSource: string; begin FormSource := ’{ ----------------------------------------------------------------- ’ + #13#10 + ’%s - description’+ #13#10 +

Extensions de l’EDI

14-15

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

’Copyright © %y Your company, inc.’+ #13#10 + ’Created on %d’+ #13#10 + ’By %u’+ #13#10 + ’ ----------------------------------------------------------------- }’ + #13#10;

#13#10 +

return TFile.Create(Format(FormSource, ModuleIdent, FormIdent, AncestorIdent)); }

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

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

14-16

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

Guide du concepteur de composants

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

Tableau 14.3 Interfaces de notification (suite) Interface

Description

IOTAEditorNotifier

Modification ou enregistrement d’un fichier source ou passage d’un fichier à l’autre dans l’éditeur

IOTAFormNotifier

Enregistrement d’une fiche ou modification de la fiche ou de ses composants (ou d’un module de données)

IOTAIDENotifier

Chargement de projets, installation de paquets et autres événements de l’EDI de portée globale

IOTAMessageNotifier

Ajout ou suppression d’onglets (groupes de messages) dans la vue des messages

IOTAModuleNotifier

Modification, enregistrement ou changement de nom d’un module

IOTAProcessModNotifier

Chargement d’un module de processus dans le débogueur

IOTAProcessNotifier

Création ou destruction de threads ou de processus dans le débogueur

IOTAThreadNotifier

Modification de l’état d’un thread dans le débogueur

IOTAToolsFilterNotifier

Appel d’un outil de filtrage

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

L’un des moyens d’écrire un notificateur consiste à le faire se recenser lui-même dans son constructeur. Le destructeur annule le recensement du notificateur. Dans le cas d’un notificateur de module, l’EDI appelle la méthode Destroyed quand l’utilisateur ferme le fichier. Dans ce cas, le notificateur doit lui-même

Extensions de l’EDI

14-17

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

annuler son recensement et libérer sa référence à l’interface de module. L’EDI libère sa référence au notificateur, ce qui ramène à zéro son compteur de références et libère l’objet. Il n’est donc pas nécessaire d’écrire le destructeur défensivement : le notificateur n’est peut être déjà plus recensé. constructor TModuleNotifier.Create( const Module: IOTAModule); begin FIndex := -1; FModule := Module; { Recense ce notificateur. } FIndex := Module.AddNotifier(self); { Stocke l’ancien nom du module. } FName := ChangeFileExt(ExtractFileName(Module.FileName), ’’); end; destructor TModuleNotifier.Destroy; begin { Annule le recensement du notificateur si ce n’est pas déjà fait. } if Findex >= 0 then FModule.RemoveNotifier(FIndex); end; procedure TModuleNotifier.Destroyed; begin { L’interface du module est détruite, nettoyage du notificateur. } if Findex >= 0 then begin { Annule le recensement du notificateur. } FModule.RemoveNotifier(FIndex); FIndex := -1; end; FModule := nil; end;

L’EDI rappelle la fonction ModuleRenamed du notificateur quand l’utilisateur renomme le fichier. La fonction attend comme paramètre le nouveau nom qui est utilisé par l’expert pour actualiser le commentaire dans le fichier. Pour modifier le tampon source, l’expert utilise une interface de position d’édition. L’expert recherche la position appropriée, vérifie si le texte trouvé est le bon et remplace ce texte par le nouveau nom. procedure TModuleNotifier.ModuleRenamed(const NewName: string); var ModuleName: string; I: Integer; Editor: IOTAEditor; Buffer: IOTAEditBuffer; Pos: IOTAEditPosition; Check: string; begin { Obtient le nom du module à partir du nouveau nom de fichier. } ModuleName := ChangeFileExt(ExtractFileName(NewName), ’’); for I := 0 to FModule.GetModuleFileCount - 1 do begin { Actualise tous les tampons de l’éditeur de code source. } Editor := FModule.GetModuleFileEditor(I);

14-18

Guide du concepteur de composants

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

if Supports(Editor, IOTAEditBuffer, Buffer) then begin Pos := Buffer.GetEditPosition; { Le nom du module se trouve dans la ligne 2 du commentaire. Saute les espaces de début et copie l’ancien nom de module, pour vérifier si c’est le bon emplacement. } Pos.Move(2, 1); Pos.MoveCursor(mmSkipWhite or mmSkipRight); Check := Pos.RipText(’’, rfIncludeNumericChars or rfIncludeAlphaChars); if Check = FName then begin Pos.Delete(Length(Check)); // Supprimer l’ancien nom. Pos.InsertText(ModuleName); // Insérer le nouveau nom. FName := ModuleName; // Mémoriser le nouveau nom. end; end; end; end;

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

Extensions de l’EDI

14-19

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

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

Le reste de l’expert gère les détails du recensement de l’expert, l’installation des options de menu, etc.

14-20

Guide du concepteur de composants

Index A A propos de, boîte de dialogue 13-2, 13-3 ajout de propriétés 13-4 exécution 13-6 About, unité 13-3 AboutDlg, unité 13-2 abstraites, classes 1-3 affectation, instructions 3-2 Aide 8-4 aide en ligne 8-4 années bissextiles 11-8 API Open Tools Voir API Tools API outils natifs 14-2 API Tools 14-1–14-20 créateurs 14-3, 14-12–14-16 création de fichiers 14-12–14-16 débogage 14-9–14-10 éditeurs 14-3, 14-11–14-12 experts 14-3, 14-3–14-5 modules 14-3, 14-11–14-12 notificateurs 14-3 services 14-2, 14-5–14-12 applications graphiques 1-8, 6-1 réalisation de palettes 6-5, 6-6 applications multithreads, envoi de messages 7-10 attributs, éditeurs de propriétés 8-11

B bases de données 12-1 propriétés d’accès 12-6–12-7 BEGIN_MESSAGE_MAP, macro 7-4, 7-7 BeginAutoDrag, méthode 7-14 bibliothèques de composants, ajout de composants 1-16 bibliothèques, contrôles personnalisés 1-5 bitmap de palette, fichiers 1-14 bitmaps 6-4 ajout aux composants 1-14 chargement 6-5 comparés aux contrôles graphiques 10-3 hors écran 6-6–6-7 surfaces de dessin 6-4 boîtes de dialogue 13-1–13-7 création 13-1 définition de l’état initial 13-2 éditeurs de propriété comme 8-10 standard Windows 13-2 création 13-2 exécution 13-5

boîtes de dialogue standard 13-1, 13-2 création 13-2 exécution 13-5 boîtes liste 11-1 BorlandIDEServices, variable 14-6 BoundsChanged, méthode 7-15 Broadcast, méthode 7-9 Brush, propriété 6-3 BrushCopy, méthode 6-3, 6-7

C calendriers 11-1–11-14 ajout de dates 11-5–11-11 définition des propriétés et événements 11-3, 11-7, 11-12 déplacement dans les 11-11–11-14 en lecture seule 12-3–12-5 redimensionnement 11-4 sélection du jour en cours 11-10 canevas 1-8, 6-2, 6-3 outils de dessin par défaut 10-6 palettes 6-5–6-6 Canvas, propriété 1-8 caractères 3-2 carrés, dessin 10-10 cercles, dessin 10-10 chaînes 3-2, 3-9 renvoi 3-9 champs bases de données 12-6, 12-8 enregistrements de message 7-2, 7-4, 7-7 champs de classe 10-4 attribution de nom 4-3 déclaration 10-6 Change, méthode 12-13 classe lien de données sur un champ 12-12 classes 1-2, 1-3, 2-1, 3-3 abstraites 1-3 accès 2-4–2-7, 10-6 ancêtre 2-4 création 2-1 définition 1-13, 2-2 méthodes statiques et 2-8 méthodes virtuelles et 2-9 dérivation 2-2, 2-9 dérivées 2-4, 2-9 éditeurs de propriété comme 8-7 héritage 2-8 hiérarchie 2-4 instanciation 2-2 par défaut 2-4 Index

I-1

partie publiée 2-7 partie publique 2-6 propriétés comme 3-3 transmission comme paramètres 2-10 classes ancêtres 2-4 classes dérivées 2-4 redéfinition des méthodes 2-9 clic, événements 4-2, 4-8 Click, méthode 4-2, 7-14 redéfinition 4-7, 11-12 closures 4-9 CLX notifications système 7-10–7-16 signaux 7-11–7-12 CM_EXIT, message 12-13 CMExit, méthode 12-13 code 5-4 ColorChanged, méthode 7-15 composant, expert 1-10 composants 1-1, 2-1, 3-3 abstraites 1-3 aide en ligne 8-4 ajout à l’unité existante 1-13 ajout à la palette de composants 8-1 ajout aux unités 1-13 bitmaps 1-14 bitmaps de palette 1-14 changement 9-1–9-4 classes dérivées 1-3, 1-13, 10-2 création 1-2, 1-9 dépendances 1-6 déplacement 1-16 double-clic 8-16, 8-18–8-19 initialisation 3-14, 10-7, 12-7 installation 8-20 interfaces 2-4, 13-2 conception 2-7 exécution 2-6 menus contextuels 8-16, 8-17–8-18 non visuels 1-5, 1-13, 13-3 orientés données 12-1 paquets 8-20 personnalisation 1-3, 3-2, 4-1 pour modifier les données 12-9–12-14 pour scruter les données 12-2–12-8 recensement 1-13, 8-2 répondre aux événements 4-6, 4-8, 4-10, 12-8 ressources, libération 13-5 test 1-17, 1-19, 13-6–13-7 composants non visuels 1-5, 1-13, 13-3 constructeurs 1-18, 3-13, 5-3, 11-4, 11-5, 12-7 objets ayant un propriétaire et 10-6, 10-7 redéfinition 9-2 Contains, liste (paquets) 8-20 contextes de périphériques 1-8, 6-2 I-2

Guide du concepteur de composants

contrôles changement 1-3 fenêtrés 1-4 forme 10-8 graphiques 6-4, 10-1–10-10 création 1-4, 10-3 dessin 10-3, 10-8–10-10 événements 6-7 palettes et 6-5–6-6 personnalisés 1-5 pour modifier les données 12-9–12-14 pour scruter les données 12-2–12-8 réception de la focalisation 1-4 redessin 10-8, 10-9, 11-4, 11-5 redimensionnement 6-7, 11-4 contrôles de redimensionnement graphiques 6-7 contrôles graphiques 1-4, 6-4, 10-1–10-10 comparés aux bitmaps 10-3 création 1-4, 10-3 dessin 10-3, 10-8–10-10 enregistrement des ressources système 1-4 événements 6-7 contrôles mémo 3-9 modification 9-1 contrôles orientés données 12-1 création 12-2–12-14 destruction 12-7 pour modifier les données 12-9–12-14 pour scruter les données 12-2–12-8 réponse aux changements 12-8 contrôles personnalisés 1-5 bibliothèques 1-5 contrôles préexistants 1-5 conventions d’attribution de nom champs 4-3 événements 4-9 méthodes 5-2 propriétés 3-7 types d’enregistrement de message 7-7 CopyMode, propriété 6-3 CopyRect, méthode 6-3, 6-7 crayons 10-6 changement 10-8 créateurs 14-3, 14-12–14-16 CursorChanged, méthode 7-15

D DataChange, méthode 12-12 DataField, propriété 12-6 DataSource (propriété), contrôles orientés données 12-6 Day, propriété 11-6 DblClick, méthode 7-14 .dcr, fichiers 1-14

débogage des experts 14-9–14-10 déclarations classes 2-10, 10-6 public 2-6 published 2-7 gestionnaires d’événements 4-6, 4-9, 11-13 gestionnaires de messages 7-4, 7-6, 7-8 méthodes 5-4 dynamiques 2-10 public 5-3 statiques 2-8 virtuelles 2-9 nouveaux types de composants 2-3 propriétés 3-3, 3-4–3-7, 3-8, 3-14, 4-9, 10-4 stored 3-14 types définis par l’utilisateur 10-3 déclarations de type, propriétés 10-3 default directive 3-13, 9-3 mot réservé 3-8 DefaultHandler, méthode 7-3 délégation 4-1 DelphiInterface, classe 14-6 déploiement extensions de l’EDI 14-20 déploiement des extensions de l’EDI 14-5 déréférencement de pointeurs d’objet 2-10 dérivation de classes 2-9 destructeurs 5-3, 12-7 objets ayant un propriétaire et 10-6, 10-7 fichiers .dfm 3-12 directives default 3-13, 9-3 dynamic 2-10 override 2-9, 7-4 protected 4-6 public 4-6 published 3-3, 4-6, 13-4 stored 3-14 virtual 2-9 Dispatch, méthode 7-3, 7-5 DoExit, méthode 12-14 DoMouseWheel, méthode 7-14 données, accès 12-1 double-clics composants 8-16 réponse aux 8-18–8-19 DragOver, méthode 7-14 Draw, méthode 6-3, 6-7 dynamic, directives 2-10

E écriture seule, propriétés 3-7 EDI ajout d’actions 14-7–14-8

ajout d’images 14-7 personnalisation 14-1 suppression de boutons 14-8 Edit, méthode 8-10, 8-11 Editeur d’image, utilisation 1-14 éditeur de code, affichage 8-19 éditeurs de composants 8-16–8-20 par défaut 8-17 recensement 8-20 éditeurs de propriété 3-3 attributs 8-11 boîtes de dialogue comme 8-10 comme classes dérivées 8-7 éditeurs de propriétés 8-7–8-13 recensement 8-12–8-13 éditeurs, API Tools 14-3, 14-11–14-12 Ellipse, méthode 6-3 ellipses, dessin 10-10 EnabledChanged, méthode 7-15 END_MESSAGE_MAP, macro 7-4, 7-7 ensemble, types 3-2 ensembles 3-2 enveloppes 1-5, 13-2 initialisation 13-3 Voir aussi enveloppes de composants enveloppes de composants 1-5, 13-2 initialisation 13-3 envoi des messages 7-8–7-10 erreurs de compilation, directive override et 2-9 événements 1-8, 4-1–4-10 accès 4-6 attribution de nom 4-9 contrôles graphiques 6-7 définition de nouveaux 4-7–4-10 fourniture d’aide 8-4 gestion de message 7-4, 7-6 hérités 4-5 implémentation 4-2, 4-5 récupérer 4-4 réponse aux 4-6, 4-8, 4-10, 12-8 standard 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-16 EventFilter (méthode), événements système 7-15 exceptions 5-2, 7-3, 13-5 Execute (méthode), boîtes de dialogue 13-5 experts API Tools 14-3 Composant 1-10 création 14-2, 14-3–14-5 débogage 14-9–14-10 installation 14-5, 14-20

Index

I-3

réponse aux événements de l’EDI 14-16 types 14-4 experts fiche 14-4 experts menu 14-4 experts projet 14-4 experts référentiel d’objets 14-4

F fenêtre classe 1-5 contrôles 1-4 gestion de message 11-4 handles 1-4, 1-6 procédures 7-3 fiches, en composants 13-1 fichiers, graphiques 6-4 FillRect, méthode 6-3 finally, mot réservé 6-7, 13-5 FloodFill, méthode 6-3 focalisation 1-4 focalisation de saisie 1-4 fonctions 1-7 API Windows 1-4, 6-1 attribution de nom 5-2 événements et 4-3 graphiques 6-2 lecture des propriétés 3-7, 8-9, 8-11 fonctions membres paramètres de propriété 3-7 Font, propriété 6-3 FontChanged, méthode 7-15 formes géométriques, dessin 10-10 FReadOnly 12-9

G GDI, applications 1-8, 6-1 gestion de message 7-4–7-5 gestionnaires d’événements 1-8, 4-2, 4-9, 12-8 affichage de l’éditeur de code 8-19 déclarations 4-6, 4-9, 11-13 méthodes 4-3, 4-5, 4-6 par défaut, redéfinition 4-10 paramètres 4-3, 4-8, 4-9, 4-10 notification, événements 4-8 pointeurs 4-2, 4-3, 4-9 transmission de paramètres par référence 4-10 types 4-3–4-4, 4-8–4-9 vides 4-10 gestionnaires de messages 7-2, 7-3, 11-4, 11-5 création 7-6–7-8 déclarations 7-6, 7-8 méthodes, surcharge 7-7 par défaut 7-3 redéfinition 7-4 I-4

Guide du concepteur de composants

GetAttributes, méthode 8-11 GetFloatValue, méthode 8-9 GetMethodValue, méthode 8-9 GetOrdValue, méthode 8-9 GetPalette, méthode 6-6 GetProperties, méthode 8-11 GetStrValue, méthode 8-9 GetValue, méthode 8-9 glisser-déplacer, événements 10-2 Graphic, propriété 6-4 graphiques 6-1–6-8 chargement 6-4, 6-5 complexes 6-6 conteneurs 6-4 enregistrement 6-4 fonctions, appel 6-2 indépendants 6-3 méthodes 6-3, 6-5, 6-7 copie d’images 6-7 palettes 6-6 outils de dessin 6-2, 6-8, 10-6 changement 10-8 redessiner les images 6-7 redimensionnement 6-7 stockage 6-4 graphiques (méthodes), palettes 6-6 grilles 11-1, 11-3, 11-5, 11-12

H Handle, propriété 1-4, 1-6, 6-3 HandleException, méthode 7-3 héritage de classe 2-8 héritées méthodes 4-7 propriétés 10-2, 11-3 publication 3-3 hérités événements 4-5 hiérarchie (classes) 2-4 HookEvents, méthode 7-12 hors écran, bitmaps 6-6–6-7

I icônes 6-4 ajout aux composants 1-14 identificateurs champs de classe 4-3 événements 4-9 méthodes 5-2 paramètres de propriété 3-7 types d’enregistrement de message 7-7 images 6-3, 6-3–6-6 dessin 10-8 redessiner 6-7 réduction du scintillement 6-6

indépendants du périphérique, graphiques 6-1 index 3-9 index, mot réservé 11-7 indicateurs 12-5 informations de types à l’exécution 2-7 inspecteur d’objets 3-2, 8-7 aide sur 8-4 modification des propriétés de tableau 3-3 Installation de composants, boîte de dialogue 1-16 instances 4-2 INTAComponent 14-12 INTAServices 14-5, 14-6–14-7, 14-16 interfaces 2-4, 13-2, 13-3 API Tools 14-1, 14-4 numéros de version 14-10–14-11 conception 2-7 éléments de programme non visuels 1-5 exécution 2-6 propriétés 3-11 propriétés, déclaration 13-4 interfaces d’exécution 2-6 interfaces de conception 2-7 interfaces du composant création 13-3 propriétés, déclaration 13-4 Invalidate, méthode 10-9 IOTAActionServices 14-5 IOTABreakpointNotifier 14-16 IOTACodeCompletionServices 14-5 IOTAComponent 14-12 IOTACreator 14-13 IOTADebuggerNotifier 14-16 IOTADebuggerServices 14-5 IOTAEditLineNotifier 14-16 IOTAEditor 14-12 IOTAEditorNotifier 14-17 IOTAEditorServices 14-6 IOTAFile 14-13, 14-15 IOTAFormNotifier 14-17 IOTAFormWizard 14-3 IOTAIDENotifier 14-17 IOTAKeyBindingServices 14-6 IOTAKeyboardDiagnostics 14-6 IOTAKeyboardServices 14-6 IOTAMenuWizard 14-3 IOTAMessageNotifier 14-17 IOTAMessageServices 14-6 IOTAModule 14-11 IOTAModuleNotifier 14-17 IOTAModuleServices 14-6, 14-11 IOTANotifier 14-16 IOTAPackageServices 14-6 IOTAProcessModNotifier 14-17 IOTAProcessNotifier 14-17 IOTAProjectWizard 14-3

IOTAServices 14-6 IOTAThreadNotifier 14-17 IOTAToDoServices 14-6 IOTAToolsFilter 14-6 IOTAToolsFilterNotifier 14-17 IOTAWizard 14-2, 14-3 IOTAWizardServices 14-6

K K, notes de bas de page (système d’aide) 8-5 KeyDown, méthode 7-14, 12-11 KeyPress, méthode 7-14 KeyString, méthode 7-14 KeyUp, méthode 7-14

L lecture des paramètres de propriété 3-7 lecture seule, propriétés 2-6, 2-7, 3-7, 12-3 libellés 1-4 libération des ressources 13-5 liens de données 12-5–12-8 initialisation 12-7 Lines, propriété 3-9 LineTo, méthode 6-3 listes de recherche (système d’aide) 8-5 Loaded, méthode 3-14 LoadFromFile (méthode), graphiques 6-5 LParam, paramètre 7-9 lParam, paramètre 7-2

M macros 7-4 MainWndProc, méthode 7-3 mémoire (gestion), méthodes dynamiques ou virtuelles 2-10 menus contextuels, ajout d’éléments 8-17–8-18 MESSAGE_HANDLER, macro 7-4 MESSAGE_MAP, macro 7-7 messages 7-1–7-8, 11-4 applications multithreads 7-10 déclarations de gestionnaires 7-4 décomposeur 7-2 définis par l’utilisateur 7-6, 7-8 définition 7-1, 7-2 enregistrement (types), déclaration 7-7 enregistrements 7-2, 7-4 envoi 7-8–7-10 identificateurs 7-6 interception 7-5 liés à la souris et au clavier 12-10 Linux Voir notifications système souris 12-10 touche 12-10 Windows 7-1–7-10 Index

I-5

messages de frappes de touches 4-6, 12-10 messages définis par l’utilisateur 7-6, 7-8 messages liés à la souris 12-10 messages souris 7-2, 12-10 métafichiers 6-4 méthodes 1-7, 5-1, 11-11 appel 4-6, 5-3, 10-4 attribution de nom 5-2 déclaration 5-4 dynamiques 2-10 public 5-3 statiques 2-8 virtuelles 2-9 dessin 10-9, 10-10 gestion des messages 7-2, 7-3, 7-5 gestionnaires d’événements 4-3, 4-5, 4-6 redéfinition 4-6 graphiques 6-3, 6-5, 6-7 palettes 6-6 héritées 4-7 initialisation 3-14 propriétés et 3-5–3-7, 5-1, 5-2, 10-4 protected 5-3 public 5-3 redéfinition 2-9, 7-4, 7-5, 7-7, 11-12 répartition 2-8 virtuelles 2-9, 5-4 méthodes dynamiques 2-10 méthodes statiques 2-8 modèles de composants 2-2 Modified, méthode 12-13 modules 1-12 API Tools 14-3, 14-11–14-12 mois, renvoyer actuel 11-8 Month, propriété 11-6 mots clés 8-5 protected 4-6 MouseDown, méthode 7-14, 12-10 MouseMove, méthode 7-14 MouseUp, méthode 7-14 MoveTo, méthode 6-3 Msg, paramètre 7-3 MyEvent_ID, type 7-16

N nombres 3-2 valeurs de propriété 3-13 notificateurs 14-3 API Tools 14-16–14-20 écriture 14-19 notification, événements 4-8 notifications système 7-10–7-16 notifications système Linux 7-10–7-16 Nouveau, commande 1-12 Nouvelle unité, commande 1-12 I-6

Guide du concepteur de composants

O objets ayant un propriétaire 10-5–10-8 initialisation 10-7 instanciation 4-2 temporaires 6-7 objets ayant un propriétaire 10-5–10-8 initialisation 10-7 OnChange, événement 6-7, 10-8, 11-13, 12-13 OnClick, événement 4-2, 4-3, 4-5 OnCreate, événement 1-17 OnDataChange, événement 12-8, 12-12 OnDblClick, événement 4-5 OnDragDrop, événement 4-5 OnDragOver, événement 4-5 OnEndDrag, événement 4-5 OnEnter, événement 4-5 OnExit, événement 12-14 OnKeyDown, événement 4-5, 7-13, 12-11 OnKeyPress, événement 4-5, 7-13 OnKeyString, événement 7-13 OnKeyUp, événement 4-5, 7-13 OnMouseDown, événement 4-5, 7-13, 12-10 OnMouseMove, événement 4-5, 7-13 OnMouseUp, événement 4-5, 7-13 opérations avec effet 6-7 optimisation des ressources système 1-4 outils de dessin 6-2, 6-8, 10-6 changement 10-8 outils natifs de l’API 14-6–14-10 override, directive 2-9, 7-4 Owner, propriété 1-18

P Paint, méthode 6-7, 10-9, 10-10 PaintRequest, méthode 7-14 palette des composants ajout de bitmaps personnalisés 1-14 ajout de composants 8-1 déplacement des composants 1-16 PaletteChanged, méthode 6-6, 7-15 palettes 6-5–6-6 comportement par défaut 6-6 spécification 6-6 paquets 8-20 composants 8-20 liste Contains 8-20 liste Requires 8-20 par défaut classe ancêtre 2-4 gestionnaires événements 4-10 message 7-3 redéfinition 4-10

valeurs de propriété 3-8 changement 9-2, 9-3 spécification 3-13 paramètres classes comme 2-10 gestionnaires d’événements 4-3, 4-8, 4-9, 4-10 messages 7-2, 7-3, 7-4, 7-7, 7-9, 7-10 paramètres de propriété 3-7 propriétés de tableaux 3-9 paramètres de propriété écriture 3-9 lecture 3-9 Parent, propriété 1-18 Pen, propriété 6-3 Perform, méthode 7-9 personnalisation des composants 3-2 picture, objets 6-4 pinceaux 10-6 changement 10-8 Pixel, propriété 6-3 pointeurs classes 2-10 méthode 4-2, 4-3, 4-9 valeurs de propriété par défaut 3-13 pointeurs de classe 2-10 pointeurs de méthode 4-2, 4-3, 4-9 PostMessage, méthode 7-10 Presse-papiers (formats), ajout 8-16, 8-19 private, propriétés 3-5 procédures 1-7, 4-3 attribution de nom 5-2 paramètres de propriété 8-12 programmation orientée objet 2-1–2-10 déclarations 2-3, 2-10 classes 2-6, 2-7 méthodes 2-8, 2-9, 2-10 propriétés 3-1–3-14 accès 3-5–3-7 actualisation 1-7 boîtes de dialogue standard 13-2 changement 8-7–8-13, 9-2, 9-3 chargement 3-14 comme classes 3-3 composants enveloppe 13-4 déclaration 3-3, 3-4–3-7, 3-8, 3-14, 4-9, 10-4 stored 3-14 types définis par l’utilisateur 10-3 écriture de valeurs 3-7, 8-9 écriture seule 3-7 événements et 4-1, 4-2 fourniture d’aide 8-4 héritées 3-3, 10-2, 11-3 interfaces 3-11 lecture des valeurs 8-9 lecture et écriture 3-5

lecture seule 2-6, 2-7, 3-7, 12-3 modification sous forme textuelle 8-8 nodefault 3-8 présentation 1-6 publiées 11-3 redéclaration 3-13, 4-6 sous-composants 3-10 spécification des valeurs 3-13, 8-9 stockage 3-14 stockage de données interne 3-4, 3-7 stockage et chargement des propriétés non publiées 3-15–3-17 tableau 3-3, 3-9 types 3-2, 3-9, 8-9, 10-3 valeurs par défaut 3-8, 3-13 redéfinition 9-2, 9-3 visualisation 8-9 protected directive 4-6 mot clé 3-3, 4-6 protégés événements 4-6 public directive 4-6 mot clé 4-6 partie de classes 2-6 propriétés 3-13 publiées propriétés exemple 10-2, 11-3 published 3-3 directive 3-3, 4-6, 13-4 mot clé 4-6 partie de classes 2-7 propriétés 3-13, 3-14

Q QApplication_postEvent, méthode 7-16 QCustomEvent_create, fonction 7-16 QEvent 7-13 QKeyEvent 7-13 QMouseEvent 7-13 Qt (événements), messages 7-16

R read, méthode 3-7 read, mot réservé 3-9, 10-4 ReadOnly, propriété 12-4, 12-9, 12-11 réalisation de palettes 6-5, 6-6 recensement composants 1-13 éditeurs de composants 8-20 éditeurs de propriété 8-12–8-13 Rectangle, méthode 6-3

Index

I-7

rectangles, dessin 10-10 redéfinition des méthodes 2-9, 7-4, 7-5, 11-12 redessin des contrôles 10-8, 10-9, 11-4, 11-5 redessiner les images 6-7 redimensionnement des contrôles 11-4 Register, procédure 1-13, 8-2 RegisterComponents, procédure 1-14, 8-2 RegisterPropertyEditor, procédure 8-12 Requires, liste (paquets) 8-20 .res, fichiers 1-16 ressources 1-8, 6-1 en mémoire cache 6-2 libération 13-5 système, optimisation 1-4 ressources en mémoire cache 6-2 ressources système, préservation 1-4 Result, paramètre 7-7 RTTI 2-7

S SaveToFile (méthode), graphiques 6-5 SelectCell, méthode 11-14, 12-4 Self, paramètre 1-18 SendMessage, méthode 7-10 services, API Tools 14-2, 14-5–14-12 SetFloatValue, méthode 8-9 SetMethodValue, méthode 8-9 SetOrdValue, méthode 8-9 SetStrValue, méthode 8-9 SetValue, méthode 8-9 ShowHintChanged, méthode 7-15 signaux, réponse aux signaux (CLX) 7-11–7-12 simple, types 3-2 sous-classement des contrôles Windows 1-5 sous-composants, propriétés 3-10 stored, directive 3-14 StretchDraw, méthode 6-3, 6-7 StyleChanged, méthode 7-15 systèmes d’aide 8-4 fichiers 8-4 mots clés 8-5

T tableaux 3-3, 3-9 TabStopChanged, méthode 7-15 Tapplication, événements système 7-13 TBitmap 6-4 TCalendar 11-1 TCharProperty, type 8-8 TClassProperty, type 8-8 TColorProperty, type 8-8 TComponent 1-5 TComponentProperty, type 8-8 TControl 1-4, 4-5, 4-6 I-8

Guide du concepteur de composants

TCustomControl 1-4 TCustomGrid 11-1, 11-3 TCustomListBox 1-4 TDateTime, type 11-6 TDefaultEditor 8-17 temporaires, objets 6-7 TEnumProperty, type 8-8 test composants 1-17, 1-19, 13-6–13-7 valeurs 3-7 TextChanged, méthode 7-15 TextHeight, méthode 6-3 TextOut, méthode 6-3 TextRect, méthode 6-3 TextWidth, méthode 6-3 TFieldDataLink 12-5 TFloatProperty, type 8-8 TFontNameProperty, type 8-8 TFontProperty, type 8-8 TGraphic 6-4 TGraphicControl 1-4, 10-2 THandleComponent 7-12 TIcon 6-4 TiledDraw, méthode 6-7 TIntegerProperty, type 8-7, 8-9 TKeyPressEvent, type 4-4 TLabel 1-4 TListBox 1-4 TMessage 7-5, 7-7 TMetafile 6-4 TMethod, type 7-12 TMethodProperty, type 8-8 TNotifyEvent 4-8 TObject 2-4 ToolsAPI, unité 14-2 TOrdinalProperty, type 8-7 TPicture, type 6-4 TPropertyAttributes 8-11 TPropertyEditor, classe 8-7 transfert d’enregistrements 13-2 try, mot réservé 6-7, 13-5 TSetElementProperty, type 8-8 TSetProperty, type 8-8 TStringProperty, type 8-8 TWinControl 1-4, 4-5 types définis par l’utilisateur 10-3 enregistrement de message 7-7 propriétés 3-2, 3-9, 8-9 types définis par l’utilisateur 10-3 types énumérés 3-2, 10-3

U unités, ajout de composants 1-13 UpdateCalendar, méthode 12-4

V valeurs 3-2 booléennes 3-2, 3-13, 12-5 propriété par défaut 3-8, 3-13 redéfinition 9-2, 9-3 test 3-7 valeurs booléennes 3-2, 3-13, 12-5 var (mot réservé), gestionnaires d’événements 4-4 VCL 1-1–1-2 virtual directive 2-9 virtuelle tables de méthode 2-9 virtuelles méthodes 2-9, 5-4 éditeurs de propriété 8-8–8-10 propriétés comme 3-2 VisibleChanged, méthode 7-15

W

contextes de périphériques 1-8, 6-2 contrôles, sous-classement 1-5 événements 4-5 fonctions API 1-4, 6-1 messages 7-1–7-10 WM_APP, constante 7-6 WM_KEYDOWN, message 12-10 WM_LBUTTONBUTTON, message 12-10 WM_MBUTTONDOWN, message 12-10 WM_PAINT, message 7-4 WM_RBUTTONDOWN, message 12-10 WM_SIZE, message 11-4 WndProc, méthode 7-3, 7-5 WordWrap, propriété 9-1 WParam, paramètre 7-9 wParam, paramètre 7-2 write, méthode 3-7 write, mot réservé 3-9, 10-4

Y Year, propriété 11-6

WidgetDestroyed, propriété 7-14 Windows boîtes de dialogue standard 13-2 création 13-2 exécution 13-5

Index

I-9

I-10

Guide du concepteur de composants

View more...

Comments

Copyright © 2017 KUPDF Inc.
SUPPORT KUPDF