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