Curso Básico de Programación
en Visual Basic

Entrega Veintidos: 4/Sep/98
por Guillermo "guille" Som

Si quieres linkar con las otras entregas, desde el índice lo puedes hacer

 

Ya habrás notado a lo largo de todas estas entregas, que de planificación, "nasti de plasti", osease nada de nada... Y es que las cosas van surgiendo y mezclándose y al final... hasta casi se entiende lo que digo... y eso que es difícil, sobre todo cuando entre una entrega y la siguiente casi han pasado dos meses... pero, como digo, suerte para los que empiezan cuando ya hay un montón de entregas, al menos se las leen del tirón... porque en más de una ocasión me han escrito, sin ánimo de ser demasiados criticones, supongo, que si el que empezara el curso en abril del año pasado, tuviese que esperar a que lo terminase... lo tenía claro... y es cierto, pero confío que los que empezasteis de los primeros, ya hayáis aprendido a leer el manual y la ayuda del Visual Basic, y hasta os hayáis atrevido con algunos de los muchos libros que hay sobre este lenguaje... que si bien, al principio os podrían parecer difíciles de entender, al menos ahora podáis entenderlos, aunque sea un poco...

Como decía al principio, o al menos intentaba decir, el curso no lo tengo planificado y las cosas van surgiendo... la verdad es que ahora que leo los apuntes que tenía, fechados el 9 de julio, no se a que viene todo este rollo, así que mejor lo dejo y seguimos con el tema este de los eventos y los controles que podemos usar con el Visual Basic.

Ya sabes, (y si no lo sabias, te lo cuento yo ahora), que los controles suelen estar incluidos en un formulario, también pueden estar en controles diseñados por nosotros y en otros tipos de "contenedores", (de basura habrá pensado alguno, sobre todo cuando se pone manos a la obra y no sale lo que quiere... no te desesperes, ya tendrás tiempo de pillar buenos cabreos...), pero para simplificar, vamos a pensar que están puestos en un formulario.
Esta aclaración viene a cuento para lo que te voy a contar ahora.

Insisto, esto lo tengo manuscrito desde hace dos meses y la verdad es que o recuerdo en que estaba pensando cuando lo escribí, porque nada de lo que viene a continuación tiene que ver con el hecho de que un control esté en un formulario u otro contenedor, pero al menos podrás "intuir" que los controles no tienen porqué estar siempre "puestos" en un formulario.

Cuando se produce un evento en un formulario, producido por el propio Form o por cualquier control contenido en él, se deja de procesar el código que se estaba ejecutando y se pasa a procesar el código contenido en el evento.
Es importante que tengas muy presente esto que acabo de decir, ya que es la causa de efectos no deseados.
Por supuesto, si el evento que se produce no tiene código asociado, no pasa nada.

Aclaremos esto un poco; si te has fijado en la ventana de código, cuando seleccionas un control de la ventana izquierda, incluso el propio formulario, en la ventana de la derecha te muestra los eventos disponibles, es decir los eventos que Visual Basic podrá interceptar, normalmente no son todos los que se generan, pero sí son los que el propio lenguaje nos permite interceptar y por tanto en cualquiera de ellos podemos añadir nuestro código para hacer algo cuando dicho evento se produzca.
Pero esto no quiere decir que tengamos que escribir código en todos ellos, sino sólo en los que nos interesen, pero el hecho de no escribir código en un evento, no quiere decir que no se producirá. Por ejemplo, si no escribes código en el evento LOAD de un form, no impedirá que el form se cargue, se cargará, independientemente de que queramos interceptar ese hecho o no.

Sigamos con lo que pasa cuando se está ejecutando una parte del código y se produce un evento.
Por ejemplo, si mientras se procesa el código de un evento, vuelve a producirse ese mismo evento, se vuelve a procesar el código desde el principio y una vez terminado este segundo evento, se continúa por dónde se interrumpió el anterior.
Esta es una de las gracias de Windows y todo el tema de los eventos.
No te preocupes si no te enteras que pronto veremos ejemplos.

Aunque en muchas ocasiones esto no ocurre mientras nosotros no lo permitamos, hay veces en las que no podemos "predecir" que es lo que ocurrirá, salvo que comprendamos perfectamente cómo funcionan los controles y sepamos cómo y cuando se producen algunos eventos; también puede intervenir la suerte de darnos cuenta de que... "si hago esto con este control, puede ocurrir este o aquel evento"
Y esto que os digo es tan cierto como que nadie me ha regalado aún un portátil... je, je.

Vamos a ver un pequeño ejemplo para que salgas de dudas.
Crea un nuevo proyecto, cambia la propiedad Autoredraw del form para que sea True, de esta forma, si lo redimensionas podrás ver lo que se ha imprimido en el.
Escribe el siguiente código en el evento Click del form:

Private Sub Form_Click()
    Dim i As Long
    Dim j As Long

    Print
    For i = 1 To 10
        Print i;
        For j = 1 To 2000
            DoEvents
        Next
    Next
    Print
End Sub

Ejecuta el programa, cuando hagas click en el formulario, verás que se imprimen los números del 1 al 10, van un poco lento, para que puedas hacer lo que te diré ahora, mientras se estén imprimiendo los números, vuelve a hacer click en el form, hazlo más o menos rápido, si tus reflejos no te funcionan como quisieras, cambia el valor 2000 del bucle j con otro mayor, así se irán imprimiendo los números de forma más lenta.

Habrás notado que cuando aún no se ha terminado de imprimir los primeros diez números, (si has pulsado antes de que se terminen de imprimir, claro), se empiezan a imprimir en la siguiente línea desde 1 y cuando dejes de hacer click, irán terminando los bucles... supongamos que has pulsado tres veces, el resultado podría ser este:

1 2 3 4 5 6 7 8
1 2 3 4 5 6
1 2 3 4 5 6 7 8 9 10
7 8 9 10
9 10

El primer bucle se interrumpió en 8, se inició el segundo, que se interrumpió en 6, se inició el tercero y continuó hasta finalizar, después continuó el segundo, (el que se quedó en 6), y termina, una vez que terminó el segundo, se continua con el primero que se quedó en 8 y por eso se imprimen el 9 y 10.
Como notarás, no se han perdido los valores... y el Visual recordó por dónde se quedó... esto es debido a que todas las variables de un procedimiento son locales a este procedimiento, sea un evento o no, (realmente los eventos son SUBs que son llamados por Windows), y se guardan antes de volver a entrar en el procedimiento y una vez que el procedimiento acaba, se desechan... la memoria que se usa para guardar temporalmente los valores de las variables de un procedimiento se llama STACK (o pila del programa), hay que tener en cuenta que esta "pila" no es infinita y puede llegar a llenarse... sobre todo cuando las cosas no se hacen bien. A este tipo de variables locales, también se les llama variables automáticas, por el hecho de que una vez que no se necesitan son automáticamente borradas... cosa que no ocurre cuando las variables se declaran a nivel de módulo o se declaran "estáticas", más adelante veremos más sobre esto.

Lo que este pequeño ejemplo demuestra es lo que te he explicado antes, que se puede "reentrar" en un evento (y por extensión a cualquier procedimiento); si esto mismo se hiciera por código, no porque el usuario interviene con su ratón, tendríamos lo que se llama un procedimiento "recursivo", es decir que se llama a sí mismo... tendremos ocasión de ver algún ejemplo.

El evento más dado a este tipo de "recursividad" es el evento Click. Este evento se produce cada vez que pulsamos con el ratón sobre un control, incluso sobre un formulario, como hemos visto en este ejemplo.
Este evento (Click), está presente en la mayoría de los controles, prácticamente en todos.

La verdad es que poder "seguir" esto, imaginándose cómo lo hace el VB, es un poco difícil... vamos a ver otro caso, más usual y que seguramente será más fácil de entender...

Supongamos que tenemos un código que procesa una serie de cosas y ese proceso puede llegar a ser largo. Si ese código lo tenemos en el evento Click de un botón, con idea de que se procese cada vez que se pulsa en el botón, (cosa super-habitual), si no hacemos nada especial, mientras se esté procesando el código, el form completo se quedará "congelado", una vez que haya terminado el proceso, se continuará "aceptando" nuevos eventos, normalmente se quedarán pendientes de procesar y se ejecutarán a continuación de terminar el proceso largo...
Por eso en ocasiones es conveniente usar, como en el ejemplo anterior, la instrucción DoEvents. Con esta instrucción permitimos que Windows notifique "en seguida" de que ha ocurrido otro evento y nos da la posibilidad de procesarlo en ese momento.
Eso o al menos indicar al usuario de que tenga paciencia y espere a que se termine de hacer lo que se estaba haciendo, ya que si no lo hacemos puede pensar que el programa se ha quedado "colgado".

Vamos a modificar el código anterior del Form_Click para ver esto que estoy diciendo.
Sustitúyelo por este otro:

Private Sub Form_Click()
    Dim i As Long
    Dim j As Long

    For i = 1 To 10
        Print i;
        For j = 1 To 2000000 'dos millones
        Next
    Next
    Print
End Sub

Ejecuta el programa y haz click, verás que no pasa nada, haz click de nuevo y esta vez si que ocurre, hasta puede que el form se quede en blanco y al rato volverá a la normalidad con las dos ristras de números impresos... o más si no has tenido paciencia suficiente...
Una forma de solventar esa espera... es hacer que se muestre un mensaje pidiéndonos que esperemos... pero eso no evitará que volvamos a hacer click en el form... incluso el mensaje no se mostrará hasta que se haya terminado de hacer lo que se estaba haciendo... para que el mensaje se vea enseguida, se pueden hacer dos cosas:
Una: usar un DoEvents justo después de imprimir el mensaje
Dos: usar Refresh para que se "pinte" el control, en este caso el formulario.

...
    Print "Un momento por favor..."
    Refresh
    For i = 1 To 10
...

En ambos casos tendríamos el mensaje mostrado en la pantalla, vale, pero si vuelves a hacer click... se volverá a procesar todo el código, etc... Aunque en esta ocasión, hasta que no finalice, no continúa el siguiente.

¿Cómo podemos evitar la re-entrada en un evento cuando aún no ha terminado?
Usando lo que se llama un FLAG (o bandera), es decir una variable que nos indique que ya se está procesando el código y de esta forma poder evitar que se repita si aún no ha terminado.
Como ya te he contado antes, las variables locales son automáticas y se desechan una vez finalizado el procedimiento, al ser automáticas no "recuerdan" el valor que tenían antes, salvo en las ocasiones que el VB las guarda en el Stack, pero no son recordadas en las diferentes ocasiones en las que entran en el procedimiento.
¿Recuerdas que te comenté lo de las variables estáticas?
Pues este tipo de variables son las que podemos usar para prevenir estos casos. Las variables estáticas se declaran de igual forma que las variables normales, salvo que en lugar de usar Dim, se usa Static. Una vez declarada una variable estática, deja de ser automática y VB no guarda una copia cada vez que se entra en el procedimiento, sino que usa la misma cada vez que se procese el código de ese evento. Por tanto cada vez que se "cuela" el código en un evento (o procedimiento), podemos saber si ya hemos estado antes, sin terminarlo o no... para ello habría que hacer esto:

Private Sub Form_Click()
    Dim i As Long
    Dim j As Long
    Static bFlag As Boolean

    'Si no estamos en el evento
    If bFlag = False Then
        'conectamos la bandera
        bFlag = True

        Print "Un momento por favor..."
        Refresh
        For i = 1 To 10
            Print i;
            For j = 1 To 1000000
            Next
            'Debemos permitir que se procesen los mensajes
            DoEvents
        Next
        Print

        'Desconectamos la bandera
        bFlag = False
    End If
End Sub

Aquí hay dos detalles a tener en cuenta, el primero es el uso de la variable estática, el otro es el DoEvents, sin éste, el uso de la variable estática no serviría para nada, ya que al no permitir a Windows que notifique los eventos, estos se quedan guardados en espera a que terminen los que había pendientes, y para que no se queden guardados, usamos el DoEvents.
Prueba a pulsar varias veces en el formulario mientras se muestran los números, verás que no se vuelven a mostrar.

Te explico un poco cómo funciona esto:
La primera vez que se entre en el evento, el valor de bFlag valdrá False, esto siempre es así con las variables Booleanas, cuando se quiere averiguar el valor de una variable que no se ha usado, ésta tiene un valor "vacio", cero en las numéricas, cadena vacía en las cadenas y False en el caso de las booleanas.
Como vale False, se cumple la condición, por tanto se asigna el valor True a esa variable y se continúa procesando el código.
Cuando se llega al DoEvents, Windows procesa los mensajes que tuviese pendiente y si tiene que enviarle uno a este formulario, lo hará.
Suponiendo que hemos pulsado otra vez el ratón, al procesarse los mensajes pendientes, Windows envía al form un nuevo evento Click. Entonces se entra de nuevo en este procedimiento, pero esta vez, el valor de bFlag no es False, por tanto no se hace nada, ya que la condición no se cumple y se sale del evento.
Cuando se ha salido del evento, se continúa por donde estaba antes y se sigue procesando el bucle, etc.
Una vez terminado el bucle i, se asigna de nuevo el valor False a bFlag, para así poder permitir que se procese en otra ocasión el código que hay.
Todo esto es posible gracias a que la variable bFlag es estática y el valor almacenado se mantiene entre las distintas llamadas, si no asignáramos el valor False al final, no podríamos entrar más en este evento, ya que nunca se cumpliría la condición, así que hay que tener cuidado con las variables estáticas, si el uso que le queremos dar es parecido al que hemos visto.

Hay ocasiones en las que un evento se "reproduce" muy a pesar nuestro, más que nada porque hay veces en las que no es tan obvio.
Por ejemplo, cuando se selecciona un elemento de un ListBox, se produce un evento Click; y si en ese evento Click, tenemos algún código que seleccione elementos del control... podemos tener al Visual Basic bastante atareado... entrando en un evento y desde ese evento entrando a otro y así durante un rato... mientras tanto, nuestro pobre VB guardando las variables automáticas en el Stack (pila), pero llega un momento en el que la pila se llena... y en ese momento nos suelta un mensajillo de esos que nos indican que ya está hasta la coronilla de nosotros y de nuestra mala forma de programar... y se para...

Igual que yo me voy a parar aquí, porque ya está bastante bien de tanto Click y tanto Stack y todas esas cosas...

Al final, la película que te he contado no era exactamente lo que estaba en el guión, pero más o menos lo que te he explicado era lo que quería explicarte...

Para terminar, te diré que en algunos controles, al pulsar Intro se produce también un evento Click, por ejemplo en los CommandButtons. Por supuesto para que se procese ese Intro, el control debe tener el foco.

Bueno, lo dicho, dejemos aquí esta entrega que hay más cosas que hacer.
A ver si la próxima no tarda tanto como esta y seguimos viendo... (espera que mire los apuntes, a ver que es lo que hay preparado), ... en las notas del 12 de julio están los eventos del ratón, en las del día 13, los del teclado... en fin, esto promete seguir pesadillo... pero que le vamos a hacer... todo sea para que te enteres un poco de cómo va todo esto de los eventos...

Lo dicho en otras ocasiones: pórtate bien y no hagas estropicios y si quieres mandarme un mensajillo sobre el curso, usa el link este que te pongo.

Nos vemos.
Guillermo


 
entrega anterior ir al índice siguiente entrega

Ir al índice principal del Guille