Skip to content

6. Gestion d'événements

Nous avons dans le chapitre précédent abordé la notion d'événements liés à des composants. Nous voyons maintenant comment créer des événements dans nos propres classes. C'est une notion difficile qui n'intéressera pas les développeurs débutants auxquels il est conseillé de passer directement au chapitre suivant.

6.1. Objets delegate

L'instruction

    Delegate Function opération(ByVal n1 As Integer, ByVal n2 As Integer) As Integer

définit un type appelé opération qui est en fait 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. Le type [opération] peut être affecté à une variable de la façon suivante :


        Dim op As New opération(AddressOf [fonction])

Une variable de type [délégué] se construit comme un objet. Le paramètre du constructeur est la fonction associée à l'instance du délégué. En VB, cette fonction est donnée sous la forme AddressOf fonction, où [fonction] est le nom de la fonction. Une fois l'instance d'un délégué créée, elle peut être utilisée comme une fonction :


        ' on exécute le délégué
        Dim n As Integer = op(4, 7)

Le délégué [opération] a été défini comme recevant deux paramètres de type [integer] et renvoyant un résultat de même type. Donc son instance [op] ci-dessus est utilisée comme une telle fonction. Considérons l'exemple suivant :


' fonctions déléguées
Imports System

Public Class delegate1
    ' définition d'un prototype de fonction
    ' accepte 2 entiers en paramètre et rend un entier
    Delegate Function opération(ByVal n1 As Integer, ByVal n2 As Integer) As Integer

    ' deux méthodes d'instance correspondant au prototype
    ' ajouter
    Public Function ajouter(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
        Console.Out.WriteLine(("ajouter(" & n1 & "," & n2 & ")"))
        Return n1 + n2
    End Function

    ' soustraire
    Public Function soustraire(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
        Console.Out.WriteLine(("soustraire(" & n1 & "," & n2 & ")"))
        Return n1 - n2
    End Function

    ' une méthode statique correspondant au prototype
    Public Shared Function augmenter(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
        Console.Out.WriteLine(("augmenter(" & n1 & "," & n2 & ")"))
        Return n1 + 2 * n2
    End Function

    ' programme de test
    Public Shared Sub Main()
        ' on définit un objet de type opération pour y enregistrer des fonctions
        ' on enregistre la fonction statique augmenter
        Dim op As New opération(AddressOf delegate1.augmenter)
        ' on exécute le délégué
        Dim n As Integer = op(4, 7)
        Console.Out.WriteLine(("n=" & n))
        ' création d'un objet c1 de type class1
        Dim c1 As New delegate1
        ' on enregistre dans le délégué la méthode ajouter de c1
        op = New delegate1.opération(AddressOf c1.ajouter)
        ' exécution de l'objet délégué
        n = op(2, 3)
        Console.Out.WriteLine(("n=" & n))
        ' on enregistre dans le délégué la méthode soustraire de c1
        op = New delegate1.opération(AddressOf c1.soustraire)
        n = op(2, 3)
        Console.Out.WriteLine(("n=" & n))
    End Sub
End Class

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

augmenter(4,7)
n=18
ajouter(2,3)
n=5
soustraire(2,3)
n=-1

6.2. Gestion d'événements

A quoi peut servir un objet de type delegate ?

Comme nous le verrons dans l'exemple suivant, cela sert surtout à la gestion des événements. Une classe C1 peut générer des événements evti. Lors d'un événement evti, un objet de type C1 lancera l'exécution d'un objet evtiDéclenché de type delegate. Toutes les fonctions enregistrées dans l'objet delegate evtiDéclenché seront alors exécutées. Si un objet C2 utilisant un objet C1 veut être averti de l'occurrence de l'événement evti sur l'objet C1, il enregistrera l'une de ses méthodes C2.f dans l'objet délégué C1.evtiDéclenché de l'objet C1 afin que la méthode C2.f soit exécutée à chaque fois que l'événement evti se produit sur l'objet C1. Comme l'objet délégué C1.evtiDéclenché peut enregistrer plusieurs fonctions, différents objets Ci pourront s'enregistrer auprès du délégué C1.evtiDéclenché pour être prévenus de l'événement evti sur C1.

6.2.1. Déclaration d'un événement

Un événement se déclare de la façon suivante (1) :


Delegate Sub proc(liste d'arguments)
Public Event événement as proc

ou bien (2)


Public Event événement(liste d'arguments)

C'est le mot clé [Event] qui définit une donnée comme un événement. On associe à l'événement la signature que devront avoir les gestionnaires de celui-ci. On peut définir cette signature au moyen d'un délégué (méthode 1), soit directement (méthode 2).

6.2.2. Définir les gestionnaires d'un événement

Lorsqu'un événement est déclenché par l'instruction [RaiseEvent], il faut le traiter. Les procédures chargées de traiter les événements sont appelées des gestionnaires d'événements. A la création d'un objet [Event], seule la signature des gestionnaires de l'événement est déclarée. Cette signature est celle de l'objet [delegate] associé à l'événement. Ceci fait, on va pouvoir associer à l'événement des gestionnaires. Ceux-ci sont des procédures qui doivent avoir la même signature que le [delegate] associé à l'événement. Pour associer un gestionnaire d'événement à un événement, on utilise l'instruction [AddHandler] de syntaxe :

AddHandler event, AddressOf eventhandler
  • event : variable événement à qui on associe un nouveau gestionnaire d'événements
  • eventhandler : nom du gestionnaire d'événements - doit avoir la signature du [délégué] associé à l'événement

On peut associer autant de gestionnaires que l'on veut à un événement. Ils seront tous exécutés lorsque l'événement auquel ils sont associés sera déclenché.

6.2.3. Déclencher un événement

Pour déclencher un événement par programme, on utilise l'instruction [RaiseEvent] :

RaiseEvent eventname[( argumentlist )]
  • eventname : variable événement (déclarée avec le mot clé Event)
  • argumentlist : arguments du délégué associé à l'événement.

Tous les gestionnaires qui ont été associés à l'événement par l'instruction AddHandler vont être appelés. Ils vont recevoir comme paramètres, les paramètres définis dans la signature de l'événement.

6.2.4. Un exemple

Considérons l'exemple suivant :


'gestion d'événements
Imports System

Public Class myEventArgs
    Inherits EventArgs
    ' la classe d'un évt
    ' attribut
    Private _saisie As String

    ' constructeur
    Public Sub New(ByVal saisie As String)
        _saisie = saisie
    End Sub

    ' propriété saisie en lecture seule
    Public Overrides Function ToString() As String
        Return _saisie
    End Function
End Class

Public Class émetteur
    ' la classe émettrice d'un évt
    ' attribut
    Private _nom As String    ' nom de l'émetteur

    ' constructeur
    Public Sub New(ByVal nom As String)
        _nom = nom
    End Sub

    ' ToString
    Public Overrides Function ToString() As String
        Return _nom
    End Function

    ' le prototype des fonctions chargées de traiter l'évt
    Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)

    ' le pool des gestionnaires d'évts
    Public Event evtHandler As _evtHandler

    ' méthode de demande d'émission d'un évt
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' on prévient tous les abonnés
        ' on fait comme si l'évt provenait d'ici
        RaiseEvent evtHandler(Me, evt)
    End Sub
End Class

' une classe de traitement de l'évt
Public Class souscripteur
    ' attribut
    Private nom As String    ' nom du souscripteur
    Private sender As émetteur    ' l'émetteur des évts

    ' constructeur
    Public Sub New(ByVal nom As String, ByVal e As émetteur)
        ' on note le nom du souscripteur
        Me.nom = nom
        ' et l'émetteur des évts
        Me.sender = e
        ' on s'inscrit pour recevoir les evts de l'émetteur e
        AddHandler e.evtHandler, AddressOf traitementEvt
    End Sub

    ' gestionnaire d'évt
    Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
        ' affichage evt
        Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
    End Sub
End Class

' un programme de test
Public Class test
    Public Shared Sub Main()
        ' création d'un émetteur d'evts
        Dim émetteur1 As New émetteur("émetteur1")
        ' création d'un tableau de souscripteurs
        ' pour les évts émis par émetteur1
        Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
        ' on lit une suite d'entiers au clavier
        ' dès que l'un est erroné, on émet un évt
        Console.Out.Write("Nombre entier (rien pour arrêter) : ")
        Dim saisie As String = Console.In.ReadLine().Trim()
        ' tant que la ligne saisie est non vide
        While saisie <> ""
            ' la saisie est-elle un nombre entier ?
            Try
                Dim n As Integer = Integer.Parse(saisie)
            Catch
                ' ce n'est pas un entier
                ' on prévient tout le monde
                émetteur1.envoyerEvt(New myEventArgs(saisie))
            End Try
            ' on prévient tout le monde
            ' nouvelle saisie
            Console.Out.Write("Nombre entier (rien pour arrêter) : ")
            saisie = Console.In.ReadLine().Trim()
        End While
        ' fin
        Environment.Exit(0)
    End Sub
End Class

Le code précédent est assez complexe. Détaillons-le. Dans une gestion d'événements, il y a un émetteur d'événements (sender) qui envoie le détail des événements (EventArgs) à des souscripteurs qui se sont déclarés intéressés par les événements en question. La classe émettrice des événements est ici la suivante :


Public Class émetteur
    ' la classe émettrice d'un évt
    ' attribut
    Private _nom As String    ' nom de l'émetteur

    ' constructeur
    Public Sub New(ByVal nom As String)
        _nom = nom
    End Sub

    ' ToString
    Public Overrides Function ToString() As String
        Return _nom
    End Function

    ' le prototype des fonctions chargées de traiter l'évt
    Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)

    ' le pool des gestionnaires d'évts
    Public Event evtHandler As _evtHandler

    ' méthode de demande d'émission d'un évt
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' on prévient tous les abonnés
        ' on fait comme si l'évt provenait d'ici
        RaiseEvent evtHandler(Me, evt)
    End Sub
End Class

Chaque objet émetteur a un nom fixé par construction. La méthode ToString a été redéfinie de telle façon que lorsqu'on transforme un objet émetteur en string, c'est son nom qu'on récupère. La classe définit un événement :


        ' le prototype des fonctions chargées de traiter l'évt
        Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)

        ' l'événement
        Public Event evtHandler As _evtHandler

Le premier argument d'une fonction de traitement d'un événement sera l'objet qui émet l'événement. Le second argument sera de type myEventArgs, un objet qui donnera des détails sur l'événement et sur lequel nous reviendrons. Afin de pouvoir déclencher un événement de l'extérieur d'un objet émetteur, nous ajoutons à la classe la méthode envoyerEvt :


    ' méthode de demande d'émission d'un évt
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' on prévient tous les abonnés
        ' on fait comme si l'évt provenait d'ici
        RaiseEvent evtHandler(Me, evt)
    End Sub

Nous devons définir quel sera le type d'événements déclenchés par la classe émetteur. Nous avons pour cela défini la classe myEventArgs :


Public Class myEventArgs
    ' la classe d'un évt
    ' attribut
    Private _saisie As String

    ' constructeur
    Public Sub New(ByVal saisie As String)
        _saisie = saisie
    End Sub

    ' propriété saisie en lecture seule
    Public Overrides Function ToString() As String
        Return _saisie
    End Function
End Class

Les événements qui vont nous intéresser sont des saisies au clavier erronées. On va demander à un utilisateur de taper des nombres entiers au clavier et dès qu'il tapera une chaîne qui ne représente pas un entier, on déclenchera un événement. Comme détail de l'événement, nous nous conterons de donner la saisie erronée. C'est le sens de l'attribut _saisie de la classe. Un objet myEventArgs est donc construit avec pour paramètre la saisie erronée. On redéfinit par ailleurs la méthode ToString pour quem lorsqu'on transforme un objet myEventArgs en chaîne, on obtienne l'attribut _saisie.

Nous avons défini l'émetteur des événements et le type d'événements qu'il émet. Nous définissons ensuite le type des souscripteurs intéressés par ces événements.


    ' une classe de traitement de l'évt
    Public Class souscripteur
        ' attribut
        Private nom As String     ' nom du souscripteur
        Private sender As émetteur     ' l'émetteur des évts

        ' constructeur
        Public Sub New(ByVal nom As String, ByVal e As émetteur)
            ' on note le nom du souscripteur
            Me.nom = nom
            ' et l'émetteur des évts
            Me.sender = e
            ' on s'inscrit pour recevoir les evts de l'émetteur e
            AddHandler e.evtHandler, AddressOf traitementEvt
        End Sub

        ' gestionnaire d'évt
        Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
            ' affichage evt
            Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
        End Sub
    End Class

Un souscripteur sera défini par deux paramètres : son nom (attribut nom) et l'objet émetteur dont il veut traiter les événements (attribut sender). Ces deux paramètres seront passés au constructeur de l'objet. Au cours de cette même construction, le souscripteur s'abonne aux événements de l'émetteur :


            ' on s'inscrit pour recevoir les evts de l'émetteur e
            AddHandler e.evtHandler, AddressOf traitementEvt

La fonction enregistrée auprès de l'émetteur est traitementEvt. Cette méthode de la classe souscripteur affiche les deux arguments qu'elle a reçues (sender, evt) ainsi que le nom du récepteur (nom). Ont été définis, le type des événements produits, le type de l'émetteur de ces événements, le type des souscripteurs. Il ne nous reste plus qu'à les mettre en oeuvre :


    ' un programme de test
    Public Class test
        Public Shared Sub Main()
            ' création d'un émetteur d'evts
            Dim émetteur1 As New émetteur("émetteur1")
            ' création d'un tableau de souscripteurs
            ' pour les évts émis par émetteur1
            Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
            ' on lit une suite d'entiers au clavier
            ' dès que l'un est erroné, on émet un évt
            Console.Out.Write("Nombre entier (rien pour arrêter) : ")
            Dim saisie As String = Console.In.ReadLine().Trim()
            ' tant que la ligne saisie est non vide
            While saisie <> ""
                ' la saisie est-elle un nombre entier ?
                Try
                    Dim n As Integer = Integer.Parse(saisie)
                Catch
                    ' ce n'est pas un entier
                    ' on prévient tout le monde
                    émetteur1.envoyerEvt(New myEventArgs(saisie))
                End Try
                ' on prévient tout le monde
                ' nouvelle saisie
                Console.Out.Write("Nombre entier (rien pour arrêter) : ")
                saisie = Console.In.ReadLine().Trim()
            End While
            ' fin
            Environment.Exit(0)
        End Sub

Nous créons un objet émetteur :


        ' création d'un émetteur d'evts
        Dim émetteur1 As New émetteur("émetteur1")

Nous créons un tableau de deux souscripteurs pour les événements émis par l'objet émetteur1 :


        ' création d'un tableau de souscripteurs
        ' pour les évts émis par émetteur1
        Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}

Nous demandons à l'utilisateur de taper des nombres entiers au clavier. Dès qu'une saisie est erronée, nous demandons à émetteur1 d'envoyer un événement à ses souscripteurs :


                ' on prévient tout le monde
                émetteur1.envoyerEvt(New myEventArgs(saisie))

L'événement envoyé est de type myEventArgs et contient la saisie erronée. Les deux souscripteurs devraient recevoir cet événement et le signaler. C'est ce que montre l'exécution qui suit.

dos>evt1
Nombre entier (rien pour arrêter) : 4
Nombre entier (rien pour arrêter) : a
L'objet [émetteur1] a signalé la saisie erronée [a] au souscripteur [s1]
L'objet [émetteur1] a signalé la saisie erronée [a] au souscripteur [s2]
Nombre entier (rien pour arrêter) : 1.6
L'objet [émetteur1] a signalé la saisie erronée [1.6] au souscripteur [s1]
L'objet [émetteur1] a signalé la saisie erronée [1.6] au souscripteur [s2]
Nombre entier (rien pour arrêter) :