5.5 Les événements avec Delphi



Plan du chapitre:
 
1. Programmes événementiels avec Delphi
1.1 Pointeur de méthode
1.2 Affecter un pointeur de méthode
1.3 Un événement est un pointeur de méthode
1.4 Quel est le code engendré

2. Exercice-récapitulatif

2.1 Objectif de réalisation
2.2 Réalisation de l’implantation en Delphi
2.3 Améliorations de sécurité du premier niveau par plan d’action
2.4 Améliorations de sécurité du second niveau par filtrage
2.5 Améliorations de sécurité du second niveau par exceptions

Conclusion

Notice méthodologique pour créer un nouvel événement



1. Programmes événementiels avec Delphi

Delphi comme les autres RAD événementiels permet de construire du code qui  est exécuté en réponse à des événements. Un événement Delphi est une propriété d'un type spécial que nous allons examiner plus loin.

Le code de réaction à un événement particulier est une méthode qui s'appelle un gestionnaire de cet événement.

Un événement est en fait un pointeur vers la méthode qui est chargée de le gérer. Définissons la notion de pointeur de méthode qui est utilisée ici, notion largement utilisée en général dans les langages objets.


1.1 Pointeur de méthode

Un pointeur de méthode est  une paire de pointeurs (adresses mémoire), le premier contient l'adresse d'une méthode et le second une référence à l'objet auquel appartient la méthode.



Schéma ci-après d'un objet Obj1 de classe clA contenant un champ du type pointeur vers la méthode meth1 de l'objet Obj2 de classe clB :


 

Pointeur de méthode en  Delphi

Pour pointer la méthode d'une instance d'objet en Delphi,  nous devons déclarer un nouveau  type auquel nous ajoutons à la fin le qualificateur of object .


Exemples de types pointeurs de méthode et de variable de type pointeur de méthode :

type
  
  
ptrMethode1  procedure of  object ;
  
ptrMethode2  procedure  (x  real of  object ;
  
ptrMethode3  procedure  (x,y integer ; var  z :char of  object ;
  
 var
  
Proc1  ptrMethode1 ;   // pointeur vers une méthode sans paramètre
  
Proc2  ptrMethode2 // pointeur vers une méthode è un  paramètre real

Schéma ci-après d'un objet Obj1 de classe clA contenant un champ proc1 du type ptrMethode1 qui pointe vers la meth1 de l'objet Obj2 de classe clB. On suppose que l'adresse mémoire de l'objet est 14785 et l'adresse de la méthode meth1 de l'objet est 14792 :



Il est impératif que la méthode vers laquelle pointe la variable de pointeur de méthode soit du type prévu par le type pointeur de méthode, ici la méthode meth1 doit obligatoirement être une procédure sans paramètre (compatibilité d'en-tête).

Schéma ci-après d'un objet Obj1 de classe clA contenant un champ  proc2 du type ptrMethode2 qui pointe vers la meth2 de l'objet Obj2 de classe clB. On suppose que l'adresse mémoire de l'objet est 14785 et l'adresse de la méthode meth2 de l'objet est 14805  :


La remarque précédente sur l'obligation de compatibilité de l'en-tête de la méthode et le type pointeur de méthode implique que la méthode meth2 doit nécessairement être une procédure à un seul paramètre real.


1.2 Affecter un pointeur de méthode

Nous venons de voir comment déclarer un type pointeur de méthode et un champ de ce même type, nous avons signalé que ce champ doit pointer vers une méthode ayant une en-tête compatible avec le type pointeur de méthode, il nous reste à connaître le mécanisme qu'utilise Delphi pour lier un champ pointeur et une méthode à pointer.

 Types pointeurs de méthodes

  ptrMethode1 = procedure of object;
  ptrMethode2 = procedure (x : real) of object;


champs de pointeurs de méthodes

  Proc1 : ptrMethode1;  // pointeur vers une méthode sans paramètre
  Proc2 : ptrMethode2; // pointeur vers une méthode à un  paramètre real


Diverses méthodes :

procedure P1;
begin

end;
procedure P3 (x:char);
begin

end;
procedure P2 (x:real);
begin

end;
procedure P4;
begin

end;


Recensons d'abord les compatibilités d'en-tête qui autoriseront le pointage de la méthode :
Proc1  peut pointer vers P1 , P4 qui sont les deux seules méthodes compatibles.
Proc2 ne peut pointer que vers P2 qui est la seule méthode à un paramètre de type real.

La liaison (le pointage) s'effectue tout naturellement à travers une affectation :
L'affectation Proc1 := P1; indique que Proc1 pointe maintenant vers la méthode P1 et peut être utilisé comme un identificateur de procédure ayant la même signature que P1.

Exemple d'utilisation :

Proc2 := P2; // liaison du pointeur et de la procédure P2
Proc2(45.8); // appel de la procédure vers laquelle Proc2 pointe avec passage du paramètre 45.8


1.3 Un événement est un pointeur de méthode

Nous avons indiqué que les gestionnaires d'événements sont des méthodes, les champs du genre événements présents dans les classes Delphi sont en fait des pointeurs de méthode, qui peuvent pointer vers des gestionnaires d'événements.

 
Un type d'événement est donc un type  pointeur de méthode, Delphi possède plusieurs types d'événements par exemple :

     TNotifyEvent  procedure  ( Sender TObject)  of  object ;
    
TMouseMoveEvent  procedure  ( Sender TObject Shift TShiftState X, Y Integer of  object ;
    
TKeyPressEvent  procedure  ( Sender TObject ; var  Key Char of  object ;
   etc …

Un événement est donc une propriété de type pointeur de méthode (type événement) :
 
  property  OnClick TNotifyEvent ;            // événement click de souris
 
property  OnMouseMove TMouseMoveEvent // événement passage de la souris
 
property  OnKeyPress TKeyPressEvent // événement touche de clavier pressée



1.4 Quel est le code engendré pour gérer un événement ?

Intéressons nous maintenant au code engendré par un programme simple constitué d'une fiche Form1 de classe TForm1 avec un objet Button1 de classe Tbutton déposé sur la fiche :

Le code source apparent  fournit au programmeur. Il permet l'intervention du programmeur sur la fiche et sur ses composants.
Le code intermédiaire caché au programmeur. Il permet l'initialisation automatique de la fiche et de ses composants.


unit  Unit1 ;
 interface
 uses
  
Windows, Messages, SysUtils, Variants, Classes,
  Graphics, Controls, Forms,   Dialogs, StdCtrls
;
  
 type
  
TForm1  class (TForm)
  Button1
TButton ;
 private
 
{ Déclarations privées }
 
public
 
{ Déclarations publiques }
end;

 var   
Form1 TForm1 ;
  
implementation
 
{$R *.dfm}
end.

object  Form1 TForm1
Left 
198
Top 
109
Width 
245
Height 
130
Caption 
'Form1'
Color  clBtnFace
Font.Charset 
DEFAULT_CHARSET
Font.Color 
clWindowText
Font.Height 
= - 11
Font.Name 
'MS Sans Serif'
Font.Style  [ ]
OldCreateOrder 
False
PixelsPerInch 
96
TextHeight 
13
object  Button1 TButton
Left  80
Top  32
Width  75
Height  25
Caption  'Button1'
TabOrder  0
end
end.


Demandons à Delphi de nous fournir (à partir de l'inspecteur d'objet) les gestionnaires des 3 événements OnClick, OnMouseMove et OnKeyPress de réaction de l'objet Button1 au click de souris, au passage de la souris et à l'appui sur une touche du clavier:





Delphi engendre un code source visible en pascal objet modifiable et un code intermédiaire (que l'on peut voir et éventuellement modifier) :



Nous venons de voir que Delphi a généré en code intermédiaire pour nous, les affectations de chaque événement à un gestionnaire :

    OnClick = Button1Click
    OnKeyPress = Button1KeyPress
    OnMouseMove = Button1MouseMove

Et il nous a fournit les squelettes vides de chacun des trois gestionnaires :

procedure TForm1.Button1Click (Sender: TObject); …
procedure TForm1.Button1MouseMove (Sender: TObject;  Shift:  TShiftState; X,Y: Integer); …
procedure TForm1.Button1KeyPress (Sender: TObject;    var Key: Char); …


La dernière étape du processus de programmation de la réaction du Button1 est de programmer du code à l'intérieur des squelettes des gestionnaires.

Lors de l'exécution si nous cliquons avec la souris sur le Button1, un mécanisme d'interception et de répartition figuré ci-dessous appelle le gestionnaire de l'événément OnClick dont le corps a été programmé :

               fig : Appel du gestionnaire procedure TForm1.Button1Click (Sender: TObject) sur click de souris



2. Exercice-récapitulatif

 

2.1 Objectif de réalisation
 
 
Nous voulons faire saisir par l’utilisateur deux entiers et l’autoriser à effectuer leur somme et leur produit uniquement lorsque les deux entiers sont entrés. Aucune sécurité n’est apportée pour l’instant sur les données.

       
      Voici notre interface au départ :  

      Dès que les deux entiers sont entrés, le bouton calcul est activé:
       

       

      Lorsque l’on clique sur le bouton calcul, le bouton effacer est activé et les résultats apparaissent dans leurs zones :
       


       

      Un clic sur le bouton effacer ramène l’interface à l’état initial.


     

    2.2 Réalisation de l’implantation en Delphi

    Un graphe événementiel complet de notre interface pourrait être :


Avec comme conventions sur les messages :

X1 = exécuter les calculs sur les valeurs entrées.

X2 = exécuter l’effacement des résultats.

M1 = message à l’edit1 de tout effacer.

M2 = message à l’edit2 de tout effacer.

Changer
Edit1
Calcul activable
(si changer Edit2 a eu lieu)
Changer
EDIT2
Calcul activable
(si changer Edit1 a eu lieu)
Clic
CALCUL
Exécuter X1
EFFACER activable
Afficher les résultats
Clic
EFFACER
EFFACER désactivé 
CALCUL désactivé
Message M1
Message M2
Edit1 activé
Edit2 activé
Bcalcul désactivé
Beffacer désactivé
 
Implantation :
 
ButtonCalcul: TButton;


Buttoneffacer: TButton;


Edit1: TEdit;
Edit2: TEdit;
LabelSomme: TLabel;
LabelProduit: TLabel;
   


Edit1 activé
Edit2 activé
Bcalcul désactivé
Beffacer désactivé
procedure  TForm1.Autorise ( Ed  TEdit  ; var  flag  boolean  ) ;
begin
   if  Ed.text < ' ' then  // champ text non vide ok !
    begin
     flag  := true ;
     TestEntrees ;
   end
   else
   begin
     ButtonCalcul.enabled := false //bouton désactivé
     flag := false // drapeau de Ed baissé
    end
end;
procedure  TForm1.Edit1Change( Sender TObject) ;
begin
    Autorise ( Edit1 , Som_ok ) ;
end;
procedure  tform1.edit2change( sender tobject) ;
begin
    Autorise ( Edit2 , Prod_ok ) ;
end;
unit  UFcalcul ;
 
 interface
 
 uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,   StdCtrls, ExtCtrls ;
  
 type
   TForm1 class  ( TForm )
  Edit1 TEdit ;
   Edit2 TEdit ;
   ButtonCalcul TButton ;
   LabelSomme TLabel ;
   LabelProduit TLabel ;
   Buttoneffacer TButton ;
  procedure  FormCreate( Sender TObject) ;
  procedure  Edit1Change( Sender TObject) ;
  procedure  Edit2Change( Sender TObject) ;
  procedure  ButtonCalculClick( Sender TObject) ;
  procedure  ButtoneffacerClick( Sender TObject) ;
 private     { Déclarations privées }
   Som_ok , Prod_ok  :
   procedure  TestEntrees ;
  procedure  RAZTout ;
  procedure  Autorise ( Ed  TEdit  ; var  flag  boolean  ) ;
 public     { Déclarations publiques }
end;

implementation
{ --------------- Méthodes privées ---------------------}
  procedure  TForm1.TestEntrees ;
  {les drapeaux sont-ils levés tous les deux ?}
  begin
  if   Prod_ok  and  Som_ok  then
    ButtonCalcul.Enabled := true   //  si oui: le bouton calcul est activé
  end;
 
 procedure  TForm1.RAZTout ;
 begin 
   Buttoneffacer.Enabled := false //le bouton effacer se désactive
   ButtonCalcul.Enabled := false //le bouton calcul se désactive
   LabelSomme.caption := '0' // RAZ valeur somme affichée
   LabelProduit.caption := '0' // RAZ valeur produit affichée
   Edit1.clear // message M1
   Edit2.clear // message M2
   Prod_ok := false // RAZ drapeau Edit2
   Som_ok := false // RAZ drapeau Edit1
  end;
 
 procedure  TForm1.Autorise ( Ed  TEdit  ; var  flag  boolean  ) ;
 begin
  if  Ed.text < ' ' then  // champ text non vide ok !
   begin
    flag  := true ;
    TestEntrees ;
  end
  else
  begin
    ButtonCalcul.enabled := false //bouton désactivé
    flag := false // drapeau de Ed baissé
   end
 end;
 
  { ------------------- Gestionnaires d'événements -------------------}
  procedure  TForm1.FormCreate( Sender TObject) ;
 begin
   RAZTout ;
 end;
 
 procedure  TForm1.Edit1Change( Sender TObject) ;
 begin
   Autorise ( Edit1 , Som_ok ) ;
 end;
 
 procedure  TForm1.Edit2Change( Sender TObject) ;
 begin
   Autorise ( Edit2 , Prod_ok ) ;
 end;
 
 procedure  TForm1.ButtonCalculClick( Sender TObject) ;
 var  S , P  integer ;
 begin
   S := strtoint(Edit1.text) // transtypage : string è integer
   P := strtoint(Edit2.text) // transtypage : string è integer
   LabelSomme.caption := inttostr(P + S) ;
   LabelProduit.caption := inttostr(P * S) ;
   Buttoneffacer.Enabled := true  // le bouton effacer est activé
  end;
 
 procedure  TForm1.ButtoneffacerClick( Sender TObject) ;
 begin
   RAZTout ;
 end;
 
end.
procedure  TForm1.Edit1Change( Sender TObject) ;
begin
    Autorise ( Edit1 , Som_ok ) ;
end;
procedure  TForm1.Edit2change(  Sender tobject) ;
begin
    Autorise ( Edit2 , Prod_ok ) ;
end;
procedure  TForm1.TexteChange( Sender TObject) ;
  begin
   if   Sender is  TEdit  then
   begin
    if  ( Sender as  TEdit ) = Edit1  then
      Autorise ( ( Sender as  TEdit ) , Som_ok ) ;
    else
      Autorise ( ( Sender as  TEdit ) , Prod_ok ) ;
   end
  end;    
procedure  TForm1.FormCreate( Sender TObject) ;
begin
    RAZTout ;
    Edit1.OnChange  :=  TexteChange  ;
    Edit2.OnChange  :=  TexteChange  ;
end;   
procedure  TForm1.FormCreate( Sender TObject) ;
  begin
    RAZTout ;
    Edit1.OnChange  :=  TexteChange  ;
    Edit2.OnChange  :=  TexteChange  ;
  end;