eduardo@olaz.net E Ed du ua ar rd do o O Ol la az z
Estructuras de Control, segunda parte Las Instrucciones While - - - Wend La estructura de bucle For Contador = ValorInicial To ValorFinal Step Salto - - Next Contador que hemos analizado en la entrega anterior, realiza una iteracin del cdigo un nmero de veces que resulta previsible en funcin de los valores ValorInicial, ValorFinal y Salto. En las sucesivas iteraciones, la variable Contador va tomando valores que varan de forma constante entre un ciclo y otro. El cdigo incluido en el bucle se ejecutar al menos una vez, aunque fuera de forma incompleta si en su camino se tropezara con una sentencia Exit For. Supongamos que necesitamos una estructura que se vaya ejecutando mientras el valor que va tomando una variable cumpla determinadas caractersticas, y adems que esa variable pueda cambiar en forma no lineal. Para realizar esta tarea podemos contar con la clsica estructura While - - Wend. Digo lo de clsica porque es un tipo de estructura que ha existido desde las primeras versiones de Basic. Esta estructura tiene la siguiente sintaxis While condicin [intrucciones] Wend Condicin es una expresin numrica o de tipo texto, que puede devolver True, False Null. Si devolviera Null, While lo considerara como False. Las instrucciones de cdigo se ejecutarn mientras condicin de cmo resultado True. Supongamos que queremos crear un procedimiento que nos muestre los sucesivos valores que va tomando una variable, mientras esta variable sea menor que 100. Los valores que ir tomando la variable sern cada vez el doble que la anterior. Podramos realizarlo de esta forma Public Sub PruebaWhile() Dim lngControl As Long lngControl = 1 While lngControl < 100 Debug.Print lngControl lngControl = lngControl * 2 Wend End Sub
Entrega 10 Estructuras de Control II 10 - 3
Comencemos a programar con VBA - Access
Este cdigo nos mostrar en la ventana inmediato: 1 2 4 8 16 32 64 Tras efectuar el 7 ciclo, la variable lngControl tomar el valor 128, por lo que la expresin lngControl < 100 devolver False. Esto har que el cdigo pase a la lnea siguiente a Wend, con lo que el procedimiento de prueba finalizar. Una utilizacin tradicional para While - - Wend ha sido la lectura de ficheros secuenciales de texto, utilizando la funcin Eof, ficheros de los que de entrada no se conoce el nmero de lneas,. Esta funcin, mientras no se llega al final del fichero devuelve el valor False. Cuando llega al final devuelve el valor True. Por ello el valor Not Eof, mientras no se haya llegado al final del fichero, devolver lo contrario, es decir True. Veamos el siguiente cdigo: Public Sub MuestraFichero( _ ByVal Fichero As String) Dim intFichero As Integer Dim strLinea As String
intFichero = FreeFile Open Fichero For Input As #intFichero While Not EOF(intFichero) Line Input #intFichero, strLinea Debug.Print strLinea Wend End Sub ste es el clsico cdigo para leer el contenido de un fichero secuencial. Vamos a fijarnos en la estructura While - - Wend. Traducido a lenguaje humano quiere decir: Mientras no llegues al final del fichero #intFichero Lee la lnea del fichero, hasta que encuentres un retorno de carro y asgnaselo a la variable strLinea. Imprime el contenido de la variable en la ventana inmediato Vuelve a la lnea de While para repetir el proceso 10 - 4
eduardo@olaz.net E Ed du ua ar rd do o O Ol la az z
Las Instrucciones Do - - - Loop El conjunto de instrucciones While - - Wend nos permite crear bucles que se ejecuten slo si una variable, o expresin toma determinados parmetros. While - - Wend no posee ninguna expresin que permita salir desde dentro del bucle en un momento dado, sin antes haberlo completado. VBA posee una instruccin ms potente, es la instruccin Do - - - Loop. Su sintaxis posee dos formas distintas de utilizacin Do [{While | Until} condicin] [instrucciones] [Exit Do] [instrucciones] Loop O con esta otra sintaxis: Do [instrucciones] [Exit Do] [instrucciones] Loop [{While | Until} condicin] Veamos la primera forma: Despus de Do nos permite seleccionar While condicin, Until condicin. Si ponemos While, despus de Do el bucle se ejecutara mientras la condicin sea cierta. Si escribimos Until, el bucle se ejecutara hasta que la condicin sea cierta. Si la condicin no fuese cierta se ejecutara el bucle si hemos puesto While. En cambio no se ejecutara si hubiramos escrito Until despus de Do. Por lo tanto podra ocurrir, tanto con While como con Until en funcin del resultado de Condicin, que no se llegara a ejecutar el bucle ni una sola vez. Si deseramos que siempre se ejecutara al menos una vez el bucle, deberamos usar While Until despus de Loop. Supongamos que queremos escribir una funcin a la que pasndole un nmero entero positivo, nos indique si ese nmero es no primo. Supongo que no har falta recordaros que un nmero primo es aqul que slo es divisible por 1 por s mismo. Este es el mtodo que voy a emplear. S. Ya se que no es el ptimo: Dividir el nmero entre valores enteros, empezando por el dos, y a continuacin por los sucesivos valores impares, hasta que encontremos un valor que divida de forma exacta al nmero a probar (su resto = 0). Si el resto de la divisin da cero indica que el nmero es divisible por ese valor, por lo que el nmero no ser primo y deberemos salir del bucle. Seguir con el ciclo mientras el valor por el que se va a dividir el nmero no sea mayor que la raz cuadrada del nmero. Necesitis saber que en VBA, el operador que devuelve el resto de una divisin es Mod. Entrega 10 Estructuras de Control II 10 - 5
Comencemos a programar con VBA - Access
Si dividimos 15 entre 3 da de resto 2 15 Mod 3 2 Ya s que este cdigo es manifiestamente mejorable, pero funciona y me viene bien para el ejemplo con Do Loop. Funciona si el nmero que probamos es menor igual que 2.147.483.647 Este es el mximo nmero Long positivo. Este nmero tambin es primo. Public Function EsPrimo( _ ByVal Numero As Long _ ) As Boolean Dim lngValor As Long Dim dblRaiz As Double
Select Case Numero Case Is < 1 MsgBox (Numero & " est fuera de rango") EsPrimo = False Exit Function Case 1, 2 EsPrimo = True Exit Function Case Else dblRaiz = Numero ^ 0.5 lngValor = 2 ' Comprobamos si Numero es divisible por lngValor If Numero Mod lngValor = 0 Then EsPrimo = False Exit Function End If lngValor = 3 EsPrimo = True Do While lngValor <= dblRaiz If Numero Mod lngValor = 0 Then EsPrimo = False Exit Function End If lngValor = lngValor + 2 Loop End Select End Function Nota: En este cdigo he usado para calcular la raz cuadrada de un nmero, elevar ste a 0,5. 10 - 6
eduardo@olaz.net E Ed du ua ar rd do o O Ol la az z
En VBA hay una funcin que calcula la raz cuadrada directamente: Sqr(Nmero). Es equivalente a Nmero^0.5 Habiendo escrito la funcin EsPrimo, en un mdulo estndar, vamos a crear un formulario en el que introduciendo un nmero en un cuadro de texto, tras pulsar un botn, nos diga si es primo no. Cerramos el editor de cdigo y creamos un nuevo formulario y lo ponemos en Vista Diseo. Aadimos al formulario una etiqueta, un cuadro de texto y un botn. Nombres aplicados a los controles: Etiqueta lblMensaje Cuadro de texto txtNumero Etiqueta del cuadro de texto lblNumero Botn cmdPrimo
Ajustamos algunas de las propiedades del formulario, por ejemplo para quitar los separadores de registro, botones, etc. Ya que va a ser un formulario con muy pocos controles, ponemos los textos algo mayores que lo normal, e incluso podemos jugar con los colores. A m me ha quedado as
Abrimos la ventana de propiedades y teniendo seleccionado el formulario, vamos a la pgina de Eventos. Hacemos que al abrir el formulario ponga como ttulo del mismo "Test de nmeros primos", y como texto de la etiqueta lblMensaje, "Introduzca un nmero entero". Private Sub Form_Open(Cancel As Integer) Caption = "Test de nmeros primos" lblmensaje.Caption = _ "Introduzca un nmero mayor que cero" End Sub
Entrega 10 Estructuras de Control II 10 - 7
Comencemos a programar con VBA - Access
Al abrir el formulario quedar as:
Para que el formulario tenga este aspecto, he modificado algunas de sus propiedades: Propiedad Valor Selectores de registro No Botones de desplazamiento No Separadores de registro No Estilo de los bordes Dilogo Vamos a hacer ahora que tras introducir un nmero en el cuadro de texto, y presionar el botn, nos diga en la etiqueta si el nmero es primo. Volvemos a la hoja de propiedades y seleccionamos Eventos. Teniendo seleccionado el botn, activamos el evento Al hacer clic, pulsamos en el botoncito que aparece con los tres puntos y seleccionamos Generador de cdigo, y a continuacin Aceptar. Vamos a escribir el cdigo: Os recuerdo que detrs de la comilla simple lo que se escriba es un comentario (lneas en verde). Estas lneas VBA las ignora, sirviendo slo como ayuda al usuario. Tambin os recuerdo que el espacio en blanco seguido de la barra inferior, al final de una lnea, hace que la lnea siguiente se considere como la misma lnea. El dividir as las lneas lo hago como ayuda para la composicin de este texto y para ordenar el cdigo. Private Sub cmdPrimo_Click() Dim strNumero As String Dim lngNumero As Long
' Pasamos a la variable el contenido _ de txtNumero, sin blancos en las esquinas ' Nz(txtNumero, "") devuelve una cadena vaca _ si txtNumero contuviera Null ' Trim (Cadena) quita los "Espacios en blanco" _ de las esquinas de la Cadena strNumero = Trim(Nz(txtNumero, ""))
10 - 8
eduardo@olaz.net E Ed du ua ar rd do o O Ol la az z
' IsNumeric(strNumero) devuelve True _ si strNumero representa a un nmero If IsNumeric(strNumero) Then
' La funcin EsPrimo() _ funciona con nmeros long positivos _ entre 1 y 2147483647 If Val(strNumero) > 2147483647# _ Or Val(strNumero) < 1 Then lblmensaje.Caption = _ "El nmero est fuera de rango" txtNumero.SetFocus Exit Sub End If lngNumero = Val(strNumero) ' Format(lngNumero, "#,##0") _ devuelve una cadena con separadores de miles strNumero = Format(lngNumero, "#,##0") If EsPrimo(lngNumero) Then lblmensaje.Caption = _ "El nmero " _ & strNumero _ & " es primo" Else lblmensaje.Caption = _ "El nmero " _ & strNumero _ & " no es primo" End If Else lblmensaje.Caption = _ "No ha introducido un nmero" End If ' El mtodo SetFocus _ hace que el control txtNumero tome el foco txtNumero.SetFocus End Sub Tras presionar el botn cmdPrimo se produce el evento clic, por lo que se ejecuta el procedimiento cmdPrimo_Click()que maneja ese evento Este procedimiento lo primero que hace es declarar dos variables, strNumero de tipo string y lngNumero de tipo Long.
Entrega 10 Estructuras de Control II 10 - 9
Comencemos a programar con VBA - Access
A continuacin asigna el contenido del cuadro de texto txtNumero, procesado primero con la funcin Nz, que devuelve una cadena vaca si tiene el valor Null, y a continuacin le quita los posibles espacios en blanco de los extremos mediante la funcin Trim. Seguidamente pasa por la primera estructura de decisin If, controlando si la cadena strNumero es de tipo numrico. Si no lo fuera muestra en la etiqueta el mensaje "No ha introducido un nmero". Si lo fuera, primero comprueba si la expresin numrica de strNumero est entre 1 y 214748364, rango de valores vlidos en el rango de los Long, para la funcin EsPrimo. Si no fuera as, muestra el mensaje " El nmero est fuera de rango", lleva el cursor al control txtNumero y sale del procedimiento. Supongamos que el contenido de strNumero ha logrado pasar todos estos controles. Mediante la funcin Val(strNumero) asigna el valor a la variable lngNumero. Como ya no vamos a utilizar la cadena strNumero para ms clculos, para mostrar el nmero, le asignamos el resultado de la funcin Format(lngNumero, "#,##0"). Con esta utilizacin, la funcin Format devuelve una cadena formada por el nmero con los separadores de miles. La funcin Format tiene un amplio abanico de posibilidades en la conversin de nmeros y fechas a cadenas de texto. Merece por s misma un tratamiento ms extenso. Se lo daremos en una prxima entrega. El siguiente paso es comprobar si el nmero lngNumero es primo, utilizando la funcin EsPrimo que escribimos anteriormente. Si lo fuera, escribiramos en la etiqueta "El nmero " seguido del contenido de la cadena strNumero, y el texto " es primo". Si no lo fuera, escribiramos lo mismo, pero indicando " no es primo". Terminado todo esto llevamos el cursor al cuadro de texto txtNumero mediante su mtodo SetFocus. Todo muy bien. El cliente est contento y el programa responde a lo que nos peda, pero Casi siempre hay un pero Viendo lo efectivos y rpidos que hemos sido, al cliente se le ocurre que sera muy interesante poner dos botoncitos que al presionarlos, dado un nmero cualquiera, nos muestre el nmero primo inmediatamente mayor menor al nmero que hemos mostrado. -Tiene que ser fcil, total ya has hecho lo ms importante y ste es un pequeo detalle adicional, que no te costar prcticamente nada de tiempo y supongo que no tendrs problemas para hacrmelo sin aumentar el importe presupuestado A alguno le suena esta conversacin?. Y adems, aunque ya has terminado lo que te pedan, como hay que aadirle este pequeo detalle no te pagan hasta que no lo termines Decido aadir dos botones con unas flechas en su interior. Al primero, con una flecha hacia arriba lo llamo cmdPrimoSiguiente, y al segundo, con una flecha hacia abajo, cmdPrimoAnterior.
Este es el diseo que le doy al formulario: 10 - 10
eduardo@olaz.net E Ed du ua ar rd do o O Ol la az z
Los eventos clic de los dos botones los escribo as: Private Sub cmdPrimoSiguiente_Click() ' La siguiente lnea hace que se ignoren _ los posibles errores en la ejecucin. On Error Resume Next Dim strNumero As String Dim lngNumero As Long Dim blnPrimo As Boolean strNumero = Trim(Nz(txtNumero, "")) If IsNumeric(strNumero) Then lngNumero = Val(strNumero) ' Si lngNumero est entre 0 y 2147483646 If lngNumero < 2147483647# And lngNumero >= 0 Then
' Mientras blnPrimo no sea Cierto _ Es decir Mientras lngNumero no sea primo. Do While Not blnPrimo lngNumero = lngNumero + 1 blnPrimo = EsPrimo(lngNumero) Loop txtNumero = CStr(lngNumero) cmdPrimo_Click Else txtNumero = "1" cmdPrimo_Click End If Else txtNumero = "1" cmdPrimo_Click End If Entrega 10 Estructuras de Control II 10 - 11
Comencemos a programar con VBA - Access
End Sub En el cdigo anterior podemos ver algunas cosas interesantes. Lo primero que nos puede llamar la atencin es la sentencia: On Error Resume Next Esta es la forma ms bsica de efectuar un control de los errores que se puedan originar durante la ejecucin de un programa en VBA. Simplemente se le est indicando a VBA que si se produjera un error en algn punto del procedimiento lo ignore y vaya a la siguiente sentencia del cdigo. El ignorar los errores no es una verdadera forma de control. Aprenderemos en otra entrega diferentes formas de manejar los posibles errores, ya sean generados por el cdigo, por datos inadecuados de los usuarios, etc. Ms adelante nos encontramos con una sentencia If que evala una expresin doble If lngNumero < 2147483647# And lngNumero >= 0 Then Para que esta expresin sea cierta, lo tienen que ser a l vez las dos expresiones unidas por And; es decir lngNumero tiene que ser menor que 2147483647 y simultneamente tiene que ser mayor igual que 0. Cuando varias expresiones estn unidas por el Operador Lgico And, para que la expresin total sea cierta, es necesario que lo sean cada una de esas expresiones. Con que haya una falsa, la expresin total ser falsa. Por el contrario, cuando varias expresiones estn unidas por el Operador Lgico Or, para que la expresin total sea cierta, es suficiente con que lo sea una cualquiera de las expresiones que la forman. A continuacin nos encontramos con otro Operador, es el operador negacin Not. Do While Not blnPrimo Not hace que la expresin lgica que le sigue cambie su valor. As si blnPrimo contiene el valor True Not blnPrimo devolver el valor False. La expresin equivale a: Mientras blnPrimo no sea cierto Que es equivalente a Mientras blnPrimo sea falso. Con ello se ejecutar el cdigo contenido entre la lnea de Do y la lnea del Loop. Cuando lngNumero sea primo, la funcin EsPrimo asignar True a blnPrimo, con lo que se saldr del bucle, pondr la cadena de texto del nmero txtNumero en el cuadro de texto y ejecutar el procedimiento cmdPrimo_Click, como si se hubiera presionado en el botn [cmdPrimo]. Si el valor de lngNumero no hubiera cumplido con el rango de valores, pone un 1 en el cuadro de texto txtNumero, y ejecuta el procedimiento cmdPrimo_Click. En el procedimiento que maneja la pulsacin de la tecla [cmdPrimoAnterior] aunque tiene una estructura semejante, se introducen unos cambios que considero interesante remarcar. 10 - 12
eduardo@olaz.net E Ed du ua ar rd do o O Ol la az z
Private Sub cmdPrimoAnterior_Click() ' Ignorar el error On Error Resume Next
Dim strNumero As String Dim lngNumero As Long
strNumero = Trim(Nz(txtNumero, "")) If IsNumeric(strNumero) Then lngNumero = Val(strNumero) If lngNumero < 2147483648# And lngNumero > 1 Then lngNumero = lngNumero - 1
Do Until EsPrimo(lngNumero) lngNumero = lngNumero - 1 Loop txtNumero = CStr(lngNumero) cmdPrimo_Click Else txtNumero = "2147483647" cmdPrimo_Click End If Else txtNumero = "2147483647" cmdPrimo_Click End If End Sub En primer lugar utilizamos una estructura del tipo Do Until, en vez de Do While. Adems, como condicin no utiliza una variable como en el caso anterior, sino que lo compara directamente con el valor devuelto por la funcin EsPrimo, que devuelve True False segn sea el caso: Do Until EsPrimo(lngNumero) Con esto nos evitamos utilizar una variable y una sentencia adicional. Adems el cdigo resulta algo ms claro.. En este caso, si la variable no supera los filtros, pone el valor "2147483647" en el cuadro de texto.