Skip to content

6. Gestión de eventos

En el capítulo anterior abordamos el concepto de eventos vinculados a componentes. Ahora veremos cómo crear eventos en nuestras propias clases. Se trata de un concepto complejo que no resultará de interés para los desarrolladores principiantes, a quienes se recomienda pasar directamente al siguiente capítulo.

6.1. Objetos delegados

La instrucción

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

define un tipo llamado opération que, en realidad, es un prototipo de función que acepta dos enteros y devuelve un entero. Es la palabra clave delegate la que convierte a opération en una definición de prototipo de función. El tipo [opération] se puede asignar a una variable de la siguiente manera:


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

Una variable de tipo [délégué] se construye como un objeto. El parámetro del constructor es la función asociada a la instancia del delegado. En VB, esta función se proporciona en la forma AddressOf función, donde [fonction] es el nombre de la función. Una vez creada la instancia de un delegado, se puede utilizar como una función:


        ' ejecución de la función delegada
        Dim n As Integer = op(4, 7)

El delegado [opération] se ha definido para recibir dos parámetros de tipo [integer] y devolver un resultado del mismo tipo. Por lo tanto, su instancia [op] anterior se utiliza como tal función. Consideremos el siguiente ejemplo:


' funciones delegadas
Imports System

Public Class delegate1
    ' definición de un prototipo de función
    ' acepta 2 enteros como parámetros y devuelve un entero
    Delegate Function opération(ByVal n1 As Integer, ByVal n2 As Integer) As Integer

    ' dos métodos de instancia correspondientes al prototipo
    ' sumar
    Public Function ajouter(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
        Console.Out.WriteLine(("ajouter(" & n1 & "," & n2 & ")"))
        Return n1 + n2
    End Function

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

    ' un método estático correspondiente al prototipo
    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

    ' programa de prueba
    Public Shared Sub Main()
        ' se define un objeto de tipo operación para registrar funciones en él
        ' se registra la función estática aumentar
        Dim op As New opération(AddressOf delegate1.augmenter)
        ' se ejecuta el delegado
        Dim n As Integer = op(4, 7)
        Console.Out.WriteLine(("n=" & n))
        ' creación de un objeto c1 de tipo class1
        Dim c1 As New delegate1
        ' se registra en el delegado el método «añadir» de c1
        op = New delegate1.opération(AddressOf c1.ajouter)
        ' ejecución del objeto delegado
        n = op(2, 3)
        Console.Out.WriteLine(("n=" & n))
        ' se registra en el delegado el método restar de c1
        op = New delegate1.opération(AddressOf c1.soustraire)
        n = op(2, 3)
        Console.Out.WriteLine(("n=" & n))
    End Sub
End Class

Los resultados de la ejecución son los siguientes:

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

6.2. Gestión de eventos

¿Para qué puede servir un objeto de tipo delegate?

Como veremos en el siguiente ejemplo, esto sirve principalmente para la gestión de eventos. Una clase C1 puede generar eventos evti. Durante un evento evti, un objeto de tipo C1 iniciará la ejecución de un objeto evtiDéclenché de tipo delegate. A continuación, se ejecutarán todas las funciones registradas en el objeto delegado evtiDéclenché. Si un objeto C2 que utiliza un objeto C1 desea ser notificado de la ocurrencia del evento evti en el objeto C1, registrará uno de sus métodos C2.f en el objeto delegado C1.evtiDéclenché del objeto C1 para que el método C2.f se ejecute cada vez que elevento evti se produzca en el objeto C1. Dado que el objeto delegado C1.evtiDéclenché puede registrar varias funciones, diferentes objetos Ci podrán registrarse en el delegado C1.evtiDéclenché para recibir notificaciones del evento evti en C1.

6.2.1. Declaración de un evento

Un evento se declara de la siguiente manera (1):


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

o bien (2)


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

Es la palabra clave [Event] la que define un dato como un evento. Al evento se le asocia la firma que deberán tener los gestores del mismo. Esta firma se puede definir mediante un delegado (método 1) o directamente (método 2).

6.2.2. Definir los controladores de un evento

Cuando se activa un evento mediante la instrucción [RaiseEvent], es necesario procesarlo. Los procedimientos encargados de procesar los eventos se denominan gestores de eventos. Al crear un objeto [Event], solo se declara la firma de los gestores del evento. Esta firma es la del objeto [delegate] asociado al evento. Una vez hecho esto, podremos asociar controladores al evento. Estos son procedimientos que deben tener la misma firma que el [delegate] asociado al evento. Para asociar un controlador de eventos a un evento, se utiliza la instrucción [AddHandler] con la sintaxis:

AddHandler event, AddressOf eventhandler
  • event: variable de evento a la que se asocia un nuevo gestor de eventos
  • eventhandler: nombre del gestor de eventos; debe tener la firma del [délégué] asociado al evento

Se pueden asociar tantos controladores como se desee a un evento. Todos ellos se ejecutarán cuando se active el evento al que están asociados.

6.2.3. Disparar un evento

Para activar un evento mediante un programa, se utiliza la instrucción [RaiseEvent]:

RaiseEvent eventname[( argumentlist )]
  • eventname: variable de evento (declarada con la palabra clave Event)
  • argumentlist: argumentos del delegado asociado al evento.

Se llamarán a todos los gestores que se hayan asociado al evento mediante la instrucción AddHandler. Recibirán como parámetros los definidos en la firma del evento.

6.2.4. Un ejemplo

Consideremos el siguiente ejemplo:


'gestión de eventos
Imports System

Public Class myEventArgs
    Inherits EventArgs
    ' la clase de un evento
    ' atributo
    Private _saisie As String

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

    ' propiedad de solo lectura
    Public Overrides Function ToString() As String
        Return _saisie
    End Function
End Class

Public Class émetteur
    ' la clase emisora de un evento
    ' atributo
    Private _nom As String    ' nom de l'émetteur

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

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

    ' el prototipo de las funciones encargadas de gestionar el evento
    Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)

    ' el grupo de gestores de eventos
    Public Event evtHandler As _evtHandler

    ' método de solicitud de emisión de un evento
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' se avisa a todos los suscriptores
        ' se simula que el evento proviene de aquí
        RaiseEvent evtHandler(Me, evt)
    End Sub
End Class

' una clase de procesamiento del evento
Public Class souscripteur
    ' atributo
    Private nom As String    ' nom du souscripteur
    Private sender As émetteur    ' l'émetteur des évts

    ' constructor
    Public Sub New(ByVal nom As String, ByVal e As émetteur)
        ' anotamos el nombre del suscriptor
        Me.nom = nom
        ' y el emisor de los eventos
        Me.sender = e
        ' se suscribe para recibir los eventos del emisor e
        AddHandler e.evtHandler, AddressOf traitementEvt
    End Sub

    ' gestor de eventos
    Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
        ' visualización de eventos
        Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
    End Sub
End Class

' un programa de prueba
Public Class test
    Public Shared Sub Main()
        ' creación de un emisor de eventos
        Dim émetteur1 As New émetteur("émetteur1")
        ' creación de una tabla de suscriptores
        ' para los eventos emitidos por el emisor 1
        Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
        ' se lee una secuencia de números enteros desde el teclado
        ' en cuanto uno es erróneo, se emite un evento
        Console.Out.Write("Nombre entier (rien pour arrêter) : ")
        Dim saisie As String = Console.In.ReadLine().Trim()
        ' mientras la línea introducida no esté vacía
        While saisie <> ""
            ' ¿Es la entrada un número entero?
            Try
                Dim n As Integer = Integer.Parse(saisie)
            Catch
                ' no es un número entero
                ' se avisa a todo el mundo
                émetteur1.envoyerEvt(New myEventArgs(saisie))
            End Try
            ' se avisa a todo el mundo
            ' nueva entrada
            Console.Out.Write("Nombre entier (rien pour arrêter) : ")
            saisie = Console.In.ReadLine().Trim()
        End While
        ' fin
        Environment.Exit(0)
    End Sub
End Class

El código anterior es bastante complejo. Analicémoslo en detalle. En una gestión de eventos, hay un emisor de eventos (sender) que envía los detalles de los eventos (EventArgs) a los suscriptores que han manifestado su interés por los eventos en cuestión. La clase emisora de los eventos es aquí la siguiente:


Public Class émetteur
    ' la clase emisora de un evento
    ' atributo
    Private _nom As String    ' nom de l'émetteur

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

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

    ' el prototipo de las funciones encargadas de gestionar el evento
    Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)

    ' el grupo de gestores de eventos
    Public Event evtHandler As _evtHandler

    ' método de solicitud de emisión de un evento
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' se avisa a todos los suscriptores
        ' se simula que el evento proviene de aquí
        RaiseEvent evtHandler(Me, evt)
    End Sub
End Class

Cada objeto émetteur tiene un nom fijado por construcción. El método ToString se ha redefinido de tal manera que, al transformar un objeto émetteur en string,, se recupera su nombre. La clase define un evento:


        ' el prototipo de las funciones encargadas de gestionar el evento
        Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)

        ' el evento
        Public Event evtHandler As _evtHandler

El primer argumento de una función de tratamiento de un evento será el objeto que emite el evento. El segundo argumento será de tipo myEventArgs, un objeto que proporcionará detalles sobre el evento y al que volveremos más adelante. Para poder desencadenar un evento desde fuera de un objeto émetteur, añadimos a la clase el método envoyerEvt:


    ' método para solicitar la emisión de un evento
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' se avisa a todos los suscriptores
        ' se simula que el evento proviene de aquí
        RaiseEvent evtHandler(Me, evt)
    End Sub

Debemos definir qué tipo de eventos activará la clase émetteur. Para ello, hemos definido la clase myEventArgs:


Public Class myEventArgs
    ' la clase de un evento
    ' atributo
    Private _saisie As String

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

    ' propiedad introducida como de solo lectura
    Public Overrides Function ToString() As String
        Return _saisie
    End Function
End Class

Los eventos que nos interesan son las entradas erróneas del teclado. Le pediremos a un usuario que introduzca números enteros en el teclado y, tan pronto como introduzca una cadena que no represente un entero, se activará un evento. Como detalle del evento, nos limitaremos a indicar la entrada errónea. Este es el significado del atributo _saisie de la clase. Por lo tanto, se crea un objeto myEventArgs con la entrada errónea como parámetro. Además, redefinimos el método ToString para que, al transformar un objeto myEventArgs en una cadena, se obtenga el atributo _saisie.

Hemos definido el emisor de eventos y el tipo de eventos que emite. A continuación, definimos el tipo de suscriptores interesados en estos eventos.


    ' una clase de gestión del evento
    Public Class souscripteur
        ' atributo
        Private nom As String     ' nom du souscripteur
        Private sender As émetteur     ' l'émetteur des évts

        ' fabricante
        Public Sub New(ByVal nom As String, ByVal e As émetteur)
            ' se anota el nombre del suscriptor
            Me.nom = nom
            ' y el emisor de los eventos
            Me.sender = e
            ' se suscribe para recibir los eventos del emisor e
            AddHandler e.evtHandler, AddressOf traitementEvt
        End Sub

        ' gestor de eventos
        Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
            ' visualización de eventos
            Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
        End Sub
    End Class

Un suscriptor se definirá mediante dos parámetros: su nombre (atributo nom) y el objeto émetteur cuyos eventos desea procesar (atributo sender). Estos dos parámetros se pasarán al constructor del objeto. Durante esta misma construcción, el suscriptor se suscribe a los eventos del emisor:


            ' se registra para recibir los eventos del emisor e
            AddHandler e.evtHandler, AddressOf traitementEvt

La función registrada en el emisor es traitementEvt. Este método de la clase souscripteur muestra los dos argumentos que ha recibido (sender, evt), así como el nombre del receptor (nom). Se han definido el tipo de eventos generados, el tipo de emisor de dichos eventos y el tipo de suscriptores. Ahora solo nos queda implementarlos:


    ' un programa de prueba
    Public Class test
        Public Shared Sub Main()
            ' creación de un emisor de eventos
            Dim émetteur1 As New émetteur("émetteur1")
            ' creación de una tabla de suscriptores
            ' para los eventos emitidos por el emisor 1
            Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
            ' se lee una secuencia de números enteros desde el teclado
            ' en cuanto uno es erróneo, se emite un evento
            Console.Out.Write("Nombre entier (rien pour arrêter) : ")
            Dim saisie As String = Console.In.ReadLine().Trim()
            ' mientras la línea introducida no esté vacía
            While saisie <> ""
                ' ¿Es la entrada un número entero?
                Try
                    Dim n As Integer = Integer.Parse(saisie)
                Catch
                    ' no es un número entero
                    ' se avisa a todo el mundo
                    émetteur1.envoyerEvt(New myEventArgs(saisie))
                End Try
                ' se avisa a todo el mundo
                ' nueva entrada
                Console.Out.Write("Nombre entier (rien pour arrêter) : ")
                saisie = Console.In.ReadLine().Trim()
            End While
            ' fin
            Environment.Exit(0)
        End Sub

Creamos un objeto émetteur:


        ' creación de un emisor de eventos
        Dim émetteur1 As New émetteur("émetteur1")

Creamos una matriz de dos suscriptores para los eventos emitidos por el objeto émetteur1:


        ' creación de una tabla de suscriptores
        ' para los eventos emitidos por emisor1
        Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}

Pedimos al usuario que introduzca números enteros mediante el teclado. En cuanto se produce una entrada errónea, pedimos a émetteur1 que envíe un evento a sus suscriptores:


                ' se avisa a todo el mundo
                émetteur1.envoyerEvt(New myEventArgs(saisie))

El evento enviado es de tipo myEventArgs y contiene la entrada errónea. Ambos suscriptores deberían recibir este evento y notificarlo. Esto es lo que muestra la ejecución siguiente.

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) :