En este artículo te voy a ofrecer un ejemplo de sencillo de como manejar colecciones de objetos en Visual Basic para Access (VBA). Incluyo todas las líneas de código y una interesante conversación con ChatGPT para solucionar un error que se producía en el bucle For Each.
Usar la programación orientada a objetos (OOP) te puede ahorrar cientos de horas de trabajo, es una de las mejores técnicas que existen y la más lógica. Espero que estos ejemplos de VBA sean de ayuda y si te interesan más ejemplos te recomiendo este artículo: Trucos y ejemplos de VBA para Microsoft Access.
Índice de contenidos
Ejemplo: Lista de ciudades de una Comunidad Autónoma
El ejemplo que vamos a ver usa 3 objetos y dos colecciones. El objetivo es listar todas las ciudades de una Comunidad Autónoma agrupadas por provincias.
Bucle principal
Se inicia un objeto Region y se recorre la colección States (Provincias). Para cada provincia se recorre su colección de ciudades. En el código original se genera una tabla HTML en el fichero de salida. En este ejemplo he simplificado la salida de datos.
Public Sub ExportarMunicipiosHTML()
Dim objRegion As New clsRegion
Dim objState As New clsState
Dim objCity As clsCity
Dim txtFile As String
Dim ind As Long
' Fichero de salida
txtFile = CurrentProject.Path & "\cities.html"
If Dir(txtFile) <> "" Then Kill txtFile
Open txtFile For Output As #1 ' Para exportar municipios en HTML
Print #1, "Municipios"
' Bucle
objRegion.Inicializar 1 ' (region_id=1)
For Each objState In objRegion.StatesInRegions
Print #1, objState.state_txt
For Each objCity In objState.CitiesInState
Print #1, objCity.city_txt
Next
Next
Close #1
Set objCity = Nothing
Set objState = Nothing
MsgBox "Proceso Finalizado"
End Sub
Objetos
En esta sección se detalla el código de los 3 objetos utilizados: Region, State y City
Region
El objeto de la Comunidad Autónoma. Contiene la colección States (provincias) como propiedad.
Option Compare Database
Option Explicit
Dim rst As Recordset
Dim strSQL As String
Dim colStates As clsStates
Public region_id As Long
Public region_txt As String
Public Existe As Boolean
Public Property Get StatesInRegions() As clsStates
On Error GoTo ErrorSub
If colStates Is Nothing Then
Set colStates = New clsStates
GoSub InicializarStates
End If
Set StatesInRegions = colStates
Exit Property
InicializarStates:
strSQL = "SELECT state_id From wp_datastat_states Where region_id=" & region_id
Set rst = CurrentDb.OpenRecordset(strSQL)
Do While rst.EOF = False
colStates.Add rst!state_id
rst.MoveNext
Loop
rst.Close
Return
ErrorSub:
Debug.Print Err.Description
End Property
Public Sub Inicializar(Codigo As Long)
On Error GoTo ErrorSub
region_id = Codigo
strSQL = "SELECT * FROM wp_datastat_regions WHERE region_id=" & region_id
Set rst = CurrentDb.OpenRecordset(strSQL)
Existe = Not rst.EOF
If Not Existe Then
Existe = False
GoTo InicializarFin
End If
region_txt = ValidarString(rst!region_txt)
InicializarFin:
rst.Close
Exit Sub
ErrorSub:
MsgBox Err.Description, vbCritical, Application.Name
End Sub
State
El objeto de la Provincia. Contiene la colección Cities (ciudades o municipios) como propiedad.
Option Compare Database
Option Explicit
Dim rst As Recordset
Dim strSQL As String
Dim colCities As clsCities
Public state_id As Long
Public state_txt As String
Public Existe As Boolean
Public Property Get CitiesInState() As clsCities
On Error GoTo ErrorSub
If colCities Is Nothing Then
Set colCities = New clsCities
GoSub InicializarCities
End If
Set CitiesInState = colCities
Exit Property
InicializarCities:
strSQL = "SELECT city_id From wp_datastat_cities Where state_id=" & state_id
Set rst = CurrentDb.OpenRecordset(strSQL)
Do While rst.EOF = False
colCities.Add rst!city_id
rst.MoveNext
Loop
rst.Close
Return
ErrorSub:
Debug.Print Err.Description
End Property
Public Sub Inicializar(Codigo As Long)
On Error GoTo ErrorSub
state_id = Codigo
strSQL = "SELECT * FROM wp_datastat_states WHERE state_id=" & state_id
Set rst = CurrentDb.OpenRecordset(strSQL)
Existe = Not rst.EOF
If Not Existe Then
Existe = False
GoTo InicializarFin
End If
state_txt = ValidarString(rst!state_txt)
InicializarFin:
rst.Close
Exit Sub
ErrorSub:
MsgBox Err.Description, vbCritical, Application.Name
End Sub
City
El objeto de la ciudad o municipio. Es el objeto final a procesar. Es clase usa el objeto State como objeto heredado.
Option Compare Database
Option Explicit
Dim rst As Recordset
Dim strSQL As String
Public city_id As Long
Public city_txt As String
Public code_id As String
Public state_id As Long
Public Existe As Boolean
Public Property Get State() As clsState
Set State = New clsState
State.Inicializar state_id
End Property
Public Sub Inicializar(Codigo As Long)
On Error GoTo ErrorSub
city_id = Codigo
strSQL = "SELECT * FROM wp_datastat_cities WHERE city_id=" & city_id
Set rst = CurrentDb.OpenRecordset(strSQL)
Existe = Not rst.EOF
If Not Existe Then
Existe = False
GoTo InicializarFin
End If
city_txt = ValidarString(rst!city_txt)
code_id = Format(rst!city_id, "00000")
InicializarFin:
rst.Close
Exit Sub
ErrorSub:
MsgBox Err.Description, vbCritical, Application.Name
End Sub
Colecciones
Las colecciones son lo que permite recorrer los elementos asociados a un objeto, provincias de la región y ciudades de la provincia.
States
La colección de provincias que permite definirla en el objeto Region.
Option Compare Database
Option Explicit
Private mcolStates As New Collection
Public Function Add(ByVal state_id As Long, Optional ControlErrores As Boolean = True) As String
On Error GoTo ErrorSub
Dim clsState As New clsState
clsState.Inicializar state_id
mcolStates.Add clsState, CStr(state_id)
Add = state_id ' Devuelve la referencia al objeto creado
Set clsState = Nothing
Exit Function
ErrorSub:
Debug.Print Err.Description
End Function
Public Function AddState(ByVal objState As clsState) As clsState
On Error GoTo ErrorSub
mcolStates.Add objState, objState.state_id
Set AddState = objState ' Devuelve la referencia al objeto creado
Exit Function
ErrorSub:
Debug.Print Err.Description
End Function
Public Function Count() As Long
Count = mcolStates.Count
End Function
Public Function Item(ByVal Index As Variant) As clsState
On Error Resume Next
Set Item = mcolStates.Item(Index)
End Function
Public Function Remove(ByVal Index As Variant)
mcolStates.Remove Index
End Function
' NewEnum tiene que devolver la interfaz IUnknown del enumerador de una colección.
Public Function NewEnum() As IUnknown
Set NewEnum = mcolStates.[_NewEnum]
End Function
Cities
La colección de ciudades (municipios) que permite definirla en el objeto State.
Option Compare Database
Option Explicit
Private mcolCities As New Collection
Public Function Add(ByVal city_id As Long, Optional ControlErrores As Boolean = True) As String
On Error GoTo ErrorSub
Dim objCity As New clsCity
objCity.Inicializar city_id
mcolCities.Add objCity, CStr(city_id)
Add = CStr(city_id) ' Devuelve la referencia al objeto creado
Set objCity = Nothing
Exit Function
ErrorSub:
Debug.Print Err.Description
End Function
Public Function AddCity(ByVal objCity As clsCity) As clsCity
On Error GoTo ErrorSub
mcolCities.Add objCity, objCity.city_id
Set AddCity = objCity ' Devuelve la referencia al objeto creado
Exit Function
ErrorSub:
Debug.Print Err.Description
End Function
Public Function Count() As Long
Count = mcolCities.Count
End Function
Public Function Item(ByVal Index As Variant) As clsCity
On Error Resume Next
Set Item = mcolCities.Item(Index)
End Function
Public Function Remove(ByVal Index As Variant)
mcolCities.Remove Index
End Function
' NewEnum tiene que devolver la interfaz IUnknown del enumerador de una colección.
Public Function NewEnum() As IUnknown
Set NewEnum = mcolCities.[_NewEnum]
End Function
Error en bucle For Each y ChatGPT
En el momento de usar el bucle For Each se produjo un error, pese a crearse correctamente la colección no recorría sus elementos. La ayuda de ChatGPT fue de mucha utilidad y por eso reproduzco el diálogo.
Pregunta:
Tengo un problema con un código VBA en una base de datos Access formato accdb. Creo una colección de Cities para el objeto State, se carga bien la colección con todos los items, pero después de devolver la colección con «Set CitiesInState = colCities» se produce el error «El objeto no admite la propiedad o método» en la línea del bucle principal «For Each objCity In objState.CitiesInState»
A continuación le pego el código relacionado.
Respuesta:
Tu problema parece estar relacionado con cómo estás manejando la colección y los objetos dentro de VBA para Access. Aquí hay algunos puntos que debemos revisar y posibles soluciones:
- Tipo de Colección: En VBA, normalmente las colecciones contienen objetos de un tipo específico. Tu propiedad
CitiesInStateestá devolviendo un objeto de tipoclsCities. Es importante asegurarse de queclsCitiessea efectivamente una colección (o se comporte como tal) y que esté correctamente definido para manejar objetos del tipoclsCity. - Iteración sobre la Colección: Cuando haces
For Each objCity In objState.CitiesInState, estás asumiendo queobjState.CitiesInStatedevuelve una colección de objetosclsCity. Si ese no es el caso, recibirás un error. Asegúrate de que la coleccióncolCitiesdentro de la claseclsStateesté efectivamente almacenando y devolviendo objetos del tipoclsCity. - Inicialización de la Colección: Tu método
InicializarCitiesllena la colección con IDs de ciudad (city_id). Sin embargo, parece que debería estar llenándola con objetosclsCity. SicolCities.Add rst!city_idsimplemente añade un ID en lugar de un objeto, intentar iterar a través de la colección como si contuviera objetosclsCitycausará errores.Puedes solucionarlo creando instancias declsCityy agregándolas a la colección en lugar de solo agregar el ID: - Definición de la Colección y los Objetos: Asegúrate de que la clase
clsCitiesesté definida de manera que permita agregar y almacenar objetosclsCity. SiclsCitieses simplemente una colección genérica, esto debería funcionar, pero si es una clase personalizada, necesitas asegurarte de que tenga métodos para agregar y recuperar objetosclsCity. - Error Handling: Tu manejo de errores en
CitiesInStateestá bien, pero asegúrate de que esté capturando y mostrando los errores relevantes correctamente. El error «El objeto no admite la propiedad o método» sugiere que la línea que causa el problema está intentando usar un método o propiedad que uno de los objetos no soporta. Esto es más común cuando se espera un tipo de objeto, pero se recibe otro.
Revisa estos puntos en tu código. Es muy probable que el problema esté relacionado con la forma en que estás añadiendo elementos a la colección colCities o cómo estás intentando iterar a través de esta colección. Asegúrate de que los objetos que añades a colCities son efectivamente del tipo clsCity y que clsCity tiene definida la propiedad city_txt que intentas imprimir en tu bucle.
Pregunta:
Revisa el código de la colección Cities
Respuesta:
Tu implementación de la colección personalizada clsCities para manejar objetos de tipo clsCity parece estar en la dirección correcta, pero hay algunos aspectos y prácticas recomendadas que podemos revisar para asegurarnos de que funcione correctamente y de manera eficiente en VBA.
- Manejo de Errores: En tus funciones
AddyAddCity, el manejo de errores es adecuado, pero en la funciónItem, utilizasOn Error Resume Next. Este enfoque puede ocultar errores en tu código. Es mejor manejar los errores de manera explícita o, al menos, documentar por qué se utilizaOn Error Resume Nexty restaurar el manejo normal de errores conOn Error GoTo 0después de la línea que podría causar errores. - Método Add: En tu método
Add, estás inicializando un nuevo objetoclsCityy luego lo agregas a la colección. Esto está bien, pero asegúrate de que el métodoInicializarenclsCityesté bien definido y esté estableciendo correctamente todas las propiedades necesarias del objetoclsCitya partir delcity_id. - Método AddCity: Este método permite agregar directamente un objeto
clsCitya la colección, lo que es una buena práctica ya que ofrece flexibilidad para agregar objetos que ya han sido creados y posiblemente modificados. Solo asegúrate de que los objetosclsCitysiempre tengan uncity_idválido antes de intentar agregarlos a la colección. - Método Item: Este método permite acceder a elementos individuales de la colección por índice o clave. Asegúrate de que cuando uses este método, estés preparado para manejar casos donde el índice no existe o la clave no se encuentra en la colección, ya que
On Error Resume Nextpuede ocultar estos problemas. - Método NewEnum: Implementar este método es una buena práctica ya que permite usar la sintaxis
For Eachen la colección personalizada. Parece que lo has hecho correctamente aquí, permitiendo que la colección se comporte más como una colección estándar de VBA. - Documentación y Convenciones: Aunque tu código parece estar bien en términos de funcionalidad, no olvides documentarlo adecuadamente. Comentar tu código ayudará a otros desarrolladores (y a ti mismo en el futuro) a entender rápidamente lo que cada parte se supone que debe hacer.
- Pruebas: Después de realizar cualquier cambio, asegúrate de probar tu colección personalizada exhaustivamente. Asegúrate de probar la adición de nuevos objetos
clsCity, el acceso a elementos individuales, la eliminación de elementos y la iteración a través de la colección conFor Each. - Optimización del Código: Por último, aunque no menos importante, siempre busca maneras de optimizar tu código. Por ejemplo, si encuentras que estás utilizando una pieza de código repetidamente, considera si puede ser extraída a una función o procedimiento separado.
Si después de revisar y aplicar los puntos anteriores sigues teniendo problemas, sería útil revisar también cómo estás implementando y usando la clase clsCity, especialmente cómo se inicializa y cómo se manejan sus propiedades internas.
Pregunta:
Con el mismo código este bucle funciona bien:
For ind = 1 To objState.CitiesInState.Count
Set objCity = objState.CitiesInState.Item(ind)
Debug.Print objCity.city_txt
Next
El problema surge cuando uso el bucle For Each
Respuesta:
El hecho de que el bucle con el índice funcione correctamente pero el bucle For Each no, sugiere que hay un problema con cómo tu colección personalizada clsCities está implementando el mecanismo de enumeración necesario para el bucle For Each.
En VBA, para que una colección personalizada sea enumerable con For Each, debes asegurarte de lo siguiente:
- Implementar correctamente el método
_NewEnum: Este es el método que permite que tu colección personalizada sea utilizada con un bucleFor Each. Parece que ya lo tienes en tu código, lo cual es bueno. Sin embargo, asegúrate de que está correctamente configurado. El método_NewEnumdebe tener el atributo[VB_UserMemId]configurado en-4. Normalmente, este ajuste no se puede hacer directamente en el editor de VBA, sino que se debe hacer a través de la edición del archivo exportado o utilizando herramientas de terceros que permiten modificar los atributos de los miembros de una clase.Si después de verificar y corregir estos aspectos sigue sin funcionar, podría deberse a otras particularidades del entorno de VBA o a detalles específicos de tu implementación. Pero el ajuste más común y necesario para solucionar problemas con el bucleFor Eachen colecciones personalizadas es asegurarse de que el método_NewEnumesté correctamente atribuido y configurado, ya que es lo que permite que la colección sea enumerable. Ten en cuenta que esta modificación no se puede realizar directamente en el editor de VBA estándar. Necesitas exportar tu clase, editar el archivo de texto para añadir la líneaAttribute NewEnum.VB_UserMemId = -4justo arriba del métodoNewEnum, y luego reimportar la clase. O usar una herramienta de terceros que permita modificar estos atributos. - Implementación de la interfaz IUnknown: Asegúrate de que tu colección maneja correctamente la interfaz
IUnknowna través del método_NewEnum. Por lo general, esto se maneja automáticamente cuando se devuelvemcolCities.[_NewEnum], pero si hay un problema en cómo se ha configurado la colección o la clase, podría resultar en errores.



buenos dias.
Resulta que quiero hacer QR segun el video que has mostrado y me da el siguiente error.
He copiado el ejemplo tal cual. Pero creo que no esta muy claro ya que empiezas las explicaciones desde el final no desde el principio
Microsoft Visual Basic para aplicaciones.
Error de compilación
Se esperaba una variable o un procedimiento no un modulo.
No hay ningún vídeo en este artículo, tampoco tiene que ver con los QR. No sé si te refieres a otra entrada de mi blog.