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