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
CitiesInState
está devolviendo un objeto de tipoclsCities
. Es importante asegurarse de queclsCities
sea 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.CitiesInState
devuelve una colección de objetosclsCity
. Si ese no es el caso, recibirás un error. Asegúrate de que la coleccióncolCities
dentro de la claseclsState
esté efectivamente almacenando y devolviendo objetos del tipoclsCity
. - Inicialización de la Colección: Tu método
InicializarCities
llena la colección con IDs de ciudad (city_id
). Sin embargo, parece que debería estar llenándola con objetosclsCity
. SicolCities.Add rst!city_id
simplemente añade un ID en lugar de un objeto, intentar iterar a través de la colección como si contuviera objetosclsCity
causará errores.Puedes solucionarlo creando instancias declsCity
y 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
clsCities
esté definida de manera que permita agregar y almacenar objetosclsCity
. SiclsCities
es 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
CitiesInState
está 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
Add
yAddCity
, 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 Next
y restaurar el manejo normal de errores conOn Error GoTo 0
después de la línea que podría causar errores. - Método Add: En tu método
Add
, estás inicializando un nuevo objetoclsCity
y luego lo agregas a la colección. Esto está bien, pero asegúrate de que el métodoInicializar
enclsCity
esté bien definido y esté estableciendo correctamente todas las propiedades necesarias del objetoclsCity
a partir delcity_id
. - Método AddCity: Este método permite agregar directamente un objeto
clsCity
a 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 objetosclsCity
siempre tengan uncity_id
vá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 Next
puede ocultar estos problemas. - Método NewEnum: Implementar este método es una buena práctica ya que permite usar la sintaxis
For Each
en 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_NewEnum
debe 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 Each
en colecciones personalizadas es asegurarse de que el método_NewEnum
esté 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 = -4
justo 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
IUnknown
a 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.