Apprentissage du langage C#


précédentsommairesuivant

IX. Evénements utilisateur

Nous avons dans le chapitre précédent abordé la notion d'événements liés à des composants de formulaire. Nous voyons maintenant comment créer des événements dans nos propres classes.

IX-A. Objets delegate prédéfinis

La notion d'objet delegate a été rencontrée dans le chapitre précédent mais elle avait été alors survolée. Lorsque nous avions regardé comment les gestionnaires des événements des composants d'un formulaire étaient déclarés, nous avions rencontré du code similaire au suivant :

 
CacherSélectionnez

buttonAfficher était un composant de type [Button]. Cette classe a un champ Click défini comme suit :

Image non disponible
  • [1] : la classe [Button]
  • [2] : ses événements
  • [3,4] : l'événement Click

Le delegate EventHandler est défini comme suit :

Image non disponible

Le delegate EventHandler désigne un modèle de méthode :

  • ayant pour 1er paramètre un type Object
  • ayant pour 2ième paramètre un type EventArgs
  • ne rendant aucun résultat

Une méthode correspondant au modèle défini par EventHandler pourrait être la suivante :

 
CacherSélectionnez

Pour créer un objet de type EventHandler, on procède comme suit :

 
CacherSélectionnez

On pourra ainsi écrire :

 
CacherSélectionnez

Une variable de type delegate est en fait une liste de références sur des méthodes correspondant au modèle du delegate. Pour ajouter une nouvelle méthode M à la variable evtHandler ci-dessus, on utilise la syntaxe :

 
CacherSélectionnez

La notation += peut être utilisée même si evtHandler est une liste vide.

L'instruction :

 
CacherSélectionnez

ajoute une méthode de type EventHandler à la liste des méthodes de l'événement buttonAfficher.Click. Lorsque l'événement Click sur le composant buttonAfficher se produit, VB exécute l'instruction :

 
CacherSélectionnez

où :

  • source est le composant de type object à l'origine de l'événement
  • evt de type EventArgs et ne contient pas d'information

Toutes les méthodes de signature void M(object,EventArgs) qui ont été associées à l'événement Click par :

 
CacherSélectionnez

seront appelées avec les paramètres (source, evt) transmis par VB.

IX-B. Définir des objets delegate

L'instruction

 
CacherSélectionnez

définit un type appelé Opération qui représente un prototype de fonction acceptant deux entiers et rendant un entier. C'est le mot clé delegate qui fait de Opération une définition de prototype de fonction.

Une variable op de type Opération aura pour rôle d'enregistrer une liste de fonctions correspondant au prototype Opération :

int f1(int,int)
int f2(int,int)

int fn(int,int)

L'enregistrement d'une méthode fi dans la variable op se fait par op=new Opération(fi) ou plus simplement par op=fi. Pour ajouter une méthode fj à la liste des fonctions déjà enregistrées, on écrit op+= fj. Pour enlever une méthode fk déjà enregistrée on écrit op-=fk. Si dans notre exemple on écrit n=op(n1,n2), l'ensemble des méthodes enregistrées dans la variable op seront exécutées avec les paramètres n1 et n2. Le résultat n récupéré sera celui de la dernière méthode exécutée. Il n'est pas possible d'obtenir les résultats produits par l'ensemble des méthodes. Pour cette raison, si on enregistre une liste de méthodes dans une fonction déléguée, celles-ci rendent le plus souvent un résultat de type void.

Considérons l'exemple suivant :

 
CacherSélectionnez
  • ligne 3 : définit une classe Class1.
  • ligne 6 : définition du delegate Opération : un prototype de méthodes acceptant deux paramètres de type int et rendant un résultat de type int
  • lignes 9-12 : la méthode d'instance Ajouter a la signature du delegate Opération.
  • lignes 14-17 : la méthode d'instance Soustraire a la signature du delegate Opération.
  • lignes 20-23 : la méthode de classe Augmenter a la signature du delegate Opération.
  • ligne 25 : la méthode Main exécutée
  • ligne 20 : la variable op est de type delegate Opération. Elle contiendra une liste de méthodes ayant la signature du type delegate Opération. On lui affecte une première référence de méthode, celle sur la méthode statique Class1.Augmenter.
  • ligne 31 : le delegate op est exécuté : ce sont toutes les méthodes référencées par op qui vont être exécutées. Elles le seront avec les paramètres passés au delegate op. Ici, seule la méthode statique Class1.Augmenter va être exécutée.
  • ligne 35 : une instance c1 de la classe Class1 est créée.
  • ligne 37 : la méthode d'instance c1.Ajouter est affectée au delegate op. Augmenter était une méthode statique, Ajouter est une méthode d'instance. On a voulu montrer que cela n'avait pas d'importance.
  • ligne 39 : le delegate op est exécuté : la méthode Ajouter va être exécutée avec les paramètres passés au delegate op.
  • ligne 42 : on refait de même avec la méthode d'instance Soustraire.
  • lignes 46-47 : on met les méthodes Ajouter et Soustraire dans le delegate op.
  • ligne 49 : le delegate op est exécuté : les deux méthodes Ajouter et Soustraire vont être exécutées avec les paramètres passés au delegate op.
  • ligne 51 : la méthode Soustraire est enlevée du delegate op.
  • ligne 53 : le delegate op est exécuté : la méthode restante Ajouter va être exécutée.

Les résultats de l'exécution sont les suivants :

 
CacherSélectionnez

IX-C. Delegates ou interfaces ?

Les notions de delegates et d'interfaces peuvent sembler assez proches et on peut se demander quelles sont exactement les différences entre ces deux notions. Prenons l'exemple suivant proche d'un exemple déjà étudié :

 
CacherSélectionnez

Ligne 20, la méthode Execute attend une référence sur un objet du type delegate Opération défini ligne 6. Cela permet de passer à la méthode Execute, différentes méthodes (lignes 26, 28, 32 et 36). Cette propriété de polymorphisme peut être obtenue également avec une interface :

 
CacherSélectionnez
  • lignes 6-8 : l'interface [IOperation] définit une méthode operation.
  • lignes 11-16 et 19-24 : les classes [Ajouter] et [Soustraire] implémentent l'interface [IOperation].
  • lignes 29-31 : la méthode Execute dont le 1er paramètre est du type de l'interface IOperation. La méthode Execute va recevoir successivement comme 1er paramètre, une instance de la classe Ajouter puis une instance de la classe Soustraire.

On retrouve bien l'aspect polymorphique qu'avait le paramètre de type delegate de l'exemple précédent. Les deux exemples montrent en même temps des différences entre ces deux notions.

Les types delegate et interface sont interchangeables

  • si l'interface n'a qu'une méthode. En effet, le type delegate est une enveloppe pour une unique méthode alors que l'interface peut, elle, définir plusieurs méthodes.
  • si l'aspect multicast du delegate n'est pas utilisé. Cette notion de multicast n'existe en effet pas dans l'interface.

Si ces deux conditions sont vérifiées, alors on a le choix entre les deux signatures suivantes pour la méthode Execute :

 
CacherSélectionnez

La seconde qui utilise le delegate peut se montrer plus souple d'utilisation. En effet dans la première signature, le premier paramètre de la méthode doit implémenter l'interface IOperation. Cela oblige à créer une classe pour y définir la méthode appelée à être passée en premier paramètre à la méthode Execute. Dans la seconde signature, toute méthode existante ayant la bonne signature fait l'affaire. Il n'y a pas de construction supplémentaire à faire.

IX-D. Gestion d'événements

Les objets delegate peuvent servir à définir des événements. Une classe C1 peut définir un événement evt de la façon suivante :

  • un type delegate est défini dans ou en-dehors de la classe C1 :
 
CacherSélectionnez
  • la classe C1 définit un champ de type delegate Evt :
 
CacherSélectionnez
  • lorsque une instance c1 de la classe C1 voudra signaler un événement, elle exécutera son delegate Evt1 en lui passant les paramètres définis par le delegate Evt. Toutes les méthodes enregistrées dans le delegate Evt1 seront alors exécutées avec ces paramètres. On peut dire qu'elles ont été averties de l'événement Evt1.
  • si un objet c2 utilisant un objet c1 veut être averti de l'occurrence de l'événement Evt1 sur l'objet c1, il enregistrera l'une de ses méthodes c2.M dans l'objet délégué c1.Evt1 de l'objet c1. Ainsi sa méthode c2.M sera exécutée à chaque fois que l'événement Evt1 se produira sur l'objet c1. Il pourra également de désinscrire lorsqu'il ne voudra plus être averti de l'événement.
  • comme l'objet délégué c1.Evt1 peut enregistrer plusieurs méthodes, différents objets ci pourront s'enregistrer auprès du délégué c1.Evt1 pour être prévenus de l'événement Evt1 sur c1.

Dans ce scénario, on a :

  • une classe qui signale un événement
  • des classes qui sont averties de cet événement. On dit qu'elles souscrivent à l'événement ou s'abonnent à l'événement.
  • un type delegate qui définit la signature des méthodes qui seront averties de l'événement

Le framework .NET définit :

  • une signature standard du delegate d'un événement
 
CacherSélectionnez
  • source : l'objet qui a signalé l'événement
  • evtInfo : un objet de type EventArgs ou dérivé qui apporte des informations sur l'événement
  • le nom du delegate doit être terminé par EventHandler
  • une façon standard pour déclarer un événement de type MyEventHandler dans une classe :
 
CacherSélectionnez

Le champ Evt1 est de type delegate. Le mot clé event est là pour restreindre les opérations qu'on peut faire sur lui :

  • de l'extérieur de la classe C1, seules les opérations += et -= sont possibles. Cela empêche la suppression (par erreur du développeur par exemple) des méthodes abonnées à l'événement. On peut simplement s'abonner (+=) ou se désabonner (-=) de l'événement.
  • seule une instance de type C1 peut exécuter l'appel Evt1(source,evtInfo) qui déclenche l'exécution des méthodes abonnées à l'événement Evt1.

Le framework .NET fournit une méthode générique satisfaisant à la signature du delegate d'un événement :

 
CacherSélectionnez
  • le delegate EventHandler utilise le type générique TEventArgs qui est le type de son 2ième paramètre
  • le type TEventArgs doit dériver du type EventsArgs (where TEventArgs : EventArgs)

Avec ce delegate générique, la déclaration d'un événement X dans la classe C suivra le schéma conseillé suivant :

  • définir un type XEventArgs dérivé de EventArgs pour encapsuler les informations sur l'événement X
  • définir dans la classe C un événement de type EventHandler<XEventArgs>.
  • définir dans la classe C une méthode protégée
 
CacherSélectionnez

destinée à "publier" l'événement X aux abonnés.

Considérons l'exemple suivant :

  • une classe Emetteur encapsule une température. Cette température est observée. Lorsque cette température dépasse un certain seuil, un événement doit être lancé. Nous appellerons cet événement TemperatureTropHaute. Les informations sur cet événement seront encapsulées dans un type TemperatureTropHauteEventArgs.
  • une classe Souscripteur s'abonne à l'événement précédent. Lorsqu'elle est avertie de l'événement, elle affiche un message sur la console.
  • un programme console crée un émetteur et deux abonnés. Il saisit les températures au clavier et les enregistre dans une instance Emetteur. Si celle-ci est trop haute, l'instance Emetteur publie l'événement TemperatureTropHaute.

Pour se conformer à la méthode conseillée de gestion des événements, nous définissons tout d'abord le type TemperatureTropHauteEventArgs pour encapsuler les informations sur l'événement :

 
CacherSélectionnez
  • ligne 6 : l'information encapsulée par la classe TemperatureTropHauteEventArgs est la température qui a provoqué l'événement TemperatureTropHaute.

La classe Emetteur est la suivante :

 
CacherSélectionnez
  • ligne 5 : le seuil de température au-delà duquel l'événement TemperatureTropHaute sera publié.
  • ligne 10 : l'émetteur a un nom pour être identifié
  • ligne 12 : l'événement TemperatureTropHaute.
  • lignes 15-26 : la méthode get qui rend la température et la méthode set qui l'enregistre. C'est la méthode set qui fait publier l'événement TemperatureTropHaute si la température à enregistrer dépasse le seuil de la ligne 5. Elle fait publier l'événement par la méthode OnTemperatureTropHauteHandler de la ligne 29 en lui passant pour paramètre un objet TemperatureTropHauteEventArgs dans lequel on a enregistré la température qui a dépassé le seuil.
  • lignes 29-32 : l'événement TemperatureTropHaute est publié avec pour 1er paramètre l'émetteur lui-même et pour second paramètre l'objet TemperatureTropHauteEventArgs reçu en paramètre.

La classe Souscripteur qui va s'abonner à l'événement TemperatureTropHaute est la suivante :

 
CacherSélectionnez
  • ligne 6 : chaque souscripteur est identifié par un nom.
  • lignes 9-12 : la méthode qui sera associée à l'événement TemperatureTropHaute. Elle a la signature du type delegate EventHandler<TEventArgs> qu'un gestionnaire d'événement doit avoir. La méthode affiche sur la console : le nom du souscripteur qui affiche le message, le nom de l'émetteur qui a signalé l'événement, la température qui a déclenché ce dernier.
  • l'abonnement à l'événement TemperatureTropHaute d'un objet Emetteur n'est pas fait dans la classe Souscripteur. Il sera fait par une classe externe.

Le programme [Program.cs] lie tous ces éléments entre-eux :

 
CacherSélectionnez
  • ligne 6 : création de l'émetteur
  • lignes 8-14 : création de deux souscripteurs qu'on abonne à l'événement TemperatureTropHaute de l'émetteur.
  • lignes 20-32 : boucle de saisie des températures au clavier
  • ligne 24 : si la température saisie est correcte, elle est transmise à l'objet Emetteur e1 qui déclenchera l'événement TemperatureTropHaute si la température est supérieure à 19 ° C.

Les résultats de l'exécution sont les suivants :

 
CacherSélectionnez

précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2013 Serge Tahé. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.