Buscador

Conexión de un emisor de eventos con un manipulador de eventos

Existen dos medios para comunicar un evento con un manipulador de eventos: 
  • En tiempo de compilación, realizando un enlace estático entre la clase y el manipulador mediante las palabras clave WithEvents y Handles. Esta técnica tiene la ventaja de que permite escribir un código mucho más legible, en cuanto a la manipulación de eventos se refiere. 
  • En tiempo de ejecución, realizando un enlace dinámico entre la clase y el manipulador mediante la palabra clave AddHandler. La ventaja en este caso, es que podemos asociar procedimientos manipuladores de evento dinámicamente durante el transcurso de la ejecución del programa.

El receptor de eventos

Un receptor de eventos, también denominado manipulador de eventos (event receiver o event handler), es aquella parte del código cliente, que configuramos para que sea capaz de recibir los eventos generados por un objeto emisor. Para que ambos elementos en este canal de comunicación que es la transmisión de eventos puedan operar, es necesario conectarlos.

El emisor de eventos

Un emisor de eventos, también denominado origen de eventos (event source o event sender), es un objeto capacitado para generar y lanzar eventos al sistema, que puedan ser recuperados por otros objetos preparados para realizar su tratamiento. Para que un objeto pueda desencadenar eventos, en su clase debemos realizar dos tareas: 
  • • Declarar el propio evento usando la palabra clave Event, especificando si es necesario una lista de parámetros que acompañan al evento. 
  • Lanzar el evento mediante la palabra clave RaiseEvent, seguida del nombre del evento a provocar. Si hemos declarado el evento con parámetros, deberemos añadir los valores para cada uno de los parámetros en el mismo orden en el que los hemos declarado. 

Situándonos pues ante el problema planteado por la clase Empleado en un apartado anterior, la solución que proponemos consistirá en generar desde la clase Empleado un evento cuando se produzca un fallo en la validación del sueldo. De esta manera, el código cliente que lo necesite, responderá al evento, y el que no lo precise, hará caso omiso del evento lanzado. 
En primer lugar, declaramos en la zona de declaraciones de la clase el evento LimiteSueldo, que irá acompañado de un parámetro que nos informará del importe erróneo que se intentaba asignar a la propiedad. 
A continuación, en la propiedad Sueldo, cuando detectemos que el sueldo sobrepasa el valor permitido, en lugar de lanzar allí el mensaje a la consola, generaremos el evento LimiteSueldo, que podrá ser recuperado por el código cliente que haga uso de la clase, actuando como necesite en cada ocasión. Observe el lector, que al mismo tiempo que lanzamos el evento, le pasamos el importe del sueldo que se intentaba asignar. Veamos el Código fuente 331.

Public Class Empleado
' declaramos el evento
Public Event LimiteSueldo(ByVal ldbImporte As Double)
Private msNombre As String
Private mdbSueldo As Double
Public Property Nombre() As String
Get
Return msNombre
End Get
Set(ByVal Value As String)
msNombre = Value
End Set
End Property
Public Property Sueldo() As Double
Get
Return mdbSueldo
End Get
Set(ByVal Value As Double)
' si el valor que intentamos asignar
' al sueldo supera el permitido...
If Value > 1000 Then
' ...lanzamos el evento, y le pasamos
' como parámetro informativo el valor
' incorrecto que intentábamos asignar
RaiseEvent LimiteSueldo(Value)
Else
mdbSueldo = Value
End If
End Set
End Property
End Class
Código fuente 331
Con estas modificaciones sobre la clase Empleado, ya tenemos listo nuestro emisor de eventos. Queda ahora por completar la parte que captura los eventos lanzados por el emisor.

Esquema básico de un sistema orientado a eventos

Un sistema conducido por eventos basa su funcionamiento en dos pilares fundamentales: un emisor y un receptor de eventos. El primero genera y lanza el evento al sistema, mientras que el segundo, si está interesado en tratar el evento lanzado, lo captura y le da respuesta. Si un objeto receptor no necesita gestionar eventos, simplemente no lo obtiene. Ver Figura 126.
Figura 126. Esquema de generación y captura de eventos.
Tras una introducción conceptual, a continuación trataremos con más detalle cada uno de los elementos integrantes de la gestión de eventos.

Programación basada en eventos

La aparición de sistemas operativos basados en ventajas trajo consigo un nuevo esquema en el desarrollo de aplicaciones. En un programa que se ejecute dentro de un sistema como Windows se están produciendo constantemente eventos (sucesos), provocados por las acciones del usuario o por el propio sistema. Tan elevado es el número de eventos que se producen, que dar respuesta a todos, es decir, codificar todas aquellas situaciones que acontecen a lo largo de la ejecución de un programa, es algo impensable. 
Por tal motivo, la técnica seguida al escribir código orientado a eventos se basa en codificar sólo los eventos que nos interese tratar, ya que para el resto, será el propio sistema quien proporcione el comportamiento por defecto. En una aplicación Windows típica, todos los elementos que forman parte de la misma, es decir, la propia ventana y los controles contenidos en ella, lanzan eventos en respuesta a las acciones del usuario. 
Un ejemplo típico: al pulsar un control de tipo botón en la ventana se produce su evento clic; si queremos que el programa realice alguna acción al pulsar dicho botón, deberemos escribir código en el procedimiento de evento asociado, para dar respuesta a tal suceso.

Un escenario de trabajo sin eventos

Supongamos que nos encargan desarrollar una clase llamada Empleado, entre cuyos miembros tenemos la propiedad Sueldo. Uno de los requerimientos respecto a esta propiedad es que su valor no debe ser superior a 1000; por ello, en su procedimiento Property, realizamos una validación a tal efecto, emitiendo un mensaje cuando el sueldo que asignemos sea superior. Ver el Código fuente 330.

Public Class Empleado
' variables de propiedad
Private msNombre As String
Private mdbSueldo As Double
' propiedad Nombre
Public Property Nombre() As String
Get
Return msNombre
End Get
Set(ByVal Value As String)
msNombre = Value
End Set
End Property
' propiedad Sueldo
Public Property Sueldo() As Double
Get
Return mdbSueldo
End Get
' al asignar un valor a la propiedad,
' si el valor es superior a 1000
' mostrar un mensaje y no permitir la
' asignación del sueldo
Set(ByVal Value As Double)
If Value > 1000 Then
Console.WriteLine("Asignación de sueldo incorrecta")
Console.ReadLine()
Else
mdbSueldo = Value
End If
End Set
End Property
End Class
Código fuente 330

Una vez finalizado el desarrollo de la clase, la distribuimos a nuestro cliente. Posteriormente, un nuevo cliente nos requiere la clase, pero en esta ocasión, aunque necesita la validación sobre la propiedad Sueldo, no quiere que se muestre el mensaje al sobrepasar el sueldo asignado. 
Se nos plantea en este caso un problema, ya que si escribimos una nueva versión de la clase Empleado, tendremos el trabajo extra de mantener ambas. Para solucionarlo mediante una única versión de la clase recurriremos a los eventos.

Programación estrictamente procedural

Antes de la llegada de los sistemas y lenguajes orientados a eventos, las aplicaciones ejecutaban su código en un orden fijo, ya que estaban basadas en un modelo construido exclusivamente a base de procedimientos: se realizaban llamadas a las rutinas de código en un orden predeterminado, y una vez terminada la ejecución de tales rutinas, finalizaba la aplicación.

Eventos en VB.NET

Ciñéndonos al ámbito de la programación, un evento es, dentro de un programa, una notificación lanzada por un objeto, que podrá ser contestada por aquellos otros objetos interesados en darle respuesta.

Eventos. ¿Qué es un evento?

Un evento es un suceso o situación, que acontece en una ubicación de espacio y tiempo no predecible. 
Cuando una máquina deja de funcionar por una avería, o cuando una persona resbala y cae, estamos en ambos casos, ante ejemplos de eventos, ya que ocurren en momentos inesperados. 
Para que se desencadene un evento, se deben dar determinadas circunstancias, las cuales favorecen el que dicho evento se produzca.

Formateo de valores - III

El Código fuente 328 muestra algunos formatos personalizados, construidos a base de patrones de formato.

Sub Main()
Dim ldtFecha As Date
ldtFecha = Date.Now()
Console.WriteLine(ldtFecha.ToString("ddd, dd-MMM/yyyy"))
Console.WriteLine(ldtFecha.ToString("dddd, a dd \de MMMM ,en el año yyyy"))
Console.WriteLine(ldtFecha.ToString("H:mm:s"))
End Sub
Código fuente 328

En cuanto a los números, si necesitamos aplicar un formato para este tipo de valor, podemos hacerlo mediante los caracteres mostrados en la Tabla 27.
El Código fuente 329 muestra algunos formatos aplicados sobre un tipo numérico.

Sub Main()
Dim ldcMiNum As Decimal
' crear un array con caracteres de formato
Dim lsFormatos() As String = {"c", "e", "f", "g", "n"}
Dim lsNumFormateado As String
ldcMiNum = 850.678 ' asignar valor al número
' recorrer el array de formatos y aplicar cada
' uno de los formatos al número
For Each lsNumFormateado In lsFormatos
Console.WriteLine(ldcMiNum.ToString(lsNumFormateado))
Next
Console.ReadLine()
End Sub
Código fuente 329

Formateo de valores - II

La Tabla 26 por otra parte, muestra algunos caracteres utilizados para crear patrones de formato personalizados, los cuales, se deben combinar entre sí, para componer el formato que necesitemos.
Tabla 26. Caracteres para patrones de formato

Formateo de valores - I

La utilización de un formato sobre un tipo de dato, nos permite mostrar su valor de un modo distinto a como se encuentra almacenado en la aplicación. Por ejemplo, el valor puro de una fecha no muestra el nombre del mes; sin embargo, aplicándole el formato adecuado, podemos hacer que se muestre la fecha en un modo extendido, con el nombre del mes, día de la semana, etc. Todos los tipos de datos del entorno que pueden mostrar información formateada, disponen del método ToString( ), al cuál podemos pasarle una cadena, con la especificación de formato que necesitemos. 
A continuación mostraremos unos ejemplos de formateo para fechas y números, ya que son los tipos de datos que con más frecuencia requieren ser formateado a lo largo del código de un programa.
Respecto a las fechas, el tipo Date, aparte del método ToString( ), tiene algunos miembros que devuelven un tipo de formato fijo. Veamos el Código fuente 326.

Sub Main()
Dim ldtFecha As Date
ldtFecha = Date.Now()
Console.WriteLine("ToLongDateString: {0}", ldtFecha.ToLongDateString())
Console.WriteLine("ToUniversalTime: {0}", ldtFecha.ToUniversalTime())
End Sub
Código fuente 326

Empleando alguna de las sobrecargas del método ToString( ), podemos formatear en los modos mostrados seguidamente. 
La Tabla 25 muestra algunos caracteres asociados a los formatos predefinidos.
En el Código fuente 327 podemos ver un formateo de fechas con caracteres de formato.
Sub Main()
Dim ldtFecha As Date
Dim lsListaFormatos() As String = {"d", "D", "g", "G", "t", "T", "m", "y"}
Dim lsFormato As String
ldtFecha = Date.Now()
For Each lsFormato In lsListaFormatos
Console.WriteLine("Formato: {0}, resultado: {1}", _
lsFormato, ldtFecha.ToString(lsFormato))
Next
End Sub
Código fuente 327

Operaciones aritméticas, la clase Math

La clase Math contiene el conjunto de operaciones aritméticas más habituales. Gracias a que sus miembros son compartidos, es muy fácil su uso, ya que sólo debemos especificar el nombre de la clase, seguido del método a ejecutar.

El Código fuente 325 muestra algunos ejemplos utilizando métodos de la clase Math. Consulte el lector la documentación de .NET Framework para una explicación detallada sobre todos los miembros de esta clase.

Sub Main()
Dim liSigno As Integer
Dim ldbRedondear As Double
' Abs(): devuelve el valor absoluto del número
' pasado como parámetro
Console.WriteLine("Abs --> {0}", Math.Abs(-1867.79))
' Ceiling(): devuelve el número sin precisión decimal,
' más grande o igual que el pasado como parámetro
Console.WriteLine("Ceiling --> {0}", Math.Ceiling(256.7235))
' Floor(): devuelve el número sin precisión decimal,
' más pequeño o igual que el pasado como parámetro
Console.WriteLine("Floor --> {0}", Math.Floor(256.7235))
' Sign(): devuelve un valor informando del signo del número
' pasado como parámetro
Console.WriteLine("Introducir número para averiguar su signo")
liSigno = Console.ReadLine()
Select Case Math.Sign(liSigno)
Case -1
Console.WriteLine("El número es negativo")
Case 0
Console.WriteLine("El número es cero")
Case 1
Console.WriteLine("El número es positivo")
End Select
' Round(): redondea el número pasado como parámetro
ldbRedondear = Math.Round(28.3215)
Console.WriteLine("Redondear 28.3215 --> {0}", ldbRedondear)
ldbRedondear = Math.Round(28.63215)
Console.WriteLine("Redondear 28.63215 --> {0}", ldbRedondear)
Console.ReadLine()
End Sub
Código fuente 325