7.4. Créer un nouvel événement
( texte extrait et composé à partirdu manuel guide du développeur Delphi, Borland Ó)
Plan du chapitre:1. Annexe : éléments de construction proposés par Borland
1.1 Définition de vos propres événements2. Exemple de création d'événements dans un TEdit
1.2 Déclenchement de l’événement
1.3 Deux sortes d’événements
1.4 Définition du type de gestionnaire
1.5 Notifications simples
1.6 Gestionnaires d’événements spécifiques
1.7 Renvoi d’informations à partir du gestionnaire
1.8 Déclaration de l’événement
1.9 Les noms d’événement débutent par “On”
1.10 Appel de l’événement
1.11 Les gestionnaires vides doivent être valides
1.12 Les utilisateurs peuvent surcharger la gestion par défaut
2.1 Evènement OnColorChange
2.2 Evènement OnResize
1. Annexe : éléments de construction proposés par Borland
1.1 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
1.2 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.
Les deux sortes d’occurrences pour lesquelles vous pouvez être amené à définir des événements sont 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.
1.4 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.
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.
1.6 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.
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.
1.7 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.
1.8 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.
1.9 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.
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.
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 surchargeant 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 surcharger la gestion par défaut.
1.11 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};En effet dans la partie associée au else, il se pourrait que la gestion par défaut s'exécute avec une référence OnClick = nil, ce qui engendrerait des possibilités d'erreurs.
1.12 Les utilisateurs peuvent surcharger 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.
2. Exemple de création de deux nouveaux événements dans un TEdit
Mise en oeuvre sur l'adjonction de 2 évènements nouveaux à un composant déjà existant.
Enoncé
- un événement OnColorChange qui permet de notifier à l'utilisateur un changement dans la couleur d'un TEdit et de programmer éventuellement la gestion de cet événement.
- un événement OnResize qui permet de signaler que le TEdit vient de voir changer sa taille (l'une de ses deux propriétés width ou height).
2.1.1 Déclenchement de l'événement
Nous optons pour l'interception
du message système WM_PAINT qui est envoyé très regulièrement
à la fenêtre d'un contrôle afin qu'il se redessine.
procedure WMPaintChgColor(var Msg:TWMpaint); message WM_PAINT; |
2.1.2 Définition du gestionnaire
Nous construisons un
gestionnaire d'événements spécifiques, car nous souhaitons
disposer en paramètre de la nouvelle couleur.
TEventColor = procedure (Sender: TObject; couleur:Tcolor) of object; |
2.1.3 Déclaration de l'événement
private
FColorChange : TEventColor; |
2.1.4 Propriété d'accès
à l'événement
published |
2.1.5 Appel de l'événement
par une procédure centralisatrice
protected |
2.1.6 Le gestionnaire vide de l'événement
OnColorChange doit être valide
if Assigned(FColorChange) then |
L'événement
OnResize a été construit de la même
manière que l'événement OnColorChange, toutefois à
titre d'exemple il n'y a pas de procédure surchargeable centralisatrice
du genre procedure ColorChange. Dans ce cas le programmeur ne
pourra pas rajouter un comportement spécifique sur l'événement
OnResize. Nous ne répètons pas la démarche,
mais nous signalons dans le code ci-dessous les éléments associés
à l'évènement OnResize en
caractères violets et en bleu le code d'enregistrement du nouveau composant dans l'onglet "Les Exemples" de la palette.
Ci-dessous, nous décrivons le petit composant simple dérivé des TEdit :
type
TEventColor =
procedure (Sender: TObject; couleur:Tcolor) of object;
{ pointeur de gestionnaire
d'événement spécifique OnColorChange (paramètre
couleur : TColor, nécessaire ici }
TEditColorResize
= class(TEdit)
private
FColorChange
: TEventColor;
{ pointeur de
méthode spécifique permettant utiliser la property OnColorChange
}
ColorPrec:Tcolor;
// la couleur précédente
FOnResize:TNotifyEvent;
{ pointeur de méthode
permettant utiliser la property OnResize }
procedure
WMPaintChgColor(var Msg:TWMpaint); message WM_PAINT;
{le message WM_PAINT
qui permettra de matérialiser l'événement}
procedure
WMSizeRedim(var Msg:TWMsize); message WM_size;
{le message WM_size
est envoyé au TEdit dès son height ou son width a changé
}
protected
procedure
ColorChange(coul:TColor);dynamic;
{pour surcharge ultérieure
par le programmeur }
public
constructor
Create(AOwner: TComponent); override;
published
//nouveau gestionnaire d'événement
property
OnColorChange : TEventColor read FColorChange write FColorChange;
property OnResize:TNotifyEvent read FOnResize
writeFOnResize;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Les Exemples', [TEditColor3]);
end;
//------------------------------------------------------------//
constructor TEditColorResize.Create(AOwner:
TComponent);
begin
inherited;
color:=clwhite;
ColorPrec:=self.color
// initialisation à la couleur actuelle
end;
procedure TEditColorResize.WMSizeRedim(var Msg:TWMsize);
// intercepte le message WM_size et traite le changement de taille
begin
if Assigned(OnResize) then
FOnResize(self)//lien avec le futur gestionnaire du programmeur
end;
procedure TEditColorResize.WMPaintChgColor(var
Msg:TWMpaint);
// intercepte le message WM_Paint et traite le changement de couleur
begin
inherited
;
if color<>ColorPrec
then
begin
ColorChange(ColorPrec);
ColorPrec:=color;
change // appelle le gestionnaire d'événement
OnChange, s'il est défini.
end
end;
procedure TEditColorResize.ColorChange(coul:TColor);
// pour que le développeur puisse la surcharger éventuellement
begin
if Assigned(FColorChange) then
FColorChange(self,coul)
// lien avec le futur gestionnaire du programmeur
end;
end.
Enregistrez ce composant dans votre Delphi, puis testez-le sur les deux événements nouveaux en écrivant un projet de test et en programmant les deux gestionnaires d'événements.
Voici après dépôt d'un TEditColorResize ce que l'inspecteur d'objet nous montre sur cette classe:
toutes les propriétés
sont celles des TEdit
Nous avons ajouté
2 nouveaux événements (OnColorChange et OnResize)
Nous montrons ci-dessous comment un développeur utilisant notre composant TEditColorResize peut surcharger et implanter un comportement additionnel à l'événement OnColorChange.
interface
TEditUser = class(TEditColorResize)
protected
procedure
ColorChange(coul:TColor);override;
end;
implementation
procedure TEditUser.ColorChange(coul:TColor);
begin
inherited;// appel à la procédure centralisée
de la classe ancêtre
//...comportement nouveau rajouté par le programmeur
end;