Skip to content

6. Manejo 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 interesará a los desarrolladores principiantes, a quienes se les 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 crea 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:


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

El delegado [opération] se definió para recibir dos parámetros de tipo [integer] y devolver un resultado del mismo tipo. Por lo tanto, su instancia [op], mencionada anteriormente, 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 que corresponden 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 que corresponde 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é sirve un objeto de tipo delegate?

Como veremos en el siguiente ejemplo, 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 recibir una notificación cuando ocurra el 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 ocurra en el objeto C1. Dado que el objeto delegado C1.evtiDéclenché puede registrar varias funciones, diferentes objetos Ci podrán registrarse con 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)

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

6.2.2. Definir los manejadores 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 manejadores de eventos. Al crear un objeto [Event], solo se declara la firma de los manejadores del evento. Esta firma es la del objeto [delegate] asociado al evento. Una vez hecho esto, podremos asociar manejadores al evento. Estos son procedimientos que deben tener la misma firma que el [delegate] asociado al evento. Para asociar un manejador de eventos a un evento, se utiliza la instrucción [AddHandler] con la siguiente sintaxis:

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

Se pueden asociar tantos manejadores como se desee a un evento. Todos 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 controladores que se hayan asociado al evento mediante la instrucción AddHandler. Estos 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 manejar el evento
    Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)

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

    ' método para solicitar la emisión de un evento
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' se notifica 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)
        ' 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 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 emisor1
        Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
        ' se lee una secuencia de números enteros desde el teclado
        ' tan pronto como uno sea incorrecto, 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 ingresada no esté vacía
        While saisie <> ""
            ' ¿La entrada es un número entero?
            Try
                Dim n As Integer = Integer.Parse(saisie)
            Catch
                ' no es un número entero
                ' se avisa a todos
                émetteur1.envoyerEvt(New myEventArgs(saisie))
            End Try
            ' se avisa a todos
            ' 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 en los eventos en cuestión. La clase emisora de los eventos es, en este caso, 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 manejar el evento
    Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)

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

    ' método para solicitar la emisión de un evento
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' se notifica 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 asignado por defecto. 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 procesar 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 manejo 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, agregamos 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 notifica 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 de entrada 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 escriba números enteros en el teclado y, tan pronto como escriba una cadena que no represente un número entero, se activará un evento. Como detalle del evento, nos limitaremos a indicar la entrada errónea. Ese es el propósito 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 convertir un objeto myEventArgs en una cadena, se obtenga el atributo _saisie.

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


    ' una clase de manejo 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)
            ' se registra 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 esa misma construcción, el suscriptor se suscribe a los eventos del emisor:


            ' se suscribe 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 los eventos generados, el tipo del emisor de dichos eventos y el tipo de los 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 emisor1
            Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
            ' se lee una secuencia de números enteros desde el teclado
            ' tan pronto como uno sea incorrecto, 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 ingresada no esté vacía
            While saisie <> ""
                ' ¿La entrada es un número entero?
                Try
                    Dim n As Integer = Integer.Parse(saisie)
                Catch
                    ' no es un número entero
                    ' se avisa a todos
                    émetteur1.envoyerEvt(New myEventArgs(saisie))
                End Try
                ' se avisa a todos
                ' 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 tabla con dos suscriptores para los eventos emitidos por el objeto émetteur1:


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

Le pedimos al usuario que ingrese números enteros desde el teclado. Tan pronto como se ingrese un dato incorrecto, le pedimos a émetteur1 que envíe un evento a sus suscriptores:


                 ' se avisa a todos
                émetteur1.envoyerEvt(New myEventArgs(saisie))

El evento enviado es del 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) :