Eventos
y procedimientos de eventos
|
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.
|
Hoy no tengo nada
que rectificar de la entrega anterior... salvo varios errores
tipográficos... De todas formas si lees estas entregas y hay
posteriores, sería conveniente que fueses a la siguiente y te
leyeras el principio, ya que aparte de poner algún comentario
"chorra", suelo dar un repaso a los "errores" de
la entrega anterior.
Ahora viene
el contenido real de la quinta entrega.
No voy a dar la
solución al problema/ejercicio planteado en la entrega anterior,
voy a dejar que la deduzcas. Para que tengas base suficiente, te voy
a contar un poco cómo funciona el Visual Basic y por extensión
todas las aplicaciones de Windows.
En la segunda
entrega creamos un programa
que mostraba un form en el que teníamos una caja de texto
(TextBox), un botón (CommandButton) y dos etiquetas (Label).
Cuando, después de pulsar F5 o CRTL+F5, se ejecuta la aplicación,
de lo único que podemos tener certeza es que se ejecutará el
código que se encuentra en el procedimiento Form_Load. Este
procedimiento (sub) en concreto es lo que se llama un evento, y se
produce cada vez que el formulario (form) se carga (load) en la
memoria. Antes de entrar en detalles del porqué podemos tener la
certeza de que ese código se va a ejecutar, tengo que seguir con mi
'ponencia' de cómo funcionan las aplicaciones Windows.
Se dice, (otros lo
dicen y yo me lo creo), que Windows es un sistema o entorno
operativo 'dominado' por los eventos. Esto ya lo dejé caer al
principio de la
segunda entrega. En Windows
cada vez que movemos el ratón, pulsamos una tecla, tocamos una
ventana o cualquiera de los controles que están en ellas, se
produce un evento. Cuando se 'dispara', (los anglosajones usan
'fire' para indicar que se produce el evento), uno de estos eventos,
Windows le dice al form, (de forma bastante rápida, aunque en
algunas ocasiones no tanto como nos hubiera gustado), que se ha
movido el ratón y que el ratón ha pasado por encima de tal ventana
o de ese control y así con todas las cosas. La verdad, no es de
extrañar que de vez en cuando falle el sistema, ¡¡¡es que no
para de disparar!!! y algunos disparos se le puede escapar y...
...que chiste más
malo, ¿verdad? Ya pensabas que el comentario ese del 'fire' era
porque "el Guille se cree que está traduciendo un artículo de
VB Online"...
Lo que quiero dejar
claro es que a diferencia de los lenguajes que funcionan en MS-DOS,
en Windows no podemos 'predecir' cual será el código que se va a
ejecutar. No debes 'planificar' tu programa dando por sentado que...
"después de esto el usuario 'tiene' que hacer esto otro y así
yo podré hacer una comprobación para..." ¡
NO ! Aquí (en
Windows) no existe la programación lineal, no des nunca por hecho
que esto es lo que va a ocurrir..., porque casi con toda seguridad ¡no
ocurrirá!
Veremos cómo podemos 'controlar' que algunas cosas se hagan cuando
nosotros queramos, pero esto será sólo cuando el usuario de
nuestra aplicación realice una 'acción' que estaba prevista;
también codificaremos para que se ejecute parte del código cuando
el usuario no haga lo que habíamos previsto que hiciera. Pero eso
lo iremos viendo poco a poco...
Todo programa de
Windows tiene un punto de entrada; en el caso de Visual Basic, puede
ser bien un formulario o bien un procedimiento de un módulo BAS,
(de debe llamarse obligatoriamente Main); los módulos los veremos
en otra ocasión.
Normalmente las aplicaciones suelen tener más de un formulario y
algún que otro módulo. Pero tenga uno o ciento, siempre hay un
único punto de entrada (o de inicio). Por regla general suele ser
un formulario. En nuestro ejemplo sólo tenemos un form en el
proyecto, por tanto no hay duda alguna de cual será el punto de
entrada del programa.
Perdona si me extiendo en esto, pero tanto si tú
lo sabes como si no, creo que tú ahora lo sabes...
(es difícil esto de escribir una cosa para tanta gente con
distintos niveles...)
Cuando Windows inicia el programa, 'debe' cargar el formulario en la
memoria. Y desde el momento que se prepara para hacerlo, ya está
con los 'tiritos' y mandando 'mensajes' a la ventana (todo
formulario es una ventana), pero no sólo le está avisando a la
nuestra que la acción ha empezado, sino que lo hace prácticamente
a los cuatro vientos; si otra aplicación quiere enterarse de lo que
ocurre en Windows, sólo tiene que conectarse a la 'mensajería' de
éste entorno operativo y leer las 'noticias'... pero esto ya es
complicarse la vida y todavía no nos la vamos a complicar tanto...
o más... (pensará alguno después de respirar aliviado...)
Lo que ahora interesa es saber que el 'evento' Form_Load se produce
cuando esta ventana pasa del anonimato a la vida pública, aunque no
la veamos, estará en la memoria de Windows y se producirá el
primer evento del que tenemos 'certeza', por tanto este es un buen
sitio para poner código de inicialización.
Realmente el Form_Load no es lo primero que puede ocurrir al
iniciarse un formulario, pero por ahora vamos a pensar que sí;
porque sino esta entrega no la termino hasta después del verano...
¡¡¡que me gusta darle vueltas a las cosas!!!
Ahora que se ha
cargado el form en la memoria... ¿que ocurrirá? Pues, si el
usuario se va a tomar unas cañas: nada. Sólo ocurrirá algo cuando
interactuemos con el form, es decir, le demos razones a Windows para
'pegar tiritos'.
Nuestra aplicación, (te recuerdo que tenía, entre otras cosas, un
textbox y un botón), esperará a que se produzca algunas de las
acciones para las que está preparada.
Y la pregunta es ¿que es lo que puede ocurrir? Para saber 'todas'
las cosas que pueden ocurrir en nuestra ventana, (no recuerdo si has
pulsado F5 o no), finaliza el programa y muestra la ventana de
código.
En la parte
superior de la ventana hay dos listas desplegables, la de la
izquierda tiene todos los controles, (en otra ocasión veremos que
no siempre es así), que tenemos en el form, incluido éste, además
de uno especial que se llama General.
Pulsa en la lista de la izquierda, para que se despliegue y te
mostrará esto:

Estos son los cinco
controles, incluyendo el form, que pueden recibir mensajes de
Windows. Pulsa en cualquiera de ellos. En la lista de la derecha
están todos los procedimientos (eventos) soportados por el control
de la izquierda. Cada control mostrará los eventos que VB y/o
Windows permite que se produzcan. Estos ocurrirán por distintos
motivos, cada uno tiene su 'tarea', nos pueden avisar de que se ha
pulsado el ratón, que se acaba de pulsar una tecla, que se ha
modificado lo que antes había, etc.
Una llamada
a la precaución.
Los eventos son 'procedimientos' y no sólo se puede llegar a ellos
porque se produzca una acción del usuario o del propio Windows y si
me apuras, incluso de Visual Basic... Nosotros podemos 'provocarlos'
¿cómo? simplemente haciendo una llamada al SUB que queramos o
actuando en el control de manera que ocurra alguno de los eventos
soportados.
Por ejemplo, en el Form_Load tenemos que se asignan cadenas vacías
tanto al Label2 como al Text1:
Label2 = ""
Text1 = ""
Cuando VB asigna una cadena
vacía (o cualquier otra cosa), al Label2 está borrando el
contenido del 'Caption' de esta etiqueta y asignando algo nuevo, es
decir lo está cambiando. En nuestro programa no ocurre nada, salvo
que se borra lo que allí hubiera, pero realmente se está
produciendo el evento Label2_Change, porque hemos cambiado el
contenido. VB sabe que no hemos escrito código para manejar esta
situación, así que... hace la vista gorda y simplemente cambia el
contenido del Label2.Caption sin hacer nada más.
Pero al asignar una cadena vacía al Text1, también se borra el
contenido y se produce un Change, sólo que en este caso, al no
tener Caption, lo que se borra es el Text; de todas formas sea como
fuere, se produce el evento Text1_Change. Nuestro querido amigo
Visual Basic, sabe que hemos escrito código para este evento y sabe
que tiene que hacer lo que allí se dice...
En este caso, nuestro código se ejecuta, pero realmente no hace
nada de interés o por lo menos nada que se pueda apreciar de forma
visual. No voy a explicar para que sirve ese código, porque ya lo
hice en su día, lo que espero es que hoy lo entiendas mejor...
¿Cómo? que no sabes de qué código estoy hablando... pues del
ejemplo de la segunda entrega, creo que ya lo dije al principio... a
ver si prestamos más atención y dejamos de pensar en las
vacaciones... ¡estos niños!
El código
interesante es el que se ejecuta cuando se pulsa en el botón:
Label2 = "Hola "
& Text1
Aquí se asigna al Caption del
Label2 lo que hay después del signo igual, en este caso actúa
'casi' como una variable. Y ya sabrás que antes de asignar un valor
a una variable, se procesa todo lo que está después del signo
igual, por tanto, Visual Basic tomará el contenido del Text1, es
decir lo que se haya escrito y lo unirá (&) a la palabra
"Hola ", una vez hecho esto, lo almacena en el Caption del
Label2.
Y si en lugar de guardarlo en un control label, lo asignáramos a
una variable... y si en lugar de escribir nuestro nombre,
escribiésemos un número... y si la variable en la que guardamos
ese número se llamara, por ejemplo, maxBucle o elMaximo...
Pues que tendríamos una parte resuelta de la tarea esa que puse
como ejercicio en la entrega anterior.
Pero, este form de la segunda entrega no nos sirve. Tendremos que
cargar el de la vez pasada y añadirle un par de cajas de textos y
un par de etiquetas, para que indique lo que se debe introducir en
cada textbox; el aspecto sería este:

Pero nos
encontramos con un problema: ¿cómo puedo asignar un valor a
maxBucle, si las constantes no pueden cambiar de valor? Fácil,
conviértela en variable. Pero debes recordar la pista que di al
final: "Las variables declaradas dentro de un procedimiento son
solamente visible dentro de ese procedimiento".
De este tipo de variables se dice que son locales.
Alumno: ¿Que significa esto?
Guille: Que sólo pueden usarse dentro del
procedimiento en el que se han DIMensionado o
declarado.
A: Vale, "mu bonito" y ¿que pasa?
G: Esto... que no pueden usarse en otro sitio...
¿Recuerdas la
recomendación de usar Option Explicit?
Pues gracias a Option Explicit, se solucionan el 99% de los fallos
'involuntarios' con las variables... y no exagero!!!
Es super-fácil escribir de forma incorrecta el nombre de una
'bariable' y no vengas con el cuento de que a tí nunca te ocurre,
porque no me lo voy a creer... bueno, de ti si, pero no todos son
tan minuciosos como tú...
(para que nadie se sienta ofendido/a, quiero que veas en esto que
acabo de poner... la intención que tiene, es decir que me dirijo a
más de un "ti"... ya sé que no eres tan torpe como para
no haberlo 'captado', pero con esta aclaración me quedo más
tranquilo.)
Según cómo y
dónde se declare una variable, su 'visibilidad' o área de
cobertura, será diferente... también se puede usar la palabra
ámbito... es que como en las emisoras de radio se habla de la
cobertura... pues...
Una variable puede tener estas coberturas:
--Privada o Local a nivel de procedimiento (Sub, Function, etc.)
--Privada o Local a nivel de módulo (FRM, BAS, etc.)
--Pública o Global a nivel de aplicación (en este tipo hay una
forma especial de usar las variables que veremos en otra ocasión)
Explicando los dos
primeros puntos.
Cuando declaramos o dimensionamos una variable 'dentro de' un
procedimiento, ésta sólo es visible en ese procedimiento; fuera de
él no es conocida y cualquier uso que se intente hacer de ella,
producirá un error... si has sido obediente y has usado el Option
Explicit. En caso de que no hayas puesto la 'obligación' de
declarar todas las variables, te llevarás una sorpresa de que no
tiene el valor esperado.
A: ¡JA!
G: ¿No te lo crees? Vale. Vamos a comprobarlo.
Abre un proyecto nuevo, pon un textbox y un botón..
Abre la ventana de código, borra el Option Explicit.
En el Form_Load haz esta asignación: Incredulo
= "No me lo creo"
En el Command1_Click escribe
esto otro: Text1 =
Incredulo
Pulsa F5 y haz click en el
botón...
¿Que ha pasado?
(Tengo que comprobarlo, para no meter la pata, pero se supone que el
texto se borrará sin poner nada...)
Bien, eso ocurre porque la variable usada en el Form_Load no tiene
nada que ver con la usada en el Command1_Click.
Con esto comprobamos o demostramos que podemos tener variables
diferentes con el mismo nombre. La única condición es que no
pueden estar juntas, aunque hay un truco para juntarlas sin que
ellas se enteren...
En este último
ejemplo, nuestra intención es tener una variable que sea 'conocida'
en todo el form. Cuando necesitemos variables con un ámbito a nivel
de módulo, tenemos que declararla o dimensionarla en la sección de
las declaraciones generales; ya sabes, en la lista izquierda de la
ventana de código seleccionas General y en la de la derecha
Declarations ( Declaraciones).
Muestra la ventana de código y en General/Declaraciones escribe:
Option Explict 'retornamos
a las buenas costumbres
Dim Incredulo As String
Pulsa F5 para ejecutar el
programa, pulsa en el botón y... ¡AHORA SI!
Tenemos una variable que puede ser 'vista' en todo el form. Ya
puedes usar 'Incredulo' donde quieras, ahora siempre será la misma
variable y contendrá lo último que se le haya asignado.
A partir de la
versión 4 del VB, entra en juego una nueva palabra, 'Private', que
suele usarse en las declaraciones de las variables a nivel de
módulo, de esta forma es más fácil entender la intención de la
misma; por tanto la declaración anterior podemos hacerla también
así:
Private Incredulo As String
Hay veces, sobre
todo si ya has programado antes en MS-DOS, que usamos variables como
a, b, c, i, j, k...etc., (al menos yo estoy acostumbrado a llamar i,
j, k a las variables que uso en los bucles FOR), cuando hago un
bucle dentro de un procedimiento uso i como variable índice, (o
variable 'contadora'), pero ¿que ocurre si esta 'costumbre' quiero
aplicarla en varios procedimientos? Pues que dimensiono una variable
i en cada uno de ellos y aquí no ha pasado nada!!!
Usar el
mismo nombre de variable en distintos procedimientos
Como indica el encabezado de este nuevo párrafo, cosa que ya he
comentado antes, podemos tener distintas variables con el mismo
nombre en distintos procedimientos; para ello, sólo hay que
dimensionarlas y el VB las almacenará en distintas posiciones de la
memoria para que no se 'mezclen'.
En la entrega
anterior, teníamos un procedimiento llamado Contar
que se usaba para eso,
contar...
En este ejemplo vamos a usar un sub también llamado contar, para
ver en acción todo esto que estoy diciendo.
Sitúate en la parte General/Declarations de la ventana de código y
escribe o "copia/pega" lo siguiente:
Private Sub Contar()
Dim i As Integer
For i = 1 To 2
Print "i en contar= "; i
Next
End Sub
Ahora en el Form_Load, escribe esto
otro:
Dim i As Integer
Show
For i = 1 To 3
Print "i en el Form_Load= "; i
Contar
Next
Ejecuta el programa
y ...
Has visto lo que ocurre... A pesar de que ambas variables tienen el
mismo nombre, son diferentes. La variable i del Form_Load no es la
misma que la variable i de Contar.
Cuando usamos variables locales es como si cambiásemos el nombre y
se llamaran NombreProcedimiento_Variable.
Sé que puede ser una forma 'rebuscada' de explicarlo, pero así te
haces una idea.
Todas las variables
declaradas en un procedimiento, sólo son visibles en ese
procedimiento. Si has usado QuickBasic o el compilador Basic de
Microsoft (que usaba el QuickBasic Extended QBX), esto ya existía y
también existía la forma de hacer que una variable declarada en un
procedimiento, fuese visible fuera de él; para ello declarabas la
variable como Shared (compartida); pero en VB eso NO EXISTE. La
única forma de compartir una variable es declarándola en la
sección General de las declaraciones.
Prueba ahora esto.
Sustituye el procedimiento Contar por este otro:
Private Sub Contar(j As Integer)
Dim i As Integer
For i = 1 To j
Print "i en contar= "; i
Next
End Sub
Aquí hacemos que
Contar reciba un parámetro y el valor que recibe lo usamos como el
límite final del bucle For, es decir contará desde UNO hasta el
valor de j.
Sustituye la llamada a Contar del Form_Load por esta:
Contar i
Le damos a Contar el parámetro i. Por tanto cada vez que se llame a
este procedimiento le estamos diciendo que i es el valor máximo que
tomará el bucle For que tiene dentro.
¿Cómo reaccionará? ¿Se confundirá? ...
No, no voy a
dejarlo para la siguiente entrega, es tan obvio que lo voy a
explicar ahora mismo:
Al procedimiento Contar le da igual que se use una variable llamada
i o cualquier otra, incluso un número. Lo único que necesita y
espera, es recibir un valor numérico (del tipo Integer) y lo
asignará a la variable j. Por tanto no ocurrirá nada extraño.
Ejecuta el programa y fíjate en lo que ocurre. Sé que lo has
deducido, eso está bien... vas aprendiendo... 8-)
¿Cómo? ¿Que tú aún no lo has captado?
Otra cosa sería
pretender usar una variable declarada a nivel de módulo, dentro del
procedimiento, que tuviese el mismo nombre.
Si te has quedado 'con la copla', tu mismo sabrás la respuesta...
¡Efectivamente! Si dentro de un procedimiento tenemos una variable
dimensionada con el mismo nombre de una declarada a nivel de módulo
o a nivel global, (para usarla en cualquier sitio de la
aplicación), tendrá 'preferencia' la variable local... Ésta
'tapará', ocultará o como prefieras decirlo a cualquier otra
variable del mismo nombre...
En la próxima
entrega veremos más casos y cosas de las variables. Comprobaremos
cómo usarlas a nivel Global. Pero por ahora vamos a terminar con el
programa que teníamos planteado en la entrega anterior:
Aunque realmente deberías saber cómo solucionarlo...
Lo que seguramente no sabrás, es cómo hacer que estas variables
tomen el valor...
De acuerdo, lo
explicaré. Carga el ejemplo de la cuarta entrega.
Hay varias soluciones a este problema; una sería usar variables
locales, esta decisión no 'justifica' el 'pedazo' de pista que te
di... pero esto es lo que hay.
La línea Const
minBucle = 1, maxBucle = 10. Debe quedar así:
Const minBucle = 1
Dim maxBucle As Integer
Esto hará que
maxBucle deje de ser una constante y pase a ser una variable, con lo
cual podremos asignarle cualquier valor.
Pero, ¿cómo le asignamos el valor?
Vamos a dar por sentado que lo que se escriba en el Text1 será el
valor que debe tener maxBucle; entonces lo único que haremos es
asignar a maxBucle lo que se escriba en el Text1...
Vale, pero ¿dónde?
Pues... por ejemplo, después de la declaración, así que en la
siguiente línea al Dim maxBucle... escribe los siguiente:
maxBucle = Text1
Esto en teoría no daría problemas, al menos en condiciones
normales, ya que el contenido de un textbox es del tipo Variant y ya
vimos que un Variant puede almacenar cualquier cosa, por tanto si es
un número 'intentará' convertir al tipo de la variable que
recibirá el valor.
Esto no siempre funciona, sobre todo si el contenido del Text1 no es
numérico. Por ahora vamos a hacerlo simple, si el usuario (en este
caso tú), escribe algo no numérico lo vamos a considerar CERO... o
casi...
Cambia la asignación anterior por esta otra...
¡¡¡ ALTO !!! Antes de hacerlo, pruébalo e
intenta escribir una palabra en lugar de un número... ¿que ocurre?
Pues que VB no se complica la vida y te dice que 'nones'...
(realmente dice Type Mismatch... Error de Tipos, es decir que lo que
has escrito no es capaz de convertirlo a Integer)... así que
escribe esto otro:
maxBucle = Val(Text1)
Con esto lo que hacemos es convertir el contenido del Text1 a un VALor
numérico y lo asignamos en la variable...
¿Problemas? Que el valor sea mayor del que se puede guardar en un
Integer, pero eso ya no es asunto de esta entrega...
Ahora nos queda
convertir elMaximo en variable y asignarle el valor que hay en el
Text2. ¡Efectivamente! hacemos lo mismo, sólo que esta vez dentro
del procedimiento Contar, por tanto la declaración Const elMaximo =
1000&, la tienes que quitar y poner estas dos líneas:
Dim elMaximo As Integer
elMaximo = Val(Text2)
Aquí el único inconveniente es que esta asignación se hace cada
vez que se entra en este procedimiento... y eso, amigo mío, no es
un buen estilo de programación... Sobrecargamos de forma
innecesaria al procesador... ten en cuenta que la conversión a
número y la asignación ¡¡¡se ejecuta cada vez que se entra en
Contar!!!
Lo mejor para este caso sería declarar elMaximo como variable a
nivel de módulo. Por tanto, borra el Dim elMaximo... del sub Contar
y colócalo en la parte de las declaraciones generales del form.
Ahora... ¿dónde asignamos el valor para evitar la sobre-carga? Ya
que tenemos la variable a nivel de módulo, ésta será 'vista' en
todos los procedimientos del formulario, por tanto lo lógico sería
hacerlo en el Command1_Click, ya que cuando nos interesa a nosotros
saber cuanto tenemos que contar es precisamente cuando pulsamos en
el botón...
Pero... ¿dónde exactamente?, después de Contando = 1
Prácticas y
ejercicios
¿Quieres algo para practicar?
Este ejercicio se lo ponía a mis alumnos, cuando daba clases de
BASIC, hace ya unos 10 años o más... y consistía en pedir el
nombre, pedir la edad y mostrar el nombre tantas veces como años
tengamos...
Claro que con el BASIC del MS-DOS era más directo y se sabia cuando
se debía empezar a mostrar el nombre, para solventar esto, se
mostrará el nombre 'edad' veces cuando se pulse en un botón. El
aspecto del form sería algo así:

No creo que sea
complicado, así que vamos a complicarlo un poco más:
Mostrar el nombre 'edad' veces, dentro de un label, para ello el
label deberá ocupar la parte izquierda del form.
Y una tercera versión, sería lo mismo que esta última, pero cada
vez que se muestre el nombre se haga en una línea diferente.
La pista:
En la segunda entrega vimos de pasada el CHR. Pues decirte que si
añadimos a una variable un CHR(13), lo que hacemos es añadirle un
retorno de carro, es decir lo que venga después se mostrará en la
siguiente línea... siempre que se 'concatene'. También existe una
constante definida en VB4 o superior que es vbCrLf, esto es un
retorno de carro (Cr) y un cambio de línea (Lf)
¡Que te diviertas!
Con esto acaba el
tema por ahora... no, no se acaba el curso, no te alarmes; la
próxima entrega será... pues, es que... yo creo que... si no... un
día de estos