|
Curso básico
de programación en Visual Basic
Lección
45
Crear
eventos en nuestras clases
|
Realizamos
programas para la gestión de empresas. Empresas medianas y
pequeñas. Programas de contabilidad, cartera de pedidos
clientes proveedores, facturación control de albaranes,
tesorería cartera de cobros y pagos y estadísticas.
Nuestro
agradecimiento a todos los que por unas causas o por otras
visitan nuestra web. Gestión de empresas PYMES. Curso de
programación de Visual Basic.
|
Bueno, parece ser
que me estoy "portando bien", ya que esta nueva entrega
está dentro de los plazos que me impuse hace poco: publicar como
mínimo una entrega cada mes. Sí, ya se que deberían ser más
entregas por mes, pero al menos es mejor tener una entrega al mes
que no tener que estar varios meses sin ninguna nueva... así que no
te quejes mucho... je, je.
A la vuelta con la
Encapsulación
Como has podido
comprobar, al "encapsular" ciertas tareas a realizar por
una clase, (en este caso las acciones de leer y guardar la
información), el código de nuestros proyectos será más fácil de
mantener, ya que si por alguna circunstancia necesitamos cambiar la
forma de acceder a ese fichero, bien porque hayamos añadido alguna
nueva propiedad o porque haya cambiado el tipo de datos, tan sólo
tendremos que cambiar el código de esos procedimientos, de forma
que en el resto del código no tengamos que hacer ningún cambio.
Por esa razón, es
importante y sobre todo aconsejable, que en la medida de lo posible,
hagamos que sea el código incluido en la propia clase el que se
encargue de manejar la información o los datos que dicha clase
manipulará.
Ya que, al menos eso es lo que se supone, la propia clase es la que
"mejor" sabe cómo manipular los datos que contiene. Si,
por ejemplo, tienes que crear una función o procedimiento para
clasificar esos datos, siempre será más conveniente que el código
que se encargue de realizar esa clasificación esté contenido en la
propia clase que usar funciones o procedimientos externos.
Haciéndolo de esta forma, sobre todo, ganamos más legibilidad en
nuestro código.
Siguiendo con el
tema de la encapsulación y la forma de manejar los datos o la
información de las clases, en ocasiones nos podemos encontrar con
la necesidad de que sea la propia clase la que nos avise de que ha
sucedido algo. Puede ser interesante que, por ejemplo, si la clase
usada en el proyecto de la entrega anterior detecta algún tipo de
error al leer o guardar los datos, nos lo comunique. O bien, que si
son muchos los datos que tiene que guardar o leer, nos vaya
mostrando cada uno de los datos que está procesando.
Esto lo podemos conseguir mediante los eventos.
Definir
Eventos en las clases
Hasta ahora hemos
estado usando los eventos de los formularios y los controles.
Ya sabes que esa es la forma en la que el propio sistema operativo
se comunica con esos controles y formularios, de forma que podamos
saber cuando se ha pulsado en un control o se ha movido el ratón
o... cualquiera de las otras cosas que Visual Basic tenga que
saber... ya que, aunque Windows comunica muchas cosas a los
controles y formularios, no le comunica TODO lo que ocurre... pero,
bueno, ese será el tema de otra entrega, lo que ahora nos interesa
saber es que podemos crear nuestros propios eventos en nuestras
clases y que podemos usarlos desde cualquier otro sitio.
Lo primero que
debemos aprender es cómo definir y "disparar" dichos
eventos en nuestras clases.
Para que los
objetos creados a partir de nuestras clases puedan comunicarse con
dicha clase mediante eventos, tenemos que definirlos.
De la misma forma que existen instrucciones o palabras clave para
definir una función o un procedimiento, existe una instrucción que
nos permite indicarle a nuestro querido VB que queremos crear un
evento, la instrucción que usaremos será: Event seguida del
nombre del evento y opcionalmente los parámetros que deba tener.
Por ejemplo:
Event
Prueba(ByVal mensaje As
String)
Nota:
De forma predeterminada, los eventos son públicos, así que, es
opcional indicar esa instrucción.
Esta línea
definiría un evento llamado Prueba que recibe un parámetro
por valor de tipo String.
Si tuviésemos un objeto declarado con la clase que contiene ese
evento, lo usaríamos de la siguiente forma:
Private elObjeto_Prueba(ByVal
mensaje As String)
' ... el código
End Sub
Es decir, de la misma forma que hasta ahora hemos estado usando los
eventos.
Fíjate que también se sigue la nomenclatura estándar de usar el
nombre del objeto un guión bajo y el nombre del evento; aunque este
es un detalle del que tenemos que despreocuparnos, ya que es el
propio entorno de desarrollo el que se encarga de dar nombre a los
eventos de la forma correcta.
Cómo lanzar un evento
Una vez que hemos
declarado un evento en una de nuestras clases, nos queda por saber
cómo hacer que dicho evento se lance, es decir cómo hacemos para
"avisar" al código que usa nuestra clase de que dicho
evento se ha producido.
Esto se consigue usando la instrucción RaiseEvent seguida
del nombre del evento y del parámetro (o parámetros) que tenga
dicho evento.
Por ejemplo, si queremos lanzar ese evento, haríamos algo como lo
siguiente:
RaiseEvent Prueba("El evento
prueba")
Cuando escribimos
la instrucción RaiseEvent, el IDE de Visual Basic nos muestra los
eventos que podemos lanzar, en la siguiente figura, podemos ver que
nos muestra los tres eventos que tenemos definidos:

Fig.1, el IDE de VB muestra los eventos que la clase puede producir.
Como sabrás,
cuando no indicamos si el parámetro es por valor o por referencia,
se supone que es por referencia, por tanto, en el caso del evento ElementoSeleccionado,
el parámetro index será ByRef y en los otros dos,
tal y como se indica en la declaración mostrada en la figura, son
del tipo ByVal.
Nota:
Como te dije antes, cuando declaramos un evento en una clase, ese
evento está definido como público, pero el que esté definido
como público no significa que se pueda lanzar desde cualquier
sitio, ya que sólo se puede usar RaiseEvent nombreEvento()
desde la propia clase en la que se ha definido el evento.
Cómo indicar que una clase debe
interceptar eventos
Debido a la forma
"especial" en la que Visual Basic define los
procedimientos de evento, no basta con declarar una variable del
tipo de la clase que produce los eventos para poder usarlos.
Me explico, para que lo comprendas mejor: Supongamos que tenemos una
clase llamada cConEventos, la cual produce los tres eventos
mostrados en la figura 1. Si declaramos una variable de ese tipo en
otra parte de nuestro proyecto, lo normal es que lo hagamos de esta
forma:
Dim
mConEvento As cConEventos
Después creamos el
objeto en la memoria (lo instanciamos) usando New:
Set
mConEvento = New cConEventos
A partir de este momento podemos
usar dicho objeto, ya que se ha creado en la memoria. Esto ya lo
tenemos claro, ¿verdad? ya que al fin y al cabo es la forma
"habitual" de declarar e instanciar una clase.
Pero esto no
nos permitiría usar los eventos declarados en la clase, a pesar de
que tengamos definidos los procedimientos Sub de los eventos, los
cuales se definirían de la siguiente forma:
Private Sub mConEvento_Aviso(ByVal elAviso As String)
'
End Sub
Private Sub mConEvento_ElementoSeleccionado(index As Integer)
'
End Sub
Private Sub mConEvento_Prueba(ByVal mensaje As String)
'
End Sub
¿Por qué?
Por la sencilla razón de que si bien la clase produce eventos,
Visual Basic no sabe que queremos usarlos, es decir, esos tres
procedimientos que, al menos en teoría, deberían interceptar los
eventos no los interceptarán.
Para que Visual Basic se entere de que la clase se va a usar para
procesar o interceptar los eventos, hay que declararla usando la
instrucción WithEvents.
Sabiendo esto, cuando queramos usar una clase que produce eventos,
tenemos que declararla de la siguiente forma:
Dim
WithEvents mConEvento As
cConEventos
A partir de ese
momento, nuestro querido (y, algunas veces, tozudo) Visual Basic
sabe que nuestra intención es usar los eventos de esa clase.
Además, una vez declarada una variable de esta forma, podemos
comprobar que dicha variable se muestra en la lista desplegable de
los objetos que producen eventos, (la que está en la izquierda del
panel de código), tal como podemos ver en la siguiente imagen:

Fig. 2, Lista con los objetos que producen eventos.
Una vez
seleccionado el objeto en la lista desplegable de la izquierda,
podemos ver los eventos que dicha clase produce, para ello debemos
desplegar la lista de la derecha. En la siguiente figura podemos ver
los tres eventos que la hipotética clase cConEventos produce:

Fig. 3, Los eventos de la clase seleccionada en la lista.
Los eventos se
muestran de forma alfabética, pero el que toma el foco, (el que se
crea nada más que mostrar ese objeto), es el primero que hayamos
definido en la clase. En este caso, el evento Prueba.
A partir de este
momento podemos usar los eventos que queramos, no es necesario tener
que codificarlos todos, sólo los que nos interese interceptar.
Cuando mostramos la lista con los eventos, el IDE de Visual Basic
nos muestra en negrita los que ya tienen código.
Si declaramos una
clase con WithEvents y esa clase no produce eventos, dicha clase no
se mostrará en la lista de clases que podemos usar para declarar
esa variable, además de que al ejecutar la aplicación, el Visual
Basic nos mostrará un error de que dicha clase no produce eventos,
tal y como podemos comprobar en la siguiente figura:

Fig. 4, Error al declarar con WithEvents una variable que no produce
eventos.
Resumen de cómo definir
y usar eventos personalizados:
Resumamos un poco
todo esto, para que quede más o menos claro cómo definir y usar
eventos en las clases:
1- Para que la
clase tenga eventos, debemos definirlos con la instrucción Events.
2- Para lanzar un evento en nuestra clase, debemos usar RaiseEvent
seguida del evento a lanzar.
3- Para poder usar los eventos de una clase desde otra parte de
nuestro proyecto, debemos declarar dicha clase con la instrucción
WithEvents.
4- Una vez definida la variable a partir de una clase que produce
eventos, debemos escribir el código de los procedimientos de los
eventos (siempre serán del tipo Sub) en la forma habitual:
NombreVariable guión bajo NombreEvento, por ejemplo: Sub
mConEventos_Prueba(parámetros)
Para ver en la
práctica todo esto que se ha comentado, vamos a crear un proyecto
en el que definiremos una clase que produce eventos y los
interceptaremos en un formulario.
Para ello vamos a
crear un nuevo proyecto, al que añadiremos una clase llamada
cConEventos que definirá dos eventos, los cuales se producirán al
llamar a un método de esta misma clase.
Uno de esos eventos se producirá mientras se añaden nuevos datos a
un array y el otro al terminar de añadir dichos datos, devolviendo
el número total de elementos.
Veamos el código de esa clase:
Option Explicit
Private mDatos() As String
'
Event NuevoDato(ByVal elDato As String)
Event DatosCreados(ByVal total As Long)
Public Sub CrearDatos()
Dim i As Long
'
ReDim mDatos(10)
For i = 0 To 10
mDatos(i) = "El dato número " & CStr(i)
RaiseEvent NuevoDato(mDatos(i))
Next
RaiseEvent DatosCreados(11)
End Sub
Como podemos
comprobar, los dos eventos que nuestra clase emitirá al
"receptor" de utilice dicha clase, serán:
NuevoDato el cual tiene un parámetro de tipo String que
representará al nuevo dato que se está manipulando y
DatosCreados cuyo parámetro de tipo Long nos indicará el
número total de datos que la clase acaba de crear.
Esos dos eventos se lanzan (o disparan) en el método CrearDatos,
que será el único que podremos usar desde cualquier variable
declarada con el tipo de la clase cConEventos.
El código usado en
este último procedimiento es simple, pero te lo detallo para que no
tengas problemas de comprensión... (sí, ya
se que lo has entendido, pero...)
-Definimos una
variable que usaremos para el bucle For.
-Redimensionamos el array para que tenga 11 elementos, de cero a
diez.
-Hacemos un bucle para que se repita desde 0 a 10.
-En cada ciclo del bucle, asignamos un valor al elemento i
(usando la variable contadora del bucle) del array mDatos y
-lanzamos el evento NuevoDato en cuyo parámetro indicamos el
contenido del elemento que acabamos de asignar.
-Continuamos repitiendo el bucle hasta que estén asignados los once
elementos.
-Por último, lanzamos el evento DatosCreados, en cuyo parámetro
indicamos el valor once.
Para poder usar
esta clase desde el formulario creado en el proyecto, al cual
añadiremos un CommandButton al que llamaremos crearDatosCmd y un
ListBox llamado List1, también definiremos la clase usando
WithEvents, crearemos una nueva instancia en el evento Load del
formulario y al pulsar en ese botón, llamaremos al método
CrearDatos.
Debido a que la variable estará definida con WithEvents, tendremos
que escribir el código en cada uno de los dos eventos para poder
comprobar que todo esto que te estoy contando realmente funciona. En
uno de ellos, el que se produce al añadir un nuevo elemento al
array, haremos que el parámetro indicado en el evento se añada al
ListBox y cuando se produzca el evento DatosCreados, mostraremos un
mensaje que nos avise de cuantos elementos se han creado, para
mostrar ese mensaje usaremos la instrucción MsgBox.
¿Te atreves a
codificar todo esto que te acabo de decir?
No me digas que no,
que me enfado...
Bueno, vale... te doy unas pistas:
Nota:
Si lo vas a hacer por tu cuenta y no quieres pistas, no leas lo
que sigue... aunque dependiendo de la resolución de tu monitor,
es posible que veas el resto del código... así que intentaré
dejar unas cuantas líneas en blanco para que no puedas ver el
código y demás pistas...
Pero me gustaría que lo intentaras antes de ver la solución...
Vale, la
solución te la muestro en una página aparte... así no
tendrás la excusa de que lo has visto sin querer...
Ampliar un formulario con eventos
personalizados
Como ya te he
comentado en otras ocasiones, los formularios realmente son clases,
con un tratamiento especial, pero clases al fin y al cabo. Y como
hemos podido comprobar, podemos definir nuestros propios eventos en
las clases, por tanto, si la lógica y los silogismos no fallan,
podemos definir eventos en los formularios.
La forma de
declarar nuevos eventos en un formulario sería usando Event y el
nombre del evento, es decir, de la misma forma que lo haríamos en
cualquier otra clase.
Para poder usar este "formulario ampliado", tendríamos
que declararlo también con WithEvents.
Como sabemos,
Visual Basic nos permite usar los formularios sin necesidad de crear
una variable que los referencie, ya que, de forma oculta crea una
variable con el nombre que le hemos dado en tiempo de diseño. Por
ejemplo, si en nuestro proyecto tenemos dos formularios y uno de
ellos se llama Form2, podemos acceder a ese formulario usando ese
nombre. Pero también vimos en la entrega anterior que podemos
definir una variable del tipo de un formulario y acceder a dicho
formulario por medio de esa variable. Usando este sistema es la
forma en la que podemos aprovecharnos de las características
"ampliables" de los formularios.
Una vez que
declaramos un evento en un formulario, cuando usamos ese formulario
con WithEvents, los únicos eventos a los que podremos acceder
serán los que estén declarados de forma explícita.
Sin embargo, si declaramos una variable de tipo genérico Form con
la instrucción WithEvents, podremos usar los eventos de dicho
formulario.
Comprobemos que
todo esto es cierto.
Para ello, crearemos un nuevo proyecto al que añadiremos un segundo
formulario.
El primer formulario (Form1) tendrá un botón (mostrarForm2Cmd) y
una etiqueta (Label1).
El segundo formulario (Form2) sólo tendrá un botón
(lanzarEventoCmd).
Veamos primero el
código del segundo formulario, ya que es algo más simple:
Option Explicit
Event Prueba(ByVal mensaje As String)
Private Sub lanzarEventoCmd_Click()
RaiseEvent Prueba("El evento prueba")
End Sub
Como puedes
comprobar, declaramos un evento llamado Prueba que tiene un
parámetro de tipo String.
Cuando se pulse en el botón de ese formulario, se lanzará el
evento Prueba.
Ahora veamos el
código del formulario principal (Form1).
Option Explicit
Private WithEvents mForm As Form
Private WithEvents mForm2 As Form2
Private Sub Form_Load()
' creamos una nueva instancia
Set mForm2 = New Form2
' asignamos a la variable mForm una referencia al objeto recién creado
Set mForm = mForm2
End Sub
Private Sub mForm_Click()
' Este evento se producirá al hacer una pulsación en el Form2
Label1 = "Evento desde Form2: Form_Click"
End Sub
Private Sub mForm_Load()
' Esto evento se producirá al cargarse el Form2
Label1 = "Evento desde Form2: Load"
End Sub
Private Sub mForm2_Prueba(ByVal mensaje As String)
' Este evento se producirá desde el Form2
Label1 = "Evento desde Form2: " & mensaje
End Sub
Private Sub mostrarForm2Cmd_Click()
' mostrar el nuevo formulario a la derecha del principal
mForm2.Move Me.Left + Me.Width + 60, Me.Top
' mostrar el formulario
mForm2.Show
End Sub
Como te he
comentado antes, si declaramos con WithEvents una variable del tipo
específico Form2, sólo podremos acceder a los eventos que nosotros
hayamos definido, por esa razón he declarado otra variable con
WithEvents: mForm que permitirá acceder a los eventos
"genéricos" del formulario, en este caso sólo
interceptamos dos, pero igualmente podríamos acceder al resto.
En el evento Load de este formulario asignamos a la variable mForm2
una nueva instancia del formulario Form2 y a continuación asignamos
también el objeto apuntado por esa variable a la variable mForm de
forma que tanto una como la otra variable estarán apuntando al
formulario Form2.
No te extrañe que se pueda realizar esa asignación, ya que esto es
polimorfismo... ¿recuerdas? La clase Form2 es en realidad un
formulario (del tipo Form), por tanto estamos asignando a la
variable mForm la parte del Form2 que es del tipo Form, es decir
todo excepto el evento que nosotros hemos definido.
Cuando se produzcan los eventos Load y Click del formulario Form2,
se producirán los dos eventos interceptados con la variable mForm,
los cuales mostrarán este hecho en la etiqueta Label1.
Por otro lado, cuando se produzca el evento Prueba, se interceptará
por medio del evento mForm2_Prueba.
Por último, cuando se pulse en el botón para mostrar el formulario
Form2, éste se posicionará a la derecha del formulario principal y
a continuación se mostrará.
Cuando ejecutes el
proyecto, podrás comprobar que al mostrarse por primera vez el
formulario Form2, en la etiqueta se mostrará el mensaje de que se
ha producido el evento Load, ese mensaje sólo se mostrará la
primera vez que pulsemos en dicho botón, o cada vez que pulses en
dicho botón y el segundo formulario no esté cargado en la memoria.
Esto último puedes comprobarlo cerrando el segundo formulario y
volviendo a pulsar en el botón.
Si añades este
código al Form1, se mostrará un mensaje cuando se cierre el
segundo formulario, así podrás comprobar mejor eso que te acabo de
comentar.
Private Sub mForm_Unload(Cancel As Integer)
' Este mensaje se mostrará al cerrar el segundo formulario
Label1 = "El segundo formulario se ha descargado."
End Sub
Si además pulsas
en el segundo formulario se producirá el evento Click y se
mostrará el mensaje correspondiente, lo mismo ocurrirá cuando
pulses en el botón, aunque en ese caso el evento que se producirá
será el que hemos definido.
Espero que con todo
esto que te he comentado tengas más claro cómo definir y lanzar
eventos en las clases, además de saber cómo poder interceptar esos
eventos desde otras partes del proyecto.
En la próxima
entrega veremos cómo definir una clase que amplíe el
funcionamiento de un control. De esa forma tendrás la posibilidad
de ampliar el funcionamiento de los controles y adaptarlos a tus
necesidades.
Nos vemos
Aquí
tienes el fichero zip con el código usado en esta entrega:
basico45_cod.zip 4.97 KB

|