Tareas repetitivas con bucles en VBA
En VBA los bucles te permiten recorrer un conjunto de objetos o valores y analizarlos uno por uno realizando tareas específicas en cada elemento. De forma sencilla: ejecutas la macro una vez y una serie de instrucciones se ejecutan un número determinado de veces.
Te pongo un ejemplo:
Imagina que en una hoja tienes un rango de celdas y te gustaría resaltar con otro color las celdas de las filas pares. Para esta tarea puedes utilizar un bucle para recorrer cada celda del rango, averiguar el número de fila y si es par, colorear el fondo.
Es un pequeñísimo ejemplo de uso de bucles para automatizar tareas. Algunos ejemplos más que me vienen a la cabeza…
- Recorrer un rango de celdas y darle un formato determinado a las que contengan un texto específico.
- Proteger o desproteger todas las hojas a la vez.
- Cerrar todos los libros excepto el que contiene el código.
- Recorrer y almacenar los valores de una matriz.
- Vaciar todos los controles de un formulario.
- … y así puedo seguir con muchas más tareas…
Ahora que más o menos sabes para qué se usan los bucles, te mostraré qué tipos existen y las sintaxis de cada uno. Por supuesto, complementaré cada uno con ejemplos.
El bucle For – Next
Estoy seguro de que este bucle es el más utilizado en cualquier lenguaje de programación.
Sirve para ejecutar un conjunto de instrucciones un número de veces determinado.
Por ejemplo, si quisieras sumar los números del 1 al 10 de forma manual supongo que lo harías sumando los dos primeros, luego sumarías el tercero al resultado, luego sumarías el cuarto al resultado… y así hasta completar los 10. ¿Me equivoco?
Pues en VBA se aplica la misma lógica en el bucle For-Next.
Al crear la macro se le indica al código cuántas veces quieres ejecutar el bucle y qué quieres que haga el código cada vez que se ejecute. Observa la sintaxis del bucle:
For contador = comienzo To final [Step paso]
[bloque de código con instrucciones
Next [contador]
La variable contador cuenta el número de veces que pasa el flujo de código por ella desde el valor comienzo hasta el final.
Siguiendo el ejemplo anterior, si quieres contar los números enteros del 1 al 10, la variable comienzo sería 1 y la variable final sería 10.
Cómo sumar los 10 primeros números
Este es el código que suma los 10 primeros números positivos usando el bucle For-Next
. Después de calcular la suma muestra un cuadro de mensaje con el resultado:
Sub Suma10()
Dim Total As Integer
Dim contador As Integer
Total = 0
For contador = 1 To 10
Total = Total + contador
Next contador
MsgBox Total
End Sub
Las variables Total
y contador
se declaran a nivel de procedimiento, por tanto, al comienzo de la ejecución sus valores son 0.
Una vez que el flujo entra en el bucle se mantienen los valores de las variables después de cada pasada. Así que en la primera vuelta el valor de contador
es 1 y el valor de Total
también es 1. Después de la segunda vuelta el valor de contador
es 2 y por tanto el de Total
será 3 (2+1). Así sucesivamente hasta completar las 10 vueltas establecidas en el bucle.
Al finalizar, la variable Total
tiene la suma de los 10 primeros números positivos. En la última línea de la macro, se muestra un mensaje (con MsgBox
) con el resultado del cálculo.
Cómo sumar los 5 primeros números pares
En el ejemplo anterior la variable contador va tomando números consecutivos de 1 a 10. Para sumar los 5 primeros números pares el código anterior no sirve porque es necesario que la variable contador vaya tomando los números pares (un número de cada dos). Observa el siguiente procedimiento:
Sub SumaPares()
Dim Total As Integer
Dim contador As Integer
Total = 0
For contador = 2 To 10 Step 2
Total = Total + contador
Next contador
MsgBox Total
End Sub
Observa que en este caso el bucle se inicia en el valor 2 y se ha añadido la cláusula Step 2
.
Cuando usas la cláusula Step
le estás diciendo al código que incremente el valor de contador en ese valor cada vez que el flujo de código completa una vuelta por el bucle.
Así que el valor de contador comienza en 2, luego se convierte en 4, 6, 8 y 10 a medida que se pasa por el inicio.
ATENCIÓN: este ejemplo es solo una forma de hacerlo. También podrías ejecutar el bucle desde el 1 hasta el 10 y dentro del bucle comprobar si se trata de un número par o impar.
El valor de Step
también puede ser negativo. En este caso el contador comienza en un número más alto y en cada vuelta irá disminuyendo hasta llegar al valor final.
Cómo introducir números consecutivos en las celdas seleccionadas
También puedes usar el bucle For-Next
para recorrer un conjunto de celdas. Observa el siguiente procedimiento:
Sub NumerosConsecutivos()
Dim Rng As Range
Dim contador As Integer
Dim CuentaFila As Integer
Set Rng = Selection
CuentaFila = Rng.Rows.Count
For contador = 1 To CuentaFila
ActiveCell.Offset(contador - 1, 0).Value = contador
Next contador
End Sub
Esta macro cuenta el número de filas seleccionadas y luego asigna este valor a la variable CuentaFila
. A continuación se ejecuta el bucle desde 1 hasta CuentaFila
.
Como antes de ejecutar la macro se pueden seleccionar cualquier número de celdas, he establecido la variable Rng
en Selection
. De esta forma ya es posible usar la variable Rng
para hacer referencia a las celdas seleccionadas.
Proteger todas las hojas en el libro activo
También es posible usar el bucle For-Next
para recorrer todas las hojas del libro activo y protegerlas (o desprotegerlas) todas a la vez.
Este es el código que lo hace posible:
Sub ProtegerHojas()
Dim i As Integer
For i = 1 To ActiveWorkbook.Worksheets.Count
Worksheets(i).Protect
Next i
End Sub
En el ejemplo se cuenta el número de hojas usando la instrucción ActiveWorkbook.Worksheets.Count
. Esto le dice a VBA cuántas veces debe ejecutarse el bucle.
En cada vuelta se hace referencia a la hoja con su número de índice (con Worksheets(i)
) y la protege.
Por supuesto puedes utilizar este mismo código para desproteger todas las hojas del libro. Solo tienes que cambiar el método Protect
por Unprotect
.
Los bucles For-Next anidados
Para conseguir automatizaciones más complejas puedes utilizar bucles For-Next
anidados (uno dentro de otro).
Imagina que tienes cinco libros abiertos y quieres proteger todas las hojas de todos los libros. Este es el código que lo hace posible:
Sub ProtegerHojasLibros()
Dim i As Integer
Dim j As Integer
For i = 1 To Workbooks.Count
For j = 1 To Workbooks(i).Worksheets.Count
Workbooks(i).Worksheets(j).Protect
Next j
Next i
End Sub
El flujo de código cuenta el número de libros abiertos y entra en el primero. A continuación, cuenta el número de hojas del primer libro. Luego las protege. Una vez protegidas entra en el segundo libro, cuenta el número de hojas, las protege… y así sucesivamente hasta que finaliza con el último libro abierto.
Cómo salir de un bucle For-Next
Cuando quieres que el flujo de ejecución de la macro salga de un bucle For-Next
antes de finalizar completamente su recorrido puedes usar la instrucción Exit For
.
Habitualmente se usa cuando se cumple una determinada condición y no quieres continuar con el recorrido (por ejemplo, cuando haces una búsqueda y encuentras el resultado, no es necesario seguir buscando).
Imagina que tienes un rango con números en la columna A y quieres resaltar los negativos aplicando una fuente roja. En este caso es necesario analizar el valor de cada celda y luego cambiar el color de la fuente en consecuencia.
Para hacer el código más eficiente, se comprueba previamente si hay valores negativos en el rango. Si no hay números negativos puedes utilizar la instrucción Exit For
para salir del procedimiento y así evitar que el flujo de código entre en el bucle.
Sub ResaltarNegativos()
Dim Rng As Range
Set Rng = Range("A1", Range("A1").End(xlDown))
contador = Rng.Count
For i = 1 To contador
If WorksheetFunction.Min(Rng) >= 0 Then Exit For
If Rng(i).Value < 0 Then Rng(i).Font.Color = vbRed
Next i
End Sub
ATENCIÓN: Cuando utilizas la instrucción Exit For
en un bucle For-Next
anidado, el flujo de código saldrá del bucle interior en el que se ejecuta y pasará a ejecutar la siguiente línea del código del bucle exterior.
Sub EjemploCodigo()
For i = 1 To 10
For j = 1 To 10
Exit For
Next j
Next i
End Sub
El bucle Do While Loop
El bucle Do While
permite comprobar una condición y ejecutar el bucle mientras se cumple esa condición. Existen dos tipos de sintaxis:
Do [While condición]
[Instrucciones a ejecutar]
Loop
y
Do
[Instrucciones a ejecutar]
Loop [While condición]
La diferencia entre las dos sintaxis está en que en la primera la condición se comprueba antes de ejecutar por primera vez el código. En el segundo ejemplo primero se ejecuta el código una vez y luego se comprueba la condición.
Esto significa que si la condición es falsa en ambos bucles, el código se ejecuta al menos una vez en el segundo caso (porque la condición se comprueba después de que el código se haya ejecutado una vez).
Te pongo algunos ejemplos:
Cómo sumar los 10 primeros números
Es el mismo ejemplo que el primero. Antes utilicé el bucle For-Next
, ahora te muestro la forma de hacerlo con Do While
.
En este caso puedes usar el bucle Do While
hasta que el siguiente número sea menor o igual a 10. Cuando el número sea mayor que 10 el bucle se detendrá.
Este es el código:
Sub Suma10()
Dim i As Integer
Dim Resultado As Integer
i = 1
Do While i <= 10
Resultado = Resultado + i
i = i + 1
Loop
MsgBox Resultado
End Sub
Al ejecutar la macro seguirá funcionando hasta que el valor de i
se convierta en 11. Cuando esto sucede, el bucle termina, pues la condición se convierte en False
.
Una vez finalizado el bucle se muestra el valor de la variable Resultado
, que ha ido recogiendo los datos en cada vuelta del bucle.
Introducir las fechas del mes actual
Imagina que quieres introducir todos los días del mes actual en una columna de la hoja.
Puedes hacerlo utilizando el siguiente código con el bucle Do While
:
Sub DiasMesActual()
Dim DiaMes As Date
Dim i As Integer
i = 0
DiaMes = DateSerial(Year(Date), Month(Date), 1)
Do While Month(DiaMes) = Month(Date)
Range("A1").Offset(i, 0) = DiaMes
i = i + 1
DiaMes = DiaMes + 1
Loop
End Sub
El procedimiento comienza introduciendo los días del mes actual en la columna A. El bucle continúa mientras el valor del mes de la variable DiaMes
coincide con el mes actual. En caso contrario, el bucle finaliza.
Cómo salir de un bucle Do
Al igual que en bucle For-Next
también es posible salir de un bucle Do While
usando la instrucción Exit Do
. Cuando el flujo de código llega a esta línea, sale del bucle y pasa el control a la siguiente línea después de Loop
.
En este tipo de bucles, muchas veces no sabes de antemano cuántas veces se repetirá. En caso de que existan fallos o condiciones que siempre se cumplen, el bucle se ejecutaría infinitas veces bloqueando el ordenador.
Por este motivo es útil tener presente la instrucción Exit Do
y utilizarla mientras estás haciendo pruebas con el código. Una vez has comprobado que el procedimiento se ejecuta el número de veces adecuado, puedes eliminar o comentar la línea Exit Do
.
El bucle Do Until Loop
El bucle Do Until
es muy parecido al Do While
.
Si el bucle Do While
se ejecuta mientras se cumple una condición, el bucle Do Until
lo hace hasta que se cumpla la condición dada.
También tiene dos sintaxis:
Do [Until condición]
[Instrucciones a ejecutar]
Loop
y
Do
[Instrucciones a ejecutar]
Loop [Until condición]
La diferencia, al igual que en los bucles Do While
es que en el primero se ejecuta la condición antes de ejecutar cualquier instrucción y en el segundo, se ejecutan primero las instrucciones y luego se evalúa la condición.
Te muestro los mismos ejemplos de la sección anterior, pero usando el bucle Do Until
:
Cómo sumar los 10 primeros números
En este caso usaré el bucle Do Until
para calcular la suma de los 10 primeros números:
Sub Suma10()
Dim i As Integer
Dim Resultado As Integer
i = 1
Do Until i > 10
Resultado = Resultado + i
i = i + 1
Loop
MsgBox Resultado
End Sub
El bucle sigue funcionando hasta que el valor de i
se convierte en 11. Cuando esto sucede el bucle finaliza, pues la condición se convierte en verdadera.
Introducir fechas del mes actual
En este ejemplo usaré el bucle Do Until
para introducir en la columna A (a partir de la fila 1), las fechas correspondientes al mes actual:
Sub DiasMesActual()
Dim DiaMes As Date
Dim i As Integer
i = 0
DiaMes = DateSerial(Year(Date), Month(Date), 1)
Do Until Month(DiaMes) <> Month(Date)
Range("A1").Offset(i, 0) = DiaMes
i = i + 1
DiaMes = DiaMes + 1
Loop
End Sub
El bucle anterior continúa funcionando hasta que el mes de la variable DiaMes
no sea igual al de la fecha de hoy.
El bucle For Each-Next
Los tres tipos de bucle anteriores podemos encontrarlos en todos los lenguajes de programación. VBA cuenta con un bucle especial, For Each-Next
, que es capaz de recorrer un conjunto de objetos o colecciones. Algunos ejemplos de colecciones son:
- Los libros de Excel abiertos.
- Las hojas de un libro
- Las celdas seleccionadas
- Las celdas que se encuentran bajo un nombre.
- Los gráficos de un libro o una hoja.
- … y cientos de etcéteras.
Con el bucle For Each-Next
puedes recorrer cada uno de los objetos de una colección y realizar alguna acción sobre ellos.
Por ejemplo, puedes recorrer todas las hojas de un libro para protegerlas (como he hecho antes con For-Next
) o recorrer todas las celdas de un rango para aplicar un formato determinado.
La ventaja de For Each-Next
con respecto a For-Next
es que no necesitas saber cuántos objetos contiene una colección.
El flujo de código pasa automáticamente por cada objeto y realiza las acciones especificadas. Por ejemplo, si quieres proteger todas las hojas de un libro, el código sería el mismo si tienes 2 hojas o 50.
Esta es la sintaxis:
For Each elemento In colección
[Instrucciones a ejecutar]
Next [elemento]
Te muestro algunos ejemplos del bucle más útil de VBA
Cómo proteger todas las hojas de un libro
Imagina que tienes un libro y quieres proteger todas las hojas.
Observa este código:
Sub ProtegerHojasLibros()
Dim ws As Worksheet
For Each ws In ActiveWorkbook.Worksheets
ws.Protect
Next ws
End Sub
En este procedimiento he definido la variable ws como un objeto de tipo hoja de trabajo.
Luego utilizo For Each-Next
para recorrer cada ws
(es decir cada hoja) en la colección de hojas del libro activo (definida con ActiveWorkbook.Worksheets
.
Observa, que a diferencia del ejemplo en el que usaba For-Next
, aquí no hace falta contar cuántas hojas tiene el libro, pues el bucle ya se encarga de analizar todos los objetos de la colección de forma individual.
Cómo guardar todos los libros abiertos
En muchas ocasiones se suele trabajar con varios libros a la vez. Para guardarlos todos una vez finalizado el trabajo, puede ser útil usar este procedimiento para ello:
Sub GuardarLibros()
Dim wb As Workbook
For Each wb In Workbooks
wb.Save
Next wb
End Sub
En este código no se muestra ningún aviso en caso de guardar el libro por primera vez. Se guardará en la carpeta predeterminada de Windows (suele ser Mis documentos).
Cómo resaltar las celdas negativas en un rango
Con un bucle For Each-Next
también puedes recorrer todas las celdas de un rango específico o de las celdas seleccionadas.
Esto puede ser útil cuando quieras analizar cada celda por separado y realizar alguna acción sobre ella. Por ejemplo, el siguiente procedimiento (ya salió en un ejemplo anterior con otro bucle) recorre todas las celdas seleccionadas y cambia el color de fondo de las celdas con valores negativos a rojo.
Sub ResaltaNegativos()
Dim Celda As Range
For Each Celda In Selection
If Celda.Value < 0 Then
Celda.Interior.Color = vbRed
End If
Next Celda
End Sub
En este código el bucle recorre la colección de celdas seleccionadas (Selection
). La instrucción If
se utiliza para comprobar si el valor de la celda es menor que cero o no. En caso de que lo sea, la celda se colorea en rojo, de lo contrario, se pasa a la siguiente celda.
Resumen
Los bucles son una de las dos estructuras de programación más utilizadas (junto con las condicionales). En VBA existen cuatro tipos de bucles para todos los gustos:
For-Next
. Cuando comienza la ejecución del bucle ya se conoce el número de veces que se ejecutará.Do While-Loop
: No se conoce de antemano cuántas vueltas dará. Se estará ejecutando ‘mientras’ se cumpla una condición.Do Until-Loop
: No se conoce de antemano cuántas vueltas dará. Se estará ejecutando ‘hasta’ que una condición se cumpla.For Each-Next
: Recorre los objetos de una colección y realiza las acciones en cada uno de ellos.
Cuando necesites realizar una acción dos o más veces, conviene hacer uso de los bucles para ahorrar bastante tiempo y facilitar la lectura del código.