You are on page 1of 418

ÍNDICE DEL CURSO VBA

Índice de contenido Capítulo 1


1.- INTRODUCCIÓN AL CURSO......................................................................................................2
2.- EL ENTORNO DE TRABAJO.......................................................................................................2
MÓDULOS......................................................................................................................................4
COMPARACIÓN Y DECLARACIÓN DE VARIABLES..............................................................4
3.- VARIABLES Y TIPOS DE DATOS...............................................................................................6
VARIABLES...................................................................................................................................6
TIPOS DE DATOS..........................................................................................................................8

Índice de contenido Capítulo 2


1.- PROCEDIMIENTOS Y FUNCIONES...........................................................................................2
UN PRIMER ACERCAMIENTO...................................................................................................2
TIPOS DE FUNCIONES Y PROCEDIMIENTOS.........................................................................4
ARGUMENTOS..............................................................................................................................4
EL ADORNO FINAL: BYVAL, BYREF y PARAMARRAY........................................................6
UN GRAN EJEMPLO.........................................................................................................................6
UN POCO MÁS DEL ENTORNO DE TRABAJO.............................................................................8
AYUDA CONTEXTUAL................................................................................................................8
SEPARACIÓN DE LA MISMA LÍNEA EN VARIAS...................................................................9
INTERDENTADO...........................................................................................................................9
COMENTARIOS...........................................................................................................................10

Índice de contenido Capítulo 3


MÓDULOS DE OBJETO, CONTROLES Y EVENTOS....................................................................2
MÓDULOS DE OBJETO...............................................................................................................2
EVENTOS.......................................................................................................................................2
CONTROLES..................................................................................................................................5
“MIX” DE LOS ANTERIORES CONCEPTOS.............................................................................5
LA FUNCIÓN MSGBOX....................................................................................................................7
LANZAR UN MENSAJE...............................................................................................................7
SALTOS DE LÍNEA EN MENSAJE..............................................................................................8
LANZAR MENSAJE Y ESPERAR RESPUESTA DE UN USUARIO.........................................9
LA FUNCIÓN INPUTBOX...............................................................................................................11
DETECTAR LA PULSACIÓN DEL BOTÓN <CANCELAR>...................................................13
OTROS “CONTROLES” PARA QUE NO NOS FALLE EL INPUTBOX..................................14
DETECTAR VALORES VACÍOS............................................................................................14
DETECTAR VALORES DE DIFERENTE TIPO....................................................................14
NORMALIZACIÓN..........................................................................................................................16
NOMBRES DE OBJETOS............................................................................................................16
NOMBRES DE CONTROLES.....................................................................................................17

1
Visítame en http://siliconproject.com.ar/neckkito/
NOMBRES DE VARIABLES.......................................................................................................17
¿Y POR QUÉ LOS PREFIJOS?....................................................................................................17
MÁS INFORMACIÓN SOBRE NORMALIZACIÓN.................................................................18

Índice de contenido Capítulo 4


BLOQUES DE DECISIÓN..................................................................................................................2
BLOQUE IF...THEN...ELSE...END IF...........................................................................................2
UN CASO ESPECIAL: EL ELSEIF...........................................................................................3
LA FUNCIÓN IIF............................................................................................................................4
IIF ANIDADOS..........................................................................................................................5
BLOQUE SELECT CASE...END SELECT...................................................................................6
VALOR DIRECTO.....................................................................................................................6
VALOR CON COMPARADOR LÓGICO.................................................................................8
BLOQUE FOR...NEXT...................................................................................................................9
BLOQUE FOR EACH...NEXT.....................................................................................................12
BLOQUE WHILE...WEND..........................................................................................................13
BLOQUE DO...LOOP Y SUS VARIACIONES............................................................................14
DO WHILE... LOOP.................................................................................................................14
DO UNTIL... LOOP.................................................................................................................16
OTRAS VARIACIONES DE ESCRITURA DEL BLOQUE DO............................................17
UN BLOQUE ESPECIAL: WITH...END WITH..............................................................................17

Índice de contenido Capítulo 5


¡Y LA ESTRELLA INVITADA... DOCMD!.......................................................................................2
CONVERSACIÓN OÍDA AL PASAR............................................................................................2
LLAMAR A LOS FORMULARIOS E INFORMES EN CÓDIGO...............................................2
CREANDO UNA MINI-BD PARA IR “JUGANDO” CON EL CÓDIGO....................................2
DOCMD... PARA ABRIR “ALGO”................................................................................................3
DOCMD... PARA CERRAR “ALGO”............................................................................................4
DOCMD PARA “SALIR”... Y BYE-BYE......................................................................................6
PROFUNDIZANDO: DOCMD CON FORMULARIOS...............................................................7
UN PASEO POR... LOS REGISTROS.......................................................................................7
¿CÓMO DESEA EL FORMULARIO? ¿POCO HECHO, AL PUNTO O MUY HECHO?......9
UTILICEMOS UN FILTRO SENCILLO.................................................................................10
Y COMPLIQUEMOS UN POCO EL FILTRO........................................................................11
UNA PUNTUALIZACIÓN SOBRE LOS FILTROS..........................................................12
Y SIGAMOS EXPLORANDO MANIPULACIONES............................................................12
UN ÚLTIMO EJEMPLO ALGO MÁS COMPLEJO...............................................................14
PARA FINALIZAR...................................................................................................................15

Índice de contenido Capítulo 6


SIGAMOS UN POCO MÁS CON DOCMD Y FORMULARIOS.....................................................2
LA PREPARACIÓN DEL EJEMPLO DE ESTE CAPÍTULO.......................................................2
SEGUNDA CONVERSACIÓN OÍDA AL PASAR........................................................................3

2
Visítame en http://siliconproject.com.ar/neckkito/
CONTROLES, CONTROLES........................................................................................................3
CUIDADO: AÑADIR REGISTROS O “PASEARSE” POR LOS REGISTROS...........................7
POR FIN APARECE NUESTRO SUBFORMULARIO.................................................................8
EL ÚLTIMO DETALLE DE NUESTRO FORMULARIO...........................................................11
REUTILIZANDO NUESTRO FORMULARIO...........................................................................13
PARA FINALIZAR ESTE CAPÍTULO........................................................................................18

Índice de contenido Capítulo 7


APLICANDO FILTROS......................................................................................................................2
UNAS PALABRAS INICIALES....................................................................................................2
LA PREPARACIÓN DEL EJEMPLO DE ESTE CAPÍTULO.......................................................2
EL PRIMER FILTRO......................................................................................................................2
LA FUNCIÓN NZ......................................................................................................................4
SEGUIMOS CON NUESTRO FILTRO.....................................................................................5
FILTRO POR CONSULTA.............................................................................................................6
FILTROS COMPUESTOS..............................................................................................................7
INCISO: ESE COMBO CON VALORES REPETIDOS............................................................8
SIGAMOS...................................................................................................................................8
FILTRO PARAMETRIZADO.........................................................................................................9
UN POCO MÁS SOBRE FILTROS COMPUESTOS..................................................................11
FILTRO EN EL PROPIO FORMULARIO...................................................................................13
A TRAVÉS DE UN “FILTERON”...........................................................................................13
UTILIZACIÓN DEL “LIKE”..............................................................................................14
A TRAVÉS DE UN “CLON” DEL RECORDSET DEL FORMULARIO...............................15
NUESTRO CLON NO ENCUENTRA EL VALOR............................................................16
MÁS ALLÁ DE LOS FILTROS: CONTROLES Y LA PROPIEDAD ROWSOURCE...............16
TIPOS DE ROWSOURCE.......................................................................................................17
DATOS “LISTA DE VALORES”.........................................................................................17
DATOS “TABLA/CONSULTA”..........................................................................................18
MODIFICAR EL ORIGEN SI EL TIPO DE DATOS ES DE “TABLA O CONSULTA”...20
DATOS “LISTA DE CAMPOS”..........................................................................................21
LOS INFORMES Y LA PROPIEDAD RECORDSOURCE........................................................22
LO MISMO, PERO CON UN FORMULARIO.......................................................................23
UNAS PALABRAS FINALES......................................................................................................24

Índice de contenido Capítulo 8


TRABAJEMOS CON FECHAS, FORMATOS Y CONVERSIONES DE DATOS............................3
COGER LA FECHA DEL SISTEMA (Date)..................................................................................3
APLICAR FORMATOS A LA FECHA..........................................................................................3
Formato “Short Date”.................................................................................................................3
Formato “Long Date”..................................................................................................................4
Formato personalizado ...............................................................................................................4
APLICAR FORMATOS A LA HORA (Time)................................................................................5
Obtener la hora del sistema.........................................................................................................5
Manipular la hora........................................................................................................................5

3
Visítame en http://siliconproject.com.ar/neckkito/
FECHA Y HORA A LA VEZ (Now)...............................................................................................6
FORMATO A CADENAS DE CARACTERES..............................................................................6
Valores a mayúsculas (UCase)....................................................................................................6
Propiedad InputMask..................................................................................................................6
Valores a minúsculas (LCase).....................................................................................................7
La función StrConv.....................................................................................................................7
Inicio de palabras en mayúsculas (vbProperCase)......................................................................8
Eliminación de espacios en blanco (Trim, LTrim, RTrim)..........................................................8
CONVERSIONES DE DATOS.......................................................................................................9
Funciones conversoras................................................................................................................9
Detección tipo de dato.................................................................................................................9
Unos cuantos ejemplos prácticos sobre funciones conversoras y tipos de datos........................9
Supuesto 1: aprendemos las funciones LEFT y RIGHT........................................................9
Supuesto 2: aprendemos la función MID.............................................................................10
Supuesto 3: aprendemos la función INSTR y la función LEN............................................11
Supuesto 4: aprendemos la función REPLACE...................................................................13
Supuesto 5: practicamos con IsNumeric()............................................................................14
SEGUIMOS MANIPULANDO FECHAS....................................................................................14
Analizar el día de una fecha: función Weekday().....................................................................14
Obtener los elementos de una fecha: funciones Day(), Month(), Year()...................................16
Ver el nombre del mes de una fecha: función MonthName()...................................................16
La función DatePart()................................................................................................................17
La función DateDiff()...............................................................................................................18
Sumar días a una fecha..............................................................................................................18
Pequeño ejercicio práctico........................................................................................................19
UN EJEMPLO FICTICIO... REAL COMO LA VIDA MISMA...................................................19
PARA ACABAR.................................................................................................................................22

Índice de contenido Capítulo 9


TRABAJANDO CON MATRICES.....................................................................................................2
MATRICES DE UNA DIMENSIÓN (MATRICES ESTÁTICAS)................................................2
OTRA MANERA DE CREAR UNA MATRIZ ESTÁTICA DE UNA DIMENSIÓN -Función
Array()-.......................................................................................................................................4
APROVECHAMOS PARA APRENDER LA FUNCIÓN Rnd() Y LA INSTRUCCIÓN
Randomize..................................................................................................................................6
MATRIZ ESTÁTICA SIN SABER LA LONGITUD DE LA MATRIZ. EJEMPLO DE
SUSTITUCIÓN DE CARACTERES.........................................................................................7
DETERMINANDO EL TIPO DE DATOS DE LA MATRIZ.........................................................9
MATRICES MULTIDIMENSIONALES (MATRICES ESTÁTICAS)........................................10
Un ejemplo para matriz de tres dimensiones............................................................................11
MATRICES DINÁMICAS............................................................................................................13
(y aprovechamos para aprender algunas SQL simples).................................................................13
ReDim Preserve........................................................................................................................16
ALGUNOS ELEMENTOS INTERESANTES DE LA MATRIZ: las funciones UBound() y
LBound() y la instrucción Erase....................................................................................................19

4
Visítame en http://siliconproject.com.ar/neckkito/
Y ACABAMOS EL TEMA DE MATRICES.....................................................................................21

Índice de contenido Capítulo 10


OBJETOS DE ACCESO A DATOS (I)................................................................................................2
OBJETOS DE ACCESO A DATOS: DAO Y ADO........................................................................2
DAO............................................................................................................................................2
Un poco de teoría...................................................................................................................2
Registro de la librería DAO....................................................................................................4
Un poco de práctica................................................................................................................5
Base de datos actual...........................................................................................................5
Referencia al espacio de trabajo: Workspace....................................................................5
Referencia a las tablas.......................................................................................................7
Referencia a las consultas..................................................................................................9
Referencia a los campos..................................................................................................10
Otros objetos que existen y que no vamos a ver aquí......................................................12
ALGUNOS CÓDIGOS INTERESANTES SOBRE LO QUE HEMOS VISTO.................12
Creación de una tabla.......................................................................................................13
Variación sobre el tema...............................................................................................18
Creación de una tabla: sistema rápido.............................................................................20
Creación de una consulta.................................................................................................22
PARA FINALIZAR ESTE CAPÍTULO........................................................................................24

Índice de contenido Capítulo 11


OBJETOS DE ACCESO A DATOS (II)...............................................................................................2
RECORDSET..................................................................................................................................2
LA MANERA MÁS FÁCIL DE ACCEDER A UN RECORDSET: CLONARLO........................3
UN POCO DE TEORÍA..................................................................................................................5
ABRIR UN RECORDSET PARA CONSULTAR UN DATO........................................................6
RECORRER UN CONJUNTO DE REGISTROS..........................................................................7
MODIFICANDO UN REGISTRO..................................................................................................9
MODIFICANDO TODOS LOS REGISTROS.............................................................................10
Modificando todos los registros pero con más de un campo....................................................11
Consultando una consulta.........................................................................................................12
Consultando una consulta que aún no existe.............................................................................14
¿Error porque el valor no existe?.........................................................................................15
BÚSQUEDA RÁPIDA: Seek........................................................................................................17
AÑADIENDO UN REGISTRO....................................................................................................18
UNOS PREPARATIVOS NECESARIOS.....................................................................................19
EL EJEMPLO FINAL: UN “MIX” DE TODO LO APRENDIDO...............................................21
Cómo sería nuestra SQL...........................................................................................................21
ESTRUCTURACIÓN DEL CÓDIGO......................................................................................22
DESARROLLANDO EL CÓDIGO.........................................................................................22
UNAS PALABRAS FINALES......................................................................................................25

5
Visítame en http://siliconproject.com.ar/neckkito/
Índice de contenido Capítulo 12
OBJETOS DE ACCESO A DATOS (III).............................................................................................2
CONFESIONES ÍNTIMAS.............................................................................................................2
UN POCO DE TEORÍA..................................................................................................................2
REGISTRANDO LIBRERÍAS........................................................................................................4
PREPARANDO NUESTRA BD.....................................................................................................4
CÓDIGOS BÁSICOS INICIALES.................................................................................................6
INICIANDO LAS CONEXIONES............................................................................................6
USO DEL “NEW” PARA NUEVAS CONEXIONES................................................................7
FINALIZANDO LAS CONEXIONES......................................................................................8
CONTROLANDO LAS TRANSACCIONES................................................................................8
LOS RECORDSET........................................................................................................................10
RECORDSET SOBRE UNA TABLA......................................................................................11
RECORDSET SOBRE UNA CONSULTA SQL......................................................................12
EL OBJETO COMMAND.............................................................................................................13
ALGUNOS CÓDIGOS DE EJEMPLO.........................................................................................15
Separación de datos...................................................................................................................15
Búsqueda de registro: método SEEK........................................................................................17
Investigando los campos de nuestras tablas..............................................................................18
Y PARA FINALIZAR...................................................................................................................19

Índice de contenido Capítulo 13


USO DE ETIQUETAS / CONTROL DE ERRORES .........................................................................2
PREPARANDO NUESTRA BD DE PRUEBAS............................................................................2
ETIQUETAS....................................................................................................................................2
ESTRUCTURA DE LAS ETIQUETAS. PROCESO DEL CÓDIGO........................................3
ETIQUETAS DENTRO DE UN BLOQUE...............................................................................6
CONTROL DE ERRORES.............................................................................................................7
MOSTRAR INFORMACIÓN DEL ERROR.............................................................................8
MANIPULANDO LOS ERRORES...........................................................................................9
UN ERROR HABITUAL QUE SE SOLUCIONA... HACIENDO NADA.............................11
LA PROPIEDAD SOURCE.....................................................................................................13
ON ERROR RESUME NEXT......................................................................................................15
GENERANDO NUESTROS PROPIOS ERRORES....................................................................16
ALGO MÁS SOBRE ERRORES..................................................................................................18
TIPOS DE ERRORES..............................................................................................................18
COMPILANDO EL CÓDIGO..................................................................................................21
DEPURACIÓN DE ERRORES................................................................................................22
UNOS BREVES TRUCOS.......................................................................................................22
PARA FINALIZAR.......................................................................................................................23

Índice de contenido Capítulo 14


AUTOMATIZACIÓN..........................................................................................................................2
INTRODUCCIÓN...........................................................................................................................2
NECESITO UN DICCIONARIO....................................................................................................2

6
Visítame en http://siliconproject.com.ar/neckkito/
PREPARANDO NUESTROS ELEMENTOS DE PRUEBAS.......................................................3
ACCESS......................................................................................................................................3
EXCEL........................................................................................................................................4
PROTOCOLO DDE........................................................................................................................4
FASE DE INICIO: DDEInitiate..................................................................................................4
RECOGER DATOS: DDERequest.............................................................................................6
ENVIAR DATOS: DDEPoke.....................................................................................................8
ENVÍO DE COMANDOS: DDEExecute...................................................................................9
FINALIZAR CONEXIÓN: DDETerminate / DDETerminateAll...............................................9
AUTOMATIZACIÓN: INDICACIONES INICIALES................................................................10
Registrar la referencia...............................................................................................................10
Abrir la aplicación servidor.......................................................................................................10
Abrir un archivo existente.........................................................................................................10
Métodos, propiedades y demás malas hierbas..........................................................................11
ACCESS – WORD........................................................................................................................11
ACCESS – EXCEL.......................................................................................................................14
ACCESS – OUTLOOK.................................................................................................................17
ACCESS – POWERPOINT...........................................................................................................18
FUNCIONES CREATEOBJECT() Y GETOBJECT()..................................................................19
PARA FINALIZAR.......................................................................................................................20

Índice de contenido Capítulo 15


UN POCO DE XML Y XSL................................................................................................................2
INTRODUCCIÓN...........................................................................................................................2
¿QUÉ ES XML Y XSL?..................................................................................................................2
PREPARANDO NUESTRA BASE DE DATOS.............................................................................3
EXPORTANDO A XML..................................................................................................................4
PROCEDIMIENTO....................................................................................................................4
ARCHIVOS DE RESULTADO..................................................................................................6
MANIPULANDO EL ARCHIVO XML....................................................................................8
EXPORTANDO UN FORMULARIO........................................................................................8
TRASPASANDO LOS DATOS A UNA APLICACIÓN EXTERNA...........................................10
IMPORTANDO XML...................................................................................................................10
PARA FINALIZAR.......................................................................................................................11

Índice de contenido Capítulo 16


MANEJO, A TODAS HORAS, DIRECTORIOS, FICHEROS E IMPRESORAS... .........................2
INTRODUCCIÓN...........................................................................................................................2
PREPARANDO EL TERRENO......................................................................................................2
COMBO CON FILTRO POR NOMBRE DE INFORME..............................................................3
IMPRIMIR UNA PÁGINA EN CONCRETO / RANGO DE PÁGINAS.......................................4
LA COLECCIÓN IMPRESORAS..................................................................................................7
RECORRIENDO LA COLECCIÓN..........................................................................................7
LA IMPRESORA PREDETERMINADA... QUE NOSOTROS QUEREMOS.........................7
IMPRESIÓN A TRAVÉS DEL CUADRO DE DIÁLOGO DE IMPRESORAS.......................8

7
Visítame en http://siliconproject.com.ar/neckkito/
ALGUNAS IDEAS DE APLICACIÓN..........................................................................................9
SELECCIÓN MEDIANTE CUADRO DE LISTA................................................................9
SELECCIÓN MEDIANTE UN MARCO DE OPCIONES.................................................10
UTILIZANDO LA IMPRESORA VIRTUAL “PDFCREATOR”.................................................11
MANIPULACIÓN DE DIRECTORIOS Y FICHEROS....................................................................12
SACANDO INFORMACIÓN DE NUESTRA APLICACIÓN................................................12
TRABAJANDO CON DIRECTORIOS........................................................................................14
CREANDO DIRECTORIOS: MkDir.......................................................................................14
BORRANDO DIRECTORIOS: RmDir....................................................................................16
EXAMINANDO CARPETAS Y FICHEROS...............................................................................18
LA FUNCIÓN Dir()..................................................................................................................18
“MATANDO” ARCHIVOS: Kill...................................................................................................20
OTRAS “CURIOSIDADES” SOBRE GESTIÓN DE CARPETAS.............................................23
CURDIR().................................................................................................................................23
CHDIR()....................................................................................................................................24
CHDRIVE()..............................................................................................................................25
PARA FINALIZAR EL CAPÍTULO.................................................................................................26

Índice de contenido Capítulo 17


SIGAMOS UN POCO MÁS CON DIRECTORIOS Y ARCHIVOS... ..............................................2
INTRODUCCIÓN...........................................................................................................................2
UTILIZANDO UNA HERRAMIENTA DE OFFICE: FileDialog..................................................2
CARACTERÍSTICAS COMUNES............................................................................................2
ELEGIR UNA CARPETA..........................................................................................................3
ELEGIR UN ARCHIVO.............................................................................................................4
ALGUNAS IDEAS QUE PUEDEN RESULTAR ÚTILES.......................................................7
Ruta “temporal” de trabajo de la BD......................................................................................7
Ruta “permanente” de trabajo de la BD.................................................................................7
Trabajo con diferentes tipos de archivo..................................................................................8
TRABAJAR CON ARCHIVOS DE TEXTO (I): VISUAL BASIC...............................................9
LEER DATOS DE UN TXT.......................................................................................................9
ESCRIBIR DATOS EN UN TXT.............................................................................................11
TRABAJAR CON ARCHIVOS DE TEXTO (II): FileSystemObject (fso)..................................12
LEER DATOS DE UN TXT.....................................................................................................13
ESCRIBIR DATOS EN UN TXT.............................................................................................14
FILESYSTEMOBJECT.................................................................................................................15
TRABAJAR CON UNIDADES DE DISCO............................................................................15
Obtener el número de serie de una unidad de disco.............................................................16
Obtenemos el nombre de la unidad de disco........................................................................16
Obtenemos el tipo de unidad (extraíble o fija).....................................................................17
Obtenemos el total de espacio y el espacio disponible........................................................17
Obtenemos la disponibilidad de la unidad...........................................................................18
Otras propiedades que podemos utilizar..............................................................................19
TRABAJAR CON CARPETAS...............................................................................................19
CREANDO UNA CARPETA (Y ALGUNAS COSILLAS MÁS)......................................19

8
Visítame en http://siliconproject.com.ar/neckkito/
ELIMINANDO UNA CARPETA........................................................................................20
OTRAS PROPIEDADES QUE PODEMOS UTILIZAR....................................................21
TRABAJAR CON ARCHIVOS...............................................................................................22
DOS BAGATELAS.......................................................................................................................23
CREAR NUESTRO “DICCIONARIO”...................................................................................23
CREAR UN BACKUP DE LA BD EN TIEMPO DE EJECUCIÓN.......................................25
PARA FINALIZAR ESTE CAPÍTULO........................................................................................27

Índice de contenido Capítulo 18


TIPOS, COLECCIONES Y CLASES..................................................................................................2
INTRODUCCIÓN...........................................................................................................................2
TIPOS..............................................................................................................................................2
COLECCIONES..............................................................................................................................6
DEFINIR UNA COLECCIÓN....................................................................................................6
AÑADIR ELEMENTOS A UNA COLECCIÓN........................................................................7
CONTAR ELEMENTOS DE UNA COLECCIÓN....................................................................7
ELIMINAR ELEMENTOS DE UNA COLECCIÓN.................................................................7
RECORRER LOS ELEMENTOS DE UNA COLECCIÓN.......................................................8
UN EJEMPLO CON TODO LO ANTERIOR............................................................................8
CLASES...........................................................................................................................................9
TRABAJANDO CON LAS CLASES...........................................................................................11
EXPORTANDO EL MÓDULO DE CLASE............................................................................12
SEGUIMOS CON EL TRABAJO CON CLASES...................................................................13
UTILIZANDO LAS PROPIEDADES: PROCEDIMIENTO PROPERTY..............................16
PROPERTY LET..................................................................................................................16
PROPERTY GET.................................................................................................................16
PROPERTY SET..................................................................................................................17
APLIQUEMOS EL PROCEDIMIENTO PROPERTY........................................................17
UNAS BREVES EXPLICACIONES TEÓRICAS BASADAS EN TODO LO ANTERIOR. 21
UN EJEMPLO INTEGRÁNDOLO CON ACCESS.....................................................................23
PARA FINALIZAR ESTE CAPÍTULO........................................................................................31

Índice de contenido Capítulo 19


PERSONALIZAR LA CINTA DE OPCIONES .................................................................................2
INTRODUCCIÓN...........................................................................................................................2
CREANDO LA ESTRUCTURA DE NUESTRA BD.....................................................................3
LA CINTA DE OPCIONES. IDEAS GENERALES.......................................................................4
CÓDIGO XML...........................................................................................................................5
NUESTRA TABLA USysRibbons..............................................................................................5
LOS NOMBRES DE LA CINTA DE OPCIONES.....................................................................6
PLANIFICANDO NUESTRA CINTA DE OPCIONES.................................................................6
EL CÓDIGO XML INICIAL......................................................................................................7
PROGRAMACIÓN DE NUESTRA CINTA COMÚN..............................................................9
¿CÓMO ACTIVAR NUESTRA CINTA COMÚN?.............................................................12
PROGRAMACIÓN DE NUESTRA CINTA DE FORMULARIO..........................................13

9
Visítame en http://siliconproject.com.ar/neckkito/
¿CÓMO ACTIVAR NUESTRA CINTA DE FORMULARIO?...........................................14
PROGRAMANDO NUESTROS BOTONES CON VBA............................................................15
PREPARATIVOS PREVIOS....................................................................................................15
AHORA SÍ, A POR EL CÓDIGO VBA...................................................................................15
UNAS ÚLTIMAS LÍNEAS SOBRE LA CINTA DE OPCIONES...............................................17
PARA FINALIZAR EL CAPÍTULO.............................................................................................18

Índice de contenido Capítulo 20


UN GRAN EJEMPLO: APRENDEMOS A MANEJAR MÓDULOS Y UNOS CUANTOS
“TRUCOS” MÁS.................................................................................................................................2
INTRODUCCIÓN...........................................................................................................................2
EL EJEMPLO..................................................................................................................................2
PLANIFICANDO LA BD..........................................................................................................2
CONFIGURANDO NUESTRAS RELACIONES.....................................................................3
CREANDO NUESTROS FORMULARIOS..............................................................................4
CERRANDO NUESTROS FORMULARIOS: CREAMOS EL MÓDULO..............................6
FMENU, FPPTO Y NUESTRO MÓDULO...............................................................................6
PARTIDAS DE INGRESO....................................................................................................6
PARTIDAS DE GASTO........................................................................................................9
FMENU, FMOV Y NUESTRO MÓDULO................................................................................9
MOVIMIENTOS DE INGRESO...........................................................................................9
MOVIMIENTO DE GASTO...............................................................................................11
CONFIGURANDO EL INICIO DE LA APLICACIÓN. ¿Y FCHIVATO?..............................12
CONFIGURANDO FCHIVATO..........................................................................................12
CONFIGURANDO EL INICIO DE LA APLICACIÓN.....................................................12
FORMULARIO DE CAMBIO DE AÑO DE TRABAJO........................................................13
¿CUÁNTO HEMOS INGRESADO/GASTADO EN UNA PARTIDA EN PARTICULAR?. .15
CREÁNDONOS UN INFORME FANTASMA.......................................................................18
PARA FINALIZAR ESTE CAPÍTULO........................................................................................22

10
Visítame en http://siliconproject.com.ar/neckkito/
CURSO DE VB

CAPÍTULO 1

Índice de contenido
1.- INTRODUCCIÓN AL CURSO......................................................................................................2
2.- EL ENTORNO DE TRABAJO.......................................................................................................2
MÓDULOS......................................................................................................................................4
COMPARACIÓN Y DECLARACIÓN DE VARIABLES..............................................................4
3.- VARIABLES Y TIPOS DE DATOS...............................................................................................6
VARIABLES...................................................................................................................................6
TIPOS DE DATOS..........................................................................................................................8

1
Visítame en http://siliconproject.com.ar/neckkito/
1.- INTRODUCCIÓN AL CURSO
Hola a tod@s:

Voy a empezar este curso diciendo que este no es un curso


de VBA para Access... en el sentido estricto del término.

Es decir: si cogéis un libro sobre VBA para Access (si tenéis


la suerte de “pillar” alguno que valga la pena) normalmente
hay una parte importante del curso que se refiere a teoría
pura y dura, con algún que otro ejemplo, pero pocos...

Por otra parte resulta prácticamente imposible realizar un curso de VBA para Access que logre
abarcar “todo” lo que puede explicarse sobre este tipo de programación. Hay códigos que
funcionan perfectamente... hasta que no funcionan. ¿Por qué? Porque el código funciona si se
está aplicando sobre un textbox (por poner un ejemplo), pero si se aplica a un combobox la
cosa se nos “espachurra”. Tener en cuenta toda esta casuística es, definitivamente, “de locos”.

Sin embargo, no debemos desanimarnos por mis anteriores palabras. La cosa no es tan
drástica, en general, como acabo de comentar, pero mi intención de empezar esta introducción
así ha sido para que quede claro que:

• Si seguimos este curso podremos hacer un montón de cosas, pero no todas. Hay que
“moverse”, por eso...
• No lo sé todo de VBA. Mi gran amiga es la tecla F1, y también los foros de ayuda que
hay por Internet. Pero, además...
• Utilizad un poquito de imaginación...

Mi gran ilusión sería que al acabar el curso fuerais capaces no sólo de hacer, sino de, si no lo
sabéis hacer, sacar de la “chistera” una solución vuestra, propia, o si encontráis algún código
que alguien ha pensado previamente, seáis capaces de “entender” que hace ese código, y
poder decir: “¡Vaya! Este “pedazo” de código me sirve para lo que yo necesito... ¡y sé cómo
adaptarlo!”

Dicho lo anterior esta primera lección será un poco “dura” porque hay que empezar por utilizar
unos conceptos básicos, y eso representa un poquito de teoría. Intentaré hacerlo lo más
“suave” posible.

2.- EL ENTORNO DE TRABAJO


Nuestro entorno de trabajo va a ser el editor de VBA, que a partir de ahora, como soy tan
fashion, lo voy a llamar VBE (Visual Basic Editor). No me voy a meter “a fondo” con el VBE,
sino que sólo explicaré la terminología fundamental que utilizaremos durante el curso, para
saber como mínimo de qué estamos hablando.

Y como dicen que una imagen vale más que mil palabras aquí os presento al VBE.

2
Visítame en http://siliconproject.com.ar/neckkito/
 

Vamos a ver a nuestros “amiguitos”:

-- Barra de menú (más que evidente)


-- Barra de herramientas (más que evidente)
-- Ventana de proyecto: en esta ventana podemos ver los objetos de Access que tienen
algún módulo asociado (hablaremos de eso más adelante), y también podemos ver los
módulos estándar (mdlNeckkito) y los módulos de clase (clsContainers)
-- Ventana de propiedades: que nos muestra las propiedades del elemento que tenemos
seleccionado en ese momento. En la imagen, yo puedo ver que, por ejemplo, el Form1 permite
la adición de registros (AllowAdditions: True)
-- Ventana inmediato: es una ventana que nos permite ver ciertos resultados de las acciones
del código que estamos programando. Hablando en plata, es un poco para hacer “guarrerías”.
Por ejemplo, si tenemos la ventana inmediato a la vista (si no la tenemos debemos ir a menú
Ver → Ventana Inmediato, o pulsar la combinación de teclas CTRL+G), podemos escribir lo
siguiente:
?4 + 3
Y pulsamos Enter
Y como Access es tan listo nos dice que el resultado de esa operación es 7.

3
Visítame en http://siliconproject.com.ar/neckkito/
Otra prueba, utilizando un poco de álgebra. Escribamos lo siguiente:

a=3
b=5
?a+b

Y la ventana inmediato nos mostrará que el valor es 8.


Flipante, ¿verdad? ;)

-- Ventana de código: aquí se nos abrirán las ventanas


de los módulos donde podremos escribir nuestros códigos.
Como vemos en el ejemplo, yo tengo abiertas tres ventanas
correspondientes a dos formularios y un informe.

Y por ahora esos seis elementos serán suficientes para “ir tirando”.

Aprovecharemos la imagen para explicar otros conceptos:

MÓDULOS

– Módulo de objeto: es el módulo que se asocia a aquel objeto de Access que contiene
código. Si miramos la ventana de proyecto veremos que mi BD tiene tres formularios y un
informe con código asociado.
– Módulo estándar: es un tipo de módulo que no está asociado a ningún objeto. En
general contiene código accesible desde cualquier módulo estándar o de objeto.
– Módulo de clase: es módulo que, como su nombre indica, sirve para definir clases. Este
tipo de módulo es algo complejo de entender para quien se inicia en VBA, por lo que lo
dejaremos para una explicación posterior.

¿Qué ventaja tiene programar módulos estándar? Para que nos entendamos, para “trabajar
menos”. Vamos a poner un ejemplo abstracto: imaginemos que tenemos un formulario con
diez cuadros de texto, y que necesitamos que en cada uno de ellos, tras escribir un valor, se
realice una comprobación para saber si el valor es correcto. Deberíamos programar el evento
“Después de actualizar” de cada TextBox de la manera siguiente:

.- Cógeme el valor introducido


.- Haz todas estas operaciones de comprobación
.- Avísame si el valor introducido no es correcto.

Ello nos obligaría a escribir ese código en cada uno de los TextBox (¡bendito copy-paste!).

Pero, ¿y si creamos un módulo que nos haga las operaciones de comprobación? Esas
operaciones sólo deberían ser escritas una sola vez, mientras que a los TextBox deberíamos
decirle: llámame al módulo y dime si ha detectado que el valor es incorrecto.

Sé que esta explicación es un poco abstracta, pero aún no tenemos las herramientas
necesarias para un ejemplo práctico. Que no cunda el pánico, que pronto podremos ver lo
abstracto “en acción” ;)

COMPARACIÓN Y DECLARACIÓN DE VARIABLES

Si nos fijamos en la imagen vemos que los tres módulos de objeto que se ven empiezan
siempre con dos líneas:

4
Visítame en http://siliconproject.com.ar/neckkito/
Option Compare Database
Option Explicit

La primera le está diciendo a Access que utilice el sistema


predeterminado para comparar valores.

Vamos a clarificar este concepto con un ejemplo:

 En el VBE nos vamos a menú → Insertar → Módulo. Nos


aparecerá la ventana de código de ese módulo, en blanco.
Escribimos lo siguiente, debajo de esas dos líneas (a quien
le salga sólo una línea que no se preocupe: llegaremos a la
segunda en breve):

Option Compare Database
Private Sub miComparación()
If "Neckkito" = "NECKKITO" Then
MsgBox "Es el mismo nombre"
Else
MsgBox "Es un nombre diferente"
End If
End Sub

Si ahora situamos el cursor “dentro” del código y en la barra de menús le damos al “Play” (o
pulsamos la tecla F5) veremos que Access nos dice que es el mismo nombre. Esto es así
porque lo ha comparado como texto.

¿Cómo podríamos hacer para discriminar entre mayúsculas y minúsculas? Pues para hacer eso
le deberíamos indicar que queremos otro sistema de comparación, que sería el BINARY.

Es decir, que si ahora cambiamos nuestra primera línea de código, de manera que nos quede
así:

Option Compare Binary
Private Sub miComparación()
If "Neckkito" = "NECKKITO" Then
MsgBox "Es el mismo nombre"
Else
MsgBox "Es un nombre diferente"
End If
End Sub

y ejecutamos el código el resultado será que se trata de un nombre diferente.

En definitiva, que tenemos tres opciones de comparación para nuestra base de datos:

– Option Compare Database


– Option Compare Text
– Option Compare Binary

 Truco: si en el VBE situamos el cursor sobre la palabra “Compare” y pulsamos F1 se nos


abrirá la ayuda, que nos ampliará un poquito más estos conceptos.

Y ahora vayamos con la segunda de las líneas. Para programar código solemos utilizar
variables (veremos las variables más adelante). Una variable es simplemente una “palabra”
que utilizamos para poder almacenar un valor. Podemos definir directamente un valor para una
variable, simplemente escribiendo esto en el código (siempre y cuando no nos aparezca la

5
Visítame en http://siliconproject.com.ar/neckkito/
línea Option Explicit):

miValor = 3

Sin embargo, como su nombre indica, podemos pensar que


miValor va a almacenar un número, y durante el código
podría ser que almacenara, por ejemplo, una fecha. Eso
produciría un error de código, que sería difícil de depurar
(según el código). Para evitar lo anterior podemos decidir
que Access nos “obligue” a indicar qué tipo de variable (qué
tipo de dato) va a ser nuestra variable. Así, cuando no se
cumpla que miValor es un número Access nos llevará a la
línea donde miValor ya no es un número, con lo que la
depuración es más eficaz.

Cuando “forzamos” la definición de variables Access nos avisa añadiendo esa línea en los
módulos: Option Explicit.

¿Y cómo podemos llevar a cabo esa definición de variables? Pues muy fácil: nos vamos a menú
→ Herramientas → Opciones, y marcamos el check “Requerir declaración de variables”. A partir
de ese momento módulo que se cree nuevo módulo que requerirá la declaración de variables.

3.- VARIABLES Y TIPOS DE DATOS


VARIABLES

Y ya que estamos hablando de variables vamos a ver cómo podemos definir las variables.
Antes de empezar vamos a ver un par de tipos de variables:

– CONST: si declaramos una variable como Const estamos diciéndole a Access que esa
variable siempre tendrá un valor constante y que no puede cambiar.
– DIM: si declaramos una variable como Dim estamos diciéndole a Access que esa
variable puede ir cambiando su valor
– STATIC: si declaramos una variable como STATIC estamos diciéndole a Access que esa
variable puede ir cambiando su valor, el cual no se reiniciará en cada “pasada” del código.

¿Claro, verdad? Como yo no lo tengo tan claro vamos a utilizar un ejemplo muy simple para
ver cómo funciona la historia.

Creamos un módulo (ahora ya deberíamos saber cómo hacerlo) y escribimos el siguiente


código:


Private Sub misVariables()
Const miConstante As Integer = 5
Dim miDim As Integer
Static miStatic As Integer

miDim = miDim + 1
miStatic = miStatic + 10

Debug.Print miConstante
Debug.Print miDim
Debug.Print miStatic
End Sub

6
Visítame en http://siliconproject.com.ar/neckkito/
Si ahora ejecutamos el código (F5) en la ventana Inmediato encontraremos los siguientes
valores:

5
1
10

Volvamos a ejecutar el código. Los valores obtenidos ahora


son:

5
1
20

La constante, como buena constante, ha seguido manteniendo su valor en 5

La variable DIM, en la primera ejecución, ha asumido que su valor inicial era cero y como le
hemos sumado uno el resultado final ha sido 1.
En la segunda ejecución la DIM se ha reinicializado, de manera que ha vuelto a suponer que su
valor inicial era cero y como le sumamos 1 el resultado final ha sido 1.

La variable STATIC, en la primera ejecución, ha asumido que su valor inicial era cero y como le
hemos sumado 10 el resultado final ha sido 10.
En la segunda ejecución, por ser static, no se ha reinicializado, sino que ha “guardado” el valor
final de la primera ejecución. Por ello, si volvemos a sumarle diez, el resultado final será,
lógicamente, 20.

◊ ¿Qué más cosas podemos aprender del código que hemos utilizado? Pues, por ejemplo,
cómo se declaran las variables. Así, ahora ya sabemos que:

– Una variable Const se declara con la siguiente estructura


– Const <nombreVariable> As <tipoDato> = <valor>

– Una variable Dim se declara con la siguiente estructura


– Dim <nombreVariable> As <tipoDato>

– Una variable Static se declara con la siguiente estructura


– Static <nombreVariable> As <tipoDato>

◊ Otra cosa que podemos ver es que las variables numéricas, como en este caso, aceptan la
realización de operaciones matemáticas.

Es decir, hemos utilizado la suma, pero podríamos haber utilizado expresiones del tipo:

miDim = miDim * 3
miDim = miDim / 2

Y lógicamente podemos mezclar las distintas variables, siempre y cuando sean del mismo tipo
de dato.

Por ejemplo, podríamos haber escrito:

miDim = miConst + miStatic

◊ También ahora ya sabemos que para ver los resultados en la ventana Inmediato debemos

7
Visítame en http://siliconproject.com.ar/neckkito/
utilizar Debug.Print <valor/variable>

TIPOS DE DATOS

Hay varios (unos cuantos) tipos de datos. Vamos a realizar


aquí una enumeración de los tipos de datos, sin entrar en
detalles, porque como durante este curso vamos a
utilizarlos la práctica nos permitirá ver peculiaridades.

De todas maneras no voy a repetir lo que Microsoft ya ha


escrito. Así, si estamos interesados en saber las
peculiaridades de cada tipo de variable, lo que tenemos que
hacer es, simplemente:

1.- En cualquier ventana de código escribimos el tipo de dato (v.gr., escribimos String)
2.- Situamos el cursor encima de lo que hemos escrito y pulsamos F1

Y ahora sí, vamos con los tipos de datos:

 String: es una cadena de texto (se incluyen los espacios en blanco). Deben definirse
siempre entre comillas. Por ejemplo:
Const miNick As String = “Neckkito”

 Date: es un dato de tipo fecha/hora. Lo debemos definir entre almohadillas. Por ejemplo
Const miDtm As Date = #22/03/11#

 Boolean: es un dato de tipo verdadero/falso. Las palabras claves para definirlo son True o
False. Por ejemplo
Const miBln As Boolean = True

Hay que tener en cuenta que también puede coger su valor en función de si se cumple una
condición o no. Por poner un ejemplo “tonto”, el booleano miBln cogería el valor False si lo
defino así: miBln = 8<3

 Variant: es un dato de tipo “cajón de sastre”. La podemos utilizar cuando no estamos


seguros de qué tipo de dato adquirirá la variable. Por ejemplo
Dim niIdea As Variant

Si escribimos en el código algo así como

niIdea = “Ahora ya sé lo que es” esta variable Variant adquirirá las características de una
variable tipo String.

Los siguientes tipos son números “sin decimales”:

 Byte: es un dato de tipo numérico, que va de 0 a 255. Por ejemplo


Const miBit As Byte = 3

 Integer: es un dato de tipo numérico, que abarca un mayor rango de valores que el tipo
anterior. Por ejemplo
Const miInt As Integer = 10000

 Long: es un dato de tipo numérico, que abarca un mayor rango de valores que los dos
anteriores. Por ejemplo
Const miLng As Long = 100000

8
Visítame en http://siliconproject.com.ar/neckkito/
Y los siguientes son tipos de números “con decimales”; es decir, lo que se denomina “con coma
flotante”:

 Single: es un dato de tipo numérico con . Por ejemplo


Const miSing As Single = 2.1

 Double: es un dato de tipo numérico, de mayor rango


que el single (y de mayor precisión en los decimales). Por
ejemplo
Const miDbl As Double = 24.456

 Currency: es un dato de tipo numérico, de mayor precisión que el double. Por ejemplo
Const miCurrency As Currency = 1526.63579

Aunque pueda parecer que el tipo currency lo utilizamos sólo para valores de moneda, lo
podemos utilizar si queremos una precisión muy grande en los decimales.

 Decimal: es un dato de tipo numérico, con una enorme precisión en los decimales. En
Access no podemos declararlo directamente, sino que primero lo tenemos que declarar como
Variant y después convertirlo a decimal. Esto lo veremos en un capítulo posterior.

Un par de puntualizaciones al tema de las variables y tipos de datos:

 Existen lo que se denominan “caracteres de definición de tipos”, que permiten identificar el


tipo de la variable sin tener que escribir dicho tipo. Es decir, que si yo escribo
Dim miVariable% es como si escribiera Dim miVariable As Integer

La lista de equivalencias existentes la tenéis aquí:

CARÁCTER DE DEFINICIÓN DE TIPO TIPO VARIABLE


$ String
% Integer
& Long
! Single
# Double
@ Currency

 Existen unos “valores” que no pueden ser definidos por los tipos de datos que hemos visto,
exceptuando uno: el tipo de dato Variant.

Es decir, si no hay valor en un campo ese campo es nulo (NULL), o también podemos tener un
dato vacío (EMPTY), un valor “nada” (NOTHING), o un valor error (ERROR). Para recoger esa
“ausencia” o tipo especial de valor debemos emplear el tipo Variant. Ahora es un poco difícil
entrar en detalles sobre esta característica, pero a lo largo del curso veremos algunos ejemplos
de lo anterior y lo entenderemos perfectamente.

Y ya para acabar con este tema vamos a ver la utilidad de nuestra Option Explicit, ahora que
“controlamos” el tema de las variables y de los tipos de datos. Vamos a hacer lo siguiente:

1.- En nuestro VBE creamos un nuevo módulo (o reciclamos uno que tengamos con las
pruebas que hayamos podido hacer). Escribimos el siguiente código:

9
Visítame en http://siliconproject.com.ar/neckkito/

Private Sub miError()
Dim miInt As Integer
miString = "Aquí hay un error"
Debug.Print miString
End Sub

Si ejecutamos el código nos salta el siguiente error:

Y nos marca la variable que no se ha definido (miString).


Ahora nos ha marcado en amarillo la primera línea del procedimiento que hemos escrito,
bloqueando cualquier ejecución de código.

Para desbloquear la interrupción del código le damos al botón “stop” (restablecer)

Ahora, detectado el error, reescribimos el código de la siguiente manera:


Private Sub miError()
Dim miInt As Integer
miInt = "Aquí hay un error"
Debug.Print miString
End Sub

Y ejecutamos el código... Je, je... ¿alguien nos “chiva” que hay algo mal?

Por tercera vez reescribimos el código de la siguiente manera:


Private Sub miError()
Dim miInt As Integer
miInt = "Aquí hay un error"
Debug.Print miInt
End Sub

Y lo ejecutamos... obteniendo lo siguiente:

10
Visítame en http://siliconproject.com.ar/neckkito/
¿Por qué? Porque hemos definido miInt como Integer y cuando el valor que hemos asignado es
de tipo String.

Es interesante que nos vayamos familiarizando con el error “13 en tiempo de ejecución”, dado
que, en nuestros primeros proyectos, con toda probabilidad se va a convertir en un buen
amigo nuestro... ¡O al menos así lo dicta mi experiencia!

Os dejo como ejercicio (fácil, fácil) la corrección del código para que en la ventana inmediato
os aparezca un valor.

¡Suerte!

11
Visítame en http://siliconproject.com.ar/neckkito/
CURSO DE VB

CAPÍTULO 2

Índice de contenido
1.- PROCEDIMIENTOS Y FUNCIONES...........................................................................................2
UN PRIMER ACERCAMIENTO...................................................................................................2
TIPOS DE FUNCIONES Y PROCEDIMIENTOS.........................................................................4
ARGUMENTOS..............................................................................................................................4
EL ADORNO FINAL: BYVAL, BYREF y PARAMARRAY........................................................6
UN GRAN EJEMPLO.........................................................................................................................6
UN POCO MÁS DEL ENTORNO DE TRABAJO.............................................................................8
AYUDA CONTEXTUAL................................................................................................................8
SEPARACIÓN DE LA MISMA LÍNEA EN VARIAS...................................................................9
INTERDENTADO...........................................................................................................................9
COMENTARIOS...........................................................................................................................10

1
Visítame en http://siliconproject.com.ar/neckkito/
1.- PROCEDIMIENTOS Y FUNCIONES

UN PRIMER ACERCAMIENTO

Vamos a empezar este segundo capítulo hablando de cosas


tan “raras” como procedimientos y funciones.

La idea para llegar a tales conceptos empieza por algo tan


simple como: vamos a escribir un bloque de código, pero,
¿dónde lo metemos?

Y un poquito más allá, si en lugar de un bloque de código


queremos escribir varios, ¿cómo lo hacemos? ¿Lo
escribimos “todo seguido”? ¿Los separamos con rayas o con
puntos?

Para poder delimitar el “inicio” y el “fin” de un código en VB (en estos inicios del curso)
tenemos dos palabras reservadas, que son:

– Sub: que correspondería a un procedimiento


– Function: que correspondería a una función

Eso sería nuestro “inicio”, y lo que permitiría a VB saber que empieza un bloque. Los “fines”
serían, pues:

– End Sub
– End Function

La siguiente pregunta lógica sería: ¿por qué hay dos tipos, y no uno? La respuesta viene dada
por lo que queramos que haga el código.

Es decir, que lo anterior representa que podemos realizar dos tipos de operaciones.

1.- Si queremos que se ejecuten acciones necesitamos un procedimiento, esto es, necesitamos
utilizar un Sub.

2.- Si queremos obtener un valor necesitamos una función, esto es, necesitamos utilizar una
Function.

Lo vamos a ver muy claro a través de un ejemplo simple: imaginemos que tenemos un
formulario con un cuadro de texto y un botón. Y lo que queremos es que, al pulsar el botón, en
el cuadro de texto nos aparezca la fecha actual.

En este caso nos encontramos con dos elementos:

– El primero, obtener un valor, que en este caso será la fecha actual.


– El segundo, realizar una acción, que en este caso sería “conseguir que textBox nos
mostrara la fecha actual”.

¿Vamos viendo la “estructura”? ;)

El ejemplo es tan simple (y aunque fuera un poco más complicado también nos serviría) que
podemos englobar ambas acciones en un procedimiento. Así, escribiríamos lo siguiente:

2
Visítame en http://siliconproject.com.ar/neckkito/

Sub escriboFecha()
'Declaramos las variables. Os recuerdo que esto lo
vimos en el primer capítulo!
Dim fechaAhora As Date
'Cogemos la fecha del sistema
fechaAhora = Date
'Escribimos el valor obtenido en el textBox
Forms!NombreForm.textBox.Value = fechaAhora
'Indicamos el fin del procedimiento
End Sub

Y al pulsar el botón nos aparecería en nuestro textBox la fecha actual.

Como vemos, lo hemos escrito todo en un procedimiento. Ahora imaginemos que la obtención
de la fecha actual fuera un proceso muy complicado, o que continuamente necesitáramos
calcular la fecha para cada uno de los 50 textBox que tenemos en nuestro formulario. En ese
caso lo mejor hubiera sido separar el anterior procedimiento en sus dos elementos: un sub y
una función.

Así por una parte, tendríamos:


Function calculoFecha() As Date
calculoFecha = Date
End Function

Ahora calculoFecha siempre nos dará el valor de la fecha actual. Sólo tendremos que invocarla
(a la función) para conseguir su valor. El procedimiento se escribiría pues así:


Sub escriboFecha()
'Declaramos la variable
Dim fechaAhora As Date
'Llamamos a la función y guardamos su valor en nuestra variable
fechaAhora = calculoFecha()
'Rellenamos el textBox
Forms!NombreForm.textBox.Value = fechaAhora
'Finalizamos el proceso
End Sub

De lo anterior podemos quedarnos también con la idea de que para nombrar un procedimiento
o una función debemos seguir esta estructura:

Sub nombreProcedimiento()

Function nombreFuncion() As <tipoDato>

Lógicamente, si la función nos devuelve un valor, debemos indicarle qué tipo de valor debe
devolver (podemos encontrar la similitud con la declaración de variables que veíamos en el
capítulo 1).

3
Visítame en http://siliconproject.com.ar/neckkito/
Bueno. La idea principal de esta primera aproximación era distinguir entre funciones y
procedimientos, y para qué sirve cada uno. Vamos a seguir profundizando en el tema.

TIPOS DE FUNCIONES Y PROCEDIMIENTOS

Hablar de funciones y procedimientos “a palo seco” queda


muy “soso”, por lo que vamos a ponerles motes. Además, al
realizar esta “puesta de motes”, vamos a conseguir un
efecto interesante, que es lo que se denomina “delimitar el
ámbito de actuación”.

Existen varios “motes” para poner a ambos elementos. Veámoslos:


– Private: esta es una definición del procedimiento o función restrictiva, de manera que
todo lo que escribamos en el bloque de código no es accesible más que dentro de ese bloque
de código.

Por ejemplo, si definimos una variable dentro de ese bloque (por ejemplo, Dim miVar as Long),
sólo podremos “llamar” a esa variable dentro de ese bloque de código. Si escribimos otro y una
línea dijera: miVar = 5, nos saltaría el error “No se ha definido la variable” porque, si bien está
definida, lo está en otro procedimiento “private”, al cual no se tiene acceso.

– Public: esta definición es lo contrario a la anterior. Indica que el procedimiento estará


accesible para el resto de procedimientos y funciones de todos los módulos.

Estos dos serán los que, en teoría, utilizaremos más comúnmente. Existen otros un poco más
“especiales”, que son:

– Friend: se utiliza sólo en módulos de clase. No entraremos más en detalles sobre él.
– Static: si recordamos en el capítulo 1, cuando hablábamos de variables, comentábamos
que podíamos definir una variable como static, lo que significaba que “almacenaba” los valores
que iba tomando y no se reinicializaba en cada ejecución. Pues, por extensión, en un
procedimiento o función static las variables locales se conservan entre distintas llamadas.

ARGUMENTOS

A un procedimiento o función le podemos indicar que hay argumentos (o no). Si no hay


argumentos escribimos, tras su nombre, un paréntesis de apertura y de cierre, tal y como
hemos visto en el ejemplo que hemos planteado un par de páginas antes.

Los argumentos pueden ser obligatorios y opcionales. Si no indicamos nada serán obligatorios;
si queremos que sean opcionales debemos indicarlo añadiendo la palabra “Optional” antes del
argumento.

Aplicado a lo que ya sabemos se estructuraría de la siguiente manera:

Public/Private Sub nombreSub(argumento1, argumento2, …, Optional argumentoX)

Public/Private Function nombreFuncion(argumento1 As <tipoDato>, argumento2 As


<tipoDato>, …, Optional argumentoX As <tipoDato>) As <tipoDato>

Y esto tan teórico, ¿cómo se come?

Vamos a verlo con un ejemplo muy simple: imaginemos que queremos que al pulsar un botón

4
Visítame en http://siliconproject.com.ar/neckkito/
nos aparezca un mensaje con la fecha de mañana. Sé que lo que voy a explicar no es el
procedimiento óptimo pero me servirá para que lo entendáis.

En el código asociado al botón vamos a realizar las


siguientes acciones:

1.- Vamos a definir una variable que nos recogerá la fecha


actual.
2.- Vamos a llamar a un procedimiento que se encargará de
encontrar el día siguiente y mostrarnos el mensaje que
queremos.
3.- Al tiempo que llamamos al procedimiento le pasaremos
el valor sobre el que debe realizar los calculos, que en
nuestro caso será la variable miFecha.

Esto lo escribiríamos así:


Private Sub...
'Definimos la variable
Dim miFecha As Date
'Le asignamos la fecha actual
miFecha = Date
'Llamamos al procedimiento
Call fechaManana(miFecha)
End Sub

Como podemos ver, a través de la palabra reservada CALL llamamos a un procedimiento, y no


sólo eso, sino que le decimos que los cálculos los debe realizar sobre nuestra variable miFecha,
indicándoselo así entre paréntesis.

En el procedimiento deberíamos indicarle esa circunstancia a través de la definición de un


argumento. Así, escribiríamos:


Public sub fechaManana(unaFecha)
'Realizamos el cálculo del día de mañana
unaFecha = unaFecha + 1
'Mostramos el mensaje
MsgBox “Mañana será día “ & unaFecha
End Sub

Si analizamos el código veremos que:

– El procedimiento fechaManana(unaFecha) lo hemos declarado como Public para tener


acceso a él desde cualquier módulo.
– El argumento “unaFecha” ha cogido su valor desde miFecha, y eso lo hemos hecho a
través de la llamada Call fechaMañana(miFecha)
– Hemos creado un Sub porque queríamos una acción (mostrar un cuadro de mensaje).
Si sólo hubiéramos querido saber el valor de mañana no hubiéramos escrito un Sub, sino una
Function.

5
Visítame en http://siliconproject.com.ar/neckkito/
EL ADORNO FINAL: BYVAL, BYREF y PARAMARRAY

Hemos visto que a las funciones se les pueden pasar


argumentos. Ahora bien, estos argumentos se les pueden
pasar de dos maneras: por valor (ByVal) o por referencia
(ByRef).

Estos conceptos pueden ser al principio algo confusos, por


lo que aquí sí tendremos que armarnos de un poco de
paciencia para “soportar” un poco de teoría. Lo intentaré
explicar con mis palabras, procurando no entrar demasiado
en tecnicismos.

Cuando tenemos una variable que nos hace de argumento tenemos que esa variable tiene una
dirección de memoria. Para que nos entendamos, tenemos una “casilla” con una dirección, y
esa “casilla” es el “original”.

Ya sabemos que tenemos un “original”, y la pregunta es: ¿queremos operar con el original o
nos interesa más operar con una copia?

Y la respuesta es: si queremos operar directamente sobre el original, el argumento debe ser
pasado ByRef; sensu contrario, si queremos operar con una copia para mantener intacto el
original el argumento debe ser pasado ByVal.

En principio, si no indicamos nada, la opción por defecto es ByRef.

¿Y cómo lo escribiríamos? Pues declararíamos la función, a modo de ejemplo, de la siguiente


manera:

Public Function miSuperFuncion(ByVal miValor As Long) As Long

En principio lo dejamos aquí. Profundizaremos un poco más adelante.

El último “adorno” se refiere a que podemos pasar una matriz a una función. Para indicarle a la
función que “eso” es una matriz utilizamos ParamArray.

Como aún no hemos hablado de matrices no complicaremos, por ahora, la historia. Sólo os
comentaré que para indicárselo a la función, como imagino habréis adivinado, debemos
escribir una cosa así:

Public Function miSuperFuncion(ParamArray())

UN GRAN EJEMPLO
Vamos a suponer que tenemos 3 valores, y queremos obtener la media aritmética de esos
valores.

Lo que vamos a hacer es crear un nuevo módulo y le vamos a escribir una función, que será la
que nos calculará esa media aritmética, y dos procedimientos (lo podríamos hacer en menos
pasos, pero lo haremos así para que podáis ver todo lo que hemos visto hasta ahora).

El proceso será el siguiente:

– El primer procedimiento, que será Private, pedirá los valores al usuario, y los irá

6
Visítame en http://siliconproject.com.ar/neckkito/
almacenando en variables. Una vez las tenga todas...
– Llamaremos a la función, que será Public, la cual nos
calculará la media aritmética. Una vez la tenga calculada...
– Nuestro procedimiento Private...
– Llamaremos al segundo procedimiento, que será
Public, que se encargará de mostrarnos un cuadro de
mensaje con la media aritmética obtenida.

 Utilizaremos la función InputBox de manera muy simple


para solicitar los valores al usuario. Veremos esta función
un poco más adelante (¡pero ahora ya sabéis que para pedir
datos al usuario podemos utilizarla!)

 No utilizaremos ningún control de errores. Eso significa que los valores que debéis introducir
son números enteros. Si introducís “valores raros” os saltarán errores, y la cosa no funcionará
como toca.

Dicho esto vamos a ver cómo hacerlo:

1.- Creamos un nuevo módulo, que podemos guardar como mdlMediaAritmetica.

2.- Creamos nuestro primer procedimiento a través del siguiente código:


Private Sub valoresIniciales()
'Declaramos las variables
Dim valor1 As Integer, valor2 As Integer, valor3 As Integer
Dim media As Double
'Solicitamos los valores al usuario, al tiempo que los almacenamos
'en las variables declaradas
valor1 = InputBox("Introduzca el primer valor")
valor2 = InputBox("Introduzca el segundo valor")
valor3 = InputBox("Introduzca el tercer valor")
'Llamamos a la función que calcula la media
media = fncCalculaMedia(valor1, valor2, valor3)
'Llamamos al procedimiento que nos mostrará el mensaje
Call subMuestraMsj(media)
End Sub

Como vemos en el código a través de los comentarios, el proceso realiza el siguiente “paseo”:

Sub valoresIniciales → Function fncCalculaMedia → Sub valoresIniciales → Sub subMuestraMsj

3.- Debajo del anterior procedimiento escribimos la función, así:


Public Function fncCalculaMedia(primerValor As Integer, segundoValor As Integer, _
tercerValor As Integer) As Double
'Definimos las variables
Dim miSuma As Integer
miSuma = primerValor + segundoValor + tercerValor
'Calculamos el resultado, asignándolo directamente a la función
fncCalculaMedia = miSuma / 3
End Function

7
Visítame en http://siliconproject.com.ar/neckkito/
4.- Y debajo de la función escribimos el segundo procedimiento, así:


Public Sub subMuestraMsj(mediaObtenida)
'Mostramos el mensaje
MsgBox "La media de los tres valores introducidos es " &
mediaObtenida
End Sub

Por si nos hemos “perdido”, de manera global deberíamos


tener:

Fijémonos que para obtener la media con decimales hemos definido la variable media, del
primer procedimiento, y la propia función, como tipo de dato Double.

UN POCO MÁS DEL ENTORNO DE TRABAJO

AYUDA CONTEXTUAL

Si en la ventana de código vamos escribiendo el código veremos que VBA nos proporciona una
ayuda contextual que, para mi gusto, es de bastante utilidad.

Por ejemplo, si empezamos a escribir

8
Visítame en http://siliconproject.com.ar/neckkito/
Dim miValor As Int...

Veremos que sobre Int... nos muestra una serie de opciones


posibles. Si no queremos escribir mucho utilizamos las
flechas de nuestro teclado para seleccionar “Integer”; o, si
en ese momento no nos acordamos de cómo se llama
exactamente la “palabra” que queremos utilizar, tenemos
un chivatillo que nos lo “susurra”.

También podremos comprobar que, al escribir una función o


procedimiento, VBA detecta qué es lo que queremos hacer y ya
nos pone la sentencia de fin de la instrucción. Es decir, que si
nosotros escribimos:

Public Sub unSub() y pulsamos Enter automáticamente VBA nos escribirá End Sub

SEPARACIÓN DE LA MISMA LÍNEA EN VARIAS

La pantalla de nuestro ordenador puede ser muy grande o muy pequeña. Podríamos escribir
toda una línea de código seguida, de manera que nos fuéramos desplazando hacia la derecha,
con lo cual aparecería una barra de desplazamiento horizontal. Eso, a la hora de leer o repasar
el código, es bastante “molesto”. Podemos pues separar una línea en “trozos” de línea, lo que
hará mucho más legible el código.

Para indicar a VBA que la línea es la misma, aunque sigue en el siguiente salto de línea,
utilizamos el subguión (_).

Si cogemos el ejemplo anterior veremos que cuando hemos definido la función hemos escrito

Public Function fncCalculaMedia(primerValor As Integer, segundoValor As Integer, _


tercerValor As Integer) As Double

Si observamos lo anterior veremos que tras el segundo argumento (segundoValor As Integer,),


he añadido un subguión. De esta manera VBA entiende que lo que sigue en la línea siguiente
en realidad debe pertenecer a la primera línea de declaración de la función.

De hecho, hay programadores que utilizan el interdentado para definir nombre de función,
argumentos y tipo de función con este sistema. Es decir, lo que se hubiera escrito hubiera sido
lo siguiente:

Public Function fncCalculaMedia( _


primerValor As Integer, _
segundoValor As Integer, _
tercerValor As Integer _
) As Double

De esta manera es más “fácil” ver la función, a golpe de vista, de qué tipo es, y cuáles son sus
argumentos. En definitiva, que sobre gustos no hay nada escrito...

INTERDENTADO

Y ya que hablamos de interdentado, para hacer más legible un código se suele utilizar el

9
Visítame en http://siliconproject.com.ar/neckkito/
interdentado para identificar los distintos “bloques” dentro del propio bloque de código. Aunque
esto lo veremos a lo largo del curso, os puedo decir que esquemáticamente la cosa funcionaría
así (visualmente y a modo de ejemplo, quiero decir):

...
Private Sub unaSub()
'Declaración de variables
'Asignación de variables
'Bloque1 de código
'Bloque2 de código (dependiente del bloque1)
'Bloque3 de código (dependiente del bloque2)
'Fin Bloque3
'Fin Bloque2
'FinBloque1
'Bloque 4
'Bloque5 de código (dependiente del bloque4)
'Fin Bloque5
'Fin Bloque4
End Sub

COMENTARIOS

Y como habremos podido intuir, podemos escribir comentarios dentro del código. Para ello
basta anteponer una comilla simple (') al inicio de la línea que contendrá nuestro comentario.
Veremos que automáticamente VBA nos marca esa línea en verde (si no hemos cambiado la
configuración, claro).

Es altamente recomendable la escritura de comentarios por varios motivos:

– Si tenemos que pasar el código a alguien lo podrá entender más fácilmente


– Si tenemos que repasar nuestro código, ya sea al momento o al cabo de un par de
años, podremos “recordar” más fácilmente qué hace nuestro código si leemos nuestros propios
comentarios.
– Nos sirve para “guardar” información que podría sernos útil en un futuro, e incluso nos
sirve en procesos de depuración para nosotros mismos.

Este último punto es interesante, y de hecho yo, personalmente, lo utilizo bastante. Vamos a
explicar un ejemplo de lo que quiero decir, pero antes vamos a utilizar una herramienta muy
útil para esta finalidad.

1.- En nuestro editor de VBA mostramos la barra de herramientas llamada “Edición”. Para ello,
nos vamos a menú Ver → Barras de Herramientas → Edición

2.- Yo, personalmente, la coloco a la derecha de la barra de herramientas estándar.

3.- En esa barra de herramientas vemos que hay dos botones en concreto, que son “Bloque
con comentarios” y “Bloque sin comentarios” (marcados en la imagen).

4.- Si seleccionamos un “trozo” de código, y pulsamos sobre el botón “Bloque con comentarios”
(el de la izquierda de la imagen), veremos como VBA nos marca todo lo que hemos
seleccionado como comentario. Para quitar los comentarios realizamos la misma operación de
selección, pero clickamos sobre el botón “Bloque sin comentarios” (el de la derecha).

10
Visítame en http://siliconproject.com.ar/neckkito/
Por ejemplo, si yo escribo estas líneas de código supuestamente dentro de un código más
amplio:

miValor = “Esto”
miValor = miValor & “ concatena”
miValor = miValor & “ todo este”
miValor = miValor & “ texto”

y, por los motivos que sean, me produce un error, o no me


da el resultado esperado, podría eliminarlo del código
simplemente borrándolo. Pero, ¿y si con posterioridad me
doy cuenta de que lo necesitaba? Pues a volverlo a escribir
todo otra vez... ¿o no?

Lo que puedo hacer es marcarlo todo y dejarlo como un bloque con comentarios. De esta
manera no afectará a mi código pero lo tendré ahí para cuando lo necesite. Siempre estamos a
tiempo para realizar un “borrado” si finalmente no lo necesitamos.

Y, en principio y como fin, esto es todo por ahora.

¡Suerte!

11
Visítame en http://siliconproject.com.ar/neckkito/
CURSO DE VB

CAPÍTULO 31

Índice de contenido
MÓDULOS DE OBJETO, CONTROLES Y EVENTOS....................................................................2
MÓDULOS DE OBJETO...............................................................................................................2
EVENTOS.......................................................................................................................................2
CONTROLES..................................................................................................................................5
“MIX” DE LOS ANTERIORES CONCEPTOS.............................................................................5
LA FUNCIÓN MSGBOX....................................................................................................................7
LANZAR UN MENSAJE...............................................................................................................7
SALTOS DE LÍNEA EN MENSAJE..............................................................................................8
LANZAR MENSAJE Y ESPERAR RESPUESTA DE UN USUARIO.........................................9
LA FUNCIÓN INPUTBOX...............................................................................................................11
DETECTAR LA PULSACIÓN DEL BOTÓN <CANCELAR>...................................................13
OTROS “CONTROLES” PARA QUE NO NOS FALLE EL INPUTBOX..................................14
DETECTAR VALORES VACÍOS............................................................................................14
DETECTAR VALORES DE DIFERENTE TIPO....................................................................14
NORMALIZACIÓN..........................................................................................................................16
NOMBRES DE OBJETOS............................................................................................................16
NOMBRES DE CONTROLES.....................................................................................................17
NOMBRES DE VARIABLES.......................................................................................................17
¿Y POR QUÉ LOS PREFIJOS?....................................................................................................17
MÁS INFORMACIÓN SOBRE NORMALIZACIÓN.................................................................18

1 Los ejemplos propuestos en este capítulo están en una BD que os podéis bajar aquí

1
Visítame en http://siliconproject.com.ar/neckkito/
MÓDULOS DE OBJETO, CONTROLES Y EVENTOS

MÓDULOS DE OBJETO
Quizá lo lógico en un curso de VBA sería empezar a explicar,
llegados a este punto, lo que se denominan “bloques de
decisión”. Sin embargo, nos vamos a separar de lo
“ortodoxo” para entrar en unas explicaciones que nos
proporcionaran una base “experimental” para posteriores
contenidos. De hecho, si este curso está enfocado a Access,
es entendible, creo yo, que lo hagamos así.
Como apuntamos en el capítulo en el que hablábamos de tipos de módulos, vamos a tratar
ahora esos módulos que se hallan asociados a un objeto de Access, en concreto módulos
asociados a formularios e informes.
Para poder ir entendiendo lo que se va a explicar vamos a ir “viéndolo” al mismo tiempo que lo
leemos. Lo que vamos a hacer es crear una base de datos. Una vez creada vamos a crear
(valga la redundancia) un objeto formulario. Es decir, creamos un nuevo formulario en blanco.
Guardamos ese formulario con el nombre de F01.
Una vez creado lo situamos en vista diseño.
Si ahora abrimos el editor de VB (a través de la combinación de teclas ALT+F11) podremos
comprobar que no existe ningún módulo de objeto. ¿Y cómo? Porque si nos fijamos en la
ventana proyecto sólo podremos ver ahí el nombre de nuestra BD, pero ningún otro elemento.
Volvamos a situarnos frente a F01 en vista diseño.
Antes de seguir necesitamos explicar alguna “cosita” más...

EVENTOS
Cojamos algo común que todo el mundo conozca (o al menos le suene), como podría ser el
mando de una Wii... Generalizando podemos decir que si pulsamos uno de los botones
confirmamos la opción que tenemos en pantalla; si movemos el mando a la izquierda se
produce un movimiento hacia la izquierda, y lo mismo para la derecha. Si agitamos el mando
de arriba a abajo pues obtenemos otro resultado...
Si lo “transformamos” en lo que nos interesa podemos hablar de “eventos”, que en caso de la
tele podríamos decir que...
– Evento al pulsar el botón
– Evento al mover el mando a la derecha
– Evento al mover el mando a la izquierda
– Evento al agitar el mando
Y, a cada uno de estos eventos, le corresponde la ejecución de una acción.
Asimilado lo anterior, nuestro formulario F01 es nuestro mando, que posee una serie de
eventos a los cuales les podemos asignar una acción, que vendría programada por nuestro
código VBA.
¿Y cuáles son esos eventos?
Pues si volvemos a nuestro F01, que teníamos situado en vista diseño, no tenemos más que
sacar sus propiedades y nos desplazamos a la pestaña Eventos. Ahí podremos ver una lista de
todas los eventos a los cuales podemos asignarles una acción a través de código. Algunos, al
leerlos, nos van a resultar más que evidentes; otros, sin embargo, no lo son tanto. No os

2
Visítame en http://siliconproject.com.ar/neckkito/
preocupéis porque ya los iremos viendo con calma a través de este curso.

Podemos decir que la mayoría de eventos son comunes


tanto a los objetos como a los controles, aunque cada uno,
lógicamente, tendrá sus propias “peculiaridades”.

Si estamos en estos momentos viendo los eventos del


formulario F01 vamos a generar una acción asociada a un
evento. Lo que de aquí en adelante llamaré “generamos el
siguiente código” incluye un proceso que explicaré a
continuación, pero que ya no repetiré para sucesivas
explicaciones (por eso es importante que lo aprendáis
ahora, aunque es muy sencillo). Para ello:
– Nos situamos en el evento “Al abrir” (o, cuando programemos un evento, en cualquiera
de ellos que nos sirva para nuestros propósitos).
– Si hacemos click en el espacio en blanco que hay a la derecha del nombre del evento
veremos que nos aparece un pequeño botón de puntos suspensivos.
– Hacemos click sobre ese pequeño botón
– Nos aparecerá una ventana que nos solicitará tres opciones:
– Generar macros: si pulsamos aquí podremos crear una macro asociada a ese
evento.
– Generador de expresiones: si pulsamos aquí se nos abrirá la ventana del
generador de expresiones, y ahí podremos especificar la expresión (nunca mejor dicho)
que queramos que se ejecute al suceder el evento.
– Generador de código: este es el que nos interesa si queremos utilizar código
VBA.
– Seleccionamos pues el generador de código y se nos abrirá... ¡nuestro amigo el VBE!

Vamos a analizar qué ha pasado al abrirse el editor de VB:


1.- Se nos ha creado un módulo de objeto, en nuestro caso relacionado con F01. Si echamos
un vistazo a la ventana de proyecto podremos ver que ahí aparece nuestro “objeto” F01
2.- Se nos han creado dos líneas por defecto: Private Sub... y End Sub. Ya deberíamos saber
que Private nos está indicando que el procedimientos va a tener carácter un ámbito de
actuación privado, es decir, “restringido” dentro del propio proceso, y que no será accesible
desde otros procedimientos/funciones/módulos.
3.- Además, nos ha identificado el evento que hemos seleccionado, que en nuestro caso es “Al
abrir” → Form_Open
4.- Automáticamente nos está indicando que este evento tiene la posibilidad de utilización de
un argumento por defecto, que es “Cancel”.
5.- Nos marca dónde está el final del procedimiento, a través del “End Sub”

Nuestro espacio de trabajo para código será, pues, entre estas dos líneas.

Vamos a programar un simple código, que será el siguiente:

3
Visítame en http://siliconproject.com.ar/neckkito/

Private Sub Form_Open(Cancel As Integer)
MsgBox "¡Hola, mundo!"
End Sub

Lo que estamos haciendo es simplemente decirle al
formulario que cuando se abra nos lance un mensaje.
Si ahora cerramos (guardando los cambios) nuestro F01 y lo
abrimos en vista formulario veremos los “efectos” de nuestra
programación.
Vamos a ir un poco más allá. Con nuestro F01 en vista diseño vamos a a mostrar el
encabezado y el pie del formulario. Ahora, en teoría, deberíamos tener tres secciones:
– Encabezado
– Detalle
– Pie
 Truco: por si no lo sabíamos podemos seleccionar lo que es el formulario en marcando el
pequeño cuadrado que hay en la esquina superior izquierda del mismo. Si después marcamos
otro elemento, y tenemos la ventana de propiedades abierta, esta irá cambiando las
propiedades en función del elemento que tengamos seleccionado.

La idea que os quiero transmitir es que el formulario tiene unos eventos asociados, pero la
sección encabezado, la sección detalle y la sección pie también tienen sus propios eventos.

4
Visítame en http://siliconproject.com.ar/neckkito/
CONTROLES

Vamos a crear una tabla, que guardaremos con el nombre


de TPrueba, con dos campos:
– [Id] → Autonumérico y clave principal
– [Dato] → Texto

Ahora creamos un formulario basado en esa tabla, que


guardaremos como FPrueba. Lo abrimos en vista diseño.
Veremos que, lógicamente, tenemos ahí los dos campos de la tabla. A esos elementos se les
denomina “controles”.
Si ahora añadimos un cuadro de texto al formulario tendremos un control más; si añadimos
una etiqueta, un botón de comando, un cuadro combinado y, en definitiva, cualquier elemento
de la cinta de opciones del grupo “controles” (lo mismo para Access 2003, aunque no
hablemos de cinta de opciones), estaremos añadiendo un “control” al formulario.
Ahora ya sabemos que cuando hablemos de un “control de formulario” nos estamos refiriendo
a todo este conjunto de elementos que acabamos de mencionar.
Si, por ejemplo, seleccionamos el campo Dato (no su etiqueta, sino el campo en sí) y sacamos
sus propiedades ya sabemos que podremos encontrar una pestaña llamada Eventos, que nos
muestra todos los eventos disponibles sobre los que podemos operar y que hacen referencia a
ese control en concreto.

“MIX” DE LOS ANTERIORES CONCEPTOS

Vamos a ver cómo se refleja una mezcla de los anteriores conceptos en nuestro editor de VB.
Operaremos sobre FPrueba.
Así pues, con FPrueba en vista diseño, sacamos las propiedades del formulario y nos vamos al
evento “Al activar registro”.
Este evento se produce cada vez que nos aparece un registro en pantalla; es decir, se
“activará” al abrir el formulario (porque nos aparece el primer registro o un registro nuevo,
dependiendo cómo tengamos configurado el formulario) y también se activará cada vez que
cambiemos de registro, ya sea porque nos desplacemos por los registros o porque vayamos a
añadir uno nuevo.
A este evento le generamos el siguiente código:

Private Sub Form_Current()
If IsNull(Me.Dato.Value) Then
MsgBox "El campo <dato> está vacío"
Else
MsgBox "El campo <dato> está lleno"
End If
End Sub

5
Visítame en http://siliconproject.com.ar/neckkito/
Ahora sacamos las propiedades del campo [Dato] y programaremos el evento llamado “Al
recibir el enfoque”. Le generamos el siguiente código:

Private Sub Dato_GotFocus()
If IsNull(Me.Dato.Value) Then
Me.Dato.Value = "¡Estoy vacio!"
End If
End Sub

¿Qué hemos aprendido con estos dos simples códigos?
– Que para examinar SI hay valor o NO hay valor utilizamos el bloque IF... END IF (que
estudiaremos un poco más adelante)
– Que para saber si un dato no contiene ningún valor debemos utilizar ISNULL(valor)
– Hemos recordado que si queremos especificar un texto debemos situarlo entre comillas.
– Que para lanzar un mensaje al usuario utilizamos la función MsgBox (que también
veremos más adelante)

Como podréis intuir, lo que hace el código asociado al formulario es “mirar” si el campo [Dato]
tiene valor o no, y en función de ello nos lanza un mensaje de advertencia.

Lo que hace el código asociado al campo [Dato] es “mirar”, cada vez que este campo recibe el
foco, si tiene valor o no, y si no lo tiene nos escribe una cadena de texto dentro del mismo.

Vamos a probarlo. Situamos FPrueba en vista formulario y... ¡sorpresa! Y si ahora pulsamos
Enter o tabulación, para saltar al campo [Dato], automáticamente se nos escribe el texto que
habíamos especificado.
Añadamos dos registros más, de manera que acabemos en el registro número 3.
Ahora, a través de los botones de desplazamiento, volvamos al registro 2 (registro anterior).
Veremos que el código de formulario detecta que ya hay valor en [Dato] y nos da el mensaje
correcto, a la vez que en ese campo, al contener ya un valor, su código no permite que se
escriba nuestro valor por defecto.

Y si ahora abrimos el VBE veremos que:


– Tenemos dos módulos asociados a objetos (F01 y FPrueba)
– Dentro de FPrueba tenemos dos bloques de código, que se nos delimitan por una línea
separadora, y que los códigos que hemos asociado a eventos son, por defecto, siempre
PRIVATE.
– Que los códigos asociados a controles del formulario quedan recogidos dentro del
módulo asociado al mismo formulario.

6
Visítame en http://siliconproject.com.ar/neckkito/
LA FUNCIÓN MSGBOX

La función MsgBox nos permite dos cosas: la primera, y


más básica, lanzar un mensaje al usuario, y la segunda,
más compleja, lanzar un mensaje a la vez que se solicita
una respuesta del usuario.

LANZAR UN MENSAJE

Aunque lo hemos visto ya varias veces durante este curso ahora vamos a ver la sintaxis casi
completa de esta función. Esta sintaxis es la siguiente:
MsgBox [Texto a mostrar], [Botón/es a mostrar], [Título de la ventana de mensaje]
Hay dos argumentos más, pero no los vamos a ver. Simplemente comentar que corresponden
a la manipulación de un archivo de ayuda que hayamos podido crear.

Analicemos los argumentos:


– [Texto a mostrar]: lógicamente será el mensaje que queremos que vea el usuario. Lo
deberemos poner entre comillas
– [Botón/es a mostrar]: podemos mostrar diversos botones en nuestro cuadro de
mensaje. La llamada a los botones se realiza a través de constantes de VB. Sin ánimo de
exhaustividad, y para este apartado, tenemos los siguientes:
– vbOkOnly → Sólo nos mostrará un botón OK
– vbExclamation → Nos aparece el icono de exclamación
– vbQuestion → Nos aparece el icono de interrogación
– vbCritical → Nos aparece el icono de aviso crítico
– vbInformation → Nos aparece el icono de información
– [Título de la ventana de mensaje]: es el título que nos aparecerá en la parte superior
de la ventana de nuestro mensaje.

Vamos a comprobar lo anterior:

Ponemos nuestro formulario F01 en vista diseño y añadimos un botón de comando. Lo


llamamos cmdLanzaMensaje (os recuerdo: propiedades → Otras → Nombre). Al evento “Al
hacer click” le generamos el siguiente código:

Private Sub cmdLanzaMensaje_Click()
MsgBox "Acabas de pulsar un botón", vbInformation, "UN MENSAJE"
End Sub

Os animo a que vayáis cambiando la constante del botón a fin de ver el tipo de icono que os
aparece.

7
Visítame en http://siliconproject.com.ar/neckkito/
SALTOS DE LÍNEA EN MENSAJE

Podemos utilizar una constante de VB para indicar que


queremos un salto de línea en nuestro mensaje. La
constante que vamos a utilizar es
VBCRLF
Sé que la palabrita “se las trae”, pero a mí me sirve pensar
en ella (si conocéis algo de las máquinas de escribir
antiguas) como: vb (por constante de VB), carro de línea
final (por CR LF). Es decir, que cuando llegábamos a la
línea final debíamos mover el carro de la máquina de
escribir a la izquierda para poder escribir una nueva línea.

¿Y cómo sería la sintaxis con “esto”? Pues vamos a probarlo empíricamente:


En nuestro F01 en vista diseño añadimos un nuevo botón de comando, al que llamaremos
cmdCarroLinea, y en el evento “Al hacer click” le generamos el siguiente código:

Private Sub cmdCarroLinea_Click()
MsgBox "Esta va a ser la primera línea de nuestro mensaje" & _
vbCrLf & "Esta va a ser la segunda línea de mensaje y..." & _
vbCrLf & vbCrLf & "Esta es la tercera, separada por un espacio", _
vbExclamation, "VARIAS LÍNEAS"
End Sub

Como podemos ver en el código (y aprender):
– Para “unir” todo nuestro mensaje usamos el ampersand (&)
– Utilizamos el subguión para hacer el código más legible, separado en varias líneas (esto
ya lo vimos en una lección anterior).
– La estructura con la constante de VB és: “Texto1” & vbCrLf & “Texto2”
– Si queremos dejar líneas en blanco debemos concatenar la constante, como hemos
hecho en la tercera línea del mensaje. Es decir: “Texto1” & vbCrLf & vbCrLf & “Texto2”

Aprovecho este espacio para comentaros que podemos crear una variable y pasarla como
mensaje. En este caso no deberíamos utilizar comillas, ya que no le pasamos el texto
directamente, sino el texto almacenado en la variable. Si queréis hacemos la prueba:
Añadimos un nuevo botón en F01 y lo llamamos cmdVariable, y le generamos el siguiente
código:

Private Sub cmdVariable_Click()
Dim miTexto As String
Dim miEdad As Integer
miTexto = "Yo soy Neckkito"

8
Visítame en http://siliconproject.com.ar/neckkito/
miEdad = 125
MsgBox miTexto & miEdad, vbCritical, "MI MENSAJE"
End Sub

Si ejecutamos este código vemos que nos sale el mensaje,
pero muy “feo” ;)
Vamos a arreglar el mensaje combinando texto directo y
variables. Así pues modificamos la línea de MsgBox por la
siguiente:
MsgBox miTexto & " y tengo sólo " & miEdad & " años", vbCritical, "MI MENSAJE"

Como podremos comprobar ahora sí que nos sale un texto “guapo”. Fijaos simplemente,
además de cómo hemos ido concatenando el texto, que debemos ir con cuidado con los
espacios en blanco al principio y final de las cadenas de texto intermedias. ¡Los espacios
también cuentan!

LANZAR MENSAJE Y ESPERAR RESPUESTA DE UN USUARIO

Antes de meternos de lleno en el tema debemos saber una cosa: las constantes de VB
devuelven un valor entero (integer). Ese valor podemos utilizarlo para realizar acciones. De
hecho, podemos hacer referencia al valor devuelto a través de otra constante de VB. ¿A que
suena divertido?
Un ejemplo nos sacará de dudas. Podemos pedirle al usuario que nos responda Sí o No a
través de sendos botones. Para ello utilizaríamos los botones vbYesNo.
Si el usuario pulsa el botón YES el valor que se devuelve es el 6, mientras que si pulsa en NO
el valor devuelto es 7. Entonces aprovecharemos este espacio para introducir el bloque IF...
ELSE... END IF (lo veremos con más detalle en la próxima sección), con lo que podríamos
generar un código que dijera:

Si <el usuario pulsa YES> Msgbox = 6 Entonces
'Haz esto
End Si

En este caso operaríamos con el número. Pero también tenemos una constante VB que
equivaldría, en este caso, al 6 (ó al 7). Esa constante es vbYes (vbNo). Luego también
podríamos programar el código así:

Si <el usuario pulsa YES> Msgbox = vbYes Entonces
'Haz esto
End Si

Es decir, o es igual a 6 o es igual a vbYes (o es igual a 7 o igual a vbNo).

9
Visítame en http://siliconproject.com.ar/neckkito/
Aunque veréis códigos que hacen la programación directa sobre todo el MsgBox a mí,
personalmente, me gusta almacenar el valor el código en una variable y operar sobre ella.
Desde mi punto de vista eso hace el código menos farragoso. Así pues os explicaré cómo lo
hago yo, aunque después os pondré lo mismo pero con programación “directa” para
que podáis ver la diferencia. A partir de ahí... a elegir lo que más guste ;)

Vamos a operar a través de un ejemplo. Con nuestro


formulario F01 en vista diseño añadimos un botón de
comando, al que llamaremos cmdSiNo, y le generaremos el
siguiente código:

Private Sub cmdSiNo_Click()
Dim resp As Integer
resp = MsgBox("Pulsa sí o no: el que quieras", vbQuestion + vbYesNo, "PETICIÓN")
If resp = vbYes Then 'o también If resp = 6 Then
MsgBox "Has pulsado SÍ. ¿A que sí?", vbExclamation, "PULSASTE SI"
Else
MsgBox "Has pulsado NO. ¿No es verdad?", vbExclamation, "PULSASTE NO"
End If
End Sub

Vamos a ver un par de cosillas del código:
– Cuando se espera una respuesta del usuario los argumentos del MsgBox deben ir entre
paréntesis.
– Para mostrar diferentes botones utilizamos el signo más (+) para unirlos
– Utilicemos valores numéricos o constantes definimos la variable como Integer.

Y un par de cosillas sobre botones:


– Tenemos varios tipos de botones para interactuar con el usuario, por ejemplo:
– vbOkCancel
– vbAbortRetryIgnore
– vbYesNoCancel
– vbRetryCancel
– Hay otra serie de botones que lo que hacen es situarnos el enfoque sobre una opción
predeterminada. Es decir, tenemos:
– vbDefaultButton1
– vbDefaultButton2
– etc. hasta el 4
En este caso podríamos escribir vbYesNo+vbDefaultButton1, lo que haría el botón Sí como
opción predeterminada.

10
Visítame en http://siliconproject.com.ar/neckkito/
Hay otra serie de botones que operan, digamos, sobre el “comportamiento de la ventana en
sí”. Estos no los veremos (pero os diré dónde encontrarlos, un poco más abajo).

El código anterior, si se hubiera programado sin la


utilización de variables, quedaría de la siguiente manera:

Private Sub cmdSiNo_Click()
If MsgBox("Pulsa sí o no: el que quieras", vbQuestion +
vbYesNo + vbDefaultButton2, "PETICIÓN") = vbYes Then
MsgBox "Has pulsado SÍ. ¿A que sí?", vbExclamation, "PULSASTE SI"
Else
MsgBox "Has pulsado NO. ¿No es verdad?", vbExclamation, "PULSASTE NO"
End If
End Sub

Los botones del MsgBox, como hemos insinuado un poco antes, no sólo devuelven un valor
numérico, sino que pueden ser representados por un valor numérico. Es decir, y a modo de
ejemplo:
– El botón vbYesNo viene representado por el valor 4
– El botón vbExclamation viene representado por el valor 48

Escribir: <MsgBox (“Texto”, vbYesNo)> podría haber sido escrito de la siguiente manera:
<Msgbox (“Texto”, 4)>

Si queremos mostrar los dos botones (SiNo + Exclamación) a través de sus valores numéricos
simplemente tenemos que sumarlos: 4 + 48 = 52, y escribir <MsgBox (“Texto”,52)>

Evidentemente esto lo dejo para quien tenga una buena memoria y sea un fenómeno en
matemáticas... je, je...

Y como lo prometido es deuda, si en nuestro código (en el VBE) nos situamos sobre la palabra
MsgBox y pulsamos la tecla F1 podremos ver toda la lista de botones que acepta la función,
con sus valores representativos, y todos los valores de respuesta (constantes de VB o entero
correspondiente), con una explicación de cada uno de ellos.

LA FUNCIÓN INPUTBOX

Con la función MsgBox hemos visto que podemos lanzar un mensaje al usuario, e incluso
esperar una respuesta del mismo limitada a la pulsación de unas pocas opciones a través de
botones.

11
Visítame en http://siliconproject.com.ar/neckkito/
La función InputBox nos permite solicitar información al usuario para que este la introduzca en
un cuadro de diálogo, información que no estará tan limitada como en el caso de MsgBox.

La sintaxis de esta función es la siguiente:


InputBox ([Texto informativo], [Título de la ventana
de mensaje], [Valor de respuesta por defecto])

Hay más argumentos que se pueden utilizar, pero no los


explicaremos aquí porque, realmente, yo no los he visto utilizar
demasiado. Si estáis interesados en profundizar la ayuda del
propio Access os dirá lo que necesitéis sobre ellos.

Vamos a plantear unj ejercicio sobre esta función. Imaginemos que queremos pedir el nombre
de usuario al usuario (nunca mejor dicho). Utilizaremos pues un InputBox. Para ello:
– Con F01 en vista diseño añadimos un botón de comando, al que llamaremos
cmdInputBox, y le generaremos el siguiente código en el evento “Al hacer click”:

Private Sub cmdInputBox_Click()
Dim miNombre As String
miNombre = InputBox("Introduzca su nombre", "NOMBRE", "XXX")
MsgBox "Su nombre es " & miNombre
End Sub

Fijaos que:
– Como pedimos la introducción de una cadena de texto hemos definido una variable
(miNombre) como String
– El valor de esa variable nos la proporciona la función InputBox
– Por si no lo habéis intuido, no es necesario utilizar todos los argumentos del InputBox.
Si hubiéramos escrito: <InputBox (“Nombre”)> hubiera bastado.

Vamos a realizar otro ejercicio práctico, pero en este caso realizaremos unos cálculos con los
datos que nos proporcione el usuario.
Añadimos otro botón de comando en F01 y le ponemos de nombre cmdInputBoxNum. Le
generamos el siguiente código en el evento “Al hacer click”:

Private Sub cmdInputBoxNum_Click()
Dim valor1 As Integer, valor2 As Integer
valor1 = InputBox("Introduzca el primer número", "#1", 0)
valor2 = InputBox("Introduzca el segundo número", "#2", 0)
MsgBox "La suma de " & valor1 & " y " & valor2 & " es " & valor1 + valor2
End Sub

12
Visítame en http://siliconproject.com.ar/neckkito/
Como podemos ver en este ejemplo:
– Hemos definido las variables como Integer porque esperamos números
– Hemos practicado cómo concatenar el mensaje de MsgBox
– Hemos visto que podemos realizar cálculos
directamente sobre el mensaje devuelto por MsgBox.

Evidentemente hubiéramos podido crear una tercera


variable que nos recogiera el resultado y pasársela al
Msgbox, con lo que el código hubiera quedado así:

Private Sub cmdInputBoxNum_Click()
Dim valor1 As Integer, valor2 As Integer, result As Integer
valor1 = InputBox("Introduzca el primer número", "#1", 0)
valor2 = InputBox("Introduzca el segundo número", "#2", 0)
result = valor1 + valor2
MsgBox "La suma de " & valor1 & " y " & valor2 & " es " & result
End Sub

DETECTAR LA PULSACIÓN DEL BOTÓN <CANCELAR>

Si hemos seguido los ejemplos hemos visto como el InputBox nos muestra dos botones:
aceptar y cancelar. Y si hemos intentado cancelar hemos visto que nos salta un error de
código.

Vamos a ver cómo programar el InputBox de manera que si el usuario cancela la acción
podamos salir del procedimiento. Para ello necesitamos utilizar la función StrPtr.
Cuando el usuario pulsa <Cancelar> la función StrPtr devuelve el valor 0 (cero). Eso nos
permite la detección de un valor para poder tomar una decisión.
Si la variable que recoge la respuesta del InputBox es miNombre, la sintaxis sería tan simple
como StrPtr(miNombre).
Vamos a modificar el primer código que nos solicitaba el nombre de usuario para añadir ese
control de pulsación de cancelar. Añadimos un nuevo botón de comando en F01 y lo
guardamos como cmdInputBoxCancelar. El nuevo código del evento “Al hacer click” debería
quedarnos así:

Private Sub cmdInputBoxCancelar_Click()
Dim miNombre As String
miNombre = InputBox("Introduzca su nombre", "NOMBRE", "XXX")
If StrPtr(miNombre) = 0 Then Exit Sub
MsgBox "Su nombre es " & miNombre
End Sub

13
Visítame en http://siliconproject.com.ar/neckkito/

Y eso es todo. No hay más secreto.

OTROS “CONTROLES” PARA QUE NO NOS


FALLE EL INPUTBOX

Como los usuarios son “extremadamente variables” puede


sucedernos que les solicitemos un nombre y no escriban nada
(dejen el valor en blanco), o que introduzcan un valor numérico, o
cualquier otra “salvajada” que no podamos prever. Vamos a ver
cómo “cubrirnos” de estas “manipulaciones” de nuestro InputBox.

DETECTAR VALORES VACÍOS

Para detectar valores vacíos o cadenas de texto vacías podemos utilizar un bloque IF. El código
para ello sería:
Si <el valor es nulo> O <es una cadena vacía> entonces …

Por ejemplo, y siguiendo con el ejemplo anterior (yo he programado un nuevo botón de
comando en F01), podríamos escribir el código como sigue:

Private Sub cmdInputBoxVacios_Click()
Dim miNombre As String
miNombre = InputBox("Introduzca su nombre", "NOMBRE", "XXX")
If StrPtr(miNombre) = 0 Then Exit Sub
If IsNull(miNombre) Or miNombre = "" Then Exit Sub
MsgBox "Su nombre es " & miNombre
End Sub

Como vemos volvemos a encontrarnos que para saber si un valor es nulo debemos utilizar
<IsNull(variable)>. Y para ver si está vacío simplemente lo indicamos con dos comillas dobles
seguidas (“”)
También podemos ver que para unir condiciones hemos utilizado OR
En este ejemplo, si se produce la condición, se sale del proceso, pero podríamos haber hecho
que saltara un mensaje al usuario indicándole que el valor introducido no es correcto (si lo
queréis probar, os lo dejo como “deberes” ;) )

DETECTAR VALORES DE DIFERENTE TIPO

14
Visítame en http://siliconproject.com.ar/neckkito/
Para “pillar” que el usuario está introduciendo un valor numérico en lugar de una cadena de
texto podemos utilizar la función IsNumeric. A partir del valor devuelto por esta función
(True/False) podremos tomar una decisión, como siempre, a través de nuestro amigo IF.
Veamos un ejemplo (que yo he programado en otro botón
de comando):

Private Sub cmdInputBoxIsNumeric_Click()
Dim miNombre As String
miNombre = InputBox("Introduzca su nombre", "NOMBRE",
"XXX")
If StrPtr(miNombre) = 0 Then Exit Sub
If IsNull(miNombre) Or miNombre = "" Then Exit Sub
If IsNumeric(miNombre) Then Exit Sub
MsgBox "Su nombre es " & miNombre
End Sub

Como podemos comprobar la sintaxis es <IsNumeric(variable)>. Si ahora el usuario introduce
un número nuestro código se dará cuenta y saldrá del proceso.

¿Y qué pasa si lo que queremos es precisamente que el dato sea numérico? Pues muy fácil:
negamos que sea numérico. Je, je... me encantan estas frases “ambiguas”...

Lo que quiero decir es que si situamos un NOT delante de IsNumeric podemos conseguir el
efecto deseado. Pongamos un ejemplo:
Creamos un nuevo botón de comando en F01, al que llamaremos cmdInputBoxEdad. En el
evento “Al hacer click” le generamos el siguiente código:

Private Sub cmdInputBoxEdad_Click()
Dim miEdad As Variant
miEdad = InputBox("Introduzca su edad", "AÑITOS")
If Not IsNumeric(miEdad) Then
MsgBox "El valor introducido no es válido", vbCritical, "MAL"
Exit Sub
Else
MsgBox "Vaya... ¡qué 'joven' eres!", vbExclamation, "CARAMBA!!!!!!!"
End If
End Sub

Un par de comentarios sobre el código:
– No nos ha quedado más remedio que definir la variable miEdad como Variant. Si la
hubiéramos definido como Integer, y el usuario hubiera introducido letras, se hubiera

15
Visítame en http://siliconproject.com.ar/neckkito/
producido un conflicto de tipos y nuestro código nos hubiera dado un error (os animo a
probarlo para ver “lo listo que es Access”).
– Las decisiones de acción se han tomado dentro del
bloque IF. Esto lo veremos con más detalle en el próximo
capítulo.

Quizá alguien pueda pensar: ¿por qué aquí declaramos la


variable como Variant, y en el ejemplo anterior la hemos
declarado como String y no pasaba nada? Os recuerdo que:
– Si se ha definido la variable como Integer y escribimos una
letra, una letra nunca puede ser un número. Aquí no hay equívoco.
– Si se ha definido la variable como String y escribimos un número, sí podemos entender
que, en primera instancia, ese número no es tal, sino que queremos que sea un carácter. Es
decir:
– 123 es un número, pero
– “123” son tres caracteres
Es por ello por lo que, en principio, nuestro código no detecta nada anómalo y por ello no nos
salta error de código.

NORMALIZACIÓN

Y como punto final a este capítulo vamos a hablar de normalización de nombres, para despejar
un poco nuestro cerebro.
No existe una norma “fija” para la nomenclatura de los elementos, tanto de Access. Por lo
tanto os voy a comentar lo que yo utilizo y lo que he visto que utilizan otras personas. Sólo
daré unas pinceladas porque la idea se va a coger muy rápido. Sí voy a dar un par de ideas
para aquellos que quieran profundizar en el tema.

NOMBRES DE OBJETOS

A los objetos de Access (tablas, formularios, etc.) yo tengo por costumbre añadir la inicial del
nombre genérico del objeto. Por ejemplo, si es una tabla añado una “T” al nombre de la tabla;
si es un formulario añado una “F”, informe una “R” (por Report), una “C” para consulta, una
macro “M”... La única excepción la hago en los subformularios, para lo que empleo “subFrm”, y
para los módulos, en los cuales sigo la nomenclatura que os explico en el siguiente párrafo.
Lo que otras personas hacen es utilizar tres letras identificativas del objeto. Así, tendríamos:
– tbl → Para tablas
– frm → Para formularios
– qry → Para consultas (de query)
– mcr → Para macros
– rpt → Para informes
– mdl → Para módulos estándar
– cls → Para módulos de clase

16
Visítame en http://siliconproject.com.ar/neckkito/
NOMBRES DE CONTROLES

Para los controles utilizo más o menos el sistema que “el


resto de mortales” suele utilizar. Es decir:
– txt → Para cuadros de texto
– lbl → Para etiqueta (de label)
– cbo → Para un cuadro combinado
– cmd → Para un botón de comando (de command)
– lst → Para un cuadro de lista
– img → Para un cuadro de imagen
– opc → Para un botón de opción
– mrc → Para un marco de opciones
– chk → Para una casilla de verificación (de check)
– tab → Para control de ficha

NOMBRES DE VARIABLES

Debo reconocer que yo no utilizo este sistema más que en ocasiones puntuales, en las cuales
pueden darse ambigüedades. Sin embargo os comento los prefijos más comunes para las
variables, que son:
– str → Para un String
– int → Para un Integer
– lng → Para un Long
– bln → Para un Boolean
– dtm → Para un Date

¿Y POR QUÉ LOS PREFIJOS?

La idea de poner prefijos a los elementos de Access y VBA es simplemente para facilitar el
reconocimiento de los mismos. Evidentemente un prefijo aporta una información
complementaria extremadamente útil para una lectura rápida de código, o para un
reconocimiento inmediato del elemento de que estamos hablando.
Por ejemplo, y aún sin saberlo, si vemos esto:
---
Private Sub lstNombres_Click()

intMax = Dlookup(“[Edad]”, “TAlumnos”)

End Sub
---

17
Visítame en http://siliconproject.com.ar/neckkito/
Con todo lo anterior ya sabemos que:
– Estamos en el evento “Al hacer click” de un cuadro de lista
– Que la variable intMax es de tipo Integer
– Que se busca en la tabla “TAlumnos”, y no en la
consulta “CAlumnos”, por ejemplo

Otra utilidad de nombrar, en este caso, los controles, es que


para revisiones de código nos ahorramos una gran cantidad
de tiempo. Es decir, supongamos que estamos en el VBE,
en un módulo de objeto formulario, repasando el código, y
vemos lo siguiente:

Private Sub Comando0_Click()
' Código
End Sub
Private Sub Comando1_Click()
' Código
End Sub
Private Sub Cuadro_Combinado34_AfterUpdate()
' Código
End Sub

Imaginaos eso, pero con 30 controles en el formulario. Vamos a arreglar el botón que nos
imprime un informe... ¿Qué “ComandoX” es? Tendríamos que poner el formulario en vista
diseño y mirar su nombre para después volver a VBE y encontrar el número de botón de
comando que es, lo cual (os lo aseguro) es muy engorroso y molesto.
Lógicamente, si yo busco: Private Sub cmdImprimeInforme_Click() la detección será muy
rápida.

MÁS INFORMACIÓN SOBRE NORMALIZACIÓN

El sistema que tiene una utilización mayoritaria es el que se denomina “sistema Leszynski”. Os
dejo un par de enlaces por si queréis profundizar sobre este tema:
● http://accessdemo.web.officelive.com/idiomas/esp/articulos/leszynski.htm
● http://en.wikipedia.org/wiki/Leszynski_naming_convention

18
Visítame en http://siliconproject.com.ar/neckkito/
CURSO DE VB

CAPÍTULO 41

Índice de contenido
BLOQUES DE DECISIÓN..................................................................................................................2
BLOQUE IF...THEN...ELSE...END IF...........................................................................................2
UN CASO ESPECIAL: EL ELSEIF...........................................................................................3
LA FUNCIÓN IIF............................................................................................................................4
IIF ANIDADOS..........................................................................................................................5
BLOQUE SELECT CASE...END SELECT...................................................................................6
VALOR DIRECTO.....................................................................................................................6
VALOR CON COMPARADOR LÓGICO.................................................................................8
BLOQUE FOR...NEXT...................................................................................................................9
BLOQUE FOR EACH...NEXT.....................................................................................................12
BLOQUE WHILE...WEND..........................................................................................................13
BLOQUE DO...LOOP Y SUS VARIACIONES............................................................................14
DO WHILE... LOOP.................................................................................................................14
DO UNTIL... LOOP.................................................................................................................16
OTRAS VARIACIONES DE ESCRITURA DEL BLOQUE DO............................................17
UN BLOQUE ESPECIAL: WITH...END WITH..............................................................................17

1 La BD donde están los ejemplos de este capítulo os la podéis bajar aquí.

1
Visítame en http://siliconproject.com.ar/neckkito/
BLOQUES DE DECISIÓN

BLOQUE IF...THEN...ELSE...END IF

Ya hemos visto en capítulos anteriores el uso de esta


función, pero vamos a detenernos brevemente en ella dado
que hablamos de bloques de decisión.

Evidentemente, el esquema de funcionamiento de este bloque es el siguiente:

SI se cumple una condición


Realiza unas acciones
SI NO se cumple la condición
Realiza Otras acciones
ACABA

Lo anterior es la versión “extensa” del IF. La segunda parte, la SI NO, se puede omitir.

Incluso hay una versión “súper-reducida”, que representa una sola línea de código, en la cual
no necesitamos decirle que ACABE.

Vamos a ver las tres posibilidades a través de un ejemplo.

1.- En una base de datos nueva creamos un formulario en blanco. En él insertamos tres
cuadros de texto.

2.- Sacamos las propiedades del primer cuadro de texto y nos vamos a la pestaña Otras →
Nombre, y le escribimos txt1. El segundo será txt2 y el tercero txtResultado

3.- Añadimos un botón de comando, al que pondremos de nombre cmdSuma. En el evento “Al
hacer click” le generamos el siguiente código:


Private Sub cmdSuma_Click()
'Bloque If super-reducido
If IsNull(Me.txt1.Value) Then Exit Sub
If IsNull(Me.txt2.Value) Then Exit Sub

'Bloque if reducido
If Not IsNumeric(Me.txt1.Value) Then
MsgBox "El valor introducido debe ser un número", vbExclamation, "INCORRECTO"
Exit Sub
End If

If Not IsNumeric(Me.txt2.Value) Then


MsgBox "El valor introducido debe ser un número", vbExclamation, "INCORRECTO"
Exit Sub
End If

'Bloque if extenso
If CInt(Me.txt1.Value) + CInt(Me.txt2.Value) > 11 Then
'Lanzamos un aviso y situamos el resultado en txtResultado
MsgBox "El valor introducido es mayor que 10", vbExclamation, "MAYOR QUE 10"

2
Visítame en http://siliconproject.com.ar/neckkito/
Me.txtResultado = CInt(Me.txt1.Value) + CInt(Me.txt2.Value)
Else
MsgBox "El valor introducido es menor que 10", vbExclamation, "MENOR QUE 10"
Me.txtResultado.Value = CInt(Me.txt1.Value) +
CInt(Me.txt2.Value)
End If
End Sub

Si analizamos el código veremos que:

– En el bloque if súper-reducido la única línea que hay lo que hace es comprobar si se ha


introducido algún valor. Si no hay valor sale del proceso. Como podemos ver, aquí no hemos
utilizado el End If
– En el If reducido comprobamos que el valor introducido sea un valor numérico. Aquí hay
implícitas dos opciones: que sea numérico o que no. Sin embargo, si es numérico nos interesa
que se siga ejecutando el código (esa sería la acción de un hipotético ELSE). Para solventar la
mecánica anterior lo que hacemos es introducir una interrupción en el proceso si se comprueba
que el valor introducido no es numérico. Por ello utilizamos un IF... End If, de manera que sólo
actúa si se cumple la condición (que no sea numérico).
– En el If extenso sí evaluamos dos condiciones: que sea mayor que 10 o no. En ambos
casos nos interesa escribir el resultado en txtResultado. De ahí que necesitemos esta
estructura: IF... se cumple la condición haz esto; SI NO (ELSE) haz esto otro. Evidentemente
en este caso sí debemos cerrar el bloque con un END IF.

Una última puntualización al código: hemos tenido que utilizar un conversor de valores (CInt).
Ello es así porque, por defecto, Access entiende que los valores introducidos son texto (aunque
si es un número pueda entender que es numérico en primera instancia). Si no hubiéramos
introducido ese conversor los resultados obtenidos hubieran sido sorprendentes para nosotros.
Hay que ir mucho cuidado con esto precisamente para que nuestros códigos no den resultados
“erróneos”.

Es decir, si no hubiéramos puesto el conversor el proceso del código hubiera sido el siguiente:
Valor en txt1: 1
Valor en txt2: 3
Compruebo si es numérico → Access entiende que es texto que puede ser numérico → No nos
salta la comprobación de IsNumeric → Pero Access sigue considerando ese valor como texto.
Operación que realiza Access: 1 + 3 = 13
Mensaje que lanza: “Es mayor que 10”
Valor en txtResultado: 13

Si queréis probarlo quitando los CInt veréis el comportamiento que os comentaba.

Por cierto, CInt significaría “Conversión a Integer”.

UN CASO ESPECIAL: EL ELSEIF

Aunque tenemos otros bloques de decisión que podrían considerarse más idóneos para realizar
lo que os voy a explicar a continuación, no podemos dejar el IF sin que, al menos, os mencione
el ElseIf. Esta función nos permite elegir no sólo entre dos opciones, como sería el caso del IF,
sino que podemos analizar más de dos opciones.

La función ElseIf la tenemos que incardinar dentro de un bloque If, y hablando en general
podríamos decir que el bloque diría lo siguiente:

3
Visítame en http://siliconproject.com.ar/neckkito/
Si secumplecondición ENTONCES
'Código
PERO SI secumpleotracondición ENTONCES
'Código2
ACABA

Vamos a verlo con un ejemplo. Aprovecharemos nuestro


formulario FSuma. Añadimos un botón de comando y le
ponemos de nombre cmdElseIf. Al evento “Al hacer click” le
generamos el siguiente código (no añadiremos ninguna
línea de control):

Private Sub cmdElseIf_Click()
If Me.txt1.Value > 5 Then
Me.txtResultado = Me.txt1.Value + Me.txt2.Value
ElseIf Me.txtResultado.Value < 10 Then
Me.txtResultado.Value = Me.txt1.Value * Me.txt2.Value
Else
Me.txtResultado.Value = 0
End If
End Sub

Lo que dice este código es lo siguiente:
SI el valor introducido en txt1 es mayor que cinco entonces → Resultado será igual a la suma
PERO SI el resultado es menor que 10 convertimos el resultado en una multiplicación
EN CASO CONTRARIO el resultado será cero

Tened en cuenta que en el ejemplo sólo hemos utilizado un ELSEIF, pero podríamos haber
puesto todos los ELSEIF que hubiéramos querido.

LA FUNCIÓN IIF

Aunque no corresponda a la idea de “bloque” la función IIF sí nos permite tomar una decisión
“rápida” sobre una situación en la que deba cumplirse una condición o no.

La sintaxis de esta función es la siguiente:

IIF (condición; caso verdadero, caso falso)

Es decir, supongamos que Pedro es rubio. Con el IIF analizaríamos el color de pelo de Pedro y
devolveríamos un mensaje según se cumpla o no la condición. Esto es:

IIF (Pedro=rubio, “Es rubio”, “Es moreno”)

Como se cumple la condición (Pedro es rubio) obtenemos el valor “Es rubio”. Si en ese
momento Pedro se tiñe el pelo de negro la condición no se cumple, por lo que obtendríamos
“Es moreno”.

¿Y cómo se refleja eso en el código?

El operador mod sirve para devolvernos el resto de una división. Si dividimos entre 2 y el resto
es cero se nos está indicando que ese número es par, y si no lo es es impar. La sintaxis sería:

valor mod 2 → Nos da el resto de la división valor/2

4
Visítame en http://siliconproject.com.ar/neckkito/
Vamos a aplicar lo anterior a un ejemplo. En nuestro formulario FSumas añadimos un botón de
comando, al que llamaremos cmdIIf. A ese botón de comando le generamos el siguiente
código:

Private Sub cmdIIf_Click()
Dim valor As Integer, resto As Integer
Dim msg As String
valor = Me.txt1.Value
resto = valor Mod 2

msg = IIf(resto = 0, "El número es par", "El número es


impar")

MsgBox msg, vbExclamation, "RESULTADO"


End Sub

Si escribimos un número en nuestro cuadro de texto txt1 y le damos al botón recién creado
sabremos si el número introducido es par o impar.

IIF ANIDADOS

Podemos complicar un poco más la cosa y establecer lo que se denomina IIF anidados: IIF
dentro de IIF. Eso nos permitirá evaluar más de una condición.

Vamos a verlo con un ejemplo: supongamos que ahora tenemos que analizar dos casos: saber
si el número es par o impar y además, si es impar, saber si es múltiplo de cinco o no.
Añadimos un nuevo botón de comando en FSuma y lo llamamos cmdIIfAnidado. Le generamos
el siguiente código:


Private Sub cmdIIfAnidado_Click()
Dim valor As Integer, resto As Integer, resto5 As Integer
Dim msg As String
valor = Me.txt1.Value
resto = valor Mod 2
resto5 = valor Mod 5

msg = IIf(resto = 0, IIf(resto5 = 0, "El número es par y múltiplo de cinco", _


"El número es par"), IIf(resto5 = 0, _
"El número es impar y múltiplo de 5", "El número es impar"))

MsgBox msg, vbExclamation, "RESULTADO"


End Sub

Analicemos el código por fases:


Si resto es = 0
• Valor verdadero: Iif(...)
◦ Si resto5 = 0
▪ Valor verdadero → Devuelve el primer mensaje: “Par y múltiplo de cinco”
▪ Valor falso → Devuelve el segundo mensaje: “Par”
• Valor falso: Iif(...)
◦ Si resto5 = 0

5
Visítame en http://siliconproject.com.ar/neckkito/
▪ Valor verdadero → Devuelve el primer mensaje: “Impar y múltiplo de cinco”
▪ Valor falso → Devuelve el segundo mensaje: “Impar”

Ni que decir tiene que hubiéramos podido anidar más IIF


dentro de los IIF anidados. Lógicamente la lectura del
código ya sería bastante engorrosa.

Como nota final os diré que la función IIF se puede utilizar


también en los objetos Consulta de Access. Pero, e
importante, debéis distinguir el ámbito de uso:

– Si lo utilizamos en código VBA los argumentos van separados por comas.


– Si lo utilizamos en Consultas los argumentos van separados por punto y coma.

Ojo con eso.

BLOQUE SELECT CASE...END SELECT

Para tomar decisiones en función de más de dos condiciones el bloque SELECT CASE...END
SELECT es “nuestro bloque”. Vamos a echar un vistazo a su funcionamiento.

Dividiremos este epígrafe en dos subepígrafes: que la condición sea un valor directo o que
necesitemos realizar una comparación lógica.

VALOR DIRECTO
Si queremos que la condición sea un valor directo utilizaremos la siguiente estructura:

SELECT CASE valor


CASE condición1
'Código1
CASE condición2
'Código2
CASE condición3
'Código3

END SELECT

Vamos a verlo a través de un ejemplo. Creamos un nuevo formulario y lo llamamos FSelect.


Añadimos un cuadro de texto, al que pondremos por nombre txtUno. Añadimos otro cuadro de
texto y le ponemos de nombre txtDos.

Añadimos un cuadro combinado y lo llamamos cboOperacion. Sacamos sus propiedades y


vamos a decirle qué valores puede mostrar ese combo. Para ello nos vamos a la pestaña Datos
→ Tipo de origen de la fila → Y seleccionamos “Lista de valores”.
Nos vamos ahora, en la misma pestaña, a origen de la fila, y escribimos lo siguiente:

+;-;*

Es decir, el combo nos mostrará si sumamos, restamos o multiplicamos.

Vamos a practicar a asignación de un código a un evento de combo. Así pues seguimos con las
propiedades del combo y nos vamos a la pestaña Eventos → Después de actualizar, y le
generamos el siguiente código (no incluiremos ningún control de comprobación):

6
Visítame en http://siliconproject.com.ar/neckkito/

Private Sub cboOperacion_AfterUpdate()
Dim v1 As Long, v2 As Long
Dim vOper As String
'Cogemos los valores de los textbox
v1 = Me.txtUno.Value
v2 = Me.txtDos.Value
'Cogemos el valor seleccionado en el combo
vOper = Me.cboOperacion.Value
'Analizamos qué operación desea el usuario
Select Case vOper
Case "+"
MsgBox "El resultado es " & v1 + v2, vbInformation, "RESULTADO"
Case "-"
MsgBox "El resultado es " & v1 - v2, vbInformation, "RESULTADO"
Case "*"
MsgBox "El resultado es " & v1 * v2, vbInformation, "RESULTADO"
End Select
End Sub

Si analizamos el código, veremos que lo que hacemos es


– Seleccionamos el caso “Operación seleccionada en el combo”
– Cada uno de los case recoge el valor posible del combo, y el código actúa en
consecuencia.

Vamos a complicar la cosa un poco más. ¿Qué pasaría si no hubiéramos bloqueado el combo y
el usuario escribe algún valor que no está en el combo? Pues para ello tenemos una palabra
reservada más que podemos utilizar dentro del SELECT CASE. Esa palabra es:

CASE ELSE

que vendría a significar “en el resto de los casos no condicionados”. Vamos a cambiar nuestro
código por el siguiente:

Private Sub cboOperacion_AfterUpdate()
Dim v1 As Long, v2 As Long
Dim vOper As String
'Cogemos los valores de los textbox
v1 = Me.txtUno.Value
v2 = Me.txtDos.Value
'Cogemos el valor seleccionado en el combo
vOper = Me.cboOperacion.Value
'Analizamos qué operación desea el usuario
Select Case vOper
Case "+"
MsgBox "El resultado es " & v1 + v2, vbInformation, "RESULTADO"
Case "-"
MsgBox "El resultado es " & v1 - v2, vbInformation, "RESULTADO"
Case "*"
MsgBox "El resultado es " & v1 * v2, vbInformation, "RESULTADO"
Case Else
MsgBox "No se puede realizar la operación", vbCritical, "ERROR"
End Select
End Sub

7
Visítame en http://siliconproject.com.ar/neckkito/
Como vemos, añadimos el CASE ELSE después de todos los CASE.

Debemos tener en cuenta que SELECT CASE realiza un


análisis secuencial. ¿Qué significa eso? Que realiza la
comprobación empezando, por orden, por el primer CASE y
acabando por el último. Cuando encuentra el CASE que
cumple la condición ejecuta el código y sale del proceso.

¿Y qué significa eso?

Pues que para optimizar recursos deberíamos programar en los primeros CASE las opciones
que consideremos más probables, dejando para las últimas las condiciones que consideremos
más improbables (si se puede, claro). No es lo mismo examinar dos casos y salir que examinar
ochenta casos y salir, ¿verdad?

VALOR CON COMPARADOR LÓGICO

Si queremos utilizar un comparador lógico la estructura de los CASE es ligeramente diferente.


Dicha diferencia radica en que debemos utilizar la palabra reservada IS para indicar al código
que vamos a realizar una comparación.

La estructura pues nos quedaría de la siguiente manera (añadiremos el CASE ELSE ahora que
ya lo “controlamos”:

SELECT CASE valor


CASE IS comparador-condición1
'Código1
CASE IS comparador-condición2
'Código2
CASE IS comparador-condición3
'Código3
...
CASE ELSE
'Código otro
END SELECT

Y, como siempre, un ejemplo nos sacará de dudas. Vamos a aprovechar nuestro formulario
FSelect y los dos textbox que ahí tenemos. Añadiremos un botón de comando, al que
llamaremos cmdCaseCond y le generaremos el siguiente código:


Private Sub cmdCaseCond_Click()
Dim v1 As Long, v2 As Long
'Cogemos los valores de los textbox
v1 = Me.txtUno.Value
v2 = Me.txtDos.Value
'Cogemos la variable v1 como base para la comparación
Select Case v1
'Si es mayor que el segundo valor
Case Is > v2
MsgBox "El primer número es mayor que el segundo", vbInformation, "MSG"
'Si es menor que el segundo valor
Case Is < v2

8
Visítame en http://siliconproject.com.ar/neckkito/
MsgBox "El primer número es menor que el segundo", vbInformation, "MSG"
'Si ambos son iguales
Case Is = v2
MsgBox "Ambos números son iguales",
vbInformation, "MSG"
Case Else
MsgBox "No se ha podido realizar la comparación",
vbInformation, "MSG"
End Select
End Sub

Como veis la mecánica es prácticamente idéntica al valor directo, sólo que utilizando un
comparador. Ni que decir tiene que el anterior ejemplo ha sido puesto para que veáis que se
puede comparar con un valor variable, pero que evidentemente podemos comparar con un
valor “fijo” de manera directa. Es decir, si tenemos un rango de valores predeterminado
podemos aplicar esos valores directamente, teniendo en cuenta la secuencialidad del código.

Por ejemplo, si mi rango de valores va del 0-3 (Mal), del 4 al 6 (Normal) y del 7 al 10 (Bien)
mi código sería (escrito de manera abreviada y sólo lo que nos interesa):

Private Sub ...
Select Case variableAComparar
Case Is <=3
variableResultado = "Mal"
Case Is <=6
variableResultado = "Normal"
Case Else
variableResultado = "Bien"
End Select
End Sub

BLOQUE FOR...NEXT

El bloque FOR...NEXT nos permite realizar un bucle de repetición. Es decir, y para que nos
entendamos, nos permite determinar el número de veces que se repetirá un conjunto de
acciones.

La estructura (en abstracto) de este bucle sería:

FOR valor=x TO valor=y


'Realiza estas acciones
NEXT

Es decir, si iniciamos el valor en uno (1) podemos obligar a repetir el proceso hasta 3 veces,
por ejemplo, de la siguiente manera:

FOR valor=1 TO 3
'Acciones
NEXT

¿Y qué “operación” nos realiza el NEXT? Pues simplemente añade una unidad a <valor>.
Cuando comienza el proceso <valor> vale 1 → Llegamos al NEXT y ahora <valor> se cambia a
2 → Llegamos de nuevo al NEXT y <valor> ya vale 3 → Como le hemos dicho <TO 3> salimos

9
Visítame en http://siliconproject.com.ar/neckkito/
del proceso.

Comentar también que, si nos gusta más, podemos indicar


qué valor es el NEXT. Es decir que el esquema anterior
también se habría podido escribir así:

FOR valor=1 TO 3
'Acciones
NEXT valor

Vamos a verlo a través de un ejemplo, y practicaremos de pasada


un poco la función InputBox, que ya vimos:

En nuestra base de datos de pruebas vamos a crear un formulario en blanco y lo guardaremos


como FFechas. Creamos un cuadro de texto y le ponemos de nombre txtFecha.

 Truco: para evitarnos tener que introducir controles de tipo de datos en el código en las
propiedades de txtFecha nos vamos a la pestaña Formato → Formato → Y le marcamos “Fecha
corta”. Así Access ya nos controlará que el formato introducido sea de fecha, porque si no lo es
el propio Access lanza un mensaje de advertencia (no es un mensaje muy elegante, pero si lo
que prima es la efectividad... pues nos sirve).

Creamos ahora un botón de comando y le ponemos de nombre cmdCalculaFecha.

La idea es que se nos solicite un número de días y que se nos muestre un mensaje con la
fecha final, pero que nos vaya mostrando también los cálculos intermedios que hace el código
para que veamos cómo está funcionando el bucle. Eso significa que es mejor ponerle un
número de días bajo, porque si no vamos a estar un buen rato aceptando OK's del MsgBox!!!

El código que generaremos al evento “Al hacer click” de cmdCalculaFecha será el siguiente (os
lo pongo comentado para que veáis, primero, la importancia de comentar el código para
entenderlo, y segundo, para que sepáis qué está haciendo el código en cada momento.
Además, voy a suponer que sois usuarios “normales” y que vais a introducir un número de
días. El código no tiene ningún tipo de control para verificar lo anterior):

Private Sub cmdCalculaFecha_Click()
'Definimos las variables
Dim vFecha As Date 'Cogerá el valor en txtFecha
Dim vDias As Integer 'Valor solicitado al usuario
Dim i As Integer 'Contador para el bucle FOR...NEXT

'Capturamos los valores de las variables


vFecha = Me.txtFecha.Value 'Del formulario en el que estoy (ME), del control txtFecha cojo
su valor
'Comprobamos que se haya escrito una fecha. Si no salimos del proceso
If IsNull(vFecha) Then Exit Sub
vDias = InputBox("¿Número de días a añadir?", "DIAS", 3)

'Realizamos el bucle que nos mostrará el proceso


For i = 1 To vDias
MsgBox "La fecha intermedia de cálculo es " & vFecha & vbCrLf & vbCrLf & _
"El contador muestra el valor " & i
'Añadimos un día a la fecha
vFecha = vFecha + 1
Next

10
Visítame en http://siliconproject.com.ar/neckkito/
'Una vez finalizado el proceso vFecha me recoge el resultado. Lo mostramos.
MsgBox "La fecha final es " & vFecha, vbExclamation, "AL FIN!!!!"
End Sub

Creo que el código está lo suficientemente comentado para


no tener que añadir nada más. Simplemente fijaos que
podemos decirle las repeticiones que queremos
directamente (FOR i=1 TO 3) o que podemos decírselo a
través de una variable (FOR i=1 TO vDias)

¿Y qué pasa si queremos que no sume una unidad, sino que añada unidades de
dos en dos, o de tres en tres? Pues para eso tenemos la palabra reservada STEP. Así, la
estructura de nuestro bucle quedaría así:

FOR valor=1 TO 3 STEP 2


'Acciones
NEXT

FOR valor=1 TO 3 STEP 3


'Acciones
NEXT

Ni que decir tiene que gracias a nuestro STEP podemos utilizar este bucle como una cuenta
regresiva, si nos interesara para nuestro código. En este caso deberíamos escribir, por
ejemplo:

FOR valor=10 TO 3 STEP -1


'Acciones
NEXT

Vamos a complicar un poco la cosa. Imaginemos que queremos que se vaya incrementando
una unidad como hasta ahora pero si se cumple una condición se interrumpa el proceso. Para
ello podemos utilizar un EXIT FOR, que lo que hace es que, como habréis intuido, forzar la
salida del bucle.

Vamos a ver un ejemplo quizá un poco absurdo, pero lógicamente lo que interesa es “coger” la
mecánica.

En nuestro formulario FFechas vamos a añadir un nuevo botón de comando, al que llamaremos
cmdInterrumpeProceso. Lo que vamos a hacer es lo siguiente:

– El usuario introduce una fecha


– Calculamos tres días posteriores a esa fecha. Eso será nuestra condición de
interrupción.
– Le decimos al código que nos calcule 5 días más a la fecha.
– Lógicamente, cuando se llegue al tercer día obligaremos al proceso a interrumpirse y a
salir del bucle.

Vamos a ver cómo se hace. El código sería el siguiente:


Private Sub cmdInterrumpeProceso_Click()
'Definimos las variables

11
Visítame en http://siliconproject.com.ar/neckkito/
Dim vFecha As Date 'Cogerá el valor en txtFecha
Dim vFechaSale As Date
Dim i As Integer 'Contador para el bucle FOR...NEXT

'Capturamos los valores de las variables


vFecha = Me.txtFecha.Value 'Del formulario en el que
estoy (ME), del control txtFecha cojo su valor
'Comprobamos que se haya escrito una fecha. Si no
salimos del proceso
If IsNull(vFecha) Then Exit Sub

'Determinamos la fecha de interrupción del proceso


vFechaSale = vFecha + 3

'Realizamos el bucle que nos mostrará el proceso


For i = 1 To 5
MsgBox "La fecha intermedia de cálculo es " & vFecha & vbCrLf & vbCrLf & _
"El contador muestra el valor " & i
'Añadimos un día a la fecha
vFecha = vFecha + 1
'Comprobamos si se cumple la condición para interrumpir el proceso
If vFecha = vFechaSale Then
MsgBox "¡Vaya! Se ha interrumpido el proceso", vbCritical, "SALIMOS"
Exit For
End If
Next

MsgBox "Y aquí acaba el código", vbInformation, "FIN"


End Sub

Insisto que el proceso en sí no tiene mucho sentido; debéis centrar vuestra atención en la
mecánica del mismo.

BLOQUE FOR EACH...NEXT

Incluyo este bucle aquí simplemente para que sepamos de su existencia, pero, por ahora, no
profundizaremos en su funcionamiento. Es un bucle que iremos viendo a lo largo del curso.

La estructura FOR EACH...NEXT se utiliza cuando recorremos colecciones de elementos de la


base de datos para realizar alguna acción sobre los mismos.

Por ejemplo, en nuestra base de datos tenemos una colección de objetos, que son los
formularios. Si nos interesara saber que el formulario FSelect existe podríamos hacer lo
siguiente:

En nuestro formulario FFechas añadimos un botón de comando, al que llamaremos


cmdForEach, y le generamos el siguiente código:


Private Sub cmdForEach_Click()
Dim frm As Object
For Each frm In CurrentProject.AllForms
If frm.Name = "FSelect" Then
MsgBox "El formulario <FSelect> sí está en la base de datos"

12
Visítame en http://siliconproject.com.ar/neckkito/
Exit Sub
End If
Next
MsgBox "No se encontró el formulario"
End Sub

Como vemos, el bucle recorre todos los formularios de la


BD y compara sus nombres con la condición del IF. Si lo
encuentra se ejecuta dicha condición.

Si ahora, en la línea <If frm.Name = "FSelect" Then>, cambiamos


“FSelect” por “miForm”, podremos ver los resultados en el caso de
que no se cumpla la condición.

Como ya he comentado no le dedicaremos más tiempo a este bloque (que como habéis visto
funciona prácticamente como el bloque FOR...NEXT). Pero he considerado interesante que
supierais que “existe”.

BLOQUE WHILE...WEND

Si os he de ser sincero este bloque de decisión no lo he utilizado en mi vida, dado que hay otro
tipo de bloques que consiguen la misma utilidad. Sin embargo, es de justicia que os explique
cómo funciona.

La estructura sería la siguiente:

WHILE condición
'Acciones
WEND

Es decir, permite una iteracción de las mismas acciones mientras se vaya cumpliendo la
condición.

Vamos a ver un ejemplo muy sencillo y divertido (si tenemos una edad inferior a los 6 años...
je, je...). Vamos a crear un juego para adivinar un número secreto.

Creamos un formulario en blanco, al que llamaremos FWhile, y en él situamos un botón de


comando, llamado cmdWhile, y le generaremos este código:

Private Sub cmdWhile_Click()
'Definimos las variables
Const nSecreto As Integer = 7
Dim nUser As Integer
Dim acertado As Boolean

'Inicializamos las variables


acertado = False

'Iniciamos el bucle
While acertado = False
nUser = InputBox("Introduzca un número del 1 al 9", "ACIÉRTAME")
If nUser = nSecreto Then
acertado = True
End If

13
Visítame en http://siliconproject.com.ar/neckkito/
Wend

MsgBox "¡Acertaste el número!", vbExclamation, "OK!!!!"


End Sub

Como podemos apreciar, el bucle se repite mientras la


condición <acertado = False> se mantenga. Por lo tanto,
cuando se acierta el número lo único que debemos hacer es
cambiar el valor del booleano a TRUE para forzar la salida
del proceso.

Este bucle no tiene mayor secreto.

BLOQUE DO...LOOP Y SUS VARIACIONES

El bucle DO...LOOP tiene dos variantes. Le podemos decir al código, de manera genérica,

HAZ MIENTRAS <se cumpla una condición>

o también

HAZ HASTA <se cumpla una condición>

Vamos a ver por separado ambas opciones, aunque la mecánica es prácticamente idéntica para
las dos.

DO WHILE... LOOP

Vamos a crear un nuevo formulario, al que pondremos de nombre FDo. Creamos un cuadro de
texto, al que le ponemos de nombre txtValor, y creamos otro cuadro de texto al que le
pondremos de nombre txtResults.

Añadimos un botón de comando, al que llamaremos cmdCalcula, y le generamos el siguiente


código:

Private Sub cmdCalcula_Click()
'Definimos las variables
Dim vValor As Long
Dim vObjetivo As Long
Dim vEncontrado As Boolean

'Inicializamos las variables


vValor = Me.txtValor.Value
vObjetivo = vValor * 10
vEncontrado = False

'Iniciamos los cálculos hasta encontrar el valor objetivo


Do While vEncontrado = False
vValor = vValor + 1
If vValor = vObjetivo Then
vEncontrado = True
End If
Loop

14
Visítame en http://siliconproject.com.ar/neckkito/
MsgBox "Se encontró el valor objetivo", vbInformation, "OK"
Me.txtResults.Value = vValor
End Sub

Lo que hace el código, si lo analizamos, es que establece


una condición booleana, representada por la variabla
vEncontrado, a la cual decimos que es FALSE. El bucle Do
obliga a ir sumando una unidad a vValor en cada iteracción
y cuando encuentra el valor objetivo cambiamos el valor de
vEncontrado a TRUE, lo que obliga a finalizar el bucle.

Como siempre, no interesa el ejemplo en sí sino la mecánica de su funcionamiento.

Podemos realizar una interrupción del código a través de

EXIT DO

lo que nos permitiría salir del bucle aunque no se cumpliera la condición. Vamos a modificar el
código anterior para establecer esa interrupción. Lo modificaremos de la manera siguiente:

Private Sub cmdCalcula_Click()
'Definimos las variables
Dim vValor As Long
Dim vObjetivo As Long
Dim vEncontrado As Boolean

'Inicializamos las variables


vValor = Me.txtValor.Value
vObjetivo = vValor * 10
vEncontrado = False

'Iniciamos los cálculos hasta encontrar el valor objetivo


Do While vEncontrado = False
vValor = vValor + 1
If vValor = 25 Then
MsgBox "Se interrumpirá el código", vbCritical, "INTERRUPCIÓN"
Exit Do
End If
If vValor = vObjetivo Then
vEncontrado = True
End If
Loop

Me.txtResults.Value = vValor
End Sub

Como vemos, hemos añadido una condición que nos permite interrumpir el código. Si el valor
fuera 25, por ejemplo, actuaría el EXIT DO. Si en nuestro textbox introducimos el número 2 no
se cumpliría la condición de interrupción, y actuaría la variable booleana; si introducimos el
número 3 sí se cumplirá la condición de interrupción, y se nos obligará a salir del bucle sin
haber hallado el resultado objetivo. Os animo a que lo probéis para ver los resultados.

15
Visítame en http://siliconproject.com.ar/neckkito/
DO UNTIL... LOOP

Como habréis podido intuir, este bucle y el anterior son “los


mismos perros con distintos collares”. La mecánica de su
funcionamiento es exactamente la misma.

Para no dejarlo sin ejemplo os propongo el siguiente,


aunque deberíais ser capaces de hacerlo ya vosotros sin
ayuda: introducido un número en nuestro textbox el código
debe ejecutarse hasta que el valor objetivo sea el triple que
ese número.

¡Pensadlo!

Y si alguien hoy tiene el día malo, le dejo aquí cómo sería ese código:

Private Sub cmdCalculaX3_Click()
'Definimos las variables
Dim vValor As Long
Dim vObjetivo As Long
Dim vEncontrado As Boolean

'Inicializamos las variables


vValor = Me.txtValor.Value
vObjetivo = vValor * 3
vEncontrado = False

'Iniciamos los cálculos hasta encontrar el valor objetivo


Do Until vEncontrado = True
vValor = vValor + 1
If vValor = vObjetivo Then
vEncontrado = True
End If
Loop

MsgBox "Se encontró el valor objetivo", vbInformation, "OK"


Me.txtResults.Value = vValor
End Sub

16
Visítame en http://siliconproject.com.ar/neckkito/
Ni que decir tiene que aquí también podríamos utilizar el EXIT DO para forzar la salida del
proceso.

OTRAS VARIACIONES DE ESCRITURA DEL BLOQUE DO


Para vuestra información os comentaré que lo que
acabamos de ver del bloque DO... admite ciertas
variaciones en su escritura. Son variaciones que yo no suelo
utilizar porque pienso (opinión muy personal) que son más
“confusas” cuando lees el código, pero como existen pues
os las comento.

La variación consiste en indicar el WHILE o el UNTIL no en la primera línea del DO, sino en la
última, la del LOOP. Es decir, nuestro anterior código podría escribirse también así (sólo os
escribo la parte del bloque DO):

Do
vValor = vValor + 1
If vValor = vObjetivo Then
vEncontrado = True
End If
Loop Until vEncontrado = True

Del mismo modo, el bloque en el ejemplo del DO...WHILE podría haberse escrito así:

Do
vValor = vValor + 1
If vValor = vObjetivo Then
vEncontrado = True
End If
Loop While vEncontrado = False

Como siempre, “para gustos, colores”.

UN BLOQUE ESPECIAL: WITH...END WITH

El bloque WITH...END WITH no es estrictamente un bloque de decisión, sino que lo que nos
permite es hacer referencia a un elemento para evitar tener que repetir su nombre en el
código (lo cual nos puede ahorrar mucho tiempo).
Es muy útil, por ejemplo, cuando nos interesa modificar un conjunto de propiedades de un
control, o cuando trabajamos con recordsets (esto lo veremos más adelante).
Sin ánimo de profundizar exhaustivamente en el tema de las propiedades (lo veremos en un
próximo capítulo) vamos a realizar un sencillo ejemplo:

17
Visítame en http://siliconproject.com.ar/neckkito/
Creamos un formulario en blanco, al que llamaremos FWith, e introducimos en él una etiqueta,
a la que llamaremos lblWith. Escribimos cualquier cosa en dicha etiqueta.
Ahora añadimos un botón de comando, al que llamaremos
cmdCambiaPropiedades, y le generaremos el siguiente
evento:

Private Sub cmdCambiaPropiedades_Click()
With lblWith
.Caption = "Me han cambiado"
.ForeColor = vbRed
.FontBold = True
.FontSize = 22
.FontName = "Verdana"
End With
End Sub

Como podemos ver, el bloque WITH nos permite hacer referencia a un control y, dentro del
bloque vemos todas las propiedades que vamos a cambiar. Evidentemente es más claro y
rápido que haber escrito:

Private Sub cmdCambiaPropiedades_Click()
lblWith.Caption = "Me han cambiado"
lblWith.ForeColor = vbRed
lblWith.FontBold = True
lblWith.FontSize = 22
lblWith.FontName = "Verdana"
End Sub

Que nos hubiera funcionado igualmente.

Aprovecharemos el ejemplo para hacer notar que:

• Si queremos cambiar el título (¡que no el nombre!) debemos operar sobre la propiedad


Caption
• Para las propiedades de la “letra” del título hemos utilizado:
◦ Color de letra: ForeColor, y la hemos igualado a una constante de VB. Si nos
situamos, en el VBE, sobre la palabra vbRed y pulsamos F1 podremos ver qué
colores están disponibles a través de este tipo de constante
◦ Para indicar si es negrita, itálica o subrayada debemos utilizar TRUE/FALSE
◦ Para indicar el tamaño de letra asignamos directamente el tamaño en números
◦ Para indicar el tipo de letra utilizamos, entre comillas, el nombre de la fuente.

18
Visítame en http://siliconproject.com.ar/neckkito/
CURSO DE VB

CAPÍTULO 51

Índice de contenido
¡Y LA ESTRELLA INVITADA... DOCMD!.......................................................................................2
CONVERSACIÓN OÍDA AL PASAR............................................................................................2
LLAMAR A LOS FORMULARIOS E INFORMES EN CÓDIGO...............................................2
CREANDO UNA MINI-BD PARA IR “JUGANDO” CON EL CÓDIGO....................................2
DOCMD... PARA ABRIR “ALGO”................................................................................................3
DOCMD... PARA CERRAR “ALGO”............................................................................................4
DOCMD PARA “SALIR”... Y BYE-BYE......................................................................................6
PROFUNDIZANDO: DOCMD CON FORMULARIOS...............................................................7
UN PASEO POR... LOS REGISTROS.......................................................................................7
¿CÓMO DESEA EL FORMULARIO? ¿POCO HECHO, AL PUNTO O MUY HECHO?......9
UTILICEMOS UN FILTRO SENCILLO.................................................................................10
Y COMPLIQUEMOS UN POCO EL FILTRO........................................................................11
UNA PUNTUALIZACIÓN SOBRE LOS FILTROS..........................................................12
Y SIGAMOS EXPLORANDO MANIPULACIONES............................................................12
UN ÚLTIMO EJEMPLO ALGO MÁS COMPLEJO...............................................................14
PARA FINALIZAR...................................................................................................................15

1 La BD donde están los ejemplos de este capítulo os la podéis bajar aquí.

1
Visítame en http://siliconproject.com.ar/neckkito/
¡Y LA ESTRELLA INVITADA... DOCMD!

CONVERSACIÓN OÍDA AL PASAR...

Código VBA: ¡Hola! ¿Qué eres?


Formulario: Un formulario
CVBA: ¡Vaya! No te había reconocido. Así, eres un Forms!
F: ¿Forms!? Pues me parece bien. Me gusta esa
descripción.
CVBA: Y por cierto, ¿cómo te llamas?
F: ¡Ah! Mi nombre es miFormulario
CVBA: Bonito nombre... Te llamaré, pues, Forms!miFormulario
F: Me gusta ese nombre, pero si no voy equivocado vivimos en el mismo edificio
CVBA: ¿Vivimos en el mismo edificio? ¡Entonces somos vecinos!
F: Sí. Por eso creo que Forms!miFormulario es un poco formal, ¿no crees?
CVBA: Tienes razón: como somos vecinos te voy a llamar... Me

LLAMAR A LOS FORMULARIOS E INFORMES EN CÓDIGO

¿Recordamos que en un capítulo anterior vimos que existían unos módulos de objeto? Es decir,
y para este caso en particular, cuando creamos código asociado a un formulario se creaba un
módulo asociado a ese formulario. Y lo mismo podemos decir para los informes.

La “conversación” anterior (teoría pura y dura, como habréis podido comprobar... je, je...)
refleja el hecho de cómo debemos llamar, desde el código, a los formularios. Y eso depende de
dónde esté el código.

Si el código se halla en el módulo asociado al formulario (son vecinos, siguiendo el símil)


podemos referirnos al formulario llamándolo simplemente ME. Si queremos hacer referencia a
otro formulario distinto debemos llamarle por su “nombre completo”; es decir, Forms!
nombreFormulario.

De la misma manera operamos con los informes: si el código está en el módulo asociado al
informe podemos llamarle ME; si, por el contrario, lo queremos llamar desde otro módulo (ya
sea de objeto o estándar) debemos referirnos a él por su nombre “largo”: Reports!
NombreInforme.

Evidentemente podemos sustituir el ME por el nombre largo, pero eso equivale a tener que
escribir mucho... y no podemos cansarnos tanto, ¿verdad? ;)

Es importante tener esto en cuenta porque a partir de ahora vamos a aplicar “al pie de la letra”
lo explicado en las líneas anteriores. Así que nadie se debe despistar y tener bien claro que ME
indica que se llama al objeto del módulo asociado al propio objeto.

CREANDO UNA MINI-BD PARA IR “JUGANDO” CON EL CÓDIGO

Vamos a crear una mini-BD muy simple para ver las “bondades” del método DoCmd para
manipular objetos. Así pues, vamos a crear una BD y vamos a:
– Añadir una tabla, a la que llamaremos TDatos, con los siguientes campos:

2
Visítame en http://siliconproject.com.ar/neckkito/
– [Id] → Autonumérico (clave principal)
– [Nombre] → Texto
– [Edad] → Número (entero, con cero decimales)

Metamos, a mano, algunos pocos registros en la tabla, a


nuestro gusto.

– Creamos un formulario, al que ponemos de nombre


FDatos, basado en nuestra tabla TDatos.

– Creamos un formulario en blanco, al que llamaremos


FMenu

– Creamos una consulta de selección sobre TDatos (como queramos) y la llamamos


CDatos.

– Creamos un informe sobre la tabla TDatos y lo llamamos RDatos.

Ya tenemos los principales objetos de Access creados. Ya podemos empezar a “destripar” el


DoCmd

DOCMD... PARA ABRIR “ALGO”

La sintaxis básica para abrir un objeto a través de código es la siguiente:

DoCmd.OpenXXX “nombreObjeto”

Donde XXX va a ser:


– Form
– Query
– Report

Y, más allá de este ejemplo, podemos abrir:


– Table
– DataAccessPage
– Diagram
– Function
– Module
– StoredProcedure
– View

Vamos a practicar un poco con nuestra BD. Nos situamos en el formulario FMenu, en vista
diseño, e introducimos cuatro botones de comando, y los llamamos así:
– cmdAbreFDatos
– cmdAbreTDatos
– cmdAbreCDatos
– cmdAbreRDatos

Cogemos el primer botón (cmdAbreFDatos) y en el evento “Al hacer click” generamos el


siguiente código:


Private Sub cmdAbreFDatos_Click()
DoCmd.OpenForm "FDatos"

3
Visítame en http://siliconproject.com.ar/neckkito/
End Sub

Como vemos si hacemos click sobre él (en vista formulario)


se nos abre el formulario en cuestión. ¿Fácil, no?

Como habréis supuesto, el código de cmdAbreTDatos sería:


Private Sub cmdAbreTDatos_Click()
DoCmd.OpenTable "TDatos"
End Sub

El código de cmdAbreCDatos es:


Private Sub cmdAbreCDatos_Click()
DoCmd.OpenQuery "CDatos"
End Sub

Y, finalmente, el código para cmdAbreRDatos es:


Private Sub cmdAbreRDatos_Click()
DoCmd.OpenReport "RDatos"
End Sub

¡Ojo! Este simple código nos abre el informe enviándolo directamente a la impresora. Más
adelante veremos cómo podemos “manipular” la apertura de informes.

DOCMD... PARA CERRAR “ALGO”

Ya sabemos cómo abrir elementos de Access... Vamos a ver cómo cerrarlos.

La sintaxis es:

DoCmd.Close acXXX, “NombreObjeto”

Vamos a añadir tres botones de comando en nuestro formulario FMenu y los vamos a llamar:

– cmdCierraFDatos
– cmdCierraTDatos
– cmdCierraCDatos

El código que vamos a aplicar al primero va a ser el siguiente:


Private Sub cmdCierraFDatos_Click()
DoCmd.Close acForm, "FDatos"
End Sub

Si ahora realizamos el siguiente proceso veremos lo bien que nos funciona: abrimos, a través

4
Visítame en http://siliconproject.com.ar/neckkito/
de nuestro botón de comando, el formulario FDatos → Seleccionamos nuestro formulario
FMenu → Cerramos FDatos con nuestro botón.

Os propongo que programéis los otros dos botones antes de


mirar el código que os indico a continuación. Creo que os va
a resultar muy fácil.

Para cerrar la tabla:



Private Sub cmdCierraTDatos_Click()
DoCmd.Close acTable, "TDatos"
End Sub

Para cerrar la consulta:


Private Sub cmdCierraCDatos_Click()
DoCmd.Close acQuery, "CDatos"
End Sub

Esto podría ser algo incómodo, ¿verdad? Me refiero a tener que ir a seleccionar el formulario
FMenu una vez abierto FDatos para poder cerrarlo con nuestro botón.

Vamos a ver cómo podemos situar el enfoque en el formulario que queramos. La idea es abrir
FDatos pero mantenerlo en segundo plano. Para ello utilizaremos el método SETFOCUS.

Si cogemos el código que teníamos asignado a cmdAbreFDatos podemos realizar una pequeña
modificación, de manera que el código nos quede así:


Private Sub cmdAbreFDatos_Click()
DoCmd.OpenForm "FDatos"
Me.SetFocus
End Sub

Como vemos, debemos preguntarnos: ¿en qué módulo de objeto estamos? Pues estamos en el
módulo asociado a FMenu. Por ello, para decir: “sitúame el enfoque en FMenu” podemos
emplear la palabra reservada ME. Y nos basta con utilizar un punto separador para indicar que:
NombreFormuario.Acción
Y de ahí que escribamos: Me.SetFocus

Si ahora probamos nuestro botón veremos que sí se nos abre FDatos, pero quedándonos en
segundo plano.

Vamos un poquito más allá. Ahora queremos operar sobre FDatos. De nuevo nos preguntamos:
¿en qué módulo de objeto estamos? Como nos hallamos en el módulo asociado a FMenu no
podemos utilizar ME para referirnos a FDatos, por lo que no nos quedará más remedio que
referirnos a él por su nombre largo.

Y si modificamos el anterior código de la siguiente manera veremos cómo se hace lo anterior:


Private Sub cmdAbreFDatos_Click()

5
Visítame en http://siliconproject.com.ar/neckkito/
DoCmd.OpenForm "FDatos"
Me.SetFocus
MsgBox "Ahora el foco pasará a FDatos", vbInformation, "SALTITO"
Forms!FDatos.SetFocus
End Sub

Creo que con este último ejemplo ha quedado claro cómo


funciona “la cosa”, y cómo debemos utilizar el ME (que, en
nuestro ejemplo, sería equivalente a Forms!FMenu).

¿Y si queremos cerrar el formulario desde el propio formulario? Vamos a abrir FDatos en vista
diseño y vamos a añadirle en el encabezado un botón de comando, al que llamaremos
cmdCerrar. A ese botón le generamos el siguiente código:


Private Sub cmdCerrar_Click()
DoCmd.Close acForm, Me.Name
End Sub

Como podemos ver, hemos utilizado la estructura que ya conocíamos, pero hemos añadido el
Me.Name para indicar que cierre el formulario de nombre X, donde X es el nombre del
formulario donde está el código. ¿Por qué lo hacemos así, y no hemos utilizado la estructura
DoCmd.Close acForm “FDatos” (que también nos hubiera funcionado)? Pues las ventajas son
las siguiente:
– No debemos “recordar” en qué formulario estamos operando (para los más perezosos ;)
– Si queremos “reutilizar” el código en otros formularios, nos sirve un copy-paste.
– Si copiamos-pegamos el propio formulario para reutilizarlo, nuestro código funcionará
perfectamente.

DOCMD PARA “SALIR”... Y BYE-BYE

Finalmente, y para acabar esta primera presentación del DoCmd, vamos a ver cómo salir de la
aplicación. En nuestro formulario FMenu creamos un botón de comando, al que llamaremos
cmdSalir, y le generamos este código:


Private Sub cmdSalir_Click()
DoCmd.Quit
End Sub

Como vemos, el código es muy simple. Basta utilizar la estructura DoCmd.Quit

Vamos a complicar un poco la cosa, recordando nuestras “enseñanzas” de capítulos anteriores.


¿Nos atrevemos a programar un botón (al que llamaremos cmdSalirPidiendo) que nos solicitara
confirmación para salir? Os animo a pensar cómo sería el código para ese botón (hay varias
maneras de hacerlo), antes de mirar el código que os propongo a continuación:


Private Sub cmdSalirPidiendo_Click()
Dim resp As Integer
resp = MsgBox("¿Realmente quiere salir?", vbQuestion + vbYesNo, "CONFIRMACIÓN")

6
Visítame en http://siliconproject.com.ar/neckkito/
If resp = vbNo Then
Exit Sub
End If
DoCmd.Quit
End Sub

¿Fácil, verdad?

PROFUNDIZANDO: DOCMD CON FORMULARIOS

Vamos a ver un poco como podemos manejar un poco más a fondo la apertura de formularios
con DoCmd.

Si nos fijamos en nuestro botón cmdAbreFDatos original veíamos que el código asignado era el
siguiente:


Private Sub cmdAbreFDatos_Click()
DoCmd.OpenForm "FDatos"
End Sub

A partir de ese código vamos a ver un par de “variaciones sobre el tema”.

UN PASEO POR... LOS REGISTROS

Como vemos, si pulsamos el botón, el código nos abre FDatos y nos lleva al primer registro. ¿Y
si queremos ir a un registro nuevo? Vamos a ver de qué maneras podemos realizar lo anterior.
Yo iré creando botones nuevos para cada ejemplo. Vosotros o bien podéis crear dichos botones
nuevos y asignarles el código correspondiente o bien vais modificando el código en el botón
que ya tenéis creado... “a gusto del consumidor”.

Sistema 1: vamos al nuevo registro sin emular acción de macro

Vamos a decirle al código que:


1.- Nos abra el formulario
2.- Nos vaya a un nuevo registro

Para eso debemos utilizar el siguiente código:


Private Sub cmdAbreFDatosNuevoRegistroStma1_Click()
DoCmd.OpenForm "FDatos"
DoCmd.GoToRecord acDataForm, "FDatos", acNewRec
End Sub

La primera línea de código la tenemos controlada. Sin comentarios.


La segunda línea de código nos dice lo siguiente:

DoCmd.VeAlRegistro (es un formulario), (se llama FDatos), aRegistroNuevo

7
Visítame en http://siliconproject.com.ar/neckkito/
Si nos fijamos en los argumentos del método DoCmd.GoToRecord vemos que:
– Para indicar qué tipo de objeto es utilizamos la constante de vb de Access acDataForm
– El nombre del formulario lo indicamos entre comillas
– Para indicar a qué registro queremos ir utilizamos la constante de vb de
Access acNewRec
– Es muy importante separar los argumentos con
comas. Si nos olvidamos una coma le estaremos diciendo
otra cosa al código (y no nos funcionará, lógicamente).

Y con este sistema no sólo sabemos ir a un registro nuevo,


sino que podemos sustituir acNewRec por otras constantes,
de manera que:
– acFirst → Nos lleva al primer registro
– acGoTo → Nos lleva al registro que le indiquemos
– acLast → Nos lleva al último registro
– acNext → Nos lleva al registro siguiente
– acPrevious → Nos lleva al registro anterior

Sistema 2: vamos al nuevo registro emulando una macro

Aunque lo iremos viendo, podemos “emular” la acción de una macro a través de

Docmd.RunCommand …

Apliquémoslo pues a nuestro supuesto. El código del botón debería ser:


Private Sub cmdAbreFDatosNuevoRegistroStma2_Click()
DoCmd.OpenForm "FDatos"
DoCmd.RunCommand acCmdRecordsGoToNew
End Sub

Como podemos ver nuestra segunda línea de código opera a través de una constante de vb de
Access, que es acCmdRecordsGoToNew

Igual que en el caso anterior, podemos utilizar las siguientes constantes:


– acCmdRecordsGoToFirst → para ir al primer registro
– acCmdRecordsGoToLast → para ir al último registro
– acCmdRecordsGoToNext → para ir al registro siguiente
– acCmdRecordsGoToPrevious → para ir al registro anterior

Vamos a practicar un poco lo que hemos aprendido. Yo sólo haré el ejemplo aquí para dos
botones (aunque en la BD de ejemplo están todos programados). Os dejo el resto de
programación para que vosotros practiquéis en vuestra BD de pruebas.

Situamos FDatos en vista diseño y añadimos cuatro botones (les podemos poner las imágenes
de las flechitas para identificarlos). Yo los llamaré:
– cmdPrimero
– cmdAnterior
– cmdSiguiente
– cmdÚltimo

Recordad que podemos crear uno y hacer copy-paste

Para programar cmdPrimero vamos a asignarle este simple código:

8
Visítame en http://siliconproject.com.ar/neckkito/

Private Sub cmdPrimero_Click()
DoCmd.RunCommand acCmdRecordsGoToFirst
End Sub

Y para programar cmdAnterior el código sería:



Private Sub cmdAnterior_Click()
DoCmd.RunCommand acCmdRecordsGoToPrevious
End Sub

Lo dicho: os dejo para vosotros la programación de los dos restantes

Ojo. Aún no hemos entrado en el tema de control de errores. Por ello, si estamos en el
primero, por ejemplo, y queremos ir al registro anterior obtendremos un error de código.
Tened un poquito de paciencia y pronto veremos cómo resolver este “inconveniente”.

¿CÓMO DESEA EL FORMULARIO? ¿POCO HECHO, AL PUNTO O MUY


HECHO?

Si complicamos la estructura del DoCmd.OpenForm veremos que se nos permite la adición de


varios argumentos:

DoCmd.OpenForm “nombreForm”, [tipo de vista], [filtro], [condición], [modo de datos], [modo


de ventana], [openArgs]

Vamos a “meternos” con el tipo de vista del formulario. Como ya sabemos (o deberíamos
saber) un formulario admite varias vistas, que son las siguientes:

TIPO DE VISTA CONSTANTE DE VB DE ACCESS


Vista diseño acDesign
Vista hoja de datos acFormDS
Vista de gráfico dinámico acFormPivotChart
Vista de tabla dinámica acFormPivotTable
Vista presentación acLayout
Vista formulario acNormal
Vista preliminar acPreview

Pues con todo lo anterior ya podemos decirle al código qué tipo de vista deseamos cuando
abramos el formulario.

En FMenu vamos a crear un botón de comando (que yo he llamado cmdAbreFDatosHojaDatos)


que nos abrirá FDatos en vista Hoja de datos. El código que debemos asignarle es el siguiente:


Private Sub cmdAbreFDatosHojaDatos_Click()
DoCmd.OpenForm "FDatos", acFormDS
End Sub

9
Visítame en http://siliconproject.com.ar/neckkito/

Y ahora crearemos otro botón, al que llamaremos cmdAbreFDatosGrafDin, y le generaremos el


siguiente código:


Private Sub cmdAbreFDatosGrafDin_Click()
DoCmd.OpenForm "FDatos", acFormPivotChart
End Sub

¿Qué tal lo llevamos?

UTILICEMOS UN FILTRO SENCILLO

Hemos creado en la tabla TDatos un campo llamado [Edad]. Supongamos que uno de los
valores que hemos introducido es el de alguien que tiene una edad de 55 años.

Vamos a crear primero un filtro directo, muy sencillo, para que se entienda la mecánica.
Después lo complicaremos un poco (pero sólo un poco, ¿eh?).

Creamos en FMenu un botón que nos permitirá abrir FDatos con sólo el registro que estamos
buscando, que es aquel en el cual la edad es de 55 años.

Para ello el código que debemos generar al botón cmdAbreFDatosFiltroSencillo es el siguiente:


Private Sub cmdAbreFDatosFiltroSencillo_Click()
DoCmd.OpenForm "FDatos", , , "[Edad]=" & 55
End Sub

Fijaos en un par de cosas:


– Hemos situado las comas en su sitio correspondiente, aunque no hayamos especificado
ningún argumento.
– Hemos indicado que la condición (el campo [Edad]) sea igual a. Todo lo anterior lo
hemos puesto entre comillas.
– Para unir condición y valor de la condición hemos utilizado el ampersand (&) y a
continuación el valor buscado.

Hubiéramos podido escribir, directamente, lo siguiente:

DoCmd.OpenForm "FDatos", , , "[Edad]=55”

Pero lo que me interesa es que veáis la unión de la condición con el valor que nos va a hacer
de filtro, para entender mejor el siguiente ejemplo.

El anterior ejemplo es algo “estático”, que salvo para algún caso puntual no es demasiado
“útil”. Lo que vamos a realizar ahora (ya lo vimos en una lección anterior) es solicitar la
colaboración del usuario. Vamos a filtrar un rango de registros para que sean inferiores a la
edad que el usuario quiera. En el botón cmdAbreFDatosFiltroUsuario que habremos creado
generamos el siguiente código:

10
Visítame en http://siliconproject.com.ar/neckkito/

Private Sub cmdAbreFDatosFiltroUsuario_Click()
Dim vEdad As Variant
vEdad = InputBox("Introduzca la edad máxima a filtrar",
"FILTRO EDAD", 0)
'Detectamos si se pulsa cancelar
If StrPtr(vEdad) = 0 Then Exit Sub
'Aplicamos el filtro, abriendo FDatos en modo Hoja de
Datos
DoCmd.OpenForm "FDatos", acFormDS, , "[Edad]<=" &
vEdad
End Sub

¿Nos acordamos el InputBox? Con este código el valor que introduce el usuario queda
guardado en la variable vEdad, y aplicamos la condición en la línea que abre el formulario a
través de "[Edad]<=" & vEdad

Aunque sea evidente, podríamos haber puesto el operador lógico que hubiéramos´querido
(mayor que, mayor o igual que, menor que, igual a...)

Y COMPLIQUEMOS UN POCO EL FILTRO

Podemos unir “condiciones” en nuestro filtro a través de la palabra reservada AND.


Supongamos que queremos filtrar por un rango de edades, con un valor inferior y un valor
superior. Y ese rango nos lo dará el propio usuario. No voy a utilizar, para este código, el
detectar el botón cancelar, porque eso ya lo sabríais aplicar vosotros (y eso me permitirá
cambiar el tipo de variable).

Vamos a aprovechar el código para ver cómo podemos definir el filtro de manera “separada”, lo
que nos permitirá escribir un código más claro a efectos de lectura.

Pues en nuestro nuevo botón cmdAbreFDatosFiltroComplejo vamos a escribir el siguiente


código:


Private Sub cmdAbreFDatosFiltroComplejo_Click()
Dim vEdadInf As Integer, vEdadSup As Integer
Dim miFiltro As String
vEdadInf = InputBox("Introduzca la edad inferior", "EDAD INFERIOR")
vEdadSup = InputBox("Introduzca la edad superior", "EDAD SUPERIOR")
miFiltro = "[Edad]>=" & vEdadInf & " AND [Edad]<=" & vEdadSup
DoCmd.OpenForm "FDatos", acFormDS, , miFiltro
End Sub

Como podemos observar, hemos:


– Definido el filtro a través de una variable de tipo String.
– miFiltro es en realidad una cadena de texto, sólo que los valores inferior y superior
vienen dados por las edades que introduzca el usuario.
– Ojo con los espacios, pues también cuentan. Como veis, hay un espacio antes de AND.
Supongamos que las edades sean 1 y 3. Si no hubiéramos puesto ese espacio la cadena de
texto sería: [Edad]>=1AND [Edad]<=3. Y el código hubiera entendido que el valor inferior es
“1AND”, además de encontrarse con una expresión que “no entiende” porque no se daría
cuenta de que hay dos condiciones (falta el AND que las une). ¡”Cuidadín” con los espacios!

11
Visítame en http://siliconproject.com.ar/neckkito/
– En la condición basta que pongamos el nombre del filtro que hemos creado, que en este
caso es miFiltro.

UNA PUNTUALIZACIÓN SOBRE LOS FILTROS

Como habéis visto, si trabajamos con números, el filtro es,


digamos, “directo”.

Sin embargo, según el tipo de dato deberemos adaptar el


filtro al tipo de dato.

¿Qué significa eso?

Significa que:

TIPO DE DATO FILTRO


Numérico “Directo”
Booleano “Directo”
String Entre comillas simples (')
Fecha Entre almohadillas (#)

Supongamos que queremos filtrar por el campo [Nombre]. Ese campo es de tipo Texto
(String). ¿Cómo debería ser nuestro código?

Si hiciéramos el filtro simple, y suponiendo que queremos filtrar por Neckkito (si existiera ese
nombre en TDatos), nuestro código debería quedar así:

DoCmd.OpenForm "FDatos", acFormDS, , “[Nombre] = 'Neckkito'”

Es decir, el filtro seria 'Neckkito'

Si operamos a través de variable debemos indicar que existen esas comillas simples. Para ello
escribiríamos lo siguiente (la primera línea está muy espaciada para que podáis distinguir bien
las diferentes comillas; la segunda es como quedaría “normal”):

DoCmd.OpenForm "FDatos", acFormDS, , “[Nombre] = ' “ & vMiNombre & “ ' ”

DoCmd.OpenForm "FDatos", acFormDS, , “[Nombre] = '“ & vMiNombre & “'”

¿Lo vemos?

Y si fuera, hipotéticamente, una fecha, escribiríamos:

DoCmd.OpenForm "FDatos", acFormDS, , “[Fecha]=#” & vMiFecha & “#”

Y SIGAMOS EXPLORANDO MANIPULACIONES

Sabemos (o deberíamos saber) que un formulario tiene propiedades. Mediante código


podemos manipular esas propiedades. Vamos a ver algunas.

Vamos a abrir nuestro formulario FDatos en modo Hoja de Datos, pero sólo para consultar los

12
Visítame en http://siliconproject.com.ar/neckkito/
registros. Ello implica que no queremos ni que se puedan añadir registros, ni editar registros ni
eliminar registros. Todas estas acciones tienen una propiedad asignada y, por tanto, son
manipulables a través de código.

Vamos a crear un botón de comando en FMenu que nos


permitirá abrir FDatos con estas características. El código a
utilizar seria:


Private Sub cmdAbreFDatosConsulta_Click()
DoCmd.OpenForm "FDatos", acFormDS
Forms!FDatos.AllowAdditions = False
Forms!FDatos.AllowEdits = False
Forms!FDatos.AllowDeletions = False
End Sub

Como podemos ver, utilizamos:


– AllowAdditions = False → para evitar añadir nuevos registros
– AllowEdits = False → para evitar ediciones de registros
– AllowDeletions = False → para evitar borrado de registros

Lógicamente, si quisiéramos activar alguna de estas características deberíamos cambiar FALSE


por TRUE.

Y aprovechemos para repasar conceptos... ¿No se os ocurre una manera más eficiente de
escribir el anterior código? ¿No? Pues podríamos haber escrito el código anterior de la
siguiente manera:


Private Sub cmdAbreFDatosConsulta_Click()
DoCmd.OpenForm "FDatos", acFormDS
With Forms!FDatos
.AllowAdditions = False
.AllowEdits = False
.AllowDeletions = False
End With
End Sub

¿Nos acordamos ahora? ;)

Vamos a utilizar otro método para abrirlo como sólo lectura. Para ello el código que podríamos
utilizar sería el siguiente:


Private Sub cmdAbreFDatosSoloLectura_Click()
DoCmd.OpenForm "FDatos", acFormDS, , , acFormReadOnly
End Sub

Es decir, el último argumento nos permite seleccionar el tipo de apertura para los datos de ese
formulario.

Como puntualización a lo anterior si queremos abrir el formulario en modo “sólo lectura”


hubiéramos podido utilizar también este otro código:

13
Visítame en http://siliconproject.com.ar/neckkito/

Private Sub cmdAbreFDatosConsultaSimple_Click()
'Para abrir como snapshot debemos saber que esa
constante es igual a 2
DoCmd.OpenForm "FDatos", acFormDS
Forms!FDatos.RecordsetType = 2
End Sub

Una vez visto lo anterior ya podemos entender la mecánica de


manipulación de propiedades del formulario. Veamos algunos
ejemplos más.

Imaginemos que queremos que nuestro formulario se abra en modo “modal”. ¿Cómo sería el
código? Pues así:


Private Sub cmdAbreFDatosModal_Click()
DoCmd.OpenForm "FDatos"
Forms!FDatos.Modal = True
End Sub

¿Y si queremos que al abrir el formulario el ciclo de registros sea “Todos los registros” o
“Registro activo”? Pues a manipular propiedades:


Private Sub cmdAbreFDatosCicloTodos_Click()
'Debemos saber que "Todos los registros" conlleva el valor 0, y que
'el "Registro activo" conlleva el valor 1
DoCmd.OpenForm "FDatos"
Forms!FDatos.Cycle = 0
End Sub


Private Sub cmdAbreFDatosCicloRegistroActivo_Click()
'Debemos saber que "Todos los registros" conlleva el valor 0, y que
'el "Registro activo" conlleva el valor 1
DoCmd.OpenForm "FDatos"
Forms!FDatos.Cycle = 1
End Sub

UN ÚLTIMO EJEMPLO ALGO MÁS COMPLEJO

Vamos a acabar este capítulo haciendo algo un poco más complejo de lo que hemos visto
hasta ahora.

Imaginemos que queremos abrir FDatos en modo emergente y modal. ¿Qué problema
tenemos? Que la propiedad “emergente” sólo es manipulable en vista Diseño. ¿Y qué hacemos
ahora?

14
Visítame en http://siliconproject.com.ar/neckkito/
Vamos a copiar FDatos y lo pegaremos con el nombre de FDatos2. ¿Ya lo hemos hecho?

Lo que tenemos que hacer, desde nuestro botón de comando en FMenu, es conseguir
que el código:

1. Abra FDatos2 en vista diseño, pero a la vez que nos


lo oculte para que el usuario no se dé cuenta de que lo
estamos manipulando.
2. Cambiarle la propiedad para que sea emergente.
3. Cerrarlo, guardando los cambios
4. Abrirlo de nuevo, y establecer su propiedad como
modal

Vamos a ver cómo podemos hacer eso. El código sería el siguiente:


Private Sub cmdAbreFDatos2EmergenteYModal_Click()
DoCmd.OpenForm "FDatos2", acDesign, , , , acHidden
Forms!FDatos2.PopUp = True
DoCmd.Close acForm, "FDatos2", acSaveYes
DoCmd.OpenForm "FDatos2"
Forms!FDatos2.Modal = True
End Sub

Si nos fijamos:
– En la línea del DoCmd.OpenForm le hemos dicho, primero, que nos lo abra en vista
diseño (acDesign) y, segundo, que nos lo abra oculto (acHidden)
– En la segunda línea de código establecemos su propiedad como emergente (PopUp
TRUE)
– En la tercera línea de código cerramos el formulario, añadiéndole la constante
acSaveYes, que lo que hace evidentemente es guardarnos los cambios de manera directa (sin
pedirnos confirmación).
– Y el resto de código ya lo conocéis

PARA FINALIZAR...

Con todo lo visto en este capítulo tenemos, creo, unas bases “importantes” para saber cómo
abrir/cerrar objetos de Access y manipular, en concreto los formularios. Insisto en que si
queremos profundizar (deberíamos, de hecho) en los diferentes aspectos que hemos “tocado”
en esta explicación sería conveniente utilizar la ayuda de Access, que para eso está.

Por ejemplo, si en nuestro VBE escribimos DoCmd.OpenForm y situamos el cursor sobre esas
“palabritas mágicas”, y a continuación pulsamos F1, obtendremos algunas explicaciones que os
pueden ayudar a entender algunas cosas mejor, e incluso descubriréis cosas nuevas (porque,
lógicamente, yo no puedo explicarlo “todo todo” ;)

Espero, de todas maneras, haberos podido dar una “ayudita” para entender estos “primeros
momentos” con nuestro amiguete DoCmd.

¡Suerte!

15
Visítame en http://siliconproject.com.ar/neckkito/
CURSO DE VB

CAPÍTULO 61

Índice de contenido
SIGAMOS UN POCO MÁS CON DOCMD Y FORMULARIOS.....................................................2
LA PREPARACIÓN DEL EJEMPLO DE ESTE CAPÍTULO.......................................................2
SEGUNDA CONVERSACIÓN OÍDA AL PASAR........................................................................3
CONTROLES, CONTROLES........................................................................................................3
CUIDADO: AÑADIR REGISTROS O “PASEARSE” POR LOS REGISTROS...........................7
POR FIN APARECE NUESTRO SUBFORMULARIO.................................................................8
EL ÚLTIMO DETALLE DE NUESTRO FORMULARIO...........................................................11
REUTILIZANDO NUESTRO FORMULARIO...........................................................................13
PARA FINALIZAR ESTE CAPÍTULO........................................................................................18

1 La BD donde están los ejemplos de este capítulo os la podéis bajar aquí .

1
Visítame en http://siliconproject.com.ar/neckkito/
SIGAMOS UN POCO MÁS CON DOCMD Y FORMULARIOS

LA PREPARACIÓN DEL EJEMPLO DE ESTE


CAPÍTULO

Para no perder las buenas costumbres vamos a crear una


BD en blanco para ir probando las cosas que veremos. Dado
que no puedo dar por supuesto que se “sepa a la
perfección” cómo realizar lo que os voy a indicar en la BD
voy a ir explicando esta preparación paso a paso.

La idea es la siguiente: vamos a dar de alta unos clientes, pero queremos dar de alta también
unos datos confidenciales del cliente. Esos datos confidenciales no los puede rellenar cualquier
usuario de la BD, sino que sólo los podremos rellenar nosotros. Emplearemos la estructura de
subformulario para hacer eso, lo que nos permitirá ver cómo podemos manipular controles
tanto de un formulario como de un subformulario.

La BD de ejemplo, en sí, quizá no tenga mucho sentido. Ya sabéis que lo que interesa es
“pillar” la mecánica de cómo se hacen las cosas, y no el ejemplo en sí.

Manos a la obra:

1.- Creamos una tabla, que llamaremos TClientes, que contendrá los siguientes campos:

– [IdCli] → Autonumérico y clave principal.


– [NomCli] → Texto (recogerá el nombre del cliente)
– [Casado] → Sí/no (con NO como valor por defecto)
– [NomConyu] → Texto (nos recogerá el nombre del cónyuge)

2.- Creamos otra tabla, que llamaremos TConfidencial, que contendrá los siguientes campos:

– [IdReg] → Autonumérico y clave principal.


– [IdCliConf] → Numérico
– [FechaCas] → Fecha/Hora, con formato de fecha corta (nos recogerá la fecha del enlace
matrimonial).
– [Deporte] → Texto

3.- Creamos un formulario basado en la tabla TClientes, al que pondremos por nombre
FClientes.

4.- Sacamos las propiedades del campo [NomConyu] y nos vamos a la pestaña Formato →
Visible, y le situamos la propiedad en NO.

5.- Insertamos un subformulario basado en la tabla TConfidencial, y definimos la relación entre


formulario principal y subformulario de la siguiente manera:
– Campo del formulario: [IdCli]
– Campo del subformulario: [IdCliConf]

6.- Guardamos el subformulario con el nombre de subFrmConfidencial

7.- Dentro del formulario principal, seleccionamos el subformulario recién creado, sacamos sus
propiedades y nos vamos a la pestaña Formato → Visible: NO.

2
Visítame en http://siliconproject.com.ar/neckkito/
8.- Guardamos los cambios y cerramos FClientes.

9.- Abrimos subFrmConfidencial en vista diseño y sacamos


sus propiedades. Nos vamos a la pestaña Formato → Vista
predeterminada: un único formulario.

10.- Sacamos las propiedades del campo [FechaCas] y nos


vamos a la pestaña Formato → Visible: NO

Y, por ahora, lo dejaremos aquí.

SEGUNDA CONVERSACIÓN OÍDA AL PASAR...

Código VB: ¡Hola, Forms!miFormulario! ¿Con quién vienes?


Formulario: Hola. Te presento a mi hijo, subFrmMiSubform.
CVB: Caramba, qué ricura. Creo que le voy a reservar un nombre especial para él.
F: ¿Y qué nombre le vas a poner?
CVB: Le llamaré... Formulario subFrmMiSubform hijo de Forms!miFormulario
F: ¡Qué cosa más larga! ¿No podrías abreviarlo un poco?
CVB: Pues... el nombre definitivo será: Forms!miFormulario.subFrmMiSubform.Form
F: Esto... ¿qué entiendes tú por “abreviar”...?

CONTROLES, CONTROLES...

Los controles, al igual que los formularios, tienen propiedades. Y, también como los
formularios, esas propiedades pueden ser modificadas a través del código. Pero antes debemos
identificar el control.

¿Y cómo se hace eso? Mi primera recomendación: poned un nombre descriptivo al control


(Propiedades → Pestaña Otras → Nombre).

Cuando creamos un botón de comando, por ejemplo, Access por defecto le pone un nombre
enumerativo, del tipo “Comando25”. Para los cuadros combinados utiliza el mismo sistema:
“Cuadro combinado13”, y lo mismo podemos decir para el resto de controles. Como no
tengamos un nombre descriptivo para ellos vamos a tener que estar continuamente mirando
“cómo se llamaba ese TextBox... ¿Texto2 o Texto63?” Eso es un latazo.

La segunda cosa que debemos saber es que para llamar a los controles debemos pasar por
identificar el formulario en el que están. Es decir, si tenemos el cboSeleccionaProvincia no
podemos poner ese valor “directamente” en el código, sino que debemos indicarle la “ruta”
completa, separada por puntos. Así pues, si llamamos a ese combo deberíamos escribir algo
así como: Forms!miFormulario.cboSeleccionaProvincia (o, como ya sabemos,
Me.cboSeleccionaProvincia).

A continuación de la llamada al control debemos especificar o bien el método que vamos a


utilizar o bien la propiedad que queremos manipular. Por ejemplo, si queremos actuar sobre el
color de la letra de un cuadro de texto txtLetra deberíamos utilizar la propiedad “forecolor”, y
lo indicaríamos así: Forms!miFormulario.txtLetra.Forecolor.

En resumen:

– O bien: Forms!nombreFormulario.nombreControl.<método>

3
Visítame en http://siliconproject.com.ar/neckkito/
– O bien: Forms!nombreFormulario.nombreControl.<propiedad>

Vamos a ver una primera manipulación muy sencilla. Si el


cliente está casado, el campo donde tengamos su nombre
se sombreará de amarillo y el color del texto será rojo. Para
ello manipularemos la propiedad “Después de actualizar”
del campo [Casado].

Ponemos pues nuestro formulario FClientes en vista diseño


y sacamos las propiedades del campo [Casado]. Nos vamos
a la pestaña Eventos → Después de actualizar, y le
generamos el siguiente código:


Private Sub Casado_AfterUpdate()
Dim vCas As Boolean
vCas = Me.Casado.Value
If vCas = True Then
With Me.NomCli
.ForeColor = vbRed
.BackColor = vbYellow
End With
End If
End Sub

Sabemos que [Casado] siempre devuelve dos valores: Falso (valor que hemos establecido por
defecto) o Verdadero. Por este motivo definimos la variable vCas como booleana.

Vamos a coger el valor de [Casado] una vez hayamos hecho click (después de actualizar). Para
ello tenemos que decir que queremos

El valor del campo / Casado / que está en el formulario FCliente

Como ya sabemos, para llamar un control debemos indicar primero en qué formulario está. Por
ello utilizamos Me (porque, como ya estudiamos en el capítulo anterior, código y formulario
“son vecinos”). A continuación indicamos el nombre del control, y finalmente, separado por un
punto, la propiedad que queremos obtener. Así, nos queda que vCas es igual a

vCas = Me.Casado.Value

Si el campo [Casado] está marcado (es Verdadero) debemos “actuar” de alguna manera. Por
ello utilizamos el bloque IF...END IF, de manera que:

If vCas = True Then

Y para llamar a las propiedades del campo que queremos manipular, que es [NomCli], ya
sabemos cómo llamarlas. Y también sabemos que como nos vamos a referir al mismo control
podemos utilizar el bloque WITH...END WITH, de manera que:

El color de la letra sea rojo → Me.NomCli.Forecolor = vbRed


El color del fondo sea amarillo → Me.NomCli.Backcolor = vbYellow

Si ahora situamos nuestro formulario en vista formulario, damos de alta un cliente y marcamos
la casilla de que está casado, veremos los cambios.

4
Visítame en http://siliconproject.com.ar/neckkito/
Pero ahora nos hemos dado cuenta de que hemos marcado que el cliente estaba casado por
error, pero que en realidad no está casado. Si desmarcamos el check... ¡no pasa nada!

No pasa nada porque en el código no hemos analizado que


pasa si el valor del campo [Casado] es False. Por ello,
podemos modificar nuestro código de la siguiente manera:


Private Sub Casado_AfterUpdate()
Dim vCas As Boolean
vCas = Me.Casado.Value
If vCas = True Then
With Me.NomCli
.ForeColor = vbRed
.BackColor = vbYellow
End With
Else
With Me.NomCli
.ForeColor = vbBlack
.BackColor = vbWhite
End With
End If
End Sub

Bueno... Ahora parece que si probamos esto de marcar y desmarcar el check la cosa mejora.

Sigamos: vamos a manipular otra propiedad. En este caso queremos que si se marca el check
casado el campo [NomConyu] se haga visible. Para ello deberemos manipular la propiedad
<Visible> del campo [NomConyu], teniendo en cuenta de que el usuario se puede “equivocar”
y marcar el check (y desmarcarlo a continuación).

Nuestro código anterior debería “ampliarse” de la siguiente manera:


Private Sub Casado_AfterUpdate()
Dim vCas As Boolean
vCas = Me.Casado.Value
If vCas = True Then
With Me.NomCli
.ForeColor = vbRed
.BackColor = vbYellow
End With
Me.NomConyu.Visible = True
Else
With Me.NomCli
.ForeColor = vbBlack
.BackColor = vbWhite
End With
Me.NomConyu.Visible = False
End If
End Sub

Como vemos, lo único que hemos hecho ha sido añadir que la propiedad <Visible> del campo
[NomConyu] de este formulario adquiera los valores True o False en función de si el check está

5
Visítame en http://siliconproject.com.ar/neckkito/
marcado o desmarcado; es decir:

Me.NomConyu.Visible = True

Me.NomConyu.Visible = False

Si probamos nuestro formulario veremos como [NomConyu]


aparece y desaparece a nuestro antojo.

Compliquemos la cosa: nuestro usuario marca nuestro


cliente como casado, escribe el nombre del cónyuge y, de
repente, se da cuenta, de que se ha equivocado.

Lógicamente, desmarca el check y asunto arreglado... ¿o no?

Si probamos lo anterior y cerramos nuestro formulario, si a continuación abrimos la tabla


TClientes veremos como el check está desmarcado, pero aparece un nombre de cónyuge. Y
eso no debería ser así.

Vamos a corregir ese error. Ya sabemos cómo coger el valor de un campo a través de la
propiedad <value>. Pues para modificar un valor también recurrimos a su propiedad <value>.

En definitiva, lo que debemos decirle al código es que si el check está desmarcado nos “borre”
el valor del campo [NomConyu] y después lo convierta en invisible.

Para ello, nuestro código quedaría así:


Private Sub Casado_AfterUpdate()
Dim vCas As Boolean
vCas = Me.Casado.Value
If vCas = True Then
With Me.NomCli
.ForeColor = vbRed
.BackColor = vbYellow
End With
Me.NomConyu.Visible = True
Else
With Me.NomCli
.ForeColor = vbBlack
.BackColor = vbWhite
End With
With Me.NomConyu
.Value = Null
.Visible = False
End With
End If
End Sub

Como podéis ver, lo que hemos hecho ha sido decirle que el valor de [NomConyu] sea NULO, a
través de Me.NomConyu.Value = Null

Si repetimos la operación anterior, cerramos el formulario y abrimos la tabla TClientes veremos

6
Visítame en http://siliconproject.com.ar/neckkito/
que ahora ya no nos aparece ningún cónyuge.

CUIDADO: AÑADIR REGISTROS O “PASEARSE”


POR LOS REGISTROS

Hay que tener en cuenta el comportamiento del formulario


según la situación en la que estemos. Hasta ahora hemos
estado añadiendo un solo registro. Pero, ¿qué ocurre
cuando nos “pasemos” por los registros (teniendo en cuenta
que cuando digo “pasear” me refiero a todas las acciones
posibles de <Ir a registro... nuevo, anterior, siguiente,
etc.>

Vamos a añadir un registro de un cliente que esté casado. Una vez tengamos los campos
rellenos, vamos a añadir otro registro. ¿Sorpresa?

Pues sí: sorpresa. Nuestro campo [NomCli] nos aparece con fondo amarillo y el campo
[NomConyu] aparece visible. Y eso no debería ser así.

El problema lo encontramos porque cuando hemos modificado las propiedades de los controles
lo hacemos independientemente del registro en el que estemos, y la última modificación es lo
último que “conserva” el formulario y controles.

Para solucionar lo anterior debemos hacer que el formulario, cada vez que cambie un registro,
examine el valor del check [Casado] y actúe en consecuencia. Y eso de “cada vez que cambie
un registro” es un evento de formulario que se denomina “Al activar registro”.

Así pues, situamos nuestro formulario FClientes en vista diseño y sacamos sus propiedades.
Nos vamos a la pestaña Eventos → Al activar registro, y le generamos el siguiente código:


Private Sub Form_Current()
Dim vCas As Boolean
vCas = Me.Casado.Value
If vCas = True Then
With Me.NomCli
.ForeColor = vbRed
.BackColor = vbYellow
End With
Me.NomConyu.Visible = True
Else
With Me.NomCli
.ForeColor = vbBlack
.BackColor = vbWhite
End With
Me.NomConyu.Visible = False
End If
End Sub

Podemos ver que el código es prácticamente igual al que hemos estado programando hasta
ahora. Lo que dice es, simplemente, que al cambiar registro me miras el valor del check
[Casado]: si su valor es Verdadero haces unas cosas; si es Falso haces otras.

Lógicamente, si ahora probamos nuestro formulario veremos que las cosas vuelven a funcionar

7
Visítame en http://siliconproject.com.ar/neckkito/
con “normalidad”.

Moraleja: cuando programemos un formulario debemos


tener siempre en cuenta “lo que pasa” cuando damos de
alta un registro y “lo que pasa” cuando recorremos los
registros. ¡Ojo!

POR FIN APARECE NUESTRO SUBFORMULARIO

Volvamos a situar nuestro formulario FClientes en vista


diseño. En su cabecera añadimos un botón de comando,
que llamaremos cmdMuestraConfidencial.

Al hacer click sobre ese botón se nos mostrarán los datos confidenciales, siempre y cuando
sepamos una contraseña. El proceso para realizar lo anterior es:

1.- Definimos la contraseña


2.- Pedimos al usuario que introduzca una contraseña
3.- Comparamos si la contraseña introducida se corresponde con la contraseña correcta. Y si es
así...
4.- Muestra el subformulario subFrmConfidencial

Vamos pues:

.- La contraseña será siempre la misma, por lo que el tipo de variable que utilizaremos será
una constante.

.- ¿Cómo solicitamos la entrada de datos al usuario? Pues a través de un InputBox

.- ¿Cómo realizamos la comparación? Pues a través de un bloque IF...END IF

.- ¿Cómo mostramos el subformulario? Pues actuando sobre su propiedad <Visible>

El código que asignaremos al evento “Al hacer click” del botón de comando será, pues:


Private Sub cmdMuestraConfidencial_Click()
Const pass As String = "a123"
Dim userPass As Variant
'Solicitamos la introducción de la constraseña
userPass = InputBox("¿Contraseña?", "PASSWORD")
'Detectamos si el usuario pulsa el botón cancelar
If StrPtr(userPass) = 0 Then Exit Sub
'Comprobamos que la contraseña sea correcta
If userPass = pass Then
'Si es correcta hacemos visible el subformulario
Me.subFrmConfidencial.Form.Visible = True
Else
'Si no lo es avisamos. Se sale tras el End If del proceso sin hacer nada
MsgBox "La contraseña introducida no es correcta", vbCritical, "ERROR"
End If
End Sub

El código está comentado, además de que la mayoría de acciones que realiza este código ya

8
Visítame en http://siliconproject.com.ar/neckkito/
las hemos estudiado en capítulos anteriores. Simplemente remarcar la llamada al
subformulario, que la realizamos de la siguiente manera:

Me.subFrmConfidencial.Form.Visible

¿Recordamos, en nuestra “conversación oída al pasar”, lo de


<Forms!nombreForm.nombreSubForm.Form.<propiedad>
?

Evidentemente, en este caso hemos sustituido Forms!


NombreForm por ME.

Si probamos nuestro formulario veremos que con el pass correcto podemos ver el
subformulario.

Ricemos el rizo: en nuestro formulario tenemos el check [casado] marcado, pero en nuestro
subformulario el campo [FechaCas] no nos aparece. Eso no está bien. Y para llamar a un
control en un subformulario desde el formulario debemos utilizar:

Forms!nombreFormulario.nombreSubForm.Form.nombreControl.Propiedad

Así pues, nuestro código del botón de comando debería ser modificado así:


Private Sub cmdMuestraConfidencial_Click()
Const pass As String = "a123"
Dim userPass As Variant
Dim vCas As Boolean
'Miramos qué valor tiene el check [Casado]
vCas = Me.Casado.Value
'Solicitamos la introducción de la constraseña
userPass = InputBox("¿Contraseña?", "PASSWORD")
'Detectamos si el usuario pulsa el botón cancelar
If StrPtr(userPass) = 0 Then Exit Sub
'Comprobamos que la contraseña sea correcta
If userPass = pass Then
'Si es correcta hacemos visible el subformulario
Me.subFrmConfidencial.Form.Visible = True
'Si el check casado es verdadero mostramos el campo [FechaCas]
If vCas = True Then
Me.subFrmConfidencial.Form.FechaCas.Visible = True
End If
Else
'Si no lo es avisamos. Se sale tras el End If del proceso sin hacer nada
MsgBox "La contraseña introducida no es correcta", vbCritical, "ERROR"
End If
End Sub

¿Todo perfecto? Pues no todo es perfecto. Vamos a suponer que queremos que el
subformulario se mantenga visible hasta que no lo ocultemos. Añadimos un nuevo registro y...
nuestro campo [FechaCas] sigue visible.

¿Cómo podemos arreglar eso? Vamos a verlo, y para ello debemos recurrir de nuevo a la
propiedad de “Al activar registro” del formulario. Pero ahora tenemos no una condición que
cumplir sino dos:

9
Visítame en http://siliconproject.com.ar/neckkito/
– Que el subformulario esté visible.
– Que el check [Casado] esté marcado

Así que en nuestro código de ese evento vamos a realizar


una pequeña modificación de la siguiente manera:


Private Sub Form_Current()
Dim vCas As Boolean
vCas = Me.Casado.Value
If vCas = True Then
With Me.NomCli
.ForeColor = vbRed
.BackColor = vbYellow
End With
Me.NomConyu.Visible = True
Else
With Me.NomCli
.ForeColor = vbBlack
.BackColor = vbWhite
End With
Me.NomConyu.Visible = False
End If
If Me.subFrmConfidencial.Form.Visible = True Then
If vCas = True Then
Me.subFrmConfidencial.Form.FechaCas.Visible = True
Else
Me.subFrmConfidencial.Form.FechaCas.Visible = False
End If
End If
End Sub

El primer bloque IF...END IF viene a decir:


.- Si el subformulario está visible
'Haz cosas
Y por omisión, si no está visible no hacemos nada

El segundo bloque IF...END IF dice que:


.- Si el check [Casado] está marcado
'Muéstrame [FechaCas]
.- Si no lo está
'Ocúltame [FechaCas]

¿Seguimos el razonamiento hasta ahora?

Y lo anterior nos plantea un problema, teniendo presente que estamos añadiendo un nuevo
registro. Si ahora damos de alta otro cliente y marcamos el check [Casado] nuestro campo de
subformulario [FechaCas] no nos aparece, porque no hemos realizado ningún tipo de
programación para este supuesto.

Evidentemente, tendremos que modificar nuestro código del evento “Después de actualizar”,
pero esta vez teniendo en cuenta si el subformulario está visible o no.

Así pues, la modificación que debemos hacer es la siguiente:

10
Visítame en http://siliconproject.com.ar/neckkito/

Private Sub Casado_AfterUpdate()
Dim vCas As Boolean
vCas = Me.Casado.Value
If vCas = True Then
With Me.NomCli
.ForeColor = vbRed
.BackColor = vbYellow
End With
Me.NomConyu.Visible = True
Else
With Me.NomCli
.ForeColor = vbBlack
.BackColor = vbWhite
End With
With Me.NomConyu
.Value = Null
.Visible = False
End With
End If
If Me.subFrmConfidencial.Form.Visible = True Then
If vCas = True Then
Me.subFrmConfidencial.Form.FechaCas.Visible = True
Else
With Me.subFrmConfidencial.Form.FechaCas
.Visible = False
.Value = Null
End With
End If
End If
End Sub

Como vemos, el código prácticamente es el mismo que el anterior. Lo único que en este caso,
en el caso de que se desmarque el check, aprovechamos para decirle que nos “borre” cualquier
valor que haya podido introducirse.

He querido que se produjera una cierta “reiteración” de los códigos (con ligeras variaciones)
con la finalidad de que captéis la mecánica de funcionamiento.

EL ÚLTIMO DETALLE DE NUESTRO FORMULARIO

Vamos a situar nuestro FClientes en vista diseño y en su cabecera vamos a añadir un botón de
comando para cerrar la vista de los datos confidenciales. A este nuevo botón lo llamaremos
cdmOcultaConfidencial.

Vamos a sacar las propiedades de este nuevo botón y le vamos a poner como propiedad
predeterminada que esté desactivado. Para ello nos vamos a la pestaña Datos->Activado: NO.

Lógicamente, queremos que cuando pulsemos sobre el botón cmdMuestraConfidencial se nos


active cmdOcultaConfidencial, pero al mismo tiempo, para evitar “clicks” accidentales, se
desactive cmdMuestraConfidencial.

De la misma manera, cuando pulsemos cmdOculta confidencial este mismo botón se desactive
para que cmdMuestraConfidencial quede activado.

11
Visítame en http://siliconproject.com.ar/neckkito/
Sacamos pues las propiedades de cmdMuestraConfidencial y modificamos el código existente
de la siguiente manera:


Private Sub cmdMuestraConfidencial_Click()
Const pass As String = "a123"
Dim userPass As Variant
Dim vCas As Boolean
'Miramos qué valor tiene el check [Casado]
vCas = Me.Casado.Value
'Solicitamos la introducción de la constraseña
userPass = InputBox("¿Contraseña?", "PASSWORD")
'Detectamos si el usuario pulsa el botón cancelar
If StrPtr(userPass) = 0 Then Exit Sub
'Comprobamos que la contraseña sea correcta
If userPass = pass Then
'Si es correcta hacemos visible el subformulario
Me.subFrmConfidencial.Form.Visible = True
'Si el check casado es verdadero mostramos el campo [FechaCas]
If vCas = True Then
Me.subFrmConfidencial.Form.FechaCas.Visible = True
End If
Else
'Si no lo es avisamos y salimos del proceso sin hacer nada
MsgBox "La contraseña introducida no es correcta", vbCritical, "ERROR"
Exit Sub
End If
With Me
.cmdOcultaConfidencial.Enabled = True
.cmdOcultaConfidencial.SetFocus
.cmdMuestraConfidencial.Enabled = False
End With
End Sub

Vamos a ver qué hemos hecho:

1.- Con nuestro código anterior, si la contraseña introducida era correcta, salíamos el bloque
IF...END IF y la siguiente instrucción ya era END SUB. En este nuevo caso no podemos dejar
que el código “siga” ejecutándose tras el END IF porque hemos añadido nuevo código, por lo
que este sí se ejecutaría. ¿Cómo lo solventamos? Pues “forzando” la salida del proceso a través
de un EXIT SUB.

2.- Para volver activo el botón cmdOcultaConfidencial debemos llamar a su propiedad y


establecerle el valor True, que es lo que hacemos a través de la propiedad ENABLED.

3.- Debemos saber (y si no no os preocupéis que nuestro amigo Access se encargará de


avisarnos a través de un mensaje) que no podemos desactivar un control si tiene el enfoque.
Es decir, que cuando yo hago click sobre el botón cmdMuestraConfidencial durante la ejecución
de todo el código el enfoque está situado sobre él. Si no obligamos a que el enfoque se mueva
a otro control no podremos manipular su propiedad.

Es por ello por lo que antes de manipular cmdMuestraConfidencial.Enabled = False debemos


mover el enfoque a otro control. Como cmdOcultaConfidencial ya está activo pues es el
candidato más idóneo, y de ahí que añadamos la línea Me.cmdOcultaConfidencial.SetFocus.

12
Visítame en http://siliconproject.com.ar/neckkito/
 Probad una cosa: convertid esa línea (Me.cmdOcultaConfidencial.SetFocus) en comentario
(os recuerdo que basta poner una comilla simple delante del ME) e intentad ejecutar el código.
Veréis qué “hermoso” mensaje nos lanza Access.

Evidentemente para solventar el “problemilla” basta quitar


la comilla simple que la convierte en comentario.

Vamos pues a por nuestro botón cmdOcultaConfidencial. Os


animo a pensar vosotros el código que debería ir en ese
botón antes de mirar el que yo os proporciono.

Pues eso... El código sería:


Private Sub cmdOcultaConfidencial_Click()
With Me
.cmdMuestraConfidencial.Enabled = True
.cmdMuestraConfidencial.SetFocus
.cmdOcultaConfidencial.Enabled = False
.subFrmConfidencial.Visible = False
End With
End Sub

¿Sencillo, no?

REUTILIZANDO NUESTRO FORMULARIO

Antes de entrar en materia vamos a hacer un par de operaciones:

1.- En FClientes añadimos una etiqueta en la cabecera y en ella escribimos: “AÑADIR


CLIENTES”. Le ponemos de nombre lblModoEntrada

2.- En las propiedades del formulario nos vamos a la pestaña Formato → Titulo, y ahí
escribimos ALTA CLIENTES

3.- Añadimos un botón de comando, que llamaremos cmdCierra, y le generaremos el siguiente


código:


Private Sub cmdCierra_Click()
DoCmd.Close acForm, Me.Name
DoCmd.OpenForm "FMenu"
End Sub

En teoría deberíais saber perfectamente qué está haciendo este código ;)

4.- Creamos un formulario en blanco y lo guardamos como FMenu.

5.- Añadimos un botón de comando y lo llamamos cmdAgregaClientes. Este botón nos servirá
para dar de alta clientes.

6.- Añadimos otro botón de comando y lo llamamos cmdConsultaClientes. Este botón nos
servirá para consultar clientes.

13
Visítame en http://siliconproject.com.ar/neckkito/
OK. Ahora tenemos dos botoncillos desde nuestro menú que nos permitirán agregar o
consultar clientes. La pregunta del millón es: ¿necesitamos un formulario para agregar y otro
formulario para consultar?

La respuesta es... No. Podemos “reciclar” nuestro


formulario para que sirva para ambas cosas. Vamos a ver la
primera opción: agregar.

Como FClientes lo hemos diseñado en principio para


agregar ninguna modificación debemos hacer. Basta que
cojamos cmdAgregaClientes y le generemos el siguiente
código (¿sabríamos programarlo sin mirar la solución?)


Private Sub cmdAgregaClientes_Click()
DoCmd.OpenForm "FClientes", , , , acFormAdd
DoCmd.Close acForm, Me.Name
End Sub

Y listo.

Vamos a por nuestro segundo botón.

La primera duda que se nos plantea es: ¿quién va a consultar? ¿Nosotros (que podemos ver
los datos confidenciales) u otro usuario? Vamos a pedírselo al propio usuario.

El código pues, inicialmente, sería el siguiente:


Private Sub cmdConsultaClientes_Click()
Const pass As String = "a123"
Dim resp As Integer
Dim userPass As Variant
'Solicitamos el modo de consulta
resp = MsgBox("¿Consulta en modo administrador?", vbQuestion + vbYesNo, "TIPO
CONSULTA")
'Si la respuesta es sí solicitamos el password
If resp = vbYes Then
userPass = InputBox("¿Contraseña?", "PASSWORD")
'Detectamos la pulsación del botón cancelar
If StrPtr(userPass) = 0 Then Exit Sub
'Comprobamos que la contraseña sea correcta
If userPass = pass Then
'Si es correcta abrimos el formulario
DoCmd.OpenForm "FClientes", , , , acFormReadOnly
DoCmd.Close acForm, Me.Name
Else
'Si no es correcta avisamos y salimos
MsgBox "Contraseña incorrecta", vbCritical, "ERROR"
Exit Sub
End If
Else
'Si la respuesta es no abrimos el formulario en modo usuario
DoCmd.OpenForm "FClientes", , , , acFormReadOnly

14
Visítame en http://siliconproject.com.ar/neckkito/
DoCmd.Close acForm, Me.Name
End If
End Sub

El código, como es obvio, aún no está completo. Vamos a


analizarlo:

 La estructura del código vemos que la conforma un


bloque IF dentro de otro bloque IF. El primer bloque lo que
hace es pedirnos si entramos como usuario o como
administrador.

 El segundo bloque IF lo que hace es solicitarnos la contraseña para verificar que quien
intenta entrar posee los privilegios de administrador.

 Como podéis ver en este código estamos combinando algo que vimos en capítulos
anteriores (MsgBox para recabar información del usuario) y algo que hemos visto en este, que
es la solicitud de una contraseña para interactuar con el formulario.

 Nos hemos “cubierto” las espaldas en el sentido de que cada cosa “mal” (por llamarlo de
alguna manera) fuerza a nuestro código a salir sin abrir FClientes. Es decir:
– Si se pulsa el botón cancelar → Salimos del procedimiento
– Si la contraseña es errónea → Salimos del procedimiento

Vamos a por nuestra segunda duda: si entramos como usuario no podemos ver el
subformulario, lo cual es correcto, pero si entramos como administrador deberíamos poder ver
el subformulario. Para ello debemos indicárselo en el código, de la siguiente manera:


Private Sub cmdConsultaClientes_Click()
Const pass As String = "a123"
Dim resp As Integer
Dim userPass As Variant
'Solicitamos el modo de consulta
resp = MsgBox("¿Consulta en modo administrador?", vbQuestion + vbYesNo, "TIPO
CONSULTA")
'Si la respuesta es sí solicitamos el password
If resp = vbYes Then
userPass = InputBox("¿Contraseña?", "PASSWORD")
'Detectamos la pulsación del botón cancelar
If StrPtr(userPass) = 0 Then Exit Sub
'Comprobamos que la contraseña sea correcta
If userPass = pass Then
'Si es correcta abrimos el formulario
DoCmd.OpenForm "FClientes", , , , acFormReadOnly
Forms!FClientes.subFrmConfidencial.Visible = True
DoCmd.Close acForm, Me.Name
Else
'Si no es correcta avisamos y salimos
MsgBox "Contraseña incorrecta", vbCritical, "ERROR"
Exit Sub
End If
Else
'Si la respuesta es no abrimos el formulario en modo usuario

15
Visítame en http://siliconproject.com.ar/neckkito/
DoCmd.OpenForm "FClientes", , , , acFormReadOnly
DoCmd.Close acForm, Me.Name
End If
End Sub

 Recordad que estamos en el módulo de FMenu, por lo
que no podemos utilizar ME, sino que debemos llamar al
formulario FClientes por su forma “larga”.

Ahora nos damos cuenta de que si el primer registro


muestra el check [Casado] marcado nuestro subformulario
no nos muestra la fecha de boda porque hemos establecido
su propiedad por defecto como no visible.

¿Qué podemos hacer? Pues simplemente decírselo al código, así:


Private Sub cmdConsultaClientes_Click()
Const pass As String = "a123"
Dim resp As Integer
Dim userPass As Variant
'Solicitamos el modo de consulta
resp = MsgBox("¿Consulta en modo administrador?", vbQuestion + vbYesNo, "TIPO
CONSULTA")
'Si la respuesta es sí solicitamos el password
If resp = vbYes Then
userPass = InputBox("¿Contraseña?", "PASSWORD")
'Detectamos la pulsación del botón cancelar
If StrPtr(userPass) = 0 Then Exit Sub
'Comprobamos que la contraseña sea correcta
If userPass = pass Then
'Si es correcta abrimos el formulario
DoCmd.OpenForm "FClientes", , , , acFormReadOnly
Forms!FClientes.subFrmConfidencial.Visible = True
DoCmd.Close acForm, Me.Name
With Forms!FClientes
If .Casado.Value = True Then
.subFrmConfidencial.Form.FechaCas.Visible = True
End If
End With
Else
'Si no es correcta avisamos y salimos
MsgBox "Contraseña incorrecta", vbCritical, "ERROR"
Exit Sub
End If
Else
'Si la respuesta es no abrimos el formulario en modo usuario
DoCmd.OpenForm "FClientes", , , , acFormReadOnly
DoCmd.Close acForm, Me.Name
End If
End Sub

 Este nuevo código nos sirve para ver que un bloque WITH...END WITH no sólo nos permite
hacer referencia “abreviada” a los controles, propiedades o métodos de un objeto, sino que
además podemos introducir bloques decisionales (en este caso un IF...END IF) para “operar”

16
Visítame en http://siliconproject.com.ar/neckkito/
según nuestras condiciones.

Bueno... Ahora ya tenemos que nuestro formulario se nos abre perfectamente adaptado a si el
usuario es un “simple” usuario o si es el administrador. Pero claro, tenemos
ahora un pequeño e insignificante problema: la vista del form nos está diciendo
que es para dar de alta, aunque no sea así.

Vamos a hacer inventario de las incongruencias:

– El el título del formulario es “ALTA CLIENTES”


– La etiqueta lblModoEntrada nos muestra “AÑADIR
CLIENTE”
– Los botones cmdMuestraConfidencial y cmdOcultaConfidencial no tienen mucho sentido
en este modo de consulta.
– Aunque no sea una incongruencia estricta, nos gustaría que nuestro botón de cerrar el
formulario estuviera a la izquierda del encabezado (suponiendo que lo tengamos a la derecha).

Lo que tenemos que hacer es cambiar todo lo anterior para que se adapte a nuestros gustos.

 Para ocultar los dos botones ya sabemos que debemos actuar sobre su propiedad VISIBLE.
Eso ya lo tenemos claro.

 Para actuar sobre títulos de formularios y controles debemos manipular una propiedad que
se denomina CAPTION. La estructura del código es, simplemente: Me.Caption = “Nombre que
queramos poner”

 Para manipular la posición horizontal de un control debemos actuar sobre la propiedad LEFT
del control, y asignarle un valor numérico, medido en twips. Aprovecho para comentaros que si
lo que queremos manipular es la posición vertical podemos actuar sobre la propiedad TOP.

Tras lo anterior, nuestro código quedaría de la siguiente manera:


Private Sub cmdConsultaClientes_Click()
Const pass As String = "a123"
Dim resp As Integer
Dim userPass As Variant
'Solicitamos el modo de consulta
resp = MsgBox("¿Consulta en modo administrador?", vbQuestion + vbYesNo, "TIPO
CONSULTA")
'Si la respuesta es sí solicitamos el password
If resp = vbYes Then
userPass = InputBox("¿Contraseña?", "PASSWORD")
'Detectamos la pulsación del botón cancelar
If StrPtr(userPass) = 0 Then Exit Sub
'Comprobamos que la contraseña sea correcta
If userPass = pass Then
'Si es correcta abrimos el formulario
DoCmd.OpenForm "FClientes", , , , acFormReadOnly
Forms!FClientes.subFrmConfidencial.Visible = True
DoCmd.Close acForm, Me.Name
With Forms!FClientes
If .Casado.Value = True Then
.subFrmConfidencial.Form.FechaCas.Visible = True
End If

17
Visítame en http://siliconproject.com.ar/neckkito/
End With
Else
'Si no es correcta avisamos y salimos
MsgBox "Contraseña incorrecta", vbCritical, "ERROR"
Exit Sub
End If
Else
'Si la respuesta es no abrimos el formulario en modo
usuario
DoCmd.OpenForm "FClientes", , , , acFormReadOnly
DoCmd.Close acForm, Me.Name
End If
'Manipulamos las propiedades de FClientes y sus
controles
With Forms!FClientes
.Caption = "CONSULTA CLIENTES"
.lblModoEntrada.Caption = "CONSULTAR CLIENTES"
.cmdMuestraConfidencial.Visible = False
.cmdOcultaConfidencial.Visible = False
.cmdCierra.Left = 10
End With
End Sub

PARA FINALIZAR ESTE CAPÍTULO...

Hemos visto cómo llamar a subformularios en un formulario, y cómo llamar también a los
controles del subformulario. Hemos visto cómo manipulamos propiedades de formulario, de
subformulario y de controles. Evidentemente no hemos visto todas las propiedades, pero sí ya
sabemos cómo es la estructura del código para poder “jugar” con ellas, por lo que no creo que
tenga mucho sentido machacar más este punto.

Cierto es que algunos controles tienen unas propiedades “especiales” que ya veremos en
capítulos posteriores, pero digamos que ya tenemos una gran base para saber qué hacer en
función de nuestras preferencias.

Además, os recuerdo que siempre está la maravillosa tecla F1 que nos muestra ayuda sobre lo
que estamos buscando y, con un poco de suerte, nos proporciona un ejemplo para entenderlo
mejor.

A partir de ahí os recomiendo que elijáis una propiedad (de control o de formulario, o ambos) y
que probéis por vosotros mismos si sois capaces de manipularla a través de código (¡ojo,
pequeñas metas son más efectivas y asequibles que metas gigantescas!).

¡Suerte!

18
Visítame en http://siliconproject.com.ar/neckkito/
CURSO DE VB

CAPÍTULO 71

Índice de contenido
APLICANDO FILTROS......................................................................................................................2
UNAS PALABRAS INICIALES....................................................................................................2
LA PREPARACIÓN DEL EJEMPLO DE ESTE CAPÍTULO.......................................................2
EL PRIMER FILTRO......................................................................................................................2
LA FUNCIÓN NZ......................................................................................................................4
SEGUIMOS CON NUESTRO FILTRO.....................................................................................5
FILTRO POR CONSULTA.............................................................................................................6
FILTROS COMPUESTOS..............................................................................................................7
INCISO: ESE COMBO CON VALORES REPETIDOS............................................................8
SIGAMOS...................................................................................................................................8
FILTRO PARAMETRIZADO.........................................................................................................9
UN POCO MÁS SOBRE FILTROS COMPUESTOS..................................................................11
FILTRO EN EL PROPIO FORMULARIO...................................................................................13
A TRAVÉS DE UN “FILTERON”...........................................................................................13
UTILIZACIÓN DEL “LIKE”..............................................................................................14
A TRAVÉS DE UN “CLON” DEL RECORDSET DEL FORMULARIO...............................15
NUESTRO CLON NO ENCUENTRA EL VALOR............................................................16
MÁS ALLÁ DE LOS FILTROS: CONTROLES Y LA PROPIEDAD ROWSOURCE...............16
TIPOS DE ROWSOURCE.......................................................................................................17
DATOS “LISTA DE VALORES”.........................................................................................17
DATOS “TABLA/CONSULTA”..........................................................................................18
MODIFICAR EL ORIGEN SI EL TIPO DE DATOS ES DE “TABLA O CONSULTA”...20
DATOS “LISTA DE CAMPOS”..........................................................................................21
LOS INFORMES Y LA PROPIEDAD RECORDSOURCE........................................................22
LO MISMO, PERO CON UN FORMULARIO.......................................................................23
UNAS PALABRAS FINALES......................................................................................................24

1 La BD donde están los ejemplos de este capítulo os la podéis bajar aquí.

1
Visítame en http://siliconproject.com.ar/neckkito/
APLICANDO FILTROS

UNAS PALABRAS INICIALES


La idea principal de este capítulo es proporcionaros
diferentes herramientas para realizar un filtro en formulario.
Veremos desde filtros simples hasta filtros más complejos;
filtros en distintos formularios y filtros en el propio
formulario.

Como ya os he avanzado, la idea es proporcionaros “ideas” de cómo podemos realizar filtros,


habrá algunos incisos donde veremos cómo construir un filtro para obtener los datos que
queremos que no serán estrictamente programación pura y dura en VBA.

Y, como la construcción de estructuras de filtro es muy parecida a la construcción de


rowsources y de recordsources, aprovecharemos para echar un vistazo a estas dos
propiedades.

Y después... ¡a aplicar el que más os guste!

LA PREPARACIÓN DEL EJEMPLO DE ESTE CAPÍTULO


Vamos a reciclar un poco la BD que desarrollamos en el capítulo anterior. Por ello, partiré de la
base (nunca mejor dicho) que tenemos delante esa aplicación.

En este capítulo veremos diversas maneras de crear un filtro, y lo complicaremos un poco para
ver cómo podemos combinar varios elementos que nos hagan de filtro.

Para practicar con diversas opciones modificaremos ligeramente nuestras tablas. Lo que
haremos será:

1.- Copiar-pegar (estructura y datos) nuestra tabla TClientes, guardándola como TClientes2. A
esta última tabla le añadiremos dos campos más, que serán:
– [Edad] → Numérico
– [FechaAlta] → Fecha/Hora

Directamente, sobre la tabla, añadiremos algunos registros, para poder desarrollar


prácticamente lo que veamos en los siguientes epígrafes.

2.- Creamos un nuevo formulario, que llamaremos FClientes2, basado sobre la tabla
TClientes2.

3.- Creamos un informe, basado en la tabla TClientes2, al que llamaremos RClientes2.

Algunos de los ejemplos los podremos aplicar tanto a formularios como informes.

Así pues, manos a la obra.

EL PRIMER FILTRO
En nuestro formulario FMenu vamos a insertar un cuadro combinado. Cuando nos salga el
asistente lo configuraremos de la siguiente manera:

2
Visítame en http://siliconproject.com.ar/neckkito/
– Buscamos los valores en una tabla
– Elegimos la tabla TClientes2
– Añadimos los campos [IdCli] y [NomCli]
– Ordenamos, por ejemplo, ascendente sobre [NomCli]
– Ocultamos la clave principal
– Le ponemos como nombre de etiqueta “Buscar
cliente”

Ya tenemos pues configurado nuestro combo que nos


permitirá visionar la ficha del cliente. Sólo nos queda
identificarlo. Sacamos sus propiedades y en la pestaña
Otras → Nombre escribimos cboBuscaCliente.

Hagamos un momento de receso: si examinamos las propiedades de ese combo, en la pestaña


Formato → Número de columnas, veremos que aparece el valor 2. Efectivamente, hay dos
columnas, [IdCli] y [NomCli]. No vemos la primera porque hemos dicho, en el asistente, que
queríamos ocultar la clave principal. Si ahora cambiamos de opinión y sí queremos verla lo que
deberíamos modificar es la propiedad “Ancho de columnas”. Donde nos pone 0 cm podríamos
ponerle, por ejemplo, 1 cm (nos quedaría: 1cm;2,54cm)

Si ahora nos vamos a la pestaña Datos → Columna dependiente veremos que el valor que
aparece ahí es 1. Eso nos está indicando que el valor que guarda el combo, tras haber
seleccionado un cliente, es el identificador del cliente. Nosotros no vamos a cambiar ese valor,
pero tened en cuenta que si quisiéramos que lo que guardara fuera el nombre del cliente
deberíamos cambiar ese valor a 2 (valor de la segunda columna).

Dicho lo anterior vamos a ver cómo filtrar nuestro formulario FClientes2 tras seleccionar un
cliente. Para ello, creamos un botón de comando (cmdBuscarCli) y en el evento “Al hacer click”
generamos el siguiente código:


Private Sub cmdBuscarCli_Click()
Dim vCli As Integer
vCli = Nz(Me.cboBuscaCliente.Value, 0)
If vCli = 0 Then Exit Sub
DoCmd.OpenForm "FClientes2", , , "[IdCli]=" & vCli
DoCmd.Close acForm, Me.Name
End Sub

Si, en lugar de querer abrir el formulario, quisiéramos abrir un informe filtrado, la mecánica es
prácticamente la misma. Suponiendo que tuviéramos un botón de comando llamado
cmdInformeFiltrado el código que deberíamos asignar a ese botón sería:


Private Sub cmdInformeFiltrado_Click()
Dim vCli As Integer
vCli = Nz(Me.cboBuscaCliente.Value, 0)
If vCli = 0 Then Exit Sub
DoCmd.OpenReport "RClientes2", acViewPreview, , "[IdCli]=" & vCli
End Sub

3
Visítame en http://siliconproject.com.ar/neckkito/
LA FUNCIÓN NZ

Vemos que en el código anterior hemos utilizado una nueva


función, que es Nz. La estructura de esta función es:

Nz (Valor, valor_si_nulo)

Si recordamos cuando hablábamos de definir el tipo de


variable comentábamos que es importante determinar qué
tipo de variable va a se la variable que vamos a utilizar.
En este caso puede darse el caso de que tras elegir un valor en el combo nos arrepintamos y
no seleccionemos ningún valor en el combo. En este caso el valor devuelto por el combo será
NULL.

Si, para cubrirnos las espaldas, definimos la variable como Variant, no estaremos optimizando
recursos, pues Access (y lo digo de esta manera para entendernos) deberá realizar tres
operaciones:
Una: saber que existe una variable, pero sin saber de qué tipo exactamente es
Dos: una vez sabe el tipo, “cargar” las características inherentes a este tipo de variable.
Tres: si la variable cambia de tipo, “cargar” de nuevo las características del nuevo tipo

La programación inicial, si hubiéramos seguido este criterio, debería haber sido la siguiente:


Private Sub cmdBuscarCli_Click()
Dim vCli As Variant
vCli = Me.cboBuscaCliente.Value
If IsNull(vCli) Then Exit Sub
DoCmd.OpenForm "FClientes2", , , "[IdCli]=" & vCli
DoCmd.Close acForm, Me.Name
End Sub

Aunque a veces no queda otro remedio que utilizar esta estructura, y por tema de optimización
de recursos que comentábamos, es mejor ya decir qué tipo de variable va a ser. ¿Y cómo
soslayamos este “problema”? Pues a través de la función NZ.

Lo que “dice” esta función es: devuélveme el valor del combo (Me.cboBuscaCliente.Value).
Pero, si su valor es NULL, me devuelves un cero. Y por ello hemos escrito:

vCli = Nz(Me.cboBuscaCliente.Value, 0)

Como podemos ver, si obtenemos un NULL, nos devuelve un Integer, que en este caso es cero.
Debemos ir con cuidado con esto. Por ejemplo, si definimos la variable vCli como String en
caso de obtener un valor NULL debería devolvernos un valor también String. Por ejemplo, una
cadena vacía. Así, en este supuesto, lo que deberíamos haber escrito sería:

Dim vCli As String


vCli = Nz(Me.cboBuscaCliente.Value, “”)
If vCli = “” Then...

Moraleja: el valor que debe devolver NZ en caso de nulo debe ser del mismo tipo con el que
hemos definido la variable.

4
Visítame en http://siliconproject.com.ar/neckkito/
SEGUIMOS CON NUESTRO FILTRO
Ok. Vemos que nuestro filtro funciona perfectamente (¡o
debería!). Imaginemos que por el motivo que sea no
queremos que el valor escogido sea el identificador del
cliente, sino su nombre.

Una opción podría ser:


– Copiar-pegar el combo que hemos creado
– Propiedades → Pestaña Datos → Columna
dependiente: 2

Es decir, le cambiamos la columna que “guarda el valor”. Si le decimos que es la 2 le estamos


indicando que guarde el nombre, y no el identificador.

Sin embargo, nosotros vamos a utilizar otra opción, que es reutilizar nuestro combo sin tocar
sus propiedades. Para ello:

– Creamos otro botón de comando, y lo llamamos cmdBuscarCli2


– Al evento “Al hacer click” le generamos el siguiente código:


Private Sub cmdBuscarCli2_Click()
Dim vCli As String
vCli = Nz(Me.cboBuscaCliente.Column(1), "")
If vCli = "" Then Exit Sub
DoCmd.OpenForm "FClientes2", , , "[NomCli]='" & vCli & "'"
DoCmd.Close acForm, Me.Name
End Sub

Vamos a ver qué ha pasado aquí:

– La variable vCli ahora nos recoge el valor de la columna 1. ¿Extrañados? Pues debéis
tener en cuenta que, cuando hacemos referencia, en VBA, a las columnas de un combo, la
numeración empieza por cero. Es decir, que si lo plasmamos en una especie de esquema
obtendríamos que:

Campo del combo Número columna Access Número columna VBA


[Id] Columna 1 Column(0)
[NomCli] Columna 2 Column(1)

– Como ahora recogemos el nombre del cliente, nuestro vCli ha de ser de tipo String
– Como es de tipo String, la función NZ debe devolver un String en caso de que el valor
sea vacío. Recordad lo visto en el epígrafe donde explicábamos esta función.
– El filtro en la línea del DoCmd.OpenForm debe ir entre comillas simples por ser,
precisamente, un String.

Y para esos “hombres de poca fe” que no lo ven claro, si queremos constatar sin ningún
género de dudas qué valor está cogiendo la variable vCli podemos modificar ligeramente
nuestros códigos, añadiendo una sola línea, para que el propio Access nos lo diga. Así,
tendremos que:

.- Para nuestro cmdBuscarCli:


5
Visítame en http://siliconproject.com.ar/neckkito/
Private Sub cmdBuscarCli_Click()
Dim vCli As Integer
vCli = Nz(Me.cboBuscaCliente.Value, 0)
If vCli = 0 Then Exit Sub
DoCmd.OpenForm "FClientes2", , , "[IdCli]=" & vCli
DoCmd.Close acForm, Me.Name
MsgBox "Se ha encontrado el cliente: " & vCli
End Sub

.- Y para nuestro cmdBuscarCli2:



Private Sub cmdBuscarCli2_Click()
Dim vCli As String
vCli = Nz(Me.cboBuscaCliente.Column(1), "")
If vCli = "" Then Exit Sub
DoCmd.OpenForm "FClientes2", , , "[NomCli]='" & vCli & "'"
DoCmd.Close acForm, Me.Name
MsgBox "Se ha encontrado el cliente: " & vCli
End Sub

FILTRO POR CONSULTA


Como habíamos comentado al principio del capítulo sobre el tema de explicaros varias
sistemáticas sobre cómo conseguir un filtro, vamos a ver cómo podemos hacer lo mismo que
hemos visto hasta ahora pero utilizando una consulta. Los pasos que debemos seguir son los
siguientes:

1.- Creamos una consulta sobre la tabla TClientes2, y la llamamos CClientes2.

2.- Al grid de la consulta añadimos todos los campos (arrastrando el asterisco). A continuación
arrastramos el campo [IdCli] al grid, desmarcamos el check de “Mostrar” y en la línea de
criterios escribimos lo siguiente:

=[Forms]![FMenu].[cboBuscaCliente].[Value]

Como vemos, el filtro en este caso está situado en la propia consulta. Ni que decir tiene que
podríamos parametrizar la consulta, o añadirle campos calculados. Así, este sistema adquiere
un poquito más de “sentido” (si necesitáis “refrescar” el tema de las consultas podéis visitar
este link y seleccionar los enlaces de los artículos de consultas).

3.- Creamos un formulario basado en la consulta que acabamos de crear. Lo llamamos


FClientesXConsulta

4.- Volvemos a nuestro FMenu, creamos un nuevo botón de comando (cmdBuscarCliXConsulta)


y le generamos el siguiente coódigo:


Private Sub cmdBuscarCliXConsulta_Click()
If IsNull(Me.cboBuscaCliente.Value) Then Exit Sub
DoCmd.OpenForm "FClientesXConsulta", , , , acFormReadOnly
DoCmd.Close acForm, Me.Name
End Sub

6
Visítame en http://siliconproject.com.ar/neckkito/
La primera línea de código, evidentemente, es de control, para evitar que se ejecute el código
si no hay valor seleccionado en el combo. La segunda abre nuestro formulario en vista “Sólo
lectura”.

Un filtro más “al saco” ;)

FILTROS COMPUESTOS
Para poder explicar este punto vamos a necesitar crear una
nueva tabla (a lo rápido) con valores “adecuados”. Así, lo
que vamos a hacer es:

1.- Creamos una tabla, que llamaremos Tventas, con los siguientes campos:

2.- Rellenamos nuestra tabla con datos. Los que yo he introducido han sido los siguientes:

3.- Finalmente, creamos un formulario sobre la tabla TVentas, que llamaremos FVentas.

4.- En nuestro FMenu creamos un cuadro de texto, al que pondremos de nombre txtZona. En
su etiqueta escribimos “Introduzca zona (N,S,E,O)”

Vamos a aprovechar este ejemplo para practicar un poco el Select Case de una manera muy
simple.

5.- Creamos un cuadro combinado, que llamaremos cboVend, y lo configuraremos a través del
asistente, diciéndole que de la tabla TVentas queremos el valor del campo [Vend]

Como podréis ver Access automáticamente, en el origen de la fila, nos ha incluido la clave

7
Visítame en http://siliconproject.com.ar/neckkito/
principal de la tabla, aunque no se la hayamos seleccionado. En este caso utilizaremos el
propio nombre del vendedor. A continuación veremos cómo arreglar este “asuntillo”.

INCISO: ESE COMBO CON VALORES


REPETIDOS...
Si ahora, sin más, ponemos FMenu en vista formulario y
desplegamos nuestro cboVend veremos que hay valores
repetidos, lo cual es un poco “incordio”. Os explico muy
rápidamente cómo podemos solucionar eso:

1.- Sacamos las propiedades del combo y nos vamos a la pestaña Datos → Origen de la fila.
2.- Si nos situamos en el espacio en blanco que hay a su derecha . Veremos que nos aparece
un pequeño botón de puntos suspensivos. Hacemos click sobre él.
3.- Nos aparecerá la consulta origen de los datos. Vamos a operar sobre dicha consulta, de la
siguiente manera:

– Eliminamos la columna correspondiente al campo [Id]


– Convertimos la consulta en una consulta de totales, a través del botón correspondiente
de la cinta de opciones (es el botón con la sigma griega -Σ- )
– No hace falta que toquemos las opciones de agrupamiento.

Con lo anterior conseguimos que los valores repetidos aparezcan como únicos.

SIGAMOS....
6.- Con los cambios que hemos realizado en el origen de la fila del combo hemos alterado ese
combo. Para “reanimarlo” vamos a hacer lo siguiente: sacamos sus propiedades y:
– Pestaña Datos → Columna dependiente: 1
– Pestaña Formato → Número de columnas: 1
– Pestaña Formato → Ancho de columnas: eliminamos el 0cm; y dejamos el segundo valor
(en la BD de ejemplo: 2,542cm)

7.- Creamos un botón de comando (cmdFiltroComp), y le generamos el siguiente código:


Private Sub cmdFiltroComp_Click()
Dim vZona As String, vVend As String
Dim miFiltro As String
vZona = Nz(Me.txtZona.Value, "")
vVend = Nz(Me.cboVend.Value, "")
'Controlamos que txtZona y cboVend no estén vacíos
If vZona = "" Or vVend = "" Then Exit Sub
'Reconvertimos las iniciales en valores a través del Select Case
Select Case vZona
Case "N"
vZona = "Norte"
Case "S"
vZona = "Sur"
Case "E"
vZona = "Este"
Case "O"
vZona = "Oeste"
Case Else

8
Visítame en http://siliconproject.com.ar/neckkito/
MsgBox "El valor introducido para la zona no es
correcto", vbExclamation, "NO CORRECTO"
Exit Sub
End Select
'Construimos el filtro
miFiltro = "[Zona]='" & vZona & "'"
miFiltro = miFiltro & " AND [Vend]='" & vVend & "'"
'Cerramos FMenu
DoCmd.Close acForm, Me.Name
'Abrimos FVentas filtrado
DoCmd.OpenForm "FVentas", , , miFiltro,
acFormReadOnly
End Sub

Veamos los puntos a destacar de este código:

– Para controlar en una sola línea de código (If vZona = "" Or vVend = "" Then Exit Sub)
si se han introducido valores hemos utilizado un operador lógico: OR. De ahí que con esa línea
“matemos dos pájaros de un tiro”, si no nos interesa un mayor nivel de control.
– Hemos utilizado el Select Case de manera que también nos “mata dos pájaros de un
tiro”. Es decir, realiza una primera función de conversión (transforma las iniciales de las zonas
en sus nombres completos), pero también realiza una segunda función de control, a través del
CASE ELSE, que detecta si el usuario introduce un valor no permitido.
– Construimos el filtro por partes. Lógicamente se podría haber construido el filtro en una
sola línea, pero yo no lo he hecho así por varios motivos:
– Por motivos didácticos
– Para que podáis ver como vamos reutilizando la misma variable.
– Porque si utilizamos una sola línea el código puede llegar a ser difícil de leer, o
como mínimo incómodo, si la línea nos sale de la pantalla y debemos desplazarnos con
la barra de desplazamiento horizontal.
– Finalmente, vemos que si tenemos el filtro almacenado en una variable en la línea del
DoCmd.OpenForm, en el espacio reservado al argumento para el filtro, simplemente
escribimos directamente esa variable.

En el código he abierto el formulario como sólo lectura, pero evidentemente vosotros ya sabéis
cómo abrirlo para editarlo, por ejemplo.

FILTRO PARAMETRIZADO
Puede ser que en algún momento necesitemos un parámetro que deba ser rellenado por el
usuario. Como ya sabemos (o si no lo sabemos lo aprenderemos ahora) un parámetro viene
configurado por la siguiente secuencia de caracteres:

Corchete de apertura-Comillas dobles-Mensaje-Comillas dobles-Corchete de cierre

Si hemos seguido los valores de la BD de ejemplo veremos que hay un registro que nos dice
que el vendedor Hulk, en la zona Norte, en Bilbao, ha vendido por 3.000 euros. Vamos a
añadir un nuevo registro en TVentas de manera que:

Hulk, en la zona Norte, en Andorra, ha vendido por 1.500 euros

E imaginemos que queremos saber en qué ciudades ha vendido Hulk por encima de los 2.000
euros.

Una manera de hacerlo sería introducir un InputBox, pedir la información del importe de venta

9
Visítame en http://siliconproject.com.ar/neckkito/
límite al usuario y añadir ese valor al filtro. Como en teoría esto ya lo deberíamos saber hacer
(os animo a que lo probéis, a ver si os sale, pero después de leer el resto de este punto por un
detalle que explicaré más adelante).

Otra manera sería construirnos y utilizar un parámetro.


Para ello vamos a seguir el siguiente proceso:

– Creamos uno a uno los elementos del parámetro


– Añadimos de manera correcta el parámetro a
nuestro filtro.

Eso es la parte teórica. La aplicación práctica sería así:

1.- Creamos, en FMenu, un nuevo botón de comando (cmdFiltroCompParam), y le generamos


el siguiente código, muy parecido al anterior:


Private Sub cmdFiltroCompParam_Click()
Dim vZona As String, vVend As String
Dim miFiltro As String
vZona = Nz(Me.txtZona.Value, "")
vVend = Nz(Me.cboVend.Value, "")
'Controlamos que txtZona y cboVend no estén vacíos
If vZona = "" Or vVend = "" Then Exit Sub
'Reconvertimos las iniciales en valores a través del Select Case
Select Case vZona
Case "N"
vZona = "Norte"
Case "S"
vZona = "Sur"
Case "E"
vZona = "Este"
Case "O"
vZona = "Oeste"
Case Else
MsgBox "El valor introducido para la zona no es correcto", vbExclamation, "NO
CORRECTO"
Exit Sub
End Select
'Construimos el filtro
miFiltro = "[Zona]='" & vZona & "'"
miFiltro = miFiltro & " AND [Vend]='" & vVend & "'"
miFiltro = miFiltro & " AND [ImpVta]>=" & "[" & "¿Importe mayor que:?" & "]"
'Cerramos FMenu
DoCmd.Close acForm, Me.Name
'Abrimos FVentas filtrado
DoCmd.OpenForm "FVentas", acFormDS, , miFiltro, acFormReadOnly
End Sub

Como vemos en la primera línea marcada en negrita, hemos añadido una tercera línea para la
construcción del filtro y hemos construido un parámetro para que al usuario le salga una casilla
con la “pregunta del millón”.

Fijaos también que:


– Los filtros con variables tipo String los hemos encerrado entre comillas simples.

10
Visítame en http://siliconproject.com.ar/neckkito/
– En el filtro del parámetro, que representa un valor
de tipo Currency (y lo mismo para variables de tipo
numérico), no hemos encerrado el valor entre comillas, sino
que lo hemos añadido “directamente”. Debéis tenerlo en
cuenta por lo que os proponía de utilizar un InputBox y
aplicar sobre la variable que recoja el valor introducido el
filtro
– Estamos uniendo los filtros a través de AND. Tened
en cuenta que no hay problema, si nos interesa, en utilizar
el OR, o una combinación de los dos operadores lógicos.
– Os he marcado la última línea como recordatorio de
algo que ya sabemos: podemos manipular la vista del
formulario desde el código. En este caso lo hemos abierto
en vista hoja de datos (acFormDS).

Si el usuario cancela la introducción del parámetro nos saltará un error. En un capítulo


posterior veremos cómo controlar estos errores.

UN POCO MÁS SOBRE FILTROS COMPUESTOS


Vamos a complicar nuestro ejemplo. De hecho, vamos a “jugar” a configurar combinaciones de
filtros.

Vamos a añadir en FMenu un cuadro de texto, al que llamaremos txtImport, donde


introduciremos (si queremos) el importe de la venta que será nuestro límite inferior (es decir,
que filtraremos por mayor o igual que)

En el punto anterior hemos visto como era necesario rellenar el campo zona y el campo
vendedor para que actuara el código. Ahora vamos a ver cómo podemos crear filtros si
rellenamos todos los campos, sólo algunos o los dejamos en blanco.

Vamos a intentar explicarlo un poco en abstracto, primero, para entender la mecánica de


funcionamiento. Después veremos cómo aplicarlo en el código. El truco está en el AND que une
los criterios de filtro. Si establecemos una prelación de orden de campos, el AND nos aparecerá
en función de si hay valor en el campo previo.

Sé que esto es un poco “lioso”, pero con el código lo veremos más claro (¡espero!). Pondré
muchos comentarios en el código para que podáis entender el proceso:

Vámonos pues...

1.- Creamos un nuevo botón de comando (cmdFiltro3Controles). Le generamos el siguiente


código:

Private Sub cmdFiltro3Controles_Click()
Dim vZona As String, vVend As String
Dim vImport As Currency
Dim miFiltro As String
vZona = Nz(Me.txtZona.Value, "")
vVend = Nz(Me.cboVend.Value, "")
vImport = Nz(Me.txtImport.Value, -1)

'El primer filtro es fácil. Si no hay valor en ninguno de los controles abrimos el formulario
sin filtrar
If vZona = "" And vVend = "" And vImport = -1 Then
DoCmd.Close acForm, Me.Name

11
Visítame en http://siliconproject.com.ar/neckkito/
DoCmd.OpenForm "FVentas", acFormDS, , , acFormReadOnly
Exit Sub
End If

'Inicializamos el filtro
miFiltro = ""

'Establezcamos un orden de prelación: 1-Zona / 2-


Vendedor / 3-Importe
'Analizamos primero la zona
If vZona <> "" Then
Select Case vZona
Case "N"
vZona = "Norte"
Case "S"
vZona = "Sur"
Case "E"
vZona = "Este"
Case "O"
vZona = "Oeste"
Case Else
MsgBox "El valor introducido para la zona no es correcto", vbExclamation, "NO
CORRECTO"
Exit Sub
End Select
'Creamos la primera parte del filtro
miFiltro = "[Zona]='" & vZona & "'"
End If

'Analizamos el segundo elemento: Vendedor, teniendo en cuenta que:


'Si vZona está lleno el filtro Vendedor deberá llevar AND
'Si vZona está vacío el filtro Vendedor no llevará AND
If vVend <> "" Then
If vZona <> "" Then
'Creamos el filtro para este supuesto: hay valor en zona AND hay valor en vendedor
miFiltro = miFiltro & " AND [Vend]='" & vVend & "'"
Else
'Creamos el filtro para este supuesto: no hay valor en zona pero sí valor en
vendedor
miFiltro = "[Vend]='" & vVend & "'"
End If
End If

'Analizamos el tercer elemento: Importe, teniendo en cuenta que debemos analizar:


'Si vVend está lleno el filtro del importe necesita AND
'Si vVend no está lleno debemos mirar si vZona está lleno. Si lo está debe llevar AND
'Si vImport es el único control relleno no debe llevar AND
If vImport >= 0 Then
'Hay valor en el control vendedor
If vVend <> "" Then
miFiltro = miFiltro & " AND [ImpVta]>=" & txtImport
Else
'No hay valor en el control vendedor. Miramos si hay valor en zona
If vZona <> "" Then
'Hay valor en zona
miFiltro = miFiltro & " AND [ImpVta]>=" & txtImport

12
Visítame en http://siliconproject.com.ar/neckkito/
Else
'No hay valor en zona
miFiltro = miFiltro & "[ImpVta]>=" & txtImport
End If
End If
End If

'Tened en cuenta que si no hay valor en ninguno de


los controles no hemos modificado el filtro,
'por lo que el valor del filtro es el mismo con que lo
hemos inicializado, es decir, una cadena vacía

'Cerramos el formulario FMenu


DoCmd.Close acForm, Me.Name
'Abrimos el formulario FVentas aplicando el filtro
DoCmd.OpenForm "FVentas", acFormDS, , miFiltro, acFormReadOnly
End Sub

Bueno... creo que tendréis un buen rato para analizar este código. Como siempre, la idea es
que cojáis la mecánica para que podáis aplicarla en vuestras BD's... ¡si el dolor de cabeza os lo
permite! Je, je...

Bromas aparte, el código está comentado, por lo que no “añadiré más leña” a este punto. Sólo
recalcar, si me permitís, que la variable vImport coge su valor a través de la función NZ, que
en caso de no tener valor devuelve -1. Recordad que como hemos definido vImport como
Currency el valor “si nulo” debe ser compatible con este tipo de datos.

FILTRO EN EL PROPIO FORMULARIO

A TRAVÉS DE UN “FILTERON”
Ahora vamos a ver cómo utilizar un filtro en el propio formulario. Vamos a preparar el terreno:

1.- Copiamos nuestro formulario FVentas y lo pegamos como FVentas2.

2.- Para ver mejor los efectos vamos a cambiar la vista predeterminada de FVentas2. Sacamos
sus propiedades y nos vamos a la pestaña Formato → Vista predeterminada → Formularios
continuos.

3.- En la cabecera del formulario añadimos un cuadro de texto, que llamaremos txtFiltro. Ahí
escribiremos el nombre del vendedor sobre el que queremos ver los registros.

4.- Añadimos también un botón de comando, que llamaremos cmdEjecutaFiltro.

Muy bien. Vamos a ver cómo programamos nuestro botón. El código sería el siguiente.


Private Sub cmdEjecutaFiltro_Click()
Dim vVend As String, miFiltro As String
vVend = Nz(Me.txtFiltro.Value, "")
If vVend = "" Then Exit Sub
'Construimos el filtro
miFiltro = "[Vend]='" & vVend & "'"
'Aplicamos el filtro al formulario

13
Visítame en http://siliconproject.com.ar/neckkito/
Me.Filter = miFiltro
'Activamos el filtro
Me.FilterOn = True
End Sub

Como vemos, el proceso se realiza en tres etapas:

1.- Construimos el filtro


2.- Aplicamos el filtro al formulario a través de Me.Filter
3.- Activamos el filtro a través de Me.FilterOn = True

¿Fácil, verdad?

Si ahora añadimos otro botón de comando (cmdQuitaFiltro) podemos generar el siguiente


código para desactivar el filtro:


Private Sub cmdQuitaFiltro_Click()
Me.txtFiltro.Value = Null
Me.FilterOn = False
End Sub

UTILIZACIÓN DEL “LIKE”


¿Qué ocurriría si queremos buscar un valor del que sólo conocemos una parte del nombre?
Pues para ello tenemos una palabrita “mágica”, que es LIKE.

El LIKE nos permite buscar “partes” en los valores de los registros, y nos muestra las
coincidencias. Debemos combinarlo con caracteres comodín, de manera que utilizaremos:

– El asterisco (*) para cualquier cadena de caracteres, incluyendo cadena vacía.


– El interrogante de cierre (?) para un carácter cualquiera
– La almohadilla (#) para un dígito cualquiera.

Vamos a ver cómo aplicarlo a nuestra BD: en un nuevo botón de comando (cmdLike), en
FVentas2, generamos el siguiente código:


Private Sub cmdLike_Click()
Dim vVend As String, miFiltro As String
vVend = Nz(Me.txtFiltro.Value, "")
If vVend = "" Then Exit Sub
'Construimos el filtro
miFiltro = "[Vend] LIKE '*" & vVend & "*'"
'Aplicamos el filtro al formulario
Me.Filter = miFiltro
'Activamos el filtro
Me.FilterOn = True
End Sub

Como vemos, hemos creado un filtro que, a través de los asteriscos como caracteres comodín,
nos busca a comienzo, en medio y a final del valor. Jugando con esos comodines podemos
indicarle que:

14
Visítame en http://siliconproject.com.ar/neckkito/
– Queremos que coincida el inicio del valor: LIKE xxx*
– Queremos que coincida el final del valor: LIKE *xxx

Tened también cuidado, si la variable es String, de utilizar


correctamente las comillas simples.

Finalmente, y como comentario general, pensad que


podríamos haber utilizado el LIKE en cualquiera de los
ejemplos de código de este capítulo (es decir, no es
exclusivo del FilterOn).

A TRAVÉS DE UN “CLON” DEL RECORDSET DEL FORMULARIO


No vamos a entrar en este capítulo con una explicación de lo que es un recordset. Dejémoslo,
como idea a nuestros efectos, que en nuestro caso el recordset representa el conjunto de
registros sobre los que se basa nuestro formulario; en definitiva, los registros de la tabla
TVentas.

Vamos a suponer que queremos encontrar la ficha de un cliente a través de un combo en el


propio formulario. Lo que vamos a hacer va a ser lo siguiente:

1.- Vamos a copiar nuestro formulario FClientes2 y lo vamos a pegar como FClientes3

2.- En la cabecera de FClientes3 añadimos un cuadro combinado. Le decimos que queremos,


de la tabla TClientes2, el nombre del cliente, ocultamos la clave principal y queremos recordar
el valor para utilizarlo más adelante.

3.- A ese combo le ponemos de nombre cboFiltraCli

4.- En el evento “Después de actualizar” le generamos el siguiente código:


Private Sub cboFiltraCli_AfterUpdate()
Dim vCli As Long
Dim miFiltro As String
Dim rst As Recordset
vCli = Nz(Me.cboFiltraCli.Value, 0)
If vCli = 0 Then Exit Sub
'Construimos el filtro
miFiltro = "[IdCli]=" & vCli
'Clonamos el recordset del formulario
Set rst = Me.Recordset.Clone
'Buscamos el valor del filtro. Eso nos dará un marcador en el recordset
rst.FindFirst (miFiltro)
'Pasamos el marcador del recordset como marcador del formulario
Me.Bookmark = rst.Bookmark
End Sub

El “asunto” está en que un recordset permite una flexibilidad amplísima a la hora de ser
manipulado, de ahí que nos sirvamos del mismo. Debemos considerar, además que:

– Este sistema nos hace las veces de filtro porque estamos operando sobre un valor
único; esto es, el campo clave.
– Si el valor no fuera único (es decir, se pudiera repetir) lo que haría este código es

15
Visítame en http://siliconproject.com.ar/neckkito/
llevarnos al primer registro coincidente (a través del FindFirst)
– Como hemos operado a través de un combo con
todos los valores a buscar posibles, y que además no
permite ediciones ni adiciones de valores, el anterior código
nos funcionará “de perlas”. Pero, ¿qué pasaría si no
existiera el valor buscado?

NUESTRO CLON NO ENCUENTRA EL VALOR


Vamos a abrir nuestro FClientes3 en vista diseño y vamos a añadir
un cuadro de texto, que llamaremos txtBusca.

Sacamos sus propiedades y nos vamos a la pestaña Eventos → Después de actualizar, y


generamos el siguiente evento2:


Private Sub txtBusca_AfterUpdate()
Dim vCli As String
Dim miFiltro As String
Dim rst As Recordset
vCli = Nz(Me.txtBusca.Value, "")
If vCli = "" Then Exit Sub
'Construimos el filtro
miFiltro = "[NomCli]='" & vCli & "'"
'Clonamos el recordset del formulario
Set rst = Me.Recordset.Clone
'Buscamos el valor del filtro. Eso nos dará un marcador en el recordset
rst.FindFirst (miFiltro)
'Pasamos el marcador del recordset como marcador del formulario
Me.Bookmark = rst.Bookmark
If rst.NoMatch Then
MsgBox "¡El cliente introducido no existe!", vbExclamation, "SIN DATOS"
End If
End Sub

Como vemos, el código es prácticamente similar al anterior (adaptándolo al hecho de que


ahora buscamos un String), pero le hemos añadido un IF de control por si el valor no existiera.
Y esto el código lo detecta a través de rst.NoMatch

MÁS ALLÁ DE LOS FILTROS: CONTROLES Y LA PROPIEDAD


ROWSOURCE
Hasta ahora hemos visto filtros que afectaban a formularios e informes. Sin embargo, existen
controles que poseen la propiedad Rowsource (origen de la fila) que también pueden ser
manipulables a través de código, en el cual los filtros pueden tener influencia. Lógicamente
aprovecharemos la coyuntura para realizar un análisis un poco más exhaustivo de estos
controles, que irá un poco más allá de los “filtros puros y duros”.

Nos centraremos en los cuadros combinados, aunque los cuadros de lista son prácticamente
iguales.

2 Si tenemos algún problema con el funcionamiento del código debemos comprobar que tengamos registrada la referencia “Microsoft
DAO 3.6 Object Library”

16
Visítame en http://siliconproject.com.ar/neckkito/
TIPOS DE ROWSOURCE

Podemos encontrarnos con tres tipos de origen de la fila:

– Datos que provienen de una tabla o de una consulta


– Datos que provienen de una lista de valores
– Datos que provienen de una lista de campos

Vamos a verlos, pero antes, en nuestra BD, vamos a crear


un formulario en blanco, al que llamaremos FControles. Ese
formulario nos servirá para ir aplicando nuestros
“conocimientos”.

DATOS “LISTA DE VALORES”


La lista de valores son una serie de valores independientes que nosotros definimos en el
combo. Vamos a ver cómo podemos hacerlo:

1.- En FControles creamos un cuadro combinado, al que pondremos por nombre cboValores.
Cuando nos salga el asistente lo cancelamos.
2.- Sacamos las propiedades de ese combo y nos vamos a la pestaña Datos → Tipo de origen
de la fila. Ahí seleccionamos “Lista de valores”

3.- En la misma pestaña → Origen de la fila, podremos escribir los valores que queremos que
muestre el combo. Esos valores deben ir separados por punto y coma (;). Por ejemplo,
escribimos ahí: Lunes; Martes; Miércoles.

4.- Si ahora ponemos nuestro formulario en vista formulario veremos que nuestro combo nos
muestra los valores que hemos introducido.

5.- Si volvemos a la vista diseño, comprobamos que las propiedades “Limitar a la lista” está
fijada en SÍ.

Vamos a ver cómo podemos ir ampliando, a través de código, nuestra lista de valores. Para ello
nos vamos a la Pestaña Eventos → Al no estar en la lista, y generamos el siguiente código:


Private Sub cboValores_NotInList(NewData As String, Response As Integer)
Dim resp As Integer
Dim miRowSce As String
'Como el propio evento detectará que no está en la lista aprovechamos
'el mismo para lanzar un mensaje de advertencia y pedir al usuario si
'desea añadirlo
resp = MsgBox("El valor introducido no está en la lista. ¿Desea añadirlo?", _
vbQuestion + vbYesNo, "VALOR NUEVO")
'Si la respuesta es NO asignamos valor al argumento RESPONSE
'y salimos del proceso
If resp = vbNo Then
Response = acDataErrContinue
Exit Sub
Else
'Si la respuesta es SÍ lo añadimos...
'Asignamos valor al argumento RESPONSE
Response = acDataErrAdded
'Cogemos el RowSource actual del combo
miRowSce = Me.cboValores.RowSource

17
Visítame en http://siliconproject.com.ar/neckkito/
'Añadimos el nuevo elemento
miRowSce = miRowSce & ";" & NewData
'Aplicamos el nuevo rowsource al combo
Me.cboValores.RowSource = miRowSce
End If
End Sub

Veamos:

.- El evento “Not in list” tiene dos argumentos: NewData y


Response. NewData recoge el valor introducido (y que no está en
la lista). Response espera que el usuario le indique qué debe hacer
con el nuevo valor. Al producirse el evento debemos asignar un
valor a estos dos elementos en función de lo que decidamos.

.- Para saber qué quiere hacer el usuario creamos la variable resp, que recoge dicha
contestación a través del botón que pulse el usuario en un MsgBox.

.- Si el usuario responde que no debemos indicarle al código que Response coge el valor de
una constante de VB, que es acDataErrContinue, para que inmediatamente le digamos que no
haga nada y que salga del proceso.

.- Si el usuario responde que SÍ debemos decirle que Response cogerá el valor, a través de una
constante, que es acDataErrAdded.

.- A continuación manejamos el Rowsource con una secuencia que, como vemos en el código,
es lógica:
.- Cogemos el RowSource actual
.- Lo modificamos, añadiéndole el nuevo valor (que, no olvidemos, está recogido ya
directamente por el argumento del evento NewData)
.- Asignamos el nuevo RowSource modificado a nuestro combo.

Debemos tener en cuenta que estas adiciones de valores se realizan en tiempo de ejecución;
es decir, se añaden sólo en la sesión de trabajo. Si cerramos el formulario y lo volvemos a abrir
los valores del RowSource volverán a ser los originales (de lunes a miércoles).

Evidentemente podríamos programar el evento “Después de actualizar”, por ejemplo, para


ejecutar las acciones que necesitemos con el valor introducido. Por ejemplo, para ver los
resultados, programamos el siguiente código:


Private Sub cboValores_AfterUpdate()
MsgBox "El valor seleccionado ha sido " & Me.cboValores.Value, vbInformation, "MENSAJE"
End Sub

¿Y si queremos que estas adiciones sean permanentes? En ese caso los valores deberán estar
guardados en una tabla...

DATOS “TABLA/CONSULTA”
1.- Vamos a crear una tabla, que llamaremos TDias, con dos campos:
– [Dia] de tipo texto. No ponemos ninguna clave principal.
– [Festivo] de tipo Sí/No, con el valor por defecto cero (0) o False

18
Visítame en http://siliconproject.com.ar/neckkito/
En ella introducimos tres registros con nuestros tres días: Lunes, Martes y Miércoles.

2.- En FControles añadimos un nuevo combo, que


llamaremos cboTDias. Cuando nos salga el asistente le
decimos que queremos, de la tabla TDias, el campo [Dia]

3.- Sacamos las propiedades del combo y en la pestaña


Datos → Limitar a la lista, le decimos que SÍ.

Vamos a ver cómo podemos añadir días que no estén en la


lista. Para ello utilizaremos una SQL de datos anexados 3,
fácil de entender aunque, a simple vista, parezca difícil de
construir.

Así pues, en la pestaña Eventos → Al no estar en la lista, le generamos el siguiente código:


Private Sub cboTDias_NotInList(NewData As String, Response As Integer)
Dim resp As Integer
Dim miSql As String
Dim diaNuevo As String
'Como el propio evento detectará que no está en la lista aprovechamos
'el mismo para lanzar un mensaje de advertencia y pedir al usuario si
'desea añadirlo
resp = MsgBox("El valor introducido no está en la lista. ¿Desea añadirlo?", _
vbQuestion + vbYesNo, "VALOR NUEVO")
'Si la respuesta es NO asignamos valor al argumento RESPONSE
'y salimos del proceso
If resp = vbNo Then
Response = acDataErrContinue
Exit Sub
Else
'Si la respuesta es SÍ lo añadimos...
'Asignamos valor al argumento RESPONSE y a la variable diaNuevo
Response = acDataErrAdded
diaNuevo = NewData
'Creamos la SQL
miSql = "INSERT INTO TDias(Dia) SELECT " & """" & diaNuevo & """" & " AS NuevoDia"
'Desactivamos los Warnings
DoCmd.SetWarnings False
'Ejecutamos la consulta
DoCmd.RunSQL (miSql)
'Activamos los Warnings
DoCmd.SetWarnings True
End If
End Sub

Vamos a fijarnos en la parte final del código, cuando el usuario pulsa el botón SÍ:

.- Pasamos el valor de NewData a una variable: diaNuevo


.- Creamos la SQL de datos anexados. Como necesitamos que diaNuevo nos aparezca entre
comillas necesitamos añadir las cuatro comillas delante y detrás de la variable.
.- Si no desactiváramos los Warnings nos saldría eso de “Va a anexar 1 fila a...”. Como sería
muy molesto recibir cada vez dicho mensaje lo desactivamos.

3 Cuando veamos el capítulo dedicado al Recordset veremos cómo incluir valores a través de un recordset

19
Visítame en http://siliconproject.com.ar/neckkito/
.- Ejecutamos la SQL
.- Volvemos a activar los Warnings. Es importante volverlos
a activar porque la desactivación no sólo afecta al código
que estamos programando, sino a todos los códigos. Si se
produjera pues “algo raro” en nuestra BD no nos saldrían
los mensajes de advertencia, con lo que perderíamos
información (muy importante a veces) para saber qué está
pasando.

Como veis, además de aprender cómo añadir un valor en la


tabla podemos “apuntarnos” un par de cosas útiles para
nuestros códigos:

– Activar y desactivar avisos: DoCmd.SetWarnings True/False


– Ejecutar una SQL: DoCmd.RunSql (nombreSql)

MODIFICAR EL ORIGEN SI EL TIPO DE DATOS ES DE “TABLA O CONSULTA”


Vamos (si no lo hemos hecho ya) a introducir dos registros directamente en nuestra tabla
TDias. Introduciremos:

– Sábado → Marcaremos el check de festivo


– Domingo → Marcaremos el check de festivo.

Ahora lo que vamos a hacer es introducir en nuestro formulario un marco de opciones, con dos
opciones (que llamaremos mrcFestivos): la opción 1, que serán los días festivos, y la opción 2,
que serán los días no festivos. Por si alguien no tiene claro cómo se hace eso lo explico paso a
paso:

1.- Con FControles en vista diseño, añadimos un marco de opciones. Nos saldrá el asistente. Lo
configuramos de la siguiente manera:

– Nombres de las etiquetas: en la primera línea escribimos Mostrar festivos; en la


segunda línea escribimos Mostrar hábiles
– Podemos seleccionar la opción predeterminada a nuestro gusto. Para el ejemplo yo he
dicho que no quería opción predeterminada.
– Dejamos los valores que salen por defecto, 1 y 2
– Configuramos el diseño del marco a nuestro gusto
– Como título ponemos, por ejemplo, “Filtrar por:”

2.- No olvidemos cambiar el nombre a nuestro marco (Propiedades → Pestaña Otras →


Nombre)

3.- En sus propiedades nos vamos a la pestaña Eventos → Después de actualizar, y generamos
el siguiente código:


Private Sub mrcFestivos_AfterUpdate()
Dim vOpc As Integer
Dim miRowSce As String
vOpc = Me.mrcFestivos.Value
'Generamos la parte común de miRowSce, sea cual sea la opción seleccionada
miRowSce = "SELECT TDias.Dia FROM TDias"
'Analizamos la opción seleccionada
If vOpc = 1 Then
'Añadimos el filtro que nos interesa para filtrar por festivos

20
Visítame en http://siliconproject.com.ar/neckkito/
miRowSce = miRowSce & " WHERE [Festivo]=TRUE"
Else
'Configuramos el filtro para filtrar por no festivos
miRowSce = miRowSce & " WHERE [Festivo]=FALSE"
End If
With Me.cboTDias
'Asignamos el nuevo rowsource a nuestro combo
cbo TDias
.RowSource = miRowSce
'Refrescamos el combo
.Requery
End With
End Sub

Vaya, vaya... vuelve a aparecer nuestro amigo el filtro, en este caso no para filtrar los datos
que mostrará un formulario sino ahora para filtrar los datos que mostrará un combo.

El código está comentado, y con todo lo que hemos aprendido hasta ahora no deberíamos
tener ningún problema para entenderlo (y aplicarlo a nuestras BD's... je, je...).

DATOS “LISTA DE CAMPOS”


Finalmente vamos a ver la última opción, que es una lista de campos. Para ello, vamos a
practicar lo anterior de la siguiente manera:

1.- En FControles creamos un cuadro combinado, que llamaremos cboCampos. Cancelamos el


asistente.

2.- Creamos un cuadro de lista, que llamaremos lstDatos. Cancelamos el asistente.

3.- Sacamos las propiedades de cboCampos y nos vamos a la pestaña Datos → Tipo de origen
de la fila. Seleccionamos la opción “Lista de campos”.

4.- Ahora, si nos situamos en la propiedad “Origen de la fila”, nos saldrá una lista con las
tablas y consultas que tengamos en nuestra BD. Nosotros seleccionamos, por ejemplo, la tabla
TVentas.

5.- Si situamos FControles en vista Formulario y desplegamos ese combo veremos que nos
muestra los campos de la tabla TVentas.

Una idea sería prepararnos una consulta con los campos que nos pudieran interesar, sin
mostrar campos que “no nos sirven para nada”. Para no hacer tan largo el ejemplo lo
dejaremos así como está, directamente de la tabla TVentas.

Vamos a ver qué podemos hacer con eso.

6.- De nuestro cboCampos, en el evento “Después de actualizar”, generamos el siguiente


evento:


Private Sub cboCampos_AfterUpdate()
Dim vCampo As String, miRowSce As String
vCampo = Me.cboCampos.Value
If vCampo = "" Then
'Si no hay valor en el combo no hay valores en la lista

21
Visítame en http://siliconproject.com.ar/neckkito/
Me.lstDatos.RowSource = ""
Else
'Si hay valor en el combo el cuadro de lista nos mostrará los
'valores del campo seleccionado
miRowSce = "SELECT TVentas." & vCampo & " FROM
TVentas"
'Aplicamos el rowsource al cuadro de lista
Me.lstDatos.RowSource = miRowSce
End If
'Refrescamos el cuadro de lista
Me.lstDatos.Requery
End Sub

Creo que, a estas alturas, no hace falta explicar el código anterior. Como vemos, la mecánica,
si se trata de un cuadro de lista, es prácticamente igual a como hemos tratado los combos. De
hecho, este ejemplo combina ambos tipos de controles.

LOS INFORMES Y LA PROPIEDAD RECORDSOURCE


Vamos a hacer unos cambios en la BD, y después entraremos en la explicación:

1.- Copiamos nuestra tabla TVentas (estructura y datos) y la pegamos con el nombre de
TVentasHistorico.

2.- Abrimos la tabla TVentasHistorico y modificamos algunos registros (simplemente para


poder apreciar la diferencia entre una tabla y otra).

3.- Creamos un informe sobre la tabla TVentas, y lo llamamos RVentas.

Bueno... pues al crear ese informe sobre TVentas lo que estamos haciendo, aunque no lo
sepamos, es indicar al informe que su Recordsource es TVentas.

La “gracia” de “toquetear” el recordsource de un informe es que sólo lo podemos hacer en


vista diseño. ¿Podríamos pues modificarlo a través de código?

La respuesta a esta vital pregunta tendrá que esperar, puesto que os voy a decir para qué
hemos hecho todo lo anterior. Imaginemos que tenemos una tabla que nos recoge las ventas
actuales (TVentas) y las ventas de años pasados las vamos guardando en otra tabla
(TVentasHistorico). De vez en cuando necesitamos sacar un informe de ventas histórico. La
pregunta del millón es: ¿necesitamos crear dos informes?

Y la respuesta es... no. ¿Por qué? Porque sí podemos actuar sobre el recordsource del informe,
aunque deba configurarse en vista diseño. Con esto contestamos a nuestra primera pregunta
de dos párrafos más arriba.

Vamos a ver cómo podemos hacerlo:

1.- En nuestro formulario FMenu añadimos un botón de comando, que llamaremos


cmdAbreRVentas.

2.- Junto a ese botón insertaremos una casilla de verificación, que llamaremos chkHist.

3.- Sacamos las propiedades de chkHist y nos vamos a la pestaña Datos → Valor
predeterminado, y le escribimos FALSE

22
Visítame en http://siliconproject.com.ar/neckkito/
4.- Ahora a nuestro botón de comando le generamos el siguiente código:


Private Sub cmdAbreRVentas_Click()
Dim vOpc As Boolean
Dim miRcdSce As String
'Miramos el valor del botón de opción
vOpc = Me.chkHist.Value
'Construimos la parte común del recordsource
miRcdSce = "SELECT * FROM"
'Actuamos según esté marcado o no el botón de opción
If vOpc = True Then
'Construimos la parte específica del recordsource
miRcdSce = miRcdSce & " TVentasHistorico"
Else
miRcdSce = miRcdSce & " TVentas"
End If
'Abrimos el informe en vista diseño y oculto
DoCmd.OpenReport "RVentas", acViewDesign, , , acHidden
'Cambiamos su recordsource
Reports!RVentas.RecordSource = miRcdSce
'Cerramos el informe guardando los cambios (sin pedírselo al usuario)
DoCmd.Close acReport, "RVentas", acSaveYes
'Abrimos el informe en vista preliminar
DoCmd.OpenReport "RVentas", acViewPreview
End Sub

El código está comentado paso a paso y poco queda por comentar, exceptuando que, os
recuerdo, así como hemos construido un recordsource con todos los campos también
podríamos haber aplicado filtros para “personalizar” aún más el informe (¿con un marco de
opciones, quizás?).

LO MISMO, PERO CON UN FORMULARIO


Os animo a que probéis a construir un código en un botón de comando
(cmdAbreFVentasRcdSce), aprovechando el check que acabamos de construir, para abrir el
formulario FVentas2. Yo os indico a continuación cómo seria el código (comentado), pero creo
que sería un buen ejercicio intentar hacerlo por vosotros mismos.

Pues lo dicho; el código sería:



Private Sub cmdAbreFVentasRcdSce_Click()
Dim vOpc As Boolean
Dim miRcdSce As String
'Miramos el valor del botón de opción
vOpc = Me.chkHist.Value
'Construimos la parte común del recordsource
miRcdSce = "SELECT * FROM"
'Actuamos según esté marcado o no el botón de opción
If vOpc = True Then
'Construimos la parte específica del recordsource
miRcdSce = miRcdSce & " TVentasHistorico"
Else
miRcdSce = miRcdSce & " TVentas"
End If

23
Visítame en http://siliconproject.com.ar/neckkito/
'Abrimos el formulario en vista diseño y oculto
DoCmd.OpenForm "FVentas2", acViewDesign, , , acHidden
'Cambiamos su recordsource
Forms!FVentas2.RecordSource = miRcdSce
'Cerramos el formulario guardando los cambios (sin
pedírselo al usuario)
DoCmd.Close acForm, "FVentas2", acSaveYes
'Cerramos FMenu
DoCmd.Close acForm, Me.Name
'Abrimos el formuario en vista “sólo lectura”
DoCmd.OpenForm "FVentas2", , , , acFormReadOnly
End Sub

UNAS PALABRAS FINALES


En este capítulo hemos visto cómo construir filtros de diversas maneras, con diferentes
técnicas, en diferentes lugares, y también hemos visto cómo podíamos cambiar los rowsource
de algunos controles para acabar viendo cómo modificar el recordsource de un informe
(extensible a un recordsource de un formulario).

Con un poco de imaginación podemos personalizar nuestros formularios, controles (combos y


listas) y nuestros informes de manera que muestren, cada vez, la información que
necesitemos, en vez de crearnos controles y más controles, o informes y más informes, uno
para cada cosa. Con ello nuestra BD saldrá ganando en tamaño y en “estilo”.

Espero que lo aprendido aquí os sea útil.

¡Suerte!

24
Visítame en http://siliconproject.com.ar/neckkito/
CURSO DE VB

CAPÍTULO 81

Índice de contenido
TRABAJEMOS CON FECHAS, FORMATOS Y CONVERSIONES DE DATOS............................3
COGER LA FECHA DEL SISTEMA (Date)..................................................................................3
APLICAR FORMATOS A LA FECHA..........................................................................................3
Formato “Short Date”.................................................................................................................3
Formato “Long Date”..................................................................................................................4
Formato personalizado ...............................................................................................................4
APLICAR FORMATOS A LA HORA (Time)................................................................................5
Obtener la hora del sistema.........................................................................................................5
Manipular la hora........................................................................................................................5
FECHA Y HORA A LA VEZ (Now)...............................................................................................6
FORMATO A CADENAS DE CARACTERES..............................................................................6
Valores a mayúsculas (UCase)....................................................................................................6
Propiedad InputMask..................................................................................................................6
Valores a minúsculas (LCase).....................................................................................................7
La función StrConv.....................................................................................................................7
Inicio de palabras en mayúsculas (vbProperCase)......................................................................8
Eliminación de espacios en blanco (Trim, LTrim, RTrim)..........................................................8
CONVERSIONES DE DATOS.......................................................................................................9
Funciones conversoras................................................................................................................9
Detección tipo de dato.................................................................................................................9
Unos cuantos ejemplos prácticos sobre funciones conversoras y tipos de datos........................9
Supuesto 1: aprendemos las funciones LEFT y RIGHT........................................................9
Supuesto 2: aprendemos la función MID.............................................................................10
Supuesto 3: aprendemos la función INSTR y la función LEN............................................11
Supuesto 4: aprendemos la función REPLACE...................................................................13
Supuesto 5: practicamos con IsNumeric()............................................................................14
SEGUIMOS MANIPULANDO FECHAS....................................................................................14
Analizar el día de una fecha: función Weekday().....................................................................14
Obtener los elementos de una fecha: funciones Day(), Month(), Year()...................................16
Ver el nombre del mes de una fecha: función MonthName()...................................................16
La función DatePart()................................................................................................................17
La función DateDiff()...............................................................................................................18
Sumar días a una fecha..............................................................................................................18
Pequeño ejercicio práctico........................................................................................................19

1 La BD donde están los ejemplos de este capítulo os la podéis bajar aquí.

1
Visítame en http://siliconproject.com.ar/neckkito/
UN EJEMPLO FICTICIO... REAL COMO LA VIDA MISMA...................................................19
PARA ACABAR.................................................................................................................................22

2
Visítame en http://siliconproject.com.ar/neckkito/
TRABAJEMOS CON FECHAS, FORMATOS
Y CONVERSIONES DE DATOS
Una de las mayores dificultades de trabajar con fechas es,
precisamente, el formato regional, y las diferentes
versiones de Access (que si versión española, que si versión
inglesa, que si “no-se-sabe-bien-qué-versión-porque-lo
mezcla-todo”...)

Vamos a ver en este capítulo cómo podemos transformar formatos


de fechas, lo cual puede sacarnos de un apuro en más de una
ocasión.

Para desarrollar este capítulo vamos a crearnos una BD en blanco. Creamos asimismo un
formulario en blanco, al que llamaremos FFechas. En ese formulario añadimos un cuadro de
texto, al que llamaremos txtFResultado (hagámoslo bien largo, pues lo vamos a necesitar).

COGER LA FECHA DEL SISTEMA (Date)


Para calentar motores vamos a realizar un proceso simple: vamos a coger la fecha del sistema.
Para ello tenemos la palabrita mágica “Date”.

Así pues, para probarlo, en FFechas añadimos un botón de comando y le generamos el


siguiente código:


Private Sub cmdFechaStma_Click()
Me.txtFResultado.Value = Date
End Sub

Como vemos, esto está “chupao”. Prosigamos.

APLICAR FORMATOS A LA FECHA


Vamos a ver que podemos dar diversos formatos a la fecha. En el fondo la mecánica es la
misma para todos los casos, pero yo desarrollaré un código para cada formato, por si queréis ir
practicando.

Formato “Short Date”


El formato “short date” es el que nos da el día/mes/año, y vendría a ser el formato por defecto
que nos coge Access. Es interesante saber que existe por si tenemos una fecha un poco
“compleja” y la queremos convertir rápidamente a una “fecha corta”.

Si tuviéramos que programar un botón en nuestra BD de ejemplo, lo podríamos programar así:


Private Sub cmdShortDate_Click()
Dim vFecha As Date
vFecha = Date
vFecha = Format(vFecha, "Short Date")
Me.txtFResultado.Value = vFecha

3
Visítame en http://siliconproject.com.ar/neckkito/
End Sub

Formato “Long Date”


El formato “Long Date” es el formato de “fecha larga”. El
código para programar un botón sería idéntico al caso
anterior, sólo que indicándole “Long Date” por “Short Date”.
Lo único que debemos tener en cuenta es que el resultado
no es un tipo Date, sino que es un tipo String. Es decir:


Private Sub cmdLongDate_Click()
Dim vFecha As String
vFecha = Date
vFecha = Format(vFecha, "Long Date")
Me.txtFResultado.Value = vFecha
End Sub

Formato personalizado
Podemos convertir el formato de fecha de un tipo de fecha español a un inglés, o viceversa.
Las abreviaturas que utilizaremos para cada uno de los elementos de la fecha serán:

d → para día
m → para mes
y → para año

Vamos a convertir nuestra fecha “española” a una fecha “inglesa”. No podemos asignar el
resultado al textbox directamente como dato tipo Date porque Access detecta que nuestra
configuración es española y lo muestra en formato español. Para poder ver el efecto debemos
tratar la fecha como si fuera un String, y el código que deberíamos utilizar es:


Private Sub cmdFechaInglesa_Click()
Dim vFecha As String
vFecha = Date
vFecha = Format(vFecha, "mm/dd/yy")
Me.txtFResultado.Value = vFecha
End Sub

Ni que decir tiene que si queremos el formato en sistema español la expresión sería:

vFecha = Format(vFecha, "dd/mm/yy")

 Nota: es importante recordar este punto porque, en ocasiones, buscamos filtrar una fecha
en una tabla y, a pesar de que constatamos que la fecha buscada existe, nuestro filtro no nos
muestra ningún registro. En este caso deberemos probar el filtro por fecha dándole el formato
“inglés”, por si Access entendiera que, aunque vemos las fechas en español, el dato está
guardado en formato inglés (un lío, ya sé, pero pasa).

4
Visítame en http://siliconproject.com.ar/neckkito/
APLICAR FORMATOS A LA HORA (Time)
De la misma manera que hemos visto el manejo de la fecha podemos obtener la hora. Vamos
a ver cómo

Obtener la hora del sistema


Para obtener la hora tenemos la palabra TIME. Así, pues,
podríamos programar un botón de la siguiente manera:


Private Sub cmdHoraStma_Click()
Me.txtFResultado.Value = Time
End Sub

Manipular la hora
Los componentes de la hora son:

hh → para la hora
mm → para los minutos
ss → para los segundos

La separación entre estos parámetros es el carácter “dos puntos” (:)

Es decir, que escribiríamos:

Format (Time, “hh:mm:ss”)

El resultado sería el mismo que hemos conseguido en el punto anterior. Es decir, que
podríamos programar un botón de la siguiente manera:


Private Sub cmdHoraFormato_Click()
Dim vHora As Date
vHora = Time
vHora = Format(vHora, "hh:mm:ss")
Me.txtFResultado.Value = vHora
End Sub

Como otra posibilidad podríamos indicar la hora en formato 12 horas. La expresión que
deberíamos utilizar sería:

Format (Time, “hh:mm:ss AMPM”)

Es decir:


Private Sub cmdHora12_Click()
Dim vHora As Date
vHora = Time
vHora = Format(vHora, "hh:mm:ss AMPM")
Me.txtFResultado.Value = vHora
End Sub

5
Visítame en http://siliconproject.com.ar/neckkito/

FECHA Y HORA A LA VEZ (Now)


Si por casualidad necesitamos obtener la fecha y la hora a
la vez tenemos la palabra NOW, que nos da estos datos.

Por ejemplo, podríamos obtener la fecha y la hora, y


presentarlas en nuestro TextBox, a través del siguiente
código:


Private Sub cmdFechaNow_Click()
Dim vFecha As Date, vHora As Date
vFecha = Now
vHora = Now
vFecha = Format(vFecha, "dd/mm/yy")
vHora = Format(vHora, "hh:mm:ss")
Me.txtFResultado.Value = vFecha & "-" & vHora
End Sub

FORMATO A CADENAS DE CARACTERES


Pensemos en el caso en que hay varios usuarios trabajando en nuestra BD, y cada uno da de
alta registros escribiendo los datos “como le viene bien” (por decirlo de alguna manera). Y eso,
a la hora de ver consultas o informes, queda un poco “feo” por la heterogeneidad de los datos.

Una primera solución a lo anterior sería aplicar máscaras de entrada cuando confeccionamos la
tabla. Pero, como siempre pasa, en ese campo X en concreto se nos pasó. Vamos a ver cómo
podemos enmendar un poco el asunto.

Para ello, vamos a insertar un cuadro de texto, que llamaremos txtCadena, y todos los códigos
que aprendamos en este apartado serán asignados al evento “Después de actualizar” (yo haré
los supuestos sobre diferentes TextBox, en un nuevo formulario llamado FCaracteres).

Valores a mayúsculas (UCase)


Para conseguir que el texto introducido se convierta en mayúsculas debemos utilizar la función
UCASE

Veamos cómo podría ser un código:


Private Sub txtCadena1_AfterUpdate()
Dim vText As String
vText = Me.txtCadena1.Value
vText = UCase(vText)
Me.txtCadena1.Value = vText
End Sub

Propiedad InputMask
Tenemos un segundo sistema, que es a través de la función INPUTMASK. En realidad, lo que

6
Visítame en http://siliconproject.com.ar/neckkito/
hace esta función es aplicar una máscara 2 al TextBox. Sólo explicaré el ejemplo aplicado a
conseguir la conversión de minúsculas a mayúsculas y no en los siguientes, dado que la
mecánica es siempre la misma (sólo tenemos que definir la máscara que queramos).

El código que nos podría hacer eso sería, por ejemplo:


Private Sub txtCadena2_AfterUpdate()
Me.txtCadena2.InputMask = ">????????????????????"
End Sub

Valores a minúsculas (LCase)


La función que consigue pasar todos los caracteres a minúsculas es LCASE.

Un ejemplo, en código, sería:


Private Sub txtCadena3_AfterUpdate()
Dim vText As String
vText = Me.txtCadena3.Value
vText = LCase(vText)
Me.txtCadena3.Value = vText
End Sub

La función StrConv
Los dos ejemplos anteriores hubiéramos podido aplicarlos a través de la función StrConv. Esta
función consta de dos elementos:

Cadena a convertir / Tipo de conversión

Hay diversos tipos de conversión, que no se explicarán aquí (podéis abrir, en VBE, buscar
StrConv en la ayuda). El tipo de conversión viene determinado por una constante de VB. De
esta manera, tendríamos que:

vbUpperCase → nos convertiría la cadena a mayúsculas.


vbLowerCase → nos convertíría la cadena a minúsculas.

La aplicación de esta función, si seguimos nuestro ejemplo, sobre la variable vText sería:

vText = StrConv(vText, vbUpperCase)

vText = StrConv(vText, vbLowerCase)

Hay una constante interesante que explicamos en el siguiente apartado.

2 Os remito a la ayuda de Access sobre el tema de máscaras de entrada. También podéis consultar el anexo al
capítulo 2 del Manual de Access, en el apartado correspondiente a máscaras de entrada.

7
Visítame en http://siliconproject.com.ar/neckkito/
Inicio de palabras en mayúsculas (vbProperCase)
Supongamos que tenemos un formulario donde se dan de alta los datos de un cliente, o de un
alumno. El usuario que introduce los datos, para “darse prisa”, nos escribe lo
siguiente:

neckkito neckk ito

Es decir, nombre y apellidos en minúsculas. Si después


tenemos que enviar una carta a este cliente / alumno, o
remitirle un mail personalizado, la verdad es que eso queda
un poco “feo”.

¿Cómo podemos “protegernos” de lo anterior? Pues a través de la constante vbPROPERCASE

Como habréis intuido, lo que hace vbProperCase es poner en mayúsculas la primera letra de
cada palabra “individual”.

El código para conseguir eso sería el siguiente:


Private Sub txtCadena4_AfterUpdate()
Dim vText As String
vText = Me.txtCadena4.Value
vText = StrConv(vText, vbProperCase)
Me.txtCadena4.Value = vText
End Sub

Eliminación de espacios en blanco (Trim, LTrim, RTrim)


Puede ocurrir que nuestro querido usuario, en su afán de ser eficiente, escriba los valores
añadiendo espacios inadvertidamente antes del valor, o al final. Esos espacios, además de
molestos visualmente, desvirtúan el contenido del control.

Para solventar esta “dificultad” podemos utilizar la función TRIM. Esta función elimina estos
espacios en blanco.

El código sería:


Private Sub txtCadena5_AfterUpdate()
Dim vText As String
vText = Me.txtCadena5.Value
vText = Trim(vText)
Me.txtCadena5.Value = vText
End Sub

Comentar también que si sólo queremos quitar los espacios iniciales (los de la izquierda),
podemos utilizar la función LTRIM; si queremos quitar los espacios finales (los de la derecha),
podemos utilizar la función RTRIM.

8
Visítame en http://siliconproject.com.ar/neckkito/
CONVERSIONES DE DATOS

Funciones conversoras
En ocasiones tenemos un tipo de dato y necesitamos
cambiarlo de tipo. Para ello existen funciones que realizan
ese “trabajo”. Vamos a ver las que considero más
interesantes:

→ Para pasar un dato a texto utilizamos CStr()


→ Para pasar un dato a fecha utilizamos CDate()
→ Para pasar un dato a booleano utilizamos CBool()
→ Para pasar un dato a variant utilizamos CVar()
→ Para pasar un dato a moneda utilizamos CCur()
→ Para pasar un dato a número utilizamos:
→ Si es un integer: CInt()
→ Si es un long: CLng()
→ Si es un double: Cdbl()

Detección tipo de dato


Aprovechemos este espacio para explicar que podemos actuar sobre un valor en función del
tipo de variable que sea. Y, para ello, podemos utilizar las siguientes funciones:

→ Para saber si un dato es numérico utilizamos IsNumeric()


→ Para saber si un dato es fecha utilizamos IsDate()
→ Para saber si un dato es una matriz utilizamos IsArray()

Si buscamos, en la ayuda de Access, funciones de inspección, encontraremos todas las


funciones disponibles dentro de esta categoría.

Unos cuantos ejemplos prácticos sobre funciones conversoras y tipos


de datos
Vamos a desarrollar una serie de supuestos prácticos sobre lo que acabamos de explicar, para
ver qué utilidad le podemos dar. Aprovecharemos también para explicar algunas funciones que
nos serán útiles para nuestras programaciones.

En la BD de ejemplo podréis encontrar estos supuestos en el formulario que he llamado


FConversion. Los resultados se mostrarán en un TextBox que llamaremos txtResultado.

Finalmente, los supuestos están desarrollados paso a paso. Hay pasos que se pueden
simplificar o resumir, pero en aras a la pedagogía el ejemplo se desarrolla “a lo largo”.

Supuesto 1: aprendemos las funciones LEFT y RIGHT


Imaginemos que obtenemos, por una parte, un valor numérico de dos dígitos, y, por otra
parte, otro valor numérico de cuatro dígitos. Queremos convertir todo lo anterior a una fecha.
Para simplificar en el ejemplo los valores numéricos que hemos comentado los definiremos en
el propio código, pero ya deberíamos saber que los podemos obtener del valor de un control, o

9
Visítame en http://siliconproject.com.ar/neckkito/
a través de un InputBox.

¿Cómo lo hacemos?

Vamos a ver cómo sería el código:


Private Sub cmdNum2Fecha_Click()
Dim grupo1 As Integer, grupo2 As Integer
Dim grupo3 As String, grupo4 As String
Dim vFinal As String
grupo1 = 24
grupo2 = 1012
'Convertimos el primer grupo a texto
grupo1 = CStr(grupo1)
'Convertimos el segundo grupo a texto
grupo2 = CStr(grupo2)
'Extraemos, del grupo2, los dos números de la izquierda, y los asignamos a la variable
grupo3
grupo3 = Left(grupo2, 2)
'Extraemos, del grupo2, los dos números de la derecha, y los asignamos a la variable
grupo4
grupo4 = Right(grupo2, 2)
'Componemos lo que sería la fecha
vFinal = grupo1 & "/" & grupo3 & "/" & grupo4
'Convertimos nuestra fecha (que es de tipo String) a tipo fecha
vFinal = CDate(vFinal)
'Mostramos el resultado
Me.txtResultado.Value = vFinal
End Sub

Como vemos, si queremos extraer caracteres de una cadena de caracteres, empezando por la
izquierda utilizamos la función Left. Su estructura es

Left(cadena, nº de caracteres a extraer)

De la misma manera, si queremos extraer caracteres empezando por la derecha utilizamos la


función Right, con la misma estructura:

Right(cadena, nº de caracteres a extraer)

Supuesto 2: aprendemos la función MID


Imaginemos que tenemos un campo de texto que nos recoge una información bajada de
Internet, o importada de un Excel. La estructura del valor en ese campo es siempre la misma,
y nos interesa capturar un valor numérico que está dentro de cada uno de los registros 3.

Por ejemplo, un registro sería: “Expediente 1563: secuestro de Neckkito”

Analicemos esa cadena de texto.


.- Vemos que desde la primera palabra hasta el número 1 (contando espacios) hay 12
caracteres.
.- Vemos que el valor numérico que queremos capturar consta de 4 caracteres.
3 En capítulos posteriores veremos cómo recorrer registros de una tabla o consulta a través de un recordset.
¡Paciencia!

10
Visítame en http://siliconproject.com.ar/neckkito/
Pues para extraer esos cuatro números de la cadena, para convertirlos posteriormente a tipo
número, podemos utilizar la función Mid()

La estructura de dicha función es:

Mid(cadena de texto, posición inicial de extracción, nº


caracteres a extraer)

Con todo lo anterior nuestro código quedaría de la siguiente


manera (para simplificar el texto lo trataré como constante,
pero ya sabemos que podemos obtenerlo de diversas
fuentes):


Private Sub cmdText2Num_Click()
Const vText As String = "Expediente 1563: secuestro de Neckkito"
Dim vNum As String
vNum = Mid(vText, 12, 4)
vNum = CInt(vNum)
Me.txtResultado.Value = vNum
End Sub

Para complementar lo anterior os propongo un ejercicio. Imaginaos que tenemos un registro


con el siguiente valor en un campo: “Fecha: 24/01/12 – Hora llegada: 14:25:00”. Lo que
deberíamos hacer es:
– Conseguir extraer la fecha, como Date, y mostrarla en un TextBox
– Conseguir extraer la hora de llegada, como Date, y mostrarla en un TextBox
– Conseguir extraer sólo fecha y hora de llegada, separadas por un guión, como texto, y
mostrar el resultado en un TextBox

Os animo a que lo penséis e intentéis programarlo. De todas maneras, en la BD de ejemplo, en


el formulario FEjercicios, tenéis un botón con el código asignado, por si queréis chequearlo.

Supuesto 3: aprendemos la función INSTR y la función LEN


Empezando por la segunda función, la función Len() nos da la longitud de la cadena de
caracteres. Es decir, que nos devuelve un tipo Long. Para ser más claros, nos cuenta cuántos
caracteres tiene el texto o número que estamos analizando.

Su estructura es

Len(texto)

Respecto de la primera función, en ocasiones necesitamos comparar cadenas de caracteres. La


función InStr() nos permite comparar, precisamente, cadenas de caracteres, y nos muestra la
posición donde se encuentra la primera coincidencia.

Su estructura es:

InStr(posición de inicio, expresión que contiene la cadena a buscar, cadena a buscar, método
de comparación)

El primer argumento (posición de inicio) es opcional, y si se omite se empieza por el principio


de la cadena; el último argumento (método de comparación) también lo es. Para ver los

11
Visítame en http://siliconproject.com.ar/neckkito/
diferentes tipos de métodos de comparación os remito a la ayuda de Access.

Pues bien... todo lo anterior es muy bonito a nivel de teórico pero... ¿alguien se ha enterado
de algo?

Imaginemos que nuestra BD gestiona los artículos de un


almacén, los cuales tienen todos un código de artículo.
Como somos muy ordenados el código de artículo tiene la
siguiente estructura:

– Código numérico de artículo, con un mínimo de 4 dígitos y


un máximo de 6
– Código de dos letras correspondiente al proveedor
– Código de dos letras correspondiente a una referencia interna
– Código alfanumérico de cuatro caracteres mínimo y 6 máximo que corresponde a su
ubicación dentro del almacén

Es decir, que, por ejemplo, un artículo tiene este código: 3410ZNDE5A6J, y podemos tener
otro artículo con el siguiente código 874532SNSP3R6H8K.

Entonces se acerca nuestro jefe con una sonrisa en la boca y nos dice: tendríamos que
actualizar la información en la base de datos. Los 5.000 artículos “y poco” que tenemos con
referencia interna SP han cambiado, y ahora su referencia debería ser ST. ¿Los cambiarás para
mañana? Por cierto, no podemos pagarte las horas extras...

Primer problema: no podemos utilizar la función Left() porque el código del artículo es
variable: de 4 a 6 caracteres.

Segundo problema: no podemos utilizar la función Right() porque el código de ubicación es


variable: de 4 a 6 caracteres

Tercer problema: si no podemos utilizar Left() ni Right(), ¿cómo utilizamos Mid()?

Un sudor frío recorre nuestro cuerpo... ¿Será verdad que tendremos que quedarnos toda la
noche cambiando códigos... o no?

Vamos a hacer el razonamiento en términos “humanos”, para después aplicarlo a código, a ver
cómo podemos solventar nuestro problema.

.- Sabemos que el número de caracteres mínimo de código de artículo es 4. Luego la posición 5


será nuestro inicio de búsqueda (el primer argumento de InsStr())
.- Sabemos que la cadena que debemos buscar es SP
.- Sabemos que InStr() nos dará la posición donde se encuentra la cadena
.- Con Len() sabemos la longitud total del código entero del artículo
.- Con todo lo anterior sabemos:
.- a) Cuántos caracteres hay antes de SP
.- b) Cuántos caracteres hay después de SP

Es decir, que si el código del artículo es 3410ZNSP5A6J tenemos que:

.- InStr() nos dirá que la posición es la 7 → Luego hay 6 caracteres antes de SP


.- Len() nos dirá que la longitud de la cadena es 12.
.- Si la posición anterior a SP es la 6, y sabemos que SP son 2 caracteres, eso nos sitúa en la
posición 8. Entonces 12 – 8 = 4 → Hay 4 caracteres de ubicación de almacén.

Ya tenemos pues los elementos necesarios para destruir nuestro código y volverlo a construir.

12
Visítame en http://siliconproject.com.ar/neckkito/
El código que podríamos aplicar sería, entonces:


Private Sub cmdFuncInStrg_Click()
Const codAntiguo As String = "3410ZNSP5A6J"
' Const codAntiguo As String = "874532SNSP3R6H8K"
Const refNueva As String = "ST"
Dim longCodigo As Integer, posicionSP As Integer
Dim antesSP As String, despuesSP As String
Dim codNuevo As String
'Cogemos la longitud del código
longCodigo = Len(codAntiguo)
'Cogemos la posición de "SP"
posicionSP = InStr(4, codAntiguo, "SP")
'Cogemos la cadena anterior a "SP"
antesSP = Left(codAntiguo, posicionSP - 1)
'Cogemos la cadena posterior a "SP"
despuesSP = Right(codAntiguo, longCodigo - (posicionSP - 1) - 2)
'Construimos el nuevo código
codNuevo = antesSP & "ST" & despuesSP
'Mostramos el resultado
Me.txtResultado.Value = codNuevo
End Sub

Veréis que, al inicio del código, os he dejado una constante como comentario, por si queréis
probar el código con un segundo artículo con diferentes dígitos. Para probarlo sólo tenéis que
convertir en comentario la primera constante codAntiguo y eliminar el comentario de la
segunda (codAntiguo).

Por suerte para nosotros, ¡¡¡el código de proveedor no coincide nunca con el código interno!!!

El código VB anterior puede parecer “complicado”, y lo he puesto, primero, para poder


entender el funcionamiento de la función InStr(), y, segundo, para ver que con la combinación
de las funciones Len(), Left(), Right() -y, si lo necesitamos, Mid() e InStr()- se pueden
conseguir muchísimas cosas sobre una cadena de texto. En tercer lugar lo he puesto también
para “exprimir” un poco esas preciosas neuronas que nos caracterizan... je, je...

Podríamos haber utilizado otra función para conseguir el mismo efecto, y que vemos en el
supuesto siguiente:

Supuesto 4: aprendemos la función REPLACE


La función Replace() nos permite reemplazar una cadena de un texto por otra. Su estructura
es la siguiente:

Replace(texto a analizar, subcadena que se busca, subcadena por la que se reemplazará,


posición del inicio de búsqueda, número de reemplazos a realizar, clase de comparación).

Los tres últimos argumentos son opcionales. Si no se indica nada los valores que cogen son:
– <posición inicio de búsqueda> = 1 (es decir, desde el principio)
– <número de reemplazos a realizar> = -1 (es decir, todos los posibles)
– <clase de comparación> → Os remito a la ayuda de la función para más información
sobre este argumento.

Es decir, que nuestro “súper-código” del apartado anterior nos podría haber quedado así:

13
Visítame en http://siliconproject.com.ar/neckkito/

Private Sub cmdReplace_Click()
Const codAntiguo As String = "3410ZNSP5A6J"
Me.txtResultado.Value = Replace(codAntiguo, "SP", "ST")
End Sub

Je, je...

Supuesto 5: practicamos con IsNumeric()

Vamos finalmente a practicar con la función IsNumeric(). El resto de funciones de inspección


que hemos comentado en un epígrafe anterior tienen un funcionamiento prácticamente
idéntico, por lo que podemos decir que, vista una, vistas todas.

Vamos a imaginar que queremos realizar una operación tan simple como pedir un valor a un
usuario y multiplicarlo por 5. Para pedir el valor al usuario utilizamos un InputBox. Lo que
queremos es asegurarnos de que el valor que introduce el usuario es, precisamente, numérico,
y no escribe, por ejemplo, una cadena de texto (lo cual, evidentemente, nos causará un error
de código).

El código que podríamos escribir sería el siguiente:


Private Sub cmdIsNumeric_Click()
Dim vNum As Variant
vNum = InputBox("Introduzca un número para ser multiplicado por 5", "MULTIPLICO x5")
'Comprobamos que el valor introducido es numérico y mostramos el resultado.
If IsNumeric(vNum) Then
Me.txtResultado.Value = vNum * 5
Else
'Si no ha introducido un valor numérico avisamos y salimos
MsgBox "El valor que ha introducido no es válido", vbCritical, "NO CORRECTO"
End If
End Sub

SEGUIMOS MANIPULANDO FECHAS


Todavía podemos realizar algunas operaciones más con las fechas o, mejor dicho, con aspectos
relacionados con las fechas.

Los ejemplos de este apartado los encontraréis en el formulario FFechas2 de la BD de ejemplo.

Analizar el día de una fecha: función Weekday()


Para analizar el nombre del día de una fecha podemos recurrir a la función Weekday. Dicha
función nos devuelve el número del día de la semana de la fecha que estamos analizando. Su
estructura es:

Weekday(fecha a analizar, primer día de la semana)

El argumento <primer día de la semana> es opcional, y por defecto es el valor 1-Domingo. Si

14
Visítame en http://siliconproject.com.ar/neckkito/
queremos que el primer día de la semana sea el lunes deberíamos especificar un 2.

Podemos obviar este argumento si nos auxiliamos con unas constantes de VB. Dichas
constantes son:
– vbMonday
– vbTuesday
– vbWednesday
– vbThursday
– vbFriday
– vbSaturday
– vbSunday

Si sabemos un poco de inglés veremos que no hace falta explicar qué significan cada una de
las anteriores constantes de VB.

Veamos pues cómo podría ser el código para saber el nombre del día de una fecha:


Private Sub cmdNomDia_Click()
Dim vFecha As Variant
vFecha = InputBox("Introduzca una fecha -Formato dd/mm/aa-", "FECHA")
If Not IsDate(vFecha) Then Exit Sub
vFecha = CDate(vFecha)
'Analizamos la fecha introducida
Select Case Weekday(vFecha)
Case vbMonday
Me.txtFResultado.Value = "Lunes"
Case vbTuesday
Me.txtFResultado.Value = "Martes"
Case vbWednesday
Me.txtFResultado.Value = "Miércoles"
Case vbThursday
Me.txtFResultado.Value = "Jueves"
Case vbFriday
Me.txtFResultado.Value = "Viernes"
Case vbSaturday
Me.txtFResultado.Value = "Sábado"
Case vbSunday
Me.txtFResultado.Value = "Domingo"
End Select
End Sub

Simplemente haceros notar que para controlar la introducción del valor por parte del usuario, y
que realmente introduzca una fecha, hemos añadido la línea:

If Not IsDate(vFecha) Then Exit Sub

Otra manera de aplicar la función anterior, por ejemplo, sería para realizar una acción en
función del día que se seleccione. Por ejemplo, supongamos que nos proponen un día para ir a
realizar un trabajo. Lo introducimos en nuestra BD para podernos planificar y... ¡vaya! El
código sería el siguiente:


Private Sub cmdDiaDomingo_Click()
Dim vDia As Date

15
Visítame en http://siliconproject.com.ar/neckkito/
'El día seleccionado es domingo
vDia = "29/01/12"
'Analizamos el día
If Weekday(vDia) = vbSunday Then
MsgBox "El día propuesto es domingo. ¡No trabajamos
en domingo!", vbExclamation, "NO HÁBIL"
Else
MsgBox "A trabajar!", vbExclamation, "HÁBIL"
End If
End Sub

Obtener los elementos de una fecha: funciones Day(), Month(), Year()


Podemos “extraer” los distintos elementos de una fecha a través de las funciones:

– Day() → Extrae el número de día


– Month() → Extrae el número de mes
– Year() → Extrae el número de año

Lo anterior nos permite una manipulación prácticamente completa sobre la fecha introducida.

Vamos a ver un código que nos “desglosa” una fecha y nos indica qué es cada elemento.


Private Sub cmdDesgloseFecha_Click()
Dim vDia As String, vMes As String, vAno As String
Dim vFecha As Variant
vFecha = InputBox("Introduzca una fecha -formato dd/mm/aa-", "FECHA")
If Not IsDate(vFecha) Then Exit Sub
vDia = Day(vFecha)
vMes = Month(vFecha)
vAno = Year(vFecha)
Me.txtFResultado.Value = "Día: " & vDia & " - Mes: " & vMes & " - Año: " & vAno
End Sub

Ver el nombre del mes de una fecha: función MonthName()


Podemos obtener el mes de una fecha a través de la función MonthName(). Su estructura es la
siguiente:

MonthName(número de mes, abreviatura)

El argumento <número de mes> está claro, ¿verdad?

Podemos conseguir el nombre de mes entero o podemos mostrar el nombre de mes abreviado.
Eso lo conseguimos a través del argumento <abreviatura>. Si no indicamos nada es como si
ese argumento tomara el valor FALSE, lo cual nos devuelve el nombre entero; si podemos
TRUE obtendremos el nombre del mes abreviado.

Por ejemplo, un código podría ser el siguiente:


Private Sub cmdMesFecha_Click()

16
Visítame en http://siliconproject.com.ar/neckkito/
Dim vFecha As Variant
Dim nomMes As String
vFecha = InputBox("Introduzca una fecha -Formato dd/mm/aa-", "FECHA")
If Not IsDate(vFecha) Then Exit Sub
nomMes = MonthName(Month(vFecha))
Me.txtFResultado.Value = nomMes
End Sub

La función DatePart()

Para complementar las anteriores explicaciones echaremos un vistazo a la función de este


epígrafe y a la del siguiente. Así tendremos, creo yo, una visión bastante amplia de todo lo que
se intentaba explicar en este capítulo.

La función DatePart() nos permite obtener una serie de informaciones sobre elementos
relacionados con la fecha. Para ser más claros, por ejemplo, nos indica el número de trimestre,
el número de semana del año, además de permitirnos obtener, con otro sistema, datos que
acabamos de explicar en apartados anteriores.

La estructura de esta función es:

DatePart(elemento a obtener, fecha, primer día de la semana, primera semana del año).

Los dos últimos argumentos son opcionales, y os remito a la ayuda de VB para ver su utilidad.

El primer argumento, <elemento a obtener>, lo deberemos definir a través de un valor. Esos


valores son los siguientes:

yyyy → Para obtener el año


q → Para obtener el trimestre
m → Para obtener el mes
y → Para obtener el día del año
d → Para obtener el día
w → Para obtener el día de la semana
ww → Para obtener el número de semana del año
h → Para obtener la hora
n → Para obtener los minutos
s → Para obtener los segundos.

Si quisiéramos obtener a qué trimestre corresponde 01/03/12, el código que programaríamos


sería:


Private Sub cmdObtTrimestre_Click()
Const miFecha As Date = "01/03/12"
Dim vTrim As Integer
vTrim = DatePart("q", miFecha)
Me.txtFResultado.Value = vTrim
End Sub

Como podemos ver, la utilización de esta función es muy sencilla. No olvidemos que la
podemos utilizar también, por ejemplo, para filtrar datos, cuando utilicemos un recordset.

17
Visítame en http://siliconproject.com.ar/neckkito/
La función DateDiff()
Si queremos calcular un periodo que hay entre dos fechas tenemos la función DateDiff(). Su
estructura es:

DateDiff(elemento a obtener, fechaInferior, fechaSuperior,


primer día de la semana, primera semana del año).

Los dos últimos argumentos son opcionales, y os remito a


la ayuda de VB para ver su utilidad.

El primer argumento, <elemento a obtener>, corresponde al


mismo conjunto de elementos que explicábamos en el epígrafe
anterior, y a él os remito.

Por ejemplo, si queremos calcular el número de semanas que hay entre una fecha introducida
por el usuario y la fecha actual utilizaríamos el siguiente código:


Private Sub cmdDateDiff_Click()
Dim vFecha As Date
Dim vSem As Long
vFecha = InputBox("Introduzca una fecha -formato dd/mm/aa-" _
& " inferior a la fecha actual", "FECHA")
If Not IsDate(vFecha) Then Exit Sub
vSem = DateDiff("ww", vFecha, Date)
Me.txtFResultado.Value = vSem
End Sub

Sumar días a una fecha


Aunque parezca una obviedad, sumar días a una fecha es tan simple como realizar una
operación matemática de adición, donde el primer sumando sería la propia fecha y el segundo
sumando sería el número de días a añadir (o al revés). Es decir,

vFechaFin = vFechaIni + vDias

Un simple código, para que quede más claro, sería:


Private Sub cmdSumaDias_Click()
Dim vFechaIni As Variant
Dim vFechaFin As Variant
vFechaIni = InputBox("Introduzca una fecha -Formato dd/mm/aa-", "FECHA")
If Not IsDate(vFechaIni) Then Exit Sub
vFechaIni = CDate(vFechaIni)
vFechaFin = vFechaIni + 5
Me.txtFResultado.Value = vFechaFin
End Sub

18
Visítame en http://siliconproject.com.ar/neckkito/
Pequeño ejercicio práctico

Os propongo un ejercicio: solicitamos al usuario que


introduzca una fecha del mes de julio, y sumamos 40 días a
esa fecha, teniendo en cuenta que no debemos incluir el
mes de agosto en los cálculos (y no es lícito utilizar
directamente el número de días de agosto, sino que hay
que detectar que el mes es agosto).

Es decir, que si se introduce el 1 de julio nos debería devolver el 10


de septiembre.

Debéis tener en cuenta lo siguiente (con alguna pista incluida):

– Debemos comprobar que el usuario introduzca un valor que sea una fecha
– Debemos comprobar que el mes introducido corresponda a julio
– Debemos “saltarnos” todos los días de agosto. Esto es un bucle, y os recuerdo que en
un capítulo anterior vimos estructuras de bucle.
– Debemos acordarnos de convertir los valores de las diferentes variables para evitar
errores de código.

En el formulario FEjercicios encontraréis dos botones con códigos con la solución. Ambos
códigos son muy similares, pero la mecánica del bucle es distinta.

¡Suerte!

UN EJEMPLO FICTICIO... REAL COMO LA VIDA MISMA


Vamos a desarrollar un ejemplo que está basado en una consulta que recibí de un usuario y,
dado que maneja fechas y cadenas de caracteres, nos servirá para “recordar” y “refrescar”
conceptos que se han visto durante esta lección.

Este ejemplo lo encontraréis desarrollado en el formulario FEjemploFicticio de la BD de


ejemplo.

El supuesto original se desarrollaba sobre un campo existente en una tabla, a través de un


formulario. Para simplificar estableceremos la entrada de datos en un cuadro de texto, y la
salida de datos en otro cuadro de texto. Al primero le llamaré txtFNotificacion, y al segundo
txtFVto.

Enunciado:

En España (no sé en otros países), cuando Hacienda envía una notificación, te da un plazo
máximo para responder a la notificación. Los periodos son los siguientes:

– Notificación recibida entre el 1 y el 15 del mes: vencimiento el día 20 del mes siguiente.
– Notificación recibida entre el 16 y el último día del mes: vencimiento el día 5 del mes
posterior al mes siguiente.

La idea es que, al dar de alta una fecha de notificación en txtFNotificacion, automáticamente


se nos rellene el control txtFVto con la fecha de vencimiento correspondiente.

19
Visítame en http://siliconproject.com.ar/neckkito/
Análisis del supuesto y casos posibles:

Un análisis inicial nos muestra lo siguiente:

→ Si el día de la notificación es entre el 1 y 15:


→ El día de vencimiento es el 20
→ El mes de vencimiento es +1 respecto del mes de
notificación.
→ Si el día de la notificación es entre el 16 y el último día
del mes:
→ El día de vencimiento es el 5
→ El mes de vencimiento es +2 respecto del mes de
notificación.

Hasta aquí todo correcto. Sin embargo, hemos analizado sólo dos de los tres elementos de la
fecha (día y mes), pero nos hemos dejado el tercer elemento: el año.

Sigamos nuestro análisis:


→ Si el periodo máximo de més es +2 respecto de la fecha de notificación, y el año tiene 12
meses, podemos entender que 12 – 2 = 10. Ello implica que, al menos hasta octubre, el año
de notificación será coincidente con el año de vencimiento.

→ ¿Qué pasa en noviembre? El propio enunciado nos determina que:


→ Si la notificación es recibida dentro de los primeros quince días del mes el
comportamiento es el mismo que para el caso general que planteábamos al principio.
→ Si la notificación es recibida dentro de los segundos quince días del mes hay dos
elementos que son afectados: el mes y el año.
Respecto del mes no podemos aplicar la fórmula general, dado que si a 11 le añadimos +2 nos
da el mes 13, que evidentemente no existe. Ello nos obliga a establecer un valor fijo para el
mes, que será el 1
Respecto del año nos sitúa en el año posterior. Es decir, debemos adicionarle una unidad al
año.

→ ¿Qué pasa en diciembre? Que también nos afecta a dos elementos: mes y año.
Respecto del año, en cualquier caso se deberá añadir una unidad al año de la notificación.
Respecto del mes, no podemos aplicar la fórmula general de adicionar +1 ó +2 al mes, puesto
que nos daría los meses 13 y 14, que no existen. Luego nos obliga a establecer valores fijos
para el mes, que serán 1 ó 2, dependiendo de la quincena en que recibamos la notificación.

Con todos estos elementos y análisis en mente ya podemos programar un código que nos
automatice el proceso.

Programación del código

El código lo vamos a programar en el evento “Después de actualizar” del control


txtFNotificacion, y, para ahondar más en la cuestión, vamos a añadir elementos de control para
asegurarnos que el usuario no se “equivoca” en la introducción del valor (evidentemente, que
no se “equivoca” en cuestión de formatos). El código sería el siguiente (está completamente
comentado, por lo que entiendo que no deberíais tener problemas en asimilarlo).


Private Sub txtFNotificacion_AfterUpdate()
'Declaramos las variables
Dim vFNot As String, vFVto As String
Dim vDia As String, vMes As String, vAno As String
'Cogemos el valor introducido

20
Visítame en http://siliconproject.com.ar/neckkito/
vFNot = Nz(Me.txtFNotificacion.Value, "")
'CONTROL -> Si no hay valor en el textbox salimos del proceso
If vFNot = "" Then Exit Sub
'CONTROL -> Verificamos que se haya introducido una fecha
If Not IsDate(vFNot) Then Exit Sub
'Convertimos el string vFNot en Date
vFNot = CDate(vFNot)
'Obtenemos el día, el mes y el año de la notificacion
vDia = Day(vFNot)
vMes = Month(vFNot)
vAno = Year(vFNot)
'Como el elemento que nos determina si aplicamos la fórmula
general
'es el mes, analizamos el mes de la fecha. Lo estudiamos a través del
'bloque SELECT CASE
Select Case vMes
'El mes va de enero a octubre. Fijaos que no tocamos el año
Case Is <= 10
'Ahora podemos analizar el día
If vDia <= 15 Then
'Aprovechamos la misma variable vDia para fijar el día de vencimiento
vDia = 20
'Le añadimos una unidad al mes (y aprovechamos la misma variable)
vMes = vMes + 1
Else
'En caso contrario es que se ha recibido en la segunda quincena
vDia = 5
vMes = vMes + 2
End If
'El mes es noviembre
Case Is = 11
'Analizamos el día
'Primera quincena
If vDia <= 15 Then
'Como en el caso anterior, aplicamos la formulación general
vDia = 20
vMes = vMes + 1
Else
'Segunda quincena. Ya no podemos aplicar la formulación general. Debemos
'fijar el mes y adicionar una unidad al año
vDia = 5
vMes = 1
vAno = vAno + 1
End If
'El mes es diciembre
Case Is = 12
'Analizamos el día
'Primera quincena
If vDia <= 15 Then
vDia = 20
'Debemos fijar el mes
vMes = 1
'Adicionamos un año
vAno = vAno + 1
Else
'Segunda quincena. Seguimos la operativa

21
Visítame en http://siliconproject.com.ar/neckkito/
vDia = 5
vMes = 2
vAno = vAno + 1
End If
End Select
'Creamos la fecha de vencimiento como un String
vFVto = vDia & "/" & vMes & "/" & vAno
'Convertimos la fecha de vencimiento a Date
vFVto = CDate(vFVto)
'Mostramos su valor en el textbox
Me.txtFVto.Value = vFVto
End Sub

¿Fácil, no? ;)

PARA ACABAR...
Creo que, tras el estudio de este capítulo, ya podremos decir que somos unos “profesionales”
en el manejo de fechas y de cadenas de caracteres... je, je...

Como siempre, espero que alguna cosa de las aquí explicadas os pueda ser útil para vuestras
aplicaciones (nótese que ya no hablo de BD's... ;)

¡Suerte!

22
Visítame en http://siliconproject.com.ar/neckkito/
CURSO DE VB

CAPÍTULO 91

Índice de contenido
TRABAJANDO CON MATRICES.....................................................................................................2
MATRICES DE UNA DIMENSIÓN (MATRICES ESTÁTICAS)................................................2
OTRA MANERA DE CREAR UNA MATRIZ ESTÁTICA DE UNA DIMENSIÓN -Función
Array()-.......................................................................................................................................4
APROVECHAMOS PARA APRENDER LA FUNCIÓN Rnd() Y LA INSTRUCCIÓN
Randomize..................................................................................................................................6
MATRIZ ESTÁTICA SIN SABER LA LONGITUD DE LA MATRIZ. EJEMPLO DE
SUSTITUCIÓN DE CARACTERES.........................................................................................7
DETERMINANDO EL TIPO DE DATOS DE LA MATRIZ.........................................................9
MATRICES MULTIDIMENSIONALES (MATRICES ESTÁTICAS)........................................10
Un ejemplo para matriz de tres dimensiones............................................................................11
MATRICES DINÁMICAS............................................................................................................13
(y aprovechamos para aprender algunas SQL simples).................................................................13
ReDim Preserve........................................................................................................................16
ALGUNOS ELEMENTOS INTERESANTES DE LA MATRIZ: las funciones UBound() y
LBound() y la instrucción Erase....................................................................................................19
Y ACABAMOS EL TEMA DE MATRICES.....................................................................................21

1 La BD donde están los ejemplos de este capítulo os la podéis bajar aquí.

1
Visítame en http://siliconproject.com.ar/neckkito/
TRABAJANDO CON MATRICES
Vamos a aprender cómo podemos trabajar con matrices en
nuestro deambular por VB para Access.

Y la pregunta del millón es... ¿qué es una matriz? Si nos


pusiéramos en plan académico podríamos decir que una
matriz es un conjunto de variables del mismo tipo, las
cuales se hallan indexadas, siendo precisamente su índice
el indicador de posición de cada variable en la matriz.

¿Bonita definición, verdad?

Vamos a “destripar” la definición con un ejemplo. Supongamos que tenemos un tubo (de
plástico, de PVC, de cristal... lo que más nos guste). Y tenemos unas bolitas (de gomaespuma)
que debemos meter en dicho tubo. Todas las bolitas son del mismo tipo (es decir, todas son
String, todas son Integer, todas son Date...).

La primera bolita lleva escrito: “A es la primera letra del abecedario”


La segundo bolita lleva escrito: “B es la segunda letra del abecedario”
La tercera bolita lleva escrito: “C es la tercera letra del abecedario”
Etc.

Como las hemos introducido en el tubo por este orden, la primera bolita llevará el índice 1; la
segunda el índice 2 y la tercera el índice 3.

Nuestro tubo podría ser representado de la siguiente manera:

Tubo(Primera bolita, Segunda bolita, Tercera bolita)

¿Qué pasaría si en nuestro código quisiéramos acceder a la “B, segunda letra del abecedario”?
Pues como sabemos que la segunda bolita lleva el índice 2, podríamos “llamarla” de la
siguiente manera:

v2Letra = Tubo(2)

¿Y si yo escribiera

vLetra = Tubo(3) ?

Ahora todo el mundo me podría contestar que vLetra cogería el valor de la tercera bolita, que
en este caso es “C”.

Creo que ahora ya podemos tener una idea más clara de lo que es una matriz (espero). Si
no... “bolitas tiene la cosa”... je, je...

MATRICES DE UNA DIMENSIÓN (MATRICES ESTÁTICAS)


Nuestro ejemplo de las bolitas anterior correspondería a una matriz de una dimensión. Es
decir, que sólo hay una “dimensión” de elementos. Quizá esto pueda resultar inicialmente
confuso, pero cuando veamos las matrices de varias dimensiones lo entenderemos mejor. ¡Un
poco de paciencia!

La denominamos estática porque conocemos de antemano el número de elementos que va a


contener la matriz.

2
Visítame en http://siliconproject.com.ar/neckkito/
Vamos a coger una BD en blanco y, en un formulario, vamos a añadir un botón de comando,
que llamaremos cmdMisDiasSemana. Primero indicaremos el código y después lo
analizaremos:

El código asignado al evento “Al hacer click” de ese botón


sería:


Private Sub cmdMisDiasSemana_Click()
'Definimos las variables
Dim numDia As Variant
Dim DiasSem(7)
'Cargamos los valores de la matriz
DiasSem(1) = "Lunes... sueño"
DiasSem(2) = "Martes... casi lunes"
DiasSem(3) = "Miércoles... a la mitad"
DiasSem(4) = "Jueves... aún quedan días"
DiasSem(5) = "Viernes... que no pasa"
DiasSem(6) = "Sábado... ¡por fin!"
DiasSem(7) = "Domingo... casi lunes"
'Pedimos al usuario que introduzca un número
numDia = InputBox("Introduzca un número del 1 al 7", "DIA DE LA SEMANA")
'Controlamos la introducción
If Not IsNumeric(numDia) Then Exit Sub
If numDia < 1 Or numDia > 8 Then Exit Sub
MsgBox DiasSem(numDia), vbExclamation, "ESTE ES MI DÍA"
End Sub

Veamos...

.- Definimos la matriz con su nombre y con su número de elementos. De ahí que hayamos
escrito Dim DiasSem(7)
.- Al igual que hacemos con una variable, que primero la definimos y después le asignamos un
valor, debemos asignar un valor a la matriz. ¿Cuál es la diferencia con una variable “normal”?
Puesto que debemos asignar tantos valores como elementos tiene la matriz.
.- A la hora de asignar valores a la matriz debemos indicar su posición dentro de la misma. De
ahí que escribamos

DiasSem(i) = “Valor”, donde i es el indicador de posición.

.- Para invocar el valor deseado, simplemente llamamos a la matriz con su número de posición.
De ahí que hayamos escrito MsgBox DiasSem(numDia), donde numDia nos lo indicaba el
usuario a través del InputBox.

También os comentaré que podemos definir la matriz indicándole la totalidad del rango. Es
decir, que en lugar de escribir

Dim DiasSem(7)

hubiéramos podido escribir

Dim DiasSem(1 To 7)

También os comentaré que no hace falta empezar por el 1. Si nos interesara podríamos definir
la matriz como

3
Visítame en http://siliconproject.com.ar/neckkito/
Dim DiasSem(6 To 13)

E incluso utilizar valore negativos, como

Dim DiasSem(-3 To 4)

Fácil, ¿verdad?

OTRA MANERA DE CREAR UNA MATRIZ


ESTÁTICA DE UNA DIMENSIÓN
-Función Array()-
Acabamos de ver como declaramos la matriz con el número de elementos y después la
cargábamos con sus valores correspondientes.

Podemos definir las matrices de otra manera, a través de la función Array(). Supongamos que
queremos que el usuario introduzca un valor del 1 al 3, y que aparezca un mensaje con la
correspondencia a un valor en una matriz. Asignaremos el siguiente código al botón cmdFallido
(después explicaré por qué le llamo “Fallido”). Nótese que no voy a introducir instrucciones de
control en el código.


Private Sub cmdFallido_Click()
'Definimos las variables
Dim vValorUser As Single
Dim vValorMatr As Variant
'Asignamos valor a las variables
vValorMatr = Array(10, 20, 30)
vValorUser = InputBox("Introduzca una valor de 1 a 3", "VALOR")
MsgBox vValorMatr(vValorUser), vbInformation, "Valor"
End Sub

Veamos...

.- Hemos definido una variable, vValorMatr, de tipo variant. Esta será nuestra matriz.
.- Hemos introducido sus valores a través de la función Array, de la manera siguiente:
vValorMatr = Array(10, 20, 30)

¿Está todo listo? Pues no. Vamos a hacer una prueba. Hacemos click sobre el botón (con el
formulario en vista formulario) y, cuando nos pide el número, escribimos 1.

El sentido común dice que nos debería devolver 10 (primera posición). Sin embargo, ¿qué
valor nos devuelve?

Volvemos a hacer click sobre el botón e introducimos el valor 3, y, para nuestra sorpresa,
obtenemos el siguiente resultado:

4
Visítame en http://siliconproject.com.ar/neckkito/
¿Demasiado alcohol para nuestro Access, quizás? Je, je... Ahora ya podéis entender lo de
“Fallido”.

Vamos a ver qué ocurre.

Con este sistema, y por defecto, el indicador de posición de la matriz empieza siempre por
cero. Es decir, que nuestra matriz tiene los siguientes valores:

vValorMatr(0) = 10
vValorMatr(1) = 20
vValorMatr(2) = 30

De ahí que, al introducir un 1, el valor devuelto sea 20, y al introducir un 3 no exista esta
posición (y en consecuencia nos salte el error 9).

¿Cómo solucionamos lo anterior? Pues tenemos dos posibilidades:

1.- Creamos un valor ficticio en la primera posición (la cero), de manera que nuestra matriz
quedaría definida de la siguiente manera:
vValorMatr = Array(0, 10, 20, 30)

Ahora sí, si introducimos el valor 1, se nos devolverá el 10.

2.- La segunda posición es indicarle a Access que queremos que nuestra matriz no empiece
con el subíndice cero, sino con el 1. Esta instrucción se la debemos indicar en la cabecera del
módulo a través de la expresión:

Option Base 1

Es decir, que la cosa nos debería quedar así:

5
Visítame en http://siliconproject.com.ar/neckkito/
Si lo hacemos así y probamos el botón veremos que ahora ya no es “fallido”.

Debemos tener esto en cuenta a la hora de confeccionar nuestras matrices para no llevarnos
alguna sorpresa “desagradable”. ¡Ojo!

APROVECHAMOS PARA APRENDER LA FUNCIÓN Rnd() Y LA


INSTRUCCIÓN Randomize
Vamos a programar un juego de azar: el ordenador contra el humano. Vamos a simular que se
lanzan tres dados y que si el humano acierta el valor de una de las tiradas gana el humano; en
caso contrario pierde.

Lo que debemos hacer para nuestra planificación es tener en cuenta los siguiente elementos:
.- El humano debe introducir un valor
.- Los resultados de las tres tiradas vamos a almacenarlos en una matriz
.- Vamos a comparar cada uno de los elementos de la matriz con el valor introducido por el
humano. Si hay coincidencia felicitamos al humano por haber ganado, si no pues lo “hundimos
en la miseria”.

¿Y cómo simulamos una tirada al azar? Pues a través de la instrucción Randomize y la función
Rnd().

La instrucción Randomize se utiliza para iniciar el generador de números aleatorios. La función


Rnd() es la que nos genera el número aleatorio. Para realizar una simulación de números
aleatorios debemos emplear la siguiente estructura:

Randomize
numAleatorio = Int((valorSuperiorDeseado * Rnd) + 1)

Donde valorSuperioDeseado es, lógicamente, la cota superior de los números que queremos
generar.

Dicho de otra manera, y en el caso de un dado, la cota superior sería 6, por lo que
valorSuperiorDeseado = 6

Con todo lo anterior vamos a ver cómo sería un posible código para nuestro juego. Fijaos que
no sólo vamos a felicitar al humano, si acierta, sino que le vamos a decir la primera tirada que
coincida con el resultado igual.

6
Visítame en http://siliconproject.com.ar/neckkito/

Private Sub cmdDados_Click()
'Definimos las variables
Dim vHumano As Single
Dim i As Integer
Dim vDados(3) As Variant
'Iniciamos el proceso aleatorio
Randomize
'Generamos las tiradas y las guardamos en la matriz
vDados(1) = (Int(6 * Rnd) + 1)
vDados(2) = (Int(6 * Rnd) + 1)
vDados(3) = (Int(6 * Rnd) + 1)
'Solicitamos el valor al humano
vHumano = InputBox("¿Por qué valor apostamos -número del 1 al 6-?", "APUESTA")
'Utilizamos un bloque For...Next para realizar las comparaciones, dado que
'i nos dará el índice de la matriz que estamos examinando
For i = 1 To 3
If vHumano = vDados(i) Then
MsgBox "¡Enhorabuena! Tu valor ha salido en la tirada número " & i, vbExclamation,
"GANASTE"
Exit Sub
End If
Next
'Si no ha habido coincidencias "machacamos" al humano
MsgBox "¡Mala suerte!. Los las tiradas han sido: " & vbCrLf _
& vDados(1) & " en la primera tirada" & vbCrLf _
& vDados(2) & " en la segunda tirada y" & vbCrLf _
& vDados(3) & " en la tercera tirada", vbCritical, "NO, NO..."
End Sub

MATRIZ ESTÁTICA SIN SABER LA LONGITUD DE LA MATRIZ. EJEMPLO


DE SUSTITUCIÓN DE CARACTERES
Si no sabemos la longitud de la matriz a priori, o sabemos que la podemos cambiar por
determinadas circunstancias a lo largo del tiempo, es realmente molesto tener que, cada vez,
acordarse de cambiar la longitud de la matriz.

Para evitar este inconveniente podemos referenciar la longitud de la matriz a una variable o
constante, de manera que si cambia dicha variable o constante también nos cambie
automáticamente el tamaño de la matriz.

Un ejemplo de cómo se utiliza lo anterior, y que además es un ejemplo útil en según qué
ocasiones, lo constituye un código de sustitución de caracteres.

La idea es que el usuario introduce un texto en un TextBox (o campo) y acentúa las vocales.
Por los motivos que sean nosotros no queremos que las vocales vayan acentuadas. Podemos
utilizar una matriz para recoger los caracteres “erróneos” y aplicar su sustituto.

Utilizaremos la función Replace(), que ya vimos en el capítulo anterior. También utilizaremos la


función Mid() y la función Len(). Si tenemos dudas os recomiendo echar una ojeada rápida a
dicho capítulo.

Si creamos un cuadro de texto y lo llamamos txtSustituyeTildes podríamos, en el evento


“Después de actualizar”, generar el código siguiente:

7
Visítame en http://siliconproject.com.ar/neckkito/

Private Sub txtSustituyeTildes_AfterUpdate()
'Definimos las constantes y las variables
Const cadAcentos As String = "áéíóú"
Const cadSinAcentos As String = "aeiou"
'Definimos las matrices referenciando su longitud a la
longitud
'de las cadenas de texto de las constantes
Dim mtrAcentos(Len(cadAcentos))
Dim mtrSinAcentos(Len(cadSinAcentos))
Dim i As Integer
Dim vText As String
'Asignamos valores a las matrices a través de un bloque For...Next
'El bloque va recorriendo uno a uno los caracteres.
For i = 1 To Len(cadAcentos)
mtrAcentos(i) = Mid(cadAcentos, i, 1)
Next
For i = 1 To Len(cadSinAcentos)
mtrSinAcentos(i) = Mid(cadSinAcentos, i, 1)
Next
'Cogemos el valor escrito por el usuario
vText = Me.txtSustituyeTildes.Value
'Recorremos uno a uno los caracteres "prohibidos" y, si encontramos
'un carácter acentuado, lo sustituimos en vText
For i = 1 To Len(cadAcentos)
vText = Replace(vText, mtrAcentos(i), mtrSinAcentos(i))
Next
'Reescribimos el texto sin acentos
Me.txtSustituyeTildes.Value = vText
End Sub

Como podemos ver, hay una equivalencia de posiciones entre vocal acentuada o no; es decir,
la “á” ocupa la primera posición de la matriz mtrAcentos, y la “a” ocupa la primera posición de
la matriz mtrSinAcentos. Es necesaria esta correspondencia porque, al utilizar la función
Replace() referenciada al número de posición, si el primer valor de la matriz mtrSinAcentos
fuera “j” lo que haría el código es sustituirnos todas las “á” por “j”.

Vemos que hemos definido el tamaño de la matriz en función de la longitud de las constantes.
¿Por qué? Ni que decir tiene que hubiéramos podido definirlas así:

Dim mtrAcentos(5)
Dim mtrSinAcentos(5)

Imaginaos que las definimos así. Ponemos la BD en marcha para la introducción de datos y
cuando el jefe revisa la información nos llama “contento” a su despacho y nos dice: “hay
valores con acentos, y esa no era la idea, ¿verdad?”.

Miramos lo que nos indica y nos encontramos todas las vocales mayúsculas acentuadas. ¿Qué
debemos hacer? Pues redefinir nuestras constantes de la siguiente manera:

Const cadAcentos As String = "áéíóúÁÉÍÓÚ"


Const cadSinAcentos As String = "aeiouAEIOU"

La cosa ya está arreglada. Ponemos de nuevo la BD a funcionar y... ¡error 9! Nuestra matriz

8
Visítame en http://siliconproject.com.ar/neckkito/
debería tener 10 elementos pero como nos hemos olvidado del cambio sigue definida con 5.
Vuelta a cambiar el código... a no ser que el tamaño de la matriz esté referenciado a la
longitud de las constantes, en cuyo caso... “no problem”.

Fijaos cómo hemos rellenado los valores de las matrices.


Usualmente el bloque For...Next es una excelente
herramienta para este tipo de menesteres.

Finalmente volvemos a recorrer los valores de la matriz,


aplicándolo al texto escrito. Si el texto tiene un valor que
aparece en mtrAcentos lo sustituye por su equivalente en
mtrSinAcentos. También utilizamos un bloque For...Next
para recorrer los elementos de la matriz.

También fácil... o quizá no tanto... no sé...

DETERMINANDO EL TIPO DE DATOS DE LA MATRIZ


En un capítulo anterior comentábamos los diferentes tipos de datos y sus características.
Vamos a profundizar un poquito más en ello, de manera que aprenderemos ahora que existen
unos caracteres que nos convierten la variable en el tipo de datos en el que estamos
interesados.

Es decir, nosotros escribíamos, por ejemplo,:

Dim i As Long para indicar que la variable i era un entero largo (Long). Pero si escribimos:

Dim i&

también estamos indicando que i es un entero largo, puesto que el ampersand nos representa
la tipología de los enteros largos.

¿Por qué yo, personalmente, prefiero utilizar la primera fórmula? Porque no me sé de memoria
todos los símbolos que representan los tipos de datos (maravilloso F1...). Además, para
alguien que lee el código, es mucho más cómodo leer <Dim i As Long> y saber que i es un
entero largo que no leer <Dim i&>... y quedarse pensando: “¿qué tipo de valor representaba
el ampersand?”.

¿Y cuáles son esos “símbolos mágicos”? En la siguiente tabla los tenéis detallados (ojo, no
todos los tipos tienen carácter de declaración de tipo):

TIPO SUFIJO
Integer %
Long &
Single !
Double #
Currency @
Decimal &
String $

¿Y por qué necesitamos lo anterior? Pues porque para declarar valores dentro de una matriz e
indicar el tipo de datos es la mejor manera de que disponemos.

9
Visítame en http://siliconproject.com.ar/neckkito/
Por ejemplo, supongamos que queremos definir una matriz que nos dé unos puntos de corte
para rangos explícitamente de enteros. ¿Cómo le asignaríamos los valores, a la vez que
indicamos que dichos valores son enteros? Pues de la manera siguiente:

Dim miMatriz() as Variant


miMatriz = Array (0%, 50%, 100%)

Lógicamente, si deseamos que sean enteros largos


escribiríamos:

Dim miMatriz() as Variant


miMatriz = Array (0&, 50&, 100&)

Os recuerdo que el sistema de sufijos puede ser utilizado igualmente en un código que no
afecte a los elementos de la matriz. Por ejemplo, estos dos códigos son equivalentes:

Código 1:

Private Sub...
Dim vUser As Integer
Dim vText As String
Dim i As Long
'Resto de código
End Sub

Código 2

Private Sub...
Dim vUser%
Dim vText$
Dim i&
'Resto de código
End Sub

MATRICES MULTIDIMENSIONALES (MATRICES ESTÁTICAS)


Las matrices pueden tener más de una dimensión. Vamos a desarrollar este apartado con una
matriz de dos dimensiones.

Empecemos por definir la matriz. La estructura de dicha definición sería la siguiente:

miMatriz(elementosPrimeraDimension, elementosSegundaDimension)

La “mecánica” de funcionamiento es, en el fondo, prácticamente igual a lo que hemos


explicado antes. La única “dificultad” es la imaginación del supuesto.

Expliquémoslo a través de un ejemplo:

Imaginemos que tenemos tres “contrincantes” que se enfrentan en tres pruebas diferentes.
Los resultados de esas pruebas vienen dados por 1 (primer lugar), 2 (segundo lugar) y 3
(tercer lugar). Nuestros contrincantes son:

– Donald

10
Visítame en http://siliconproject.com.ar/neckkito/
– Goofy
– Mickey

Vamos a crear una matriz de dos dimensiones que nos recoja la siguiente información:

CONTRINCANTE 1ª PRUEBA 2ª PRUEBA 3ª PRUEBA


Donald 1 1 3
Goofy 3 2 1
Mickey 2 3 2

Nuestro código pedirá al usuario que introduzca un nombre del contrincante y un número de
prueba, y le devolveremos un mensaje diciendo en qué posición quedó.

El código podría ser el siguiente:


Private Sub cmdResultadoPruebas_Click()
'Definimos las variables
Dim vConc As Byte
Dim vPrueba As Byte
Dim mtrResult(3, 3) As Variant
'Cargamos la matriz
mtrResult(1, 1) = 1
mtrResult(1, 2) = 1
mtrResult(1, 3) = 3
mtrResult(2, 1) = 3
mtrResult(2, 2) = 2
mtrResult(2, 3) = 1
mtrResult(3, 1) = 2
mtrResult(3, 2) = 3
mtrResult(3, 3) = 2
'Solicitamos la información al usuario
vConc = InputBox("Seleccione contrincante: 1 para Donald / 2 para Goofy / 3 para Mickey")
vPrueba = InputBox("Seleccione un número de prueba (1 al 3)")
'Mostramos el resultado
MsgBox "El contrincante seleccionado quedó el número " & mtrResult(vConc, vPrueba) _
& " en la prueba seleccionada", vbInformation, "POSICIÓN"
End Sub

Como podemos ver, no hay mayor dificultad.

Un ejemplo para matriz de tres dimensiones


Os planteo un ejemplo de matriz de tres dimensiones. Como idea os apunto que intentéis
desarrollarlo vosotros antes de mirar la solución, a ver dónde nos surgen las “dudas
existenciales”. Lo único que os aconsejo es que tengáis a mano una aspirina, por si de tanto

11
Visítame en http://siliconproject.com.ar/neckkito/
pensar nos coge dolor de cabeza... je, je... Vamos a olvidarnos de poner código de control para
introducción de valores, dado que lo que nos interesa es centrarnos en el manejo de la matriz.

También os indico que no tengo ni idea de medicina, por lo


que los valores que pongo en el ejemplo son totalmente
inventados y “cualquier parecido con la realidad es pura
coincidencia”.

Vayamos a por el enunciado:

Somos los técnicos de un laboratorio que estamos realizando un estudio sobre la eficacia de
una pastilla para dormir. El estudio lo realizamos sobre dos sujetos (sujeto 1 / sujeto 2), a los
cuales administramos, al sujeto uno, la medicina, y al sujeto dos, un placebo. Lo que
analizamos es su nivel de endorfinas y su nivel de adrenalina. El estudio se desarrolla durante
tres semanas.

Los resultados obtenidos son los siguientes

SUJETO ELEMENTO 1ª SEMANA 2ª SEMANA 3ª SEMANA


1 Endorfinas 50mm 60mm 80mm
Adrenalina 40mm 35mm 20mm
2 Endorfinas 45mm 55mm 50mm
Adrenalina 43mm 39mm 40mm

La idea es que el usuario introduzca un código numérico (porque esta investigación es súper-
secreta, por supuesto) de tres dígitos, y el código le devuelva el valor buscado.

Es decir, que si introduce el código 123 esté pidiendo:

– 1 = Sujeto 1
– 2 = Elemento 2 = Adrenalina
– 3 = Semana = Semana 3

y el resultado obtenido, en este caso, sería “20mm”.

Ánimo, que es fácil!!!!

El código podría ser el siguiente:


Private Sub cmdMatriz3D_Click()
'Declaramos las variables
Dim vCod As Variant
Dim Dig1 As Byte, Dig2 As Byte, Dig3 As Byte
Dim mtrDatos(2, 2, 3) As Variant
'Construimos la matriz
mtrDatos(1, 1, 1) = "50mm"
mtrDatos(1, 1, 2) = "60mm"
mtrDatos(1, 1, 3) = "80mm"
mtrDatos(1, 2, 1) = "40mm"
mtrDatos(1, 2, 2) = "35mm"
mtrDatos(1, 2, 3) = "20mm"

12
Visítame en http://siliconproject.com.ar/neckkito/
mtrDatos(2, 1, 1) = "45mm"
mtrDatos(2, 1, 2) = "55mm"
mtrDatos(2, 1, 3) = "50mm"
mtrDatos(2, 2, 1) = "43mm"
mtrDatos(2, 2, 2) = "39mm"
mtrDatos(2, 2, 3) = "40mm"
'Solicitamos el código al usuario
vCod = InputBox("Introduzca el código de tres dígitos",
"CÓDIGO")
'Extraemos los dígitos convirtiéndolos a Byte
Dig1 = CByte(Left(vCod, 1))
Dig2 = CByte(Mid(vCod, 2, 1))
Dig3 = CByte(Right(vCod, 1))
'Mostramos el valor en un MsgBox
MsgBox "El resultado del código ha sido " & mtrDatos(Dig1, Dig2, Dig3)
End Sub

¿Nos metemos con una de cuatro dimensiones...? Je, je...

MATRICES DINÁMICAS

(y aprovechamos para aprender algunas SQL simples)


Hasta ahora hemos visto matrices en las que sabíamos previamente el número de elementos
que iba a contener la matriz, ya fuera de manera directa o de manera indirecta.

Sin embargo, hay ocasiones en que nos es imposible saber, a priori, cuántos elementos va a
contener nuestra matriz. Es más, puede que iniciemos el proceso sin saberlo, en mitad del
proceso ya se pueda delimitar el número de elementos pero, más adelante, ese número puede
volver a cambiar.

Para estos casos... matrices dinámicas.

Las matrices dinámicas se declaran igual que las estáticas, pero sin indicar el número de
elementos. Es decir, podríamos declarar una matriz así:

Dim miMatriz()

Vemos que dejamos los dos paréntesis sin ningún valor entre ellos. Ya tenemos declarada
nuestra matriz.

A la hora de rellenarla hay que decirle al código, de alguna manera, que vamos a proceder a
rellenar nuestra matriz. Para ello utilizamos la instrucción ReDim.

En definitiva, el proceso “en lenguaje vulgar” sería:


Private Sub...
'Ojo, Access: tengo un tubo pero no sé de qué lo voy a llenar
Dim miMatriz()
'Código
'Ojo, Access: ahora ya tengo claro cómo voy a llenar el tubo
Redim miMatriz(...)
'Código

13
Visítame en http://siliconproject.com.ar/neckkito/
End Sub

Sigamos profundizando un poco más en las “entrañas” de


las matrices dinámicas.

Vamos a imaginarnos que utilizamos Access para controlar


a los socios de un club, y organizamos una comida. A la
comida pueden venir miembros e invitados no socios (los
cuales no tenemos en la base de datos, lógicamente), y que
para asistir a la comida es necesario recoger una invitación
personalizada.

¿Es necesario crear una tabla con un formulario, darle el formato correspondiente y demás
para dar de alta los invitados no socios? Si no nos interesa eso podemos utilizar matrices
dinámicas.

Para preparar el ejemplo vamos a seguir los siguientes pasos:

1.- Creamos una tabla, que llamaremos TInvitacion, con un sólo campo, [NomInvit], de tipo
texto. No fijamos ninguna clave principal.

2.- Crearnos un informe, que llamaremos RInvitacion, sobre nuestra tabla TInvitacion, de
acuerdo con la siguiente estructura:

¡Ojo! ¡No olvidéis insertar un salto de página aquí!!!!!

Fijaos que:
– Hemos eliminado la sección del encabezado y pie del informe
– Hemos utilizado la sección detalle para poder imprimir una secuencia de registros
– Hemos insertado un salto de página para que no nos salgan todos los nombres
consecutivamente en la misma hoja, sino que nos deben salir a nombre por hoja.

3.- En un formulario insertamos un botón de comando, y en él asignamos el siguiente código:

...
Private Sub cmdInvitacion_Click()
'Declaramos las variables
Dim sqlBorra As String, sqlAnexa As String
Dim mtrInvit() As Variant

14
Visítame en http://siliconproject.com.ar/neckkito/
Dim numInv As Variant
Dim i As Long
'Solicitamos cuántas invitaciones queremos imprimir
numInv = InputBox("¿Nº de invitaciones a imprimir?",
"Nº COMENSALES")
numInv = CLng(numInv)
'Redimensionamos la matriz
ReDim mtrInvit(numInv)
'Rellenamos la matriz
For i = 1 To numInv
mtrInvit(i) = InputBox("¿Nombre invitado?",
"INVITADO")
Next
'Eliminamos los warnings por ejecutar consultas, en este caso
DoCmd.SetWarnings False
'Actualizamos la información en la tabla TInvitados a través de
'una SQL de datos anexados
For i = 1 To numInv
sqlAnexa = "INSERT INTO TInvitacion(NomInvit) SELECT " _
& """" & mtrInvit(i) & """" & "AS Invitado"
DoCmd.RunSQL sqlAnexa
Next
'Imprimimos las invitaciones
DoCmd.OpenReport "RInvitacion", acViewPreview
'Eliminamos los registros de TInvitaciones, para dejar la tabla sólo en estructura
sqlBorra = "DELETE FROM TInvitacion"
DoCmd.RunSQL (sqlBorra)
'Reactivamos los warnings
DoCmd.SetWarnings True
End Sub

Ahora ya puede venir un socio y decirnos: “quiero dos entradas para mi mujer y yo”. Le damos
al botón, redimensionamos la matriz, cogemos los datos e imprimimos las entradas... simple y
efectivo.

Vamos a echar un vistazo al código:

– Declaramos la matriz sin asignarle índice. Cuando nuestro socio nos dice que quiere dos
entradas redimensionamos esa matriz a través de la línea:

ReDim mtrInvit(numInv)

– Rellenamos la matriz a través de los nombres que nos da. Para ello utilizamos un bucle
FOR...NEXT, que nos va pidiendo los nombres y aumentando el índice de cada elemento de la
matriz en una unidad a través de la variable i

– Para evitar los molestos mensajes de “Va a anexar 2 filas a la tabla...” desactivamos los
warnings a través de

DoCmd.SetWarnings False

– Creamos la SQL que nos va a anexar los datos que ya tenemos almacenados en la
matriz en la tabla TInvitado. Para ir recorriendo los elementos de la matriz utilizamos de nuevo
un bloque FOR...NEXT

15
Visítame en http://siliconproject.com.ar/neckkito/
– La SQL para anexar los datos vemos que es una cadena de texto, que consta de las
palabras clave: INSERT INTO / Identificamos la tabla (Identificamos el campo) / SELECT
valorAInsertar / AS nombreInventado.

El valorAInsertar nos lo da la matriz, a través de los valores


guardados. Lo que yo he llamado nombreInventado es
simplemente un nombre aclaratorio, para que al leer el
código, nos aporte información adicional. Podría haber
escrito, por ejemplo, & """" & mtrInvit(i) & """" & "AS
PersonaInvitada", y el código nos hubiera funcionado
perfectamente igual.

El valor que debe insertarse debe estar entre comillas. Es decir, que si quisiéramos insertar
sólo un nombre, por ejemplo Neckkito, y utilizaramos el objeto consulta para crear una
consulta de datos anexados, deberíamos escribir:

INSERT INTO TInvitacion(NomInvit) SELECT “Neckkito” AS Invitado;

Esas comillas dobles no se pueden escribir “directamente” en código, puesto que el propio
código entendería que estamos finalizando la cadena, lo cual nos daría error. Para indicar, en
código VB, que queremos que las comillas dobles sean “literales” debemos utilizar cuatro
comillas dobles, como hemos hecho en & """" & mtrInvit(i) & """"

– Ejecutamos la SQL a través de la sentencia:

DoCmd.RunSQL sqlAnexa

– La SQL para borrar los registros de una tabla tiene una estructura muy simple:

“DELETE FROM nombreTabla”

Esto tan simple hace desaparecer para nunca jamás todos los registros, por lo que os
recomiendo que seáis muy cuidadosos antes de ejecutar una SQL de este tipo.

– Finalmente reactivamos los warnings (más que recomendable, obligado) a través de:

DoCmd.SetWarnings True

Digo obligado porque si desactivamos los warnings no sólo los desactivamos para este código,
sino para toda la BD, y pueden producirse errores en otro procedimiento o módulo y Access no
nos avisará, con lo que si el código no nos funciona no sabremos por qué (no sabremos qué
error se está produciendo).

ReDim Preserve
Imaginemos que nuestro socio nos dice: “Vamos a ser dos”. Nosotros indicamos al código que
2 van a ser los elementos de la matriz y a continuación nuestro “querido” socio nos espeta:
“Perdón. No seremos dos, seremos tres”.

Pues a cancelar la operación y a comenzar de nuevo.

Sin embargo, si prevemos esta posibilidad podemos utilizar la palabra clave Preserve para no
tener que comenzar de cero. ¿Y qué nos “hace” el Preserve?

Supongamos que tenemos esta matriz:


Dim matriz()

16
Visítame en http://siliconproject.com.ar/neckkito/
Redim matriz(2)
matriz(1) = “Neckkito”
matriz(2) = “Access”

Si unas líneas más abajo tengo que volver a redimensionar


la matriz escribiría:

Redim matriz(3)
matriz(1) = “Octavio”
matriz(2) = “Word”
matriz(3) = “Excel”

Si yo quiero obtener el valor de matriz(2), ¿qué valor obtendré? Pues obtendré “Word”. ¿Y qué
pasa con el valor “Access”? Pues que se ha “evaporado”. En definitiva, hemos sobrescrito los
valores de la matriz.

Pero si tengo:

Dim matriz()
Redim matriz(2)
matriz(1) = “Neckkito”
matriz(2) = “Access”

y líneas de código más abajo utilizo el Preserve, podré escribir:

Redim Preserve matriz(3)


matriz(3) = “Octavio”

¿Qué consigo con eso? Que se me guarden los dos valores iniciales, y puedo añadir un valor
más a la matriz sin sobrescribir nada.

Y gracias a eso nuestro socio del club puede rectificarnos al alza el número de comensales sin
problema.

¿Cómo sería ese código? Pues sería una cosa así:


Private Sub cmdPreserve_Click()
'Declaramos las variables
Dim sqlBorra As String, sqlAnexa As String
Dim mtrInvit() As Variant
Dim numInv As Variant, numInvAdicional As Variant
Dim i As Long
Dim resp As Integer
'Solicitamos cuántas invitaciones queremos imprimir
numInv = InputBox("¿Nº de invitaciones a imprimir?", "Nº COMENSALES")
numInv = CLng(numInv)
'Redimensionamos la matriz
ReDim mtrInvit(numInv)
'Rellenamos la matriz
For i = 1 To numInv
mtrInvit(i) = InputBox("¿Nombre invitado?", "INVITADO")
Next
'Solicitamos si deben añadirse más invitados
resp = MsgBox("¿Añadimos más invitados?", vbQuestion + vbYesNo, "AÑADIR INVITADOS")
'Si la respuesta es Sí redimensionamos de nuevo la matriz

17
Visítame en http://siliconproject.com.ar/neckkito/
If resp = vbYes Then
numInvAdicional = InputBox("¿Cuántos invitados más debemos añadir?", "AÑADIR")
ReDim Preserve mtrInvit(numInv + numInvAdicional)
For i = 1 To numInvAdicional
mtrInvit(numInv + i) = InputBox("¿Nombre
invitado?", "INVITADO")
Next
numInv = numInv + numInvAdicional
End If
'Eliminamos los warnings por ejecutar consultas, en
este caso
DoCmd.SetWarnings False
'Actualizamos la información en la tabla TInvitados a
través de
'una SQL de datos anexados
For i = 1 To numInv
sqlAnexa = "INSERT INTO TInvitacion(NomInvit) SELECT " _
& """" & mtrInvit(i) & """" & "AS Invitado"
DoCmd.RunSQL sqlAnexa
Next
'Imprimimos las invitaciones
DoCmd.OpenReport "RInvitacion", acViewPreview
'Eliminamos los registros de TInvitaciones, para dejar la tabla sólo en estructura
sqlBorra = "DELETE FROM TInvitacion"
DoCmd.RunSQL (sqlBorra)
'Reactivamos los warnings
DoCmd.SetWarnings True
End Sub

Analicemos el código a través de valores numéricos

Supongamos que los invitados iniciales son 2. Tendremos:


– numInv = 2
– mtrInvit(2)

Si no hay que añadir más invitados el código salta el bloque If resp = vbYes Then, con lo que
estos valores no se modifican → Nuestra SQL añade 2 registros.

Si hay que añadir más invitados (por ejemplo, 3 más) tenemos que, inicialmente:
– numInv = 2
– mtrInvit(2)

Comienza el If...
– numInvAdicional = 3
– …....... Nuestra matriz se redimensiona a
ReDim Preserve mtrInvit(numInv + numInvAdicional) → mtrInvit(2+3) → mtrInvit(5)

– La asignación de valores a la matriz se realiza por un bucle FOR...NEXT, de manera que


mtrInvit(numInv + i) = InputBox("¿Nombre invitado?", "INVITADO")

Primera iteracción → i=1


mtrInvit(2+1) → mtrInvit(3) → Asignamos el tercer lugar de índice al elemento de la matriz

Segunda iteracción → i=2


mtrInvit(2+2) → mtrInvit(4)

18
Visítame en http://siliconproject.com.ar/neckkito/
Tercera iteracción → i=3
mtrInvit(2+3) → mtrInvit(5)

Perfecto: redimensionamos nuestra matriz a 5 elementos y


ahora tenemos:
– 2 elementos iniciales
– 3 elementos adicionales

– Fijamos el nuevo valor para numInv, a través de:


numInv = numInv + numInvAdicional → numInv = 2 + 3 =
5

End If

- Como ahora numInv=5 → Nuestra SQL actualiza 5 registros

Luego nuestra impresión de la invitación será, simplemente, perfecta... Je, je...

Quizá este código, si no se le coge el “punto”, sea un poco complicado. Yo os animo a seguirlo
imaginando su funcionamiento con valores numéricos, y ver qué resultado obtenemos.

¡Ánimo!

ALGUNOS ELEMENTOS INTERESANTES DE LA MATRIZ: las funciones


UBound() y LBound() y la instrucción Erase
Puede interesarnos, en alguna ocasión, saber cuál es el índice máximo y mínimo de una
matriz. Para saber lo anterior tenemos las funciones UBound y LBound.

UBound nos proporciona el índice máximo; LBound nos proporciona el índice mínimo.

Supongamos que tenemos la siguiente matriz:

miMatriz(-10 To 10) As Integer

Si asignamos el siguiente código a un botón obtendremos dichos valores:


Private Sub cmdUBoundLBound_Click()
Dim miMatriz(-10 To 10) As Integer
MsgBox "Cota inferior: " & LBound(miMatriz) & vbCrLf & _
"Cota superior: " & UBound(miMatriz), vbInformation, "COTAS"
End Sub

Imaginemos que, por los motivos que sean, tenemos dos matrices dinámicas, y que una de
ellas (matriz1) coge valores que dependen de diferentes variables o combinaciones de
variables. Esta matriz sufre variaciones de número de elementos a lo largo del desarrollo del
código.

Finalmente, y porque así lo necesitamos, tenemos que crear una matriz (matriz2) con el
mismo número de elementos que la primera matriz.

¿Cómo podemos saber cuántos elementos conforman nuestra primera matriz? Pues aplicando
estas dos funciones.

19
Visítame en http://siliconproject.com.ar/neckkito/
Veamos:


Private Sub cmdNumElementos_Click()
'Declaramos las variables
Dim matriz1() As Integer
Dim matriz2() As Integer
Dim numElem As Integer, i As Integer
Dim cotaSup As Integer, cotaInf As Integer
Dim valorUser As Integer, vMult As Integer
'Solicitamos un valor al usuario
valorUser = InputBox("Introduzca un número del 1 al 10", "NÚMERO")
'Creamos un valor aleatorio entre el 1 y el 10
Randomize
vMult = Int((10 * Rnd) + 1)
'Redimensionamos la matriz
ReDim matriz1(valorUser * vMult)
'Rellenamos la matriz con valores cualquiera
For i = 1 To (valorUser * vMult)
matriz1(i) = 2 * i
Next
'Sacamos las cotas
cotaSup = UBound(matriz1)
cotaInf = LBound(matriz1)
'Obtenemos el número de elementos
numElem = cotaSup - cotaInf
'Ya podemos redimensionar nuestra segunda matriz
ReDim matriz2(numElem)
'Traspasamos los valores entre matrices
For i = 1 To numElem
matriz2(i) = matriz1(i)
Next
'Hacemos una comprobación
MsgBox "El elemento número " & valorUser & " de la matriz1 es " & matriz1(valorUser) & _
vbCrLf & vbCrLf & "El mismo elemento de la matriz2 es " & matriz2(valorUser) & _
vbCrLf & vbCrLf & "El número de elementos de la matriz2 es " & numElem,
vbInformation, "DATOS MATRIZ"
'Liberamos la memoria ocupada por la matriz1
Erase matriz1
End Sub

Como vemos, se solicita un valor del 1 al 10 al usuario; se genera un número aleatorio del 1 al
10, y se dimensiona la matriz con el producto de estos dos operadores. ¿Cuál será la
dimensión de la matriz? Pues es imposible saberlo de antemano, ni siquiera en la repetición del
código.

Sacamos pues la cota superior y la inferior, y a través de una simple resta conseguimos el
número de elementos; es decir:

cotaSup = UBound(matriz1)
cotaInf = LBound(matriz1)
'Obtenemos el número de elementos
numElem = cotaSup - cotaInf

20
Visítame en http://siliconproject.com.ar/neckkito/
Tened en cuenta que podemos utilizar esta súper-fórmula porque, tal y como comentábamos
en los primeros epígrafes de este capítulo, la matriz, en este caso, asigna el valor 0 a la cota
inferior.

Si hubiéramos sentado lo de <Option Base 1> tened en


cuenta que la fórmula debería haber sido
numElem = cotaSup – cotaInf + 1

Hemos establecido una comprobación muy simple para ver


que los valores se han traspasado correctamente entre
matrices.

Finalmente, la última línea de código incluye la instrucción Erase. La función de la instrucción


Erase depende del tipo de matriz con el que estamos operando.

Si operamos con una matriz dinámica, Erase lo que consigue es liberar el espacio de memoria
que ocupaban los valores de dicha matriz. Dicho de otra manera, y en nuestro caso, vuelve a
dejar la matriz definida como

matriz1()

y si quisiéramos volverla utilizar tendríamos que redimensionarla otra vez y asignarle nuevos
valores.

Si la matriz, en cambio, es estática, la acción de Erase depende del tipo de datos que contenga
la matriz. Os copio lo que dice la ayuda de Access sobre este tema:

Tipo de matriz Efecto de Erase sobre elementos de una matriz fija


Matriz numérica fija Establece cada elemento a cero.
Matriz de cadena fija Establece cada elemento a una cadena de longitud cero ("").
(longitud variable)
Matriz de cadena fija Establece cada elemento a cero.
(longitud fija)
Matriz de Variante fija Establece cada elemento a Empty.
Matriz de tipos definidos Establece cada elemento como si se tratara de una variable separada.
por el usuario
Matriz de objetos Establece cada elemento al valor especial Nothing.

Y ACABAMOS EL TEMA DE MATRICES


Hemos dado un buen repaso al tema de matrices, y hemos aprovechado para aprender
algunos elementos y sistemáticas nuevas que podremos utilizar en nuestra BD (aunque no
trate de matrices).

Como siempre, y no me cansaré de insistir en esto (hacía tiempo que no decía esto), además
de aprender lo que es el “tema” en sí estaría muy satisfecho si podéis coger ideas de
“mecánicas de código” para aplicarlas en vuestras BD's.

Creo que con estos 9 capítulos y lo que hemos aprendido en ellos ya estamos en disposición de
desarrollar algunas “cosillas interesantes y chulas” en nuestras BD's. Sin embargo, aún nos

21
Visítame en http://siliconproject.com.ar/neckkito/
quedan por aprender un par de “cosillas interesantes y chulas” más...

¡Ánimo, y suerte!

22
Visítame en http://siliconproject.com.ar/neckkito/
CURSO DE VB

CAPÍTULO 101

Índice de contenido
OBJETOS DE ACCESO A DATOS (I)................................................................................................2
OBJETOS DE ACCESO A DATOS: DAO Y ADO........................................................................2
DAO............................................................................................................................................2
Un poco de teoría...................................................................................................................2
Registro de la librería DAO....................................................................................................4
Un poco de práctica................................................................................................................5
Base de datos actual...........................................................................................................5
Referencia al espacio de trabajo: Workspace....................................................................5
Referencia a las tablas.......................................................................................................7
Referencia a las consultas..................................................................................................9
Referencia a los campos..................................................................................................10
Otros objetos que existen y que no vamos a ver aquí......................................................12
ALGUNOS CÓDIGOS INTERESANTES SOBRE LO QUE HEMOS VISTO.................12
Creación de una tabla.......................................................................................................13
Variación sobre el tema...............................................................................................18
Creación de una tabla: sistema rápido.............................................................................20
Creación de una consulta.................................................................................................22
PARA FINALIZAR ESTE CAPÍTULO........................................................................................24

1 La BD donde están los ejemplos de este capítulo os la podéis bajar aquí.

1
Visítame en http://siliconproject.com.ar/neckkito/
OBJETOS DE ACCESO A DATOS (I)
Vamos a ver en este capítulo lo que se denominan “objetos
de acceso a datos”, que son DAO y ADO.

Según cómo se mire este es un tema “complicado”; según


cómo se mire, si se pillan los “fundamentos”, pues tampoco
es tan difícil operar con estos sistemas. Con ello quiero
decir que mi consejo, si seguís este capítulo, es que
analicéis con detenimiento lo que en él se explica y los
códigos de ejemplo, no tanto para “aprender” lo que hace el
código sino para “captar” la mecánica de cómo se utilizan
los distintos elementos que nos van a aparecer.

Para empezar este capítulo vamos a realizar una cosa inicial muy simple:

1.- Vamos, en una nueva base de datos, a crear una tabla, que llamaremos TTrabajadores.
Incluiremos dos campos, de la siguiente manera:

– [DNI] → Numérico y clave principal


– [Nombre] → Texto

Introducimos manualmente tres registros a lo rápido, como estos:

2.- Creamos un formulario sobre nuestra tabla TTrabajadores.


Vamos a llamarlo FTrabajadores.

3.- Insertamos, en la cabecera del formulario, un botón de


comando. Vamos a programar dicho botón con posterioridad.

Y, por ahora, lo dejamos aquí. Ya lo iremos utilizando a


medida que vayamos avanzando en el capítulo.

Y sin más preámbulos, os presento a...

OBJETOS DE ACCESO A DATOS: DAO Y ADO


Para poder manipular datos (y cuando digo manipular datos me refiero a buscar datos,
actualizar datos, añadir datos, modificar datos...) en tablas y consultas nos servimos de lo que
se denominan “objetos de acceso a datos”.

Tenemos dos modelos de “objetos de acceso a datos”: DAO y ADO. Vamos a ver en qué
consisten:

DAO

Un poco de teoría
Vamos con un poco de teoría, para aquellos a los que les guste esto de fardar de “siglas en
conversaciones sobre Access” (pronto entenderéis por qué digo esto de las siglas... ji, ji...) La
biblioteca de objetos DAO (Data Access Objects) se enmarca en lo que podríamos denominar
dos bloques, a los que se les suele llamar “espacios de trabajo”.

Pensad en todo momento que la idea es acceder a datos, ya sea en una base de datos local o
una base de datos remota. Y claro, cuando empezamos a hablar de estas cosas aparece no

2
Visítame en http://siliconproject.com.ar/neckkito/
sólo la figura de Access, sino que también se suman a la fiesta diferentes sistemas gestores de
bases de datos.

El primero de esos “marcos” es el espacio de trabajo


ODBCDirect. El utilizar este espacio hace que sea posible
acceder a servidores de bases ODBC sin tener que pasar el
motor de la base de datos de Microsoft Office.

Por si puede clarificaros algo os copio lo que dice la ayuda


del propio Windows (como SO -Sistema Operativo-) sobre
utilizar orígenes de datos:

<<Puede utilizar la Conectividad abierta de base de datos de orígenes de datos (ODBC) para
tener acceso a datos desde una gran variedad de sistemas de administración de bases de datos. Por
ejemplo, si tiene un programa que obtiene acceso a los datos de una base de datos de SQL,
Orígenes de datos (ODBC) le permitirá usar el mismo programa para tener acceso a los datos de
una base de datos de Visual FoxPro. Para ello, debe agregar componentes de software al sistema,
llamados controladores. Orígenes de datos (ODBC) le ayuda a agregar y a configurar estos
controladores.>>

En definitiva, este espacio de trabajo estaría recomendado para ejecutar consultas, ejecutar
procedimientos (Sub) o funciones (Function) almacenados o funciones específicas de ODBC en
un servidor remoto.

Por cierto, ODBC significa Open DataBase Connectivity.

El segundo “marco” es el espacio de trabajo Microsoft Jet. A través de un controlador ISAM


nos permite acceder a bases de datos como Access, también a servidores de bases de datos
ODBC y a otras fuentes de datos externas que pueden ser tratadas como “bases de datos”,
como por ejemplo Excel, dBase, Paradox, etc.

Y, por cierto, ISAM significa Indexed Sequential Access Method.

Existe lo que se denomina una “Jerarquía de los objetos DAO”, donde se puede apreciar la
estructura de este modelo DAO. No entraremos en sus detalles (porque eso sí que sería teoría
pura y dura). Sólo nos vamos a quedar con un par de ideas:

– Existe un “padre” del que penden el resto de elementos. Es el objeto DBEngine. Este
objeto es único (no puede haber varios).
– Bajo DBEngine encontramos dos objetos: Error y Workspace)
– Bajo DBEngine podemos encontrar toda una serie de colecciones, que a su vez incluyen
todos los objetos de la colección
– Existe una colección que se denomina Properties (menos los objetos Error). Estas
colecciones Properties se refieren a todos los objetos DAO.
– Podemos encontrar “propiedades” y “métodos” en los objetos DAO

Como veis, la cosa es un poco confusa porque estos términos se entrecruzan, y resulta un
poco difícil explicarlo (¡y que conste que intento hacerlo lo mejor posible!).

Vamos a ver un ejemplo: vamos a manipular un método de un objeto DAO. El objeto será
DBEngine, y el método será CompactDatabase (no hace falta que os diga qué significa eso).
Una vez tenemos claro qué es cada cosa podemos escribir:


Private Sub...

3
Visítame en http://siliconproject.com.ar/neckkito/
DBEngine.CompactDatabase “c:\MisBDs\miSuperBD.accdb”
End Sub
….

Si lo que queremos es utilizar una propiedad la sistemática


sería similar: objeto.propiedad. Por ejemplo:


Private Sub...
MsgBox “Mi versión del motor Microsoft Jet es “ &
DBEngine.Version
End Sub

¿Cómo distinguirlos? Afortunadamente el editor de VB nos lo pone fácil. Si nos situamos en


dicho editor y escribimos DBEngine. veremos que automáticamente nos aparece un “chivato”
que nos muestra los métodos y propiedades aceptados para ese objeto. Es decir, que nos
debería aparecer una cosa así:

La “manita con la tarjeta” (por llamarlo de una manera) indica que nos estamos refiriendo a
una propiedad; la “goma de borrar que corre” (también por llamarlo de alguna manera) nos
indica que estamos ante un método2.

Registro de la librería DAO


Si bien a partir de Access 2007 no es necesario indicar, en el código, que nos estamos
refiriendo a un objeto DAO, en Access 2003 sí es necesario indicárselo. Y para poder
indicárselo al código es necesario añadir la librería DAO.

Para ello, en el editor de VB, debemos irnos a Menú Herramientas → Referencias... y buscar la
referencia “Microsoft DAO 3.6 Object Library”. Marcamos el check y aceptamos. Ya tenemos así
la biblioteca registrada.

Si en el peor de los casos no nos apareciera esta referencia deberíamos buscarla en nuestro PC
(o bajárnosla de Internet y añadirla a nuestro sistema). El archivo se llama DAO360.dll.
Usualmente suele estar en <C:\Archivos de programa\ Archivos comunes\Microsoft
Shared\DAO>

Si tenemos ganas de leer podemos acudir al archivo DAO360.chm, que es el archivo de ayuda
de esta librería. Su ubicación depende de nuestra versión de Office, pero en mi caso está en la

2 No es objetivo de este curso explicar todas las propiedades y todos los métodos de todas las colecciones. Si estáis
interesados en algún elemento en particular os recomiendo que en el editor de VB escribáis, con esta ayuda
contextual, el que os interese y, una vez escrito, os situéis sobre lo escrito (o lo seleccionéis todo). Tras eso pulsad
F1 y la ayuda de Access os dará una explicación de lo que es, lo que puede hacer y, con suerte, os mostrará un
ejemplo. Yo sólo os explicaré los que, a mi criterio, pienso que os podrían ser más útiles.

4
Visítame en http://siliconproject.com.ar/neckkito/
dirección <C:\Archivos de programa\Archivos comunes\Microsoft Shared\OFFICE12\3082>

Un poco de práctica

Base de datos actual


Debemos tener presente que cuando nos queremos referir a
la BD en la que estamos debemos utilizar la palabra
<CurrentDB>.

Normalmente se suele referenciar la base de datos a través de la


variable dbs. Su declaración suele ser de la siguiente manera:

Dim dbs as Database

o también

Dim dbs as DAO.Database

Para asignarle valor utilizamos el SET, de la siguiente manera:

Set dbs = CurrentDb

Al acabar nuestro código es buena idea cerrar la conexión y liberar memoria. Esto se hace de
la siguiente manera:

dbs.Close
Set dbs = Nothing

Vayámonos a nuestra base de datos de ejemplo. Vamos a crear un formulario en blanco y lo


llamaremos FDAO. En un botón de comando insertamos el siguiente código:


Private Sub cmdIDBD_Click()
Dim dbs As DAO.Database
Set dbs = CurrentDb
MsgBox "Mi base de datos está en/se llama " & dbs.Name, vbInformation, "BD DNI"
dbs.Close
Set dbs = Nothing
End Sub

Si ejecutamos el código veremos como dbs nos devuelve, a través de un MsgBox, la ruta,
nombre y extensión de la base de datos.

Referencia al espacio de trabajo: Workspace


Cuando trabajamos en Access trabajamos en un “espacio de trabajo”. Y como tal, podemos
identificarlo y manipularlo.

Vamos a ver cómo saber cómo se llama nuestro Workspace (no os ilusionéis, puesto que la
respuesta puede ser un poco decepcionante).

Para ello creamos el siguiente código:

5
Visítame en http://siliconproject.com.ar/neckkito/
Private Sub cmdWrkSce_Click()
Dim wrk As DAO.Workspace
Dim miWrk As String
Set wrk = DBEngine.Workspaces(0)
miWrk = wrk.Name
MsgBox "Mi espacio de trabajo se llama: " & miWrk,
vbInformation, "WORKSPACE"
End Sub

Si lo ejecutamos sabremos el nombre tan “inspirado y original” de


nuestro espacio de trabajo.

Vamos a crear otra BD en blanco, y la llamaremos Workspace.mdb (o Workspace.accdb).


Deberemos respetar la extensión con que la hayamos creado en el código que os indicaré a
continuación, simplemente. La pondremos en el mismo directorio donde tengamos nuestra BD
de pruebas.

Lo que vamos a hacer es abrir la BD Workspace en un nuevo espacio de trabajo activo, al que
llamaremos “SuperYo”, situando dicha base en modo de apertura exclusivo (con lo que
bloquearemos su acceso a otros hipotéticos usuarios). Tened en cuenta que cuando digo
“abrir” no estoy diciendo que se nos va a abrir la BD Workspace en pantalla, sino que se va a
abrir “inmaterialmente” (por llamarlo algo). De esta manera podemos acceder “en silencio” a la
nueva BD, operar sobre ella para lo que necesitemos y cerrarla.

Pondremos un pequeño MsgBox para que nos informe sobre qué Workspace y sobre qué BD
estamos operando en ese momento.

El código sería:


Private Sub cmdAbreBDWrkSpce_Click()
Dim wrk As Workspace
Dim dbs As Database
Dim miBd As String
miBd = Application.CurrentProject.Path & "\Workspace.mdb"
Set wrk = CreateWorkspace("SuperYo", "admin", "", dbUseJet)
Set dbs = wrk.OpenDatabase(miBd, True)
MsgBox "Ahora tengo abierta, en mi nuevo espacio de trabajo " & wrk.Name & _
", la BD: " & dbs.Name
dbs.Close
wrk.Close
Set dbs = Nothing
Set wrk = Nothing
End Sub

Aún no tenemos suficientes conocimientos para poder operar sobre esa BD “a distancia”, pero
más adelante vamos a reutilizar este código para poder manipular la BD Workspace.mdb

Tened en cuenta que el anterior código lo que hace son dos cosas:

– Crea un nuevo espacio de trabajo


– Abre la BD en ese nuevo espacio de trabajo

Podríamos decir, hablando muy en general, que los Workspaces nos permiten “administrar” la

6
Visítame en http://siliconproject.com.ar/neckkito/
BD. Es un tema un tanto complejo y quizás, a nuestro nivel, poco útil. El código anterior utiliza
un Workspace de manera puntual, sin añadirlo a la colección de Workspaces de la BD en la que
estamos operando. Si alguien está interesado en profundizar sobre este elemento debe hacer
lo siguiente:

– En el editor de VB, escribir la palabra Workspace


– Situarse sobre ella y pulsar F1
– De las opciones de ayuda, seleccionar el link
<Workspace (objeto)>

Ahí podrá encontrar información más detallada sobre el espacio de


trabajo.

Aprovecho para comentaros que podemos abrir una BD “a distancia” utilizando el Workspace
actual, sin usar uno adicional. De hecho, ni siquiera tenemos que hacer referencia al
Workspace. Para hacer lo anterior simplemente nuestro código podría ser:


Private Sub cmdAbreBDSinWrkSpc_Click()
Dim dbsExterna As DAO.Database
Dim BDExterna As String
BDExterna = Application.CurrentProject.Path & "\Workspace.mdb"
Set dbsExterna = DBEngine.OpenDatabase(BDExterna)
MsgBox "Acabo de abrir la BD: " & dbsExterna.Name, vbInformation, "BD EXTERNA"
dbsExterna.Close
Set dbsExterna = Nothing
End Sub

Referencia a las tablas


Para referenciar las tablas podemos utilizar la colección TableDefs. A su vez, podemos
manipular esta colección a través de sus métodos y propiedades.

Por ejemplo, para saber cuántas tablas tengo en mi base de datos podríamos utilizar este
código:


Private Sub cmdNumTablas_Click()
Dim dbs As DAO.Database
Dim i As Integer
Set dbs = CurrentDb
i = dbs.TableDefs.Count
i=i-9
MsgBox "Tengo " & i & " tablas en mi base de datos", vbInformation, "MIS TABLAS"
dbs.Close
Set dbs = Nothing
End Sub

Y ahora alguien me dirá: ¿por qué a la variable i le restas un 9? Buena pregunta...

Cuando creamos una BD nueva automáticamente´se nos crean lo que se denominan “Objetos
del sistema”. Es decir, que se nos crean, usualmente, 9 tablas que contienen información sobre
el sistema. Estas tablas son manipulables a través de código. Si, nos situamos sobre el panel
de exploración (Access 2007) y hacemos click con el botón derecho del ratón → Opciones de

7
Visítame en http://siliconproject.com.ar/neckkito/
exploración, y marcamos el check “Mostrar objetos del sistema”, veremos como nos aparecen
nuestras tablas de sistema.

Se caracterizan porque su nombre empieza por MSys.

Podemos crear tablas de sistema propias. Para ello


deberíamos guardar la tabla con un nombre que comenzara
por USys. Hablaremos de esto en capítulos posteriores.

¿Y cuáles son los nombres de nuestras tablas? Un simple


código nos lo dirá:


Private Sub cmdNomTablas_Click()
Dim dbs As DAO.Database
Dim i As Integer
Set dbs = CurrentDb
For i = 0 To dbs.TableDefs.Count - 1
MsgBox dbs.TableDefs(i).Name
Next
dbs.Close
Set dbs = Nothing
End Sub

Fijaos que hemos hecho: For i = 0 … ¿Por qué cero? Porque los miembros de una colección
empiezan siempre su numeración por cero, y por ello debemos empezar por dicho número.
Como consecuencia, en el conteo total debemos restar una unidad, de ahí lo de

For i = 0 To dbs.TableDefs.Count - 1

Ahora os pido que “abráis vuestra mente” y recordéis lo que hemos visto en capítulos
anteriores. En realidad, si os sirve de guía, el fondo del asunto es que disponemos de un
conjunto de piezas de un mecano y debemos organizarlas a nuestra manera. ¿Por qué digo
esto? Porque el código anterior nos muestra todas las tablas de nuestra BD (las nuestras y las
del sistema), lo cual es un auténtico “rollo”.

Mi pregunta es: ¿a alguien se le ocurre cómo podemos “arreglar” esto? Os aseguro que ya
sabéis hacerlo...

Bueno. Si no se ha encendido la bombilla no pasa nada, pues todo es cuestión de práctica.


Acabamos de decir que las tablas de sistema empiezan su nombre por MSys, ¿verdad? Pues
vamos a crear un código que nos “salte” las tablas que empiezan por estas letras. Un posible
código sería el siguiente:


Private Sub cmdTablasNoStma_Click()
Dim dbs As DAO.Database
Dim i As Integer
Set dbs = CurrentDb
For i = 0 To dbs.TableDefs.Count - 1
If Left(dbs.TableDefs(i).Name, 4) <> "MSys" Then
MsgBox dbs.TableDefs(i).Name
End If
Next
dbs.Close

8
Visítame en http://siliconproject.com.ar/neckkito/
Set dbs = Nothing
End Sub

¿Le vamos cogiendo el “truquillo”?

Referencia a las consultas

Igual que hemos hecho, en el epígrafe anterior, referencia a


las tablas, podemos hacer referencia a las consultas.

Pero, antes de que alguien me diga: “Y después referencia a los formularios, a los informes...”
os diré que, si repasamos la definición del modelo, veremos que estamos hablando de “acceso
a objetos que contienen datos”, como son las tablas y consultas. Los formularios y los informes
son una “interface” para mostrarlos, y, por tanto, no entran. Hay otros sistemas para acceder a
este tipo de objetos (que ya veremos en capítulos posteriores).

Siguiendo con nuestro hilo explicativo, para acceder a consultas utilizamos la colección
Querydefs

Vamos a crear, en nuestra BD, una consulta, que llamaremos CTrabajadores. Creamos pues
una simple consulta de selección sobre los datos de TTrabajadores.

Para saber cuántas consultas tenemos creadas utilizaríamos el siguiente código:


Private Sub cmdNumQueries_Click()
Dim dbs As DAO.Database
Dim i As Integer
Set dbs = CurrentDb
i = dbs.QueryDefs.Count
MsgBox "Tenemos " & i & " consultas en la BD", vbInformation, "CONSULTAS"
dbs.Close
Set dbs = Nothing
End Sub

Ejecutamos el código y... ¿2 consultas? ¿Dónde está la otra?

Pues la otra está en el formulario FTrabajadores. ¿Recordamos que creamos este formulario
basado en la tabla TTrabajadores? En realidad es “como” si el formulario “consultara” los datos
de TTrabajadores a través de una consulta, y la colección Querydefs entiende que eso es
también una consulta.

A esa consulta “del formulario” Access le asigna el nombre, en nuestro caso,


~sq_fFTrabajadores3

Vaya... ¡qué listo es Access! Vamos a arreglar un poco el código:


Private Sub cmdNumQueriesSinForm_Click()
Dim dbs As DAO.Database

3 Para conseguir el signo ~ podemos:


- Pulsar la combinación de teclas en el teclado numérico: ALT+126
- Pulsar la combinación de teclas ALT GR+4, y a continuación pulsamos la barra espaciadora

9
Visítame en http://siliconproject.com.ar/neckkito/
Dim i As Integer, j As Integer
Set dbs = CurrentDb
i = dbs.QueryDefs.Count
For j = 0 To dbs.QueryDefs.Count - 1
If Left(dbs.QueryDefs(j).Name, 1) = "~" Then
i=i-1
End If
Next j
MsgBox "Tenemos " & i & " consultas en la BD",
vbInformation, "CONSULTAS"
dbs.Close
Set dbs = Nothing
End Sub

Un par de comentarios:

– Como hablamos de una colección recordad que la numeración debe empezar por cero
hasta el recuento de elementos de la colección menos uno:
(For j = 0 To dbs.QueryDefs.Count – 1)
– Hemos echado mano a una variable auxiliar, j, para el bucle For...Next, ya que la
variable i estaba “cumpliendo otra misión”.
– Hemos utilizado el mismo sistema de “descarte” que en el epígrafe anterior, solo que
“filtrando” por la primera letra de la consulta.

Si hubiéramos creado un informe también nos lo hubiera contado, porque, como sabemos, un
informe basado en una tabla o consulta tiene su origen, desde este punto de vista, en una
consulta sobre dicha tabla o consulta. En este caso el nombre que le hubiera asignado,
suponiendo que hubiéramos creado el informe RTrabajadores, sería: ~sq_rRTrabajadores

Finalmente, el código para saber los nombres de las consultas (eliminando las consultas de
formularios e informes) sería el siguiente:


Private Sub cmdNombreQueries_Click()
Dim dbs As DAO.Database
Dim i As Integer
Set dbs = CurrentDb
For i = 0 To dbs.QueryDefs.Count - 1
If Left(dbs.QueryDefs(i).Name, 1) <> "~" Then
MsgBox dbs.QueryDefs(i).Name
End If
Next
dbs.Close
Set dbs = Nothing
End Sub

Recordad que si podemos “pillar” un objeto → lo podemos manipular.

Referencia a los campos


Los campos de una tabla pertenecen a una colección. Dicha colección se conoce con el nombre
de Fields.

Si ahondamos más en la parte teórica, podemos decir que un objeto Field representa un

10
Visítame en http://siliconproject.com.ar/neckkito/
campo de:
– Una tabla
– Una relación
– Una consulta
– Una relación de registros
– Un índice

Aunque me repita “como la cebolla”, recordad que como es


un objeto implica que tiene propiedades y métodos, y, por
tanto, es manipulable.

Por ejemplo, vamos a escribir un código que nos diga los nombres
de los campos de nuestra tabla TTrabajadores. El código sería:

Private Sub cmdCamposTTrab_Click()
Dim dbs As DAO.Database
Dim tbl As DAO.TableDef
Dim fld As DAO.Field
Set dbs = CurrentDb
Set tbl = dbs.TableDefs("TTrabajadores")
For Each fld In tbl.Fields
MsgBox "Nombre del campo: " & fld.Name, vbInformation, "CAMPOS"
Next fld
End Sub

Si os sirve de referencia pensad en lo que en ocasiones he comentado: Access es “tonto”.


Basándome en esta premisa en el código debemos indicar con qué vamos a trabajar (bueno,
esto entendedlo como algo general y meramente abstracto). Es decir:

Private Sub...
Voy a trabajar con una base de datos
Voy a trabajar con las tablas
Voy a trabajar con los campos
La base de datos es la que tengo abierta actualmente
La tabla es “TTrabajadores”
Para cada campo de los campos de la tabla TTrabajadores...
etc.
End sub

Cuando vamos a operar con las propiedades de los campos es como si abriéramos la tabla en
vista diseño y, al situarnos sobre uno de los campos, manipuláramos manualmente las
propiedades (algunas más otras “extras”).

Voy a comentar unas pocas (no todas) para que podamos ver las equivalencias:

PROPIEDAD DEL CAMPO PROPIEDAD VBA


Nombre del campo Name
Tipo de datos Type
Tamaño del campo Size
Tamaño del campo (para memos y OLE's) FieldSize
Valor predeterminado DefaultValue
Requerido Required

11
Visítame en http://siliconproject.com.ar/neckkito/
Permitir longitud cero AllowZeroLenght
Regla de validación ValidationRule

Texto de validación ValidationText


OTRAS INTERESANTES
Para indicar que el campo puede actualizarse DataUpdatable
Para indicar el campo origen de los datos SourceField
Para indicar la tabla origen de los datos SourceTable
Para establecer una validación ValidateOnSet
inmediatamente después de actualizar su
valor

Otros objetos que existen y que no vamos a ver aquí


Existen otros objetos que no vamos a tratar en este capítulo, básicamente porque, desde mi
punto de vista, para un administrador “normal” de una BD, no son elementos que se utilicen
de manera imprescindible. Evidentemente es mi criterio y supongo que alguien habrá que esté
en desacuerdo. En fin...

Sin embargo creo necesario, como mínimo, comentarlos. De esta manera sabremos que
existen y, gracias a nuestro “fantástico” F1, podremos buscar más información sobre ellos.

Esos objetos son:

– Parameter
– Index
– Relation
– Container
– Document
– User
– Group
– Property

Pues ahí quedan, para la memoria histórica de todos.

ALGUNOS CÓDIGOS INTERESANTES SOBRE LO QUE HEMOS VISTO


Vamos a ver algunas acciones interesantes que podemos realizar sobre nuestra BD en base a
los códigos, o, en plan purista, la “esencia” de los códigos que hemos visto hasta ahora.
Aprovecharemos para ver cómo se manipulan algunos métodos y propiedades y cómo se
plasman en un código.

Los códigos los voy a comentar para que sepáis en todo momento qué es lo que están
haciendo. No toméis estos ejemplos como “bloques inamovibles” de código, sino que vedlos

12
Visítame en http://siliconproject.com.ar/neckkito/
como “pequeños códigos” que unimos para conseguir un “efecto mayor”. Es decir, que una
parte del código, si le vemos su utilidad, podría ser aplicada en otra situación para sacarnos de
un aprieto (sin necesidad de tener que coger “todas las líneas de código). Evidentemente lo
anterior es un “consejo de estudio”.

Ahí vamos con los ejemplos:

Creación de una tabla


Vamos a crear una tabla para recoger los trabajos realizados. ¿Por
qué no creamos la tabla a mano? Pongamos el supuesto que los
datos nos van a llegar a través de un Excel, y no nos interesa
tener esa tabla permanentemente en nuestra BD. Queremos sólo
que aparezca cuando a nosotros nos interese. En definitiva, el
proceso sería:

– Creo la tabla
– Importo el Excel
– Trabajo con la tabla
– Borro la tabla

Lógicamente es un supuesto que podríamos resolver de distintas maneras, pero nosotros lo


haremos a través de lo explicado hasta ahora (sí, ya sé que aún no se ha explicado cómo
importar desde un Excel, pero digamos que aprovecharemos este código para una primera
aproximación).

Ante todo, debemos tener una tabla de características similares al Excel que vamos a importar.
Supongamos que tenemos un Excel llamado ExcelTrabajos.xls con los siguientes datos:

Si analizamos ese Excel veremos que necesitamos:

– Un [Num] numérico, para la línea


– Un [Trabajador] numérico, para el identificador del trabajador
– Un [Trabajo] texto, para la descripción del trabajo
– Un [Fecha] fecha/hora, para la fecha del trabajo
– Un [Coste] moneda, para el coste del trabajo.

Fijaos que la columna <Trabajador> nos está proporcionando el DNI del trabajador.

Pues manos a la obra. La primera fase del código sería así (ojo, que no está acabado aún):

Private Sub cmdCreaTTrabajos_Click()
'Declaramos las variables
Const nomTbl As String = "TTrabajos"
Dim i As Integer
Dim miExcel As String

13
Visítame en http://siliconproject.com.ar/neckkito/
Dim dbs As DAO.Database
Dim tbl As DAO.TableDef
Dim fld As DAO.Field
'Definimos la variable dbs
Set dbs = CurrentDb
'-----FASE 1: ELIMINACION DE LA TABLA
'Primero debemos examinar si la tabla existe. Si
intentamos crearla
'y la tabla existe nos dará error de código. En teoría la
habremos
'eliminado, pero por si acaso...
For i = 0 To dbs.TableDefs.Count - 1
If dbs.TableDefs(i).Name = nomTbl Then
dbs.TableDefs.Delete nomTbl
Exit For
End If
Next i
'-----FASE 2: CREAMOS LA TABLA
'La creamos como un nuevo objeto y después le asignamos el nombre
Set tbl = New DAO.TableDef
tbl.Name = nomTbl
'La tabla está creada ahora, pero no se muestra aún en la BD. Antes
'debemos crear los campos.
'-----FASE 3: CREAMOS LOS CAMPOS
'Creamos los campos como nuevos elementos de la tabla, a la vez que
'definimos las propiedades que nos interesan
Set fld = New DAO.Field
With fld
.Name = "Num"
.Type = dbLong 'Entero largo
End With
'Incluimos el campo creado en la tabla
tbl.Fields.Append fld
Set fld = New DAO.Field
With fld
.Name = "Trabajador"
.Type = dbLong
End With
tbl.Fields.Append fld
Set fld = New DAO.Field
With fld
.Name = "Trabajo"
.Type = dbText 'Texto
.Size = 50
End With
tbl.Fields.Append fld
Set fld = New DAO.Field
With fld
.Name = "Fecha"
.Type = dbDate
End With
tbl.Fields.Append fld
Set fld = New DAO.Field
With fld
.Name = "Coste"
.Type = dbCurrency 'Moneda

14
Visítame en http://siliconproject.com.ar/neckkito/
End With
tbl.Fields.Append fld
'-----FASE 4: AÑADIMOS LA TABLA A LA BD ACTUAL
dbs.TableDefs.Append tbl
'-----FASE 5: IMPORTAMOS LOS DATOS DEL EXCEL
'Definimos la ruta donde está el Excel. En este caso lo
tenemos
'situado en el mismo directorio de la BD
miExcel = Application.CurrentProject.Path 'Nos da la ruta
del directorio
miExcel = miExcel & "\ExcelTrabajos.xls" 'Nos da la
ruta+archivo+extensión
'Importamos el Excel a la tabla recién creada
DoCmd.TransferSpreadsheet acImport, acSpreadsheetTypeExcel8, nomTbl, miExcel, True
'Cerramos conexiones y liberamos memoria
dbs.Close
Set fld = Nothing
Set tbl = Nothing
Set dbs = Nothing
End Sub

Vayamos por partes:

Fase 1: con lo visto, no deberíamos tener ningún problema para entender esta fase. Debemos
quedarnos con la idea de que si el objeto tabla ya existe nos dará un error de código. Para ello
se hace imprescindible verificar que no existe y, si existe, borrarlo.
– La sentencia que borra la tabla es, evidentemente: dbs.TableDefs.Delete nomTbl
– Si ya ha encontrado la tabla no tiene sentido seguir ejecutando el bucle. Por eso le
indicamos que salga de él a través de: Exit For

FASE 2: Con esta fase creamos la tabla de manera “inmaterial”, por decirlo de alguna manera.
Si no lo hiciéramos así, ¿dónde crearíamos los campos?

FASE 3: Creamos los campos. En el código hemos creado los campos con los atributos
indispensables. Os recuerdo que podemos establecer más propiedades a través de las
propiedades que os mostraba en la tabla de equivalencias unas páginas antes.

El proceso, como habréis visto, es:


– Creamos el campo como un nuevo campo: Set fld = New DAO.Field
– Establecemos las propiedades (a través de un bloque With...End With)
– Anexamos el campo a la tabla: tbl.Fields.Append fld

FASE 4: Añadimos la tabla a la BD, ya como objeto “visible”.

FASE 5: Importamos el Excel. No me entretendré en esta línea de código porque explicaremos


en un capítulo posterior la importación/exportación de datos. Si alguien está interesado en
saber más sobre los elementos que componen dicha línea ya sabe que sólo debe ponerse
encima de ellos y pulsar F1.

Muy bien... Ya tenemos nuestra BD creada. Sigamos con el supuesto. Imaginemos que el jefe
quiere hacer un seguimiento de un trabajador en concreto. ¿Qué hacemos? Pues como
tenemos nuestra tabla TTrabajos creada (porque habremos ejecutado el proceso al menos una
vez) lo que vamos a hacer es una consulta parametrizada sobre esa tabla, que llamaremos
CParaElJefe. La consulta será simplemente la siguiente:

15
Visítame en http://siliconproject.com.ar/neckkito/
Ojo! La relación entre los campos [DNI] y [Trabajador] la deberemos realizar manualmente.

Y como la información es para el jefe vamos a realizar un informe sobre dicha consulta, que
llamaremos RParaElJefe.

Mi informe me ha quedado así. Destacar (si hay algo que destacar) el campo calculado en el
pie del informe.

OK. Ahora ya podemos continuar con nuestro código. Para que os ubiquéis continúo a partir de
la FASE 5


'-----FASE 5: IMPORTAMOS LOS DATOS DEL EXCEL
'Definimos la ruta donde está el Excel. En este caso lo tenemos
'situado en el mismo directorio de la BD
miExcel = Application.CurrentProject.Path 'Nos da la ruta del directorio
miExcel = miExcel & "\ExcelTrabajos.xls" 'Nos da la ruta+archivo+extensión
'Importamos el Excel a la tabla recién creada
DoCmd.TransferSpreadsheet acImport, acSpreadsheetTypeExcel8, nomTbl, miExcel, True
'-----FASE 6: LANZAMOS EL INFORME DIRECTAMENTE A LA IMPRESORA

16
Visítame en http://siliconproject.com.ar/neckkito/
DoCmd.OpenReport "RParaElJefe"
'-----FASE 7: BORRAMOS LA TABLA CON LOS DATOS
dbs.TableDefs.Delete nomTbl
'Cerramos conexiones y liberamos memoria
dbs.Close
Set fld = Nothing
Set tbl = Nothing
Set dbs = Nothing
End Sub

Si ejecutamos el proceso una vez (como lo sería en un uso normal de la BD) veremos que el
informe se envía correctamente a la impresora. Pero, ¿qué hemos conseguido?

Pues que si por casualidad algún trabajador “listillo” se salta los bloqueos de la BD (o,
simplemente, no hemos “asegurado” nuestra BD) y ve que hay un informe para el jefe, si
intenta abrirlo pues... “su gozo en un pozo”. Intentad abrir ese informe (o la consulta de
origen) a ver qué obtenéis (dando por supuesto que habéis ejecutado el código y que la tabla
TTrabajos se ha borrado, claro)... je, je...

El código entero nos quedaría así:


Private Sub cmdCreaTTrabajos2_Click()
'Declaramos las variables
Const nomTbl As String = "TTrabajos"
Dim i As Integer
Dim miExcel As String
Dim dbs As DAO.Database
Dim tbl As DAO.TableDef
Dim fld As DAO.Field
'Definimos la variable dbs
Set dbs = CurrentDb
'-----FASE 1: ELIMINACION DE LA TABLA
'Primero debemos examinar si la tabla existe. Si intentamos crearla
'y la tabla existe nos dará error de código. En teoría la habremos
'eliminado, pero por si acaso...
For i = 0 To dbs.TableDefs.Count - 1
If dbs.TableDefs(i).Name = nomTbl Then
dbs.TableDefs.Delete nomTbl
Exit For
End If
Next i
'-----FASE 2: CREAMOS LA TABLA
'La creamos como un nuevo objeto y después le asignamos el nombre
Set tbl = New DAO.TableDef
tbl.Name = nomTbl
'La tabla está creada ahora, pero no se muestra aún en la BD. Antes
'debemos crear los campos.
'-----FASE 3: CREAMOS LOS CAMPOS
'Creamos los campos como nuevos elementos de la tabla, a la vez que
'definimos las propiedades que nos interesan
Set fld = New DAO.Field
With fld
.Name = "Num"

17
Visítame en http://siliconproject.com.ar/neckkito/
.Type = dbLong 'Entero largo
End With
'Incluimos el campo creado en la tabla
tbl.Fields.Append fld
Set fld = New DAO.Field
With fld
.Name = "Trabajador"
.Type = dbLong
End With
tbl.Fields.Append fld
Set fld = New DAO.Field
With fld
.Name = "Trabajo"
.Type = dbText 'Texto
.Size = 50
End With
tbl.Fields.Append fld
Set fld = New DAO.Field
With fld
.Name = "Fecha"
.Type = dbDate
End With
tbl.Fields.Append fld
Set fld = New DAO.Field
With fld
.Name = "Coste"
.Type = dbCurrency 'Moneda
End With
tbl.Fields.Append fld
'-----FASE 4: AÑADIMOS LA TABLA A LA BD ACTUAL
dbs.TableDefs.Append tbl
'-----FASE 5: IMPORTAMOS LOS DATOS DEL EXCEL
'Definimos la ruta donde está el Excel. En este caso lo tenemos
'situado en el mismo directorio de la BD
miExcel = Application.CurrentProject.Path 'Nos da la ruta del directorio
miExcel = miExcel & "\ExcelTrabajos.xls" 'Nos da la ruta+archivo+extensión
'Importamos el Excel a la tabla recién creada
DoCmd.TransferSpreadsheet acImport, acSpreadsheetTypeExcel8, nomTbl, miExcel, True
'-----FASE 6: LANZAMOS EL INFORME DIRECTAMENTE A LA IMPRESORA
DoCmd.OpenReport "RParaElJefe"
'-----FASE 7: BORRAMOS LA TABLA CON LOS DATOS
dbs.TableDefs.Delete nomTbl
'Cerramos conexiones y liberamos memoria
dbs.Close
Set fld = Nothing
Set tbl = Nothing
Set dbs = Nothing
End Sub
...

Variación sobre el tema


Ahora viene nuestro jefe y nos dice que quiere una vista preliminar del informe antes de
enviarlo a la impresora. Sin problema: cambiamos la línea DoCmd.OpenReport "RParaElJefe"
por la línea DoCmd.OpenReport "RparaElJefe", acViewPreview

18
Visítame en http://siliconproject.com.ar/neckkito/
¿Qué pasa?

Pues pasa esto:

Es decir, que no nos puede borrar la


tabla porque el informe la está utilizando. Vaya un “problemón”, ¿verdad? Porque si no
borramos la tabla la información estará al alcance de miradas indiscretas. ¿Qué podemos
hacer?

Pues lo que haremos será dividir la ejecución del código en dos partes. La primera será hasta
que se muestre el informe en vista previa. Así, utilizaríamos todo el código anterior pero lo
finalizaríamos de la siguiente manera:


'Todo el código anterior hasta FASE 5
''-----FASE 6: LANZAMOS UN PREVIEW DEL INFORME
DoCmd.OpenReport "RParaElJefe2", acViewPreview
'Cerramos conexiones y liberamos memoria
dbs.Close
Set fld = Nothing
Set tbl = Nothing
Set dbs = Nothing
End Sub

Ahora vamos a por la segunda parte. Necesitamos un evento que deba producirse
obligatoriamente para asegurarnos que borramos la tabla. El problema que nos encontramos
es que no podemos operar sobre el informe directamente porque la tabla que queremos borrar
es, precisamente, su origen de datos, y obtendríamos el error 3211 que antes comentábamos.

Luego debemos buscar en nuestra BD (y eso ya dependerá de cómo la hayamos estructurado)


algo que nos genere el evento que necesitamos para poder borrar la tabla. Por ejemplo, si
nuestra BD se abre con un formulario de inicio (como es el caso de la BD de ejemplo) podemos
sacar las propiedades de ese formulario y asignar el siguiente código al evento “Al abrir” de
ese formulario


Private Sub Form_Open(Cancel As Integer)
Dim tbl As Object
For Each tbl In CurrentData.AllTables
If tbl.Name = "TTrabajos" Then
DoCmd.DeleteObject acTable, tbl.Name
Exit For
End If
Next tbl
End Sub

19
Visítame en http://siliconproject.com.ar/neckkito/
Fijaos que en este código hemos utilizado otro sistema para identificar la tabla: no hemos
definido la variable tbl como un DAO.TableDef, sino que lo hemos definido como un “objeto” de
la base de datos. El proceso ha sido:
– Lo definimos como objeto: Dim tbl As Object
– Le decimos que ese objeto es una tabla:
CurrentData.AllTables
– Miramos si existe a través del bloque FOR EACH...NEXT e
IF...END IF
– Si existe lo borramos a través de nuestro amigo DoCmd,
con la siguiente estructura:
DoCmd.DeleteObject acTipoObjeto, nombreObjeto

Como no vamos a operar sobre las propiedades de la tabla no tiene sentido crear referencias a
DAO, borrar la tabla y después tener que cerrar conexiones y liberar memoria. Este sistema
que acabamos de aprender es bastante más práctico si no necesitamos operar sobre
elementos de la tabla.

Creación de una tabla: sistema rápido


El código anterior nos daba un control muy grande sobre todos los elementos de nuestra tabla.
Sin embargo, puede ser que queramos crear una tabla de una manera más rápida (cuando
digo rápida me refiero a no tener que escribir tantas líneas de código). Vamos a verlo.

Supongamos que queremos crear una tabla temporal por los motivos que sean. En nuestro
caso, la tabla será para guardar los trabajos de un proyecto “especial” que se repite
esporádicamente, pero no nos interesa guardar los datos de manera permanente en la BD.

El código para crear esa tabla sería:


Private Sub cmdCreaTblRapido_Click()
'Declaramos las variables
Const nomTabla As String = "TTrabEsporadico"
Dim dbs As DAO.Database
Dim tbl As DAO.TableDef
Dim fld As DAO.Field
Dim tblBorro As Object
'Definimos la BD de trabajo
Set dbs = CurrentDb
'Si la tabla existe, la eliminamos antes
For Each tblBorro In CurrentData.AllTables
If tblBorro.Name = nomTabla Then
dbs.TableDefs.Delete tblBorro.Name
Exit For
End If
Next
'Definimos la tabla creándola directamente
Set tbl = dbs.CreateTableDef(nomTabla)
'Definimos los campos anexándolos directamente a la tabla
With tbl
.Fields.Append .CreateField("Id", dbLong)
.Fields.Append .CreateField("Trabajador", dbLong)
.Fields.Append .CreateField("Fecha", dbDate)
.Fields.Append .CreateField("DescripTrabajo", dbText, 255)
End With
'Añadimos la tabla a la BD actual

20
Visítame en http://siliconproject.com.ar/neckkito/
dbs.TableDefs.Append tbl
'Cerramos conexiones y liberamos memoria
dbs.Close
Set fld = Nothing
Set tbl = Nothing
Set dbs = Nothing
End Sub

Como vemos en el código,

– Recordad que debemos borrar la tabla antes de crearla.


– Creamos la tabla al tiempo de definimos la variable:
Set tbl = dbs.CreateTableDef(nomTabla)
– Aprovechamos el bloque With...End With para
– Manipular los campos a través de .Fields.Append
– Manipular la tabla a través de .CreateField("Id", dbLong)
– Fijaos que hay un espacio entre .Fields.Append y .CreateField. Eso vendría a
significar, si escribiéramos el código sin el bloque With:
tbl.Fields.Append
tbl.CreateField(...)

Importante: tened en cuenta que a veces Access tarda en refrescar la información en el


panel de exploración. Eso significa que quizá no veáis la tabla, pero está ahí. Una manera
rápida de comprobarlo es empezar a crear una consulta en vista diseño, y veremos las tablas
que hay disponibles (cancelamos la creación de la consulta, a no ser que la necesitemos).
Cuando menos lo esperemos nuestra tabla aparecerá en el panel “por arte de magia”.

Adición de campos a una tabla existente


Imaginemos que queremos añadir un campo a una tabla existente. Primero deberemos
comprobar que ese campo no existe, y, si efectivamente no existe, lo añadiremos.

Vamos a ejecutar un código que, además, nos servirá para el epígrafe siguiente. Vamos a
añadir un campo a la tabla TTrabajadores, que nos informará del sexo del trabajador.

El código podría ser el siguiente:


Private Sub cmdAñadeCampo_Click()
'Declaramos las variables
Dim dbs As DAO.Database
Dim tbl As DAO.TableDef
Dim fld As DAO.Field
Dim hayCampo As Boolean
'Definimos las variables
Set dbs = CurrentDb
Set tbl = dbs.TableDefs!TTrabajadores
hayCampo = False
'Recorremos los campos de la tabla para ver si existe
For Each fld In tbl.Fields
If fld.Name = "Sexo" Then
hayCampo = True
Exit For
End If

21
Visítame en http://siliconproject.com.ar/neckkito/
Next fld
'Si no hay campo (es decir, hayCampo es False) lo añadimos
'con el sistema que hemos acabado de aprender
If hayCampo = False Then
With tbl
.Fields.Append .CreateField("Sexo", dbText, 6)
End With
End If
'Cerramos conexiones y liberamos memoria
dbs.Close
Set fld = Nothing
Set tbl = Nothing
Set dbs = Nothing
End Sub

Destacar una idea nueva que aparece en este código: nos hemos servido, en primer término,
de una variable booleana para, primero, definirla con un valor predeterminado y, segundo,
cambiarle ese valor si se cumple una condición (el campo ya existe). Y, en segundo término,
realizar una serie de acciones en función de lo que devuelva la booleana. Si queréis un consejo
yo tomaría nota de este recurso, porque en ocasiones no se puede ejecutar el código
directamente sin haber evaluado antes una condición, y las variables booleanas se prestan
mucho a facilitarnos el trabajo si fuera el caso.

Para acabar este epígrafe os diré que si lo que quisiéramos es lo contrario, es decir, eliminar un
campo que ya existe en la tabla, el código sería:


Private Sub cmdEliminaCampo_Click()
'Declaramos las variables
Dim dbs As DAO.Database
Dim tbl As DAO.TableDef
Dim fld As DAO.Field
'Definimos las variables
Set dbs = CurrentDb
Set tbl = dbs.TableDefs!TTrabajadores
'Recorremos los campos de la tabla para ver si existe. Si existe
'lo eliminamos
For Each fld In tbl.Fields
If fld.Name = "Sexo" Then
tbl.Fields.Delete fld.Name
Exit For
End If
Next fld
'Cerramos conexiones y liberamos memoria
dbs.Close
Set fld = Nothing
Set tbl = Nothing
Set dbs = Nothing
End Sub

Creación de una consulta


Puede interesarnos también crear consultas en tiempo de ejecución. Y ya sabemos que crear
una consulta es trabajar con SQL. Yo no voy a explicar las SQL que utilicemos aquí (que serán

22
Visítame en http://siliconproject.com.ar/neckkito/
muy fáciles), porque ahora interesa centrarnos en la creación de consultas.

Antes vamos a hacer una pequeña modificación a nuestra


tabla TTrabajadores. Vamos a completar el campo [Sexo]
que hemos creado en el epígrafe anterior en cada uno de
los registros con los valores “Hombre” o “Mujer”.

Hecho esto, vamos a ver esto de las consultas.

Supongamos que queremos filtrar por el valor “Mujer”. El


código con el que conseguiríamos lo anterior sería:


Private Sub cmdCreaConsulta_Click()
'Declaramos las variables
Dim dbs As DAO.Database
Dim qry As DAO.QueryDef
Dim qryBorro As Object
Dim miSql As String
'Definimos la variable dbs
Set dbs = CurrentDb
'Miramos si existe la consulta. Si existe la eliminamos
For Each qryBorro In CurrentData.AllQueries
If qryBorro.Name = "CTempSexo" Then
DoCmd.DeleteObject acQuery, qryBorro.Name
Exit For
End If
Next
'Definimos la SQL
miSql = "SELECT TTrabajadores.* FROM TTrabajadores"
miSql = miSql & " WHERE TTrabajadores.Sexo='Mujer'"
'Creamos la consulta
Set qry = dbs.CreateQueryDef("CTempSexo", miSql)
'Ejecutamos la consulta
DoCmd.OpenQuery "CTempSexo"
'Cerramos conexiones y liberamos memoria
qry.Close
dbs.Close
Set qry = Nothing
Set dbs = Nothing
End Sub

Tengamos en cuenta lo siguiente:

– Al igual que hacíamos con las tablas, debemos comprobar que la consulta no existe y, si
existe, borrarla. Así nos evitaremos errores de código
– Definimos la SQL a través de una variable de tipo String
– Creamos la SQL a través de la propia definición de la consulta, utilizando
CreateQueryDef, con dos argumentos separados por comas: el nombre de la consulta y
la SQL
Set qry = dbs.CreateQueryDef("CTempSexo", miSql)
– Recordad que es posible que no veamos inmediatamente la consulta en el panel de
exploración por tardanza en el refresco de la información.

¿Y por qué la he llamado CTempSexo? Si recordáis, podemos utilizar nuestro formulario de

23
Visítame en http://siliconproject.com.ar/neckkito/
inicio para “eliminar” todo aquello que no nos interese que quede como objeto en la base de
datos. Imaginaos que creamos varias consultas en tiempo de ejecución. ¿Hay que “hacer una
lista” de todas ellas para borrarlas?

Pues no: podemos decirle al código que borre sólo las que
comienzan por CTemp. Así, el código en el evento “Al abrir”
de nuestro formulario de inicio (o el que nosotros elijamos
para tal acción) sería:


Private Sub Form_Open(Cancel As Integer)
'Borramos las consultas temporales, si existen
Dim qry As Object
For Each qry In CurrentData.AllQueries
If Left(qry.Name, 5) = "CTemp" Then
DoCmd.DeleteObject acQuery, qry.Name
End If
Next qry
End Sub

Fijaos que esta vez no utilizamos el Exit For, dado que necesitamos que el código recorra todas
las consultas porque puede haber más de una consulta que empiece por CTemp

PARA FINALIZAR ESTE CAPÍTULO


Hemos visto en este capítulo qué sistemas hay para “acceso a datos”, qué es de manera
teórica DAO y cómo podemos, a través de él, crear tablas y consultas (con las “variaciones
sobre el tema” oportunas). El tema es realmente extenso y este “manual” no tiene la
pretensión de ser una “biblia de DAO”.

Sin embargo, no hemos acabado con DAO todavía. En el próximo capítulo entraremos en lo
que se conoce con el nombre de... Recordset.

Y así, por finalizar “a lo americano”, os diré...

To be continued...

24
Visítame en http://siliconproject.com.ar/neckkito/
CURSO DE VB

CAPÍTULO 111

Índice de contenido
OBJETOS DE ACCESO A DATOS (II)...............................................................................................2
RECORDSET..................................................................................................................................2
LA MANERA MÁS FÁCIL DE ACCEDER A UN RECORDSET: CLONARLO........................3
UN POCO DE TEORÍA..................................................................................................................5
ABRIR UN RECORDSET PARA CONSULTAR UN DATO........................................................6
RECORRER UN CONJUNTO DE REGISTROS..........................................................................7
MODIFICANDO UN REGISTRO..................................................................................................9
MODIFICANDO TODOS LOS REGISTROS.............................................................................10
Modificando todos los registros pero con más de un campo....................................................11
Consultando una consulta.........................................................................................................12
Consultando una consulta que aún no existe.............................................................................14
¿Error porque el valor no existe?.........................................................................................15
BÚSQUEDA RÁPIDA: Seek........................................................................................................17
AÑADIENDO UN REGISTRO....................................................................................................18
UNOS PREPARATIVOS NECESARIOS.....................................................................................19
EL EJEMPLO FINAL: UN “MIX” DE TODO LO APRENDIDO...............................................21
Cómo sería nuestra SQL...........................................................................................................21
ESTRUCTURACIÓN DEL CÓDIGO......................................................................................22
DESARROLLANDO EL CÓDIGO.........................................................................................22
UNAS PALABRAS FINALES......................................................................................................25

1 La BD donde están los ejemplos de este capítulo os la podéis bajar aquí.

1
Visítame en http://siliconproject.com.ar/neckkito/
OBJETOS DE ACCESO A DATOS (II)
En el capítulo anterior hemos visto el método de acceso a
datos DAO. Sin embargo, nos faltaba “algo”.

Ese algo es examinar con detenimiento el mundo de los


recordset con DAO. Y a eso nos vamos a dedicar
inmediatamente.

Seguiremos manejando la BD de pruebas que comenzamos


a “confeccionar” en dicho capítulo, por lo que se hará
referencia, lógicamente, a elementos que ya creamos en su
momento.

Manos a la obra.

RECORDSET
Recordset, recordset... “potito palabro”. Access nos aclara qué es un recordset de la siguiente
manera:

<<Un objeto Recordset representa los registros de una tabla base o los registros resultantes
de la ejecución de una consulta>>

Bueno... Ahora todos podemos presumir de que ya sabemos qué es un recordset, ¿verdad? Je,
je...

Por ahora, lo que tenemos claro es que es un objeto, y, como tal, tiene sus propiedades y
métodos, y es manipulable (¿os suena la frase?)

Si abrimos una tabla o de una consulta podemos ver los datos que hay en ellas, organizados
en registros. El conjunto de esos registros constituiría un recordset. Pero la idea de recordset
lleva implícita algo más, algo que podríamos llamar “inmaterial”.

Lo que voy a comentaros a continuación es algo que está totalmente alejado de “tecnicismos y
teoría” y, en el fondo, es una reflexión mía de carácter personal. Sin embargo, considero que
tras su lectura todo el mundo debería tener una idea más que clara de lo que es un recordset.

Vamos a hacer un símil, poniéndonos un poco filosóficos: abrimos una tabla (con datos, claro),
miramos intensamente la pantalla y cerramos los ojos. Si queremos podemos ver, en nuestra
mente, reproducida esa tabla con su información. Es más, podemos centrarnos en un registro
e imaginar que ese registro desaparece. O nos centramos en un registro, siempre en nuestra
mente, y más en concreto en un campo y un dato. Y ahí donde pone “electricista” (por
ejemplo) nos imaginamos que pone “fontanero”. Y , por nuestra voluntad, automáticamente
sólo “vemos” los registros en los que en ese campo hay el valor “fontanero”, y los otros
“desaparecen”.

Pues Access sería como nosotros mismos, y los recordset serían esa “imagen” que tenemos en
el cerebro. Así como nosotros podemos imaginar que desaparecen registros Access puede
hacer desaparecer registros, añadir de nuevos, sustituir los existentes, añadir más campos,
etc.

¿Dónde está físicamente ese recordset que maneja Access? Pues para nosotros “no está”, en el
sentido que no podemos verlo en la aplicación. Sólo podemos tener acceso “visible” al mismo
cuando Access escribe los resultados en una tabla o consulta, o, a veces, ni siquiera eso,

2
Visítame en http://siliconproject.com.ar/neckkito/
puesto que nos informa a través de un MsgBox.

Ni que decir tiene que nosotros manejamos las acciones


que se deben desarrollar sobre el recordset, pero es Access
quien, en ese “limbo”, los ejecuta y después los muestra de
la manera que nosotros le hemos pedido.

Evidentemente he recurrido a una “imagen onírica” para


describiros un recordset, pero, en el fondo, lo que sucede
“dentro” de Access al manejar un recordset es lo que os
acabo de explicar. De hecho, cuando programemos acciones
sobre un recordset involuntaria o voluntariamente
tendremos que imaginarnos esa “acción imaginaria” que se
ejecutará con el fin de conseguir el objetivo deseado.

Y después de este ambiente chillout en que nos hemos sumergido vamos a ver un par de cosas
sobre los recordset.

LA MANERA MÁS FÁCIL DE ACCEDER A UN RECORDSET: CLONARLO


Pero antes, os explico qué vamos a hacer:

El código que vamos a programar nos solicitará un DNI. Acto seguido clonará el recordset de la
tabla. ¿Qué significa eso? Que por una parte tendremos el recordset de la tabla y por otro
tendremos una copia de dicho recordset. En definitiva, que estaremos operando con dos
recordsets: original y copia.

¿Por qué hacemos eso? Pues porque nos es más cómodo, para la operación que vamos a
realizar, trabajar sobre un recordset “inmaterial” que no directamente sobre la tabla. Y ello es
así porque el recordset “copia” tiene una serie de propiedades y métodos que podemos
manipular a nuestro antojo.

Realizamos las operaciones que necesitamos sobre el recordset “copia” y conseguimos un


resultado. Y a continuación lo que vamos a hacer es aplicar dichos resultados sobre el
recordset original. Y como, por decirlo de alguna manera, el recordset original es el que
tenemos en pantalla, podremos ver los resultados también en pantalla.

¿Nos acordamos cuando, en el primer apartado, yo hablaba de “nuestra mente”? Pues el clon
del recordset que hemos creado se aloja “en memoria” del ordenador, lo que implica que está
consumiendo un espacio. Por ello, una vez obtenido el resultado, y dado que ya no
necesitamos más ese clon, podemos cerrarlo, primero, y eliminarlo, después, de la memoria,
tal y como aprendimos en el capítulo anterior (¿nos acordamos de “Close” y de “=Nothing”?).
Así, tal y como lo hemos creado, lo hacemos desaparecer.

Vimos este sistema en el capítulo dedicado a filtros, por lo que ya os debería “sonar” todo lo
que vamos a ver. Pero esta vez el enfoque de la explicación será desde el punto de vista de
DAO.

Si abrimos nuestro formulario FTrabajadores le añadimos un botón en la cabecera del


formulario, y ese botón será el que programaremos.

Ahora sí estamos en condiciones de ver el código (no incluye elementos de control):

….
Private Sub cmdRecordsetClon_Click()
'Declaramos las variables

3
Visítame en http://siliconproject.com.ar/neckkito/
Dim nDNI As Long
Dim filtro As String
Dim rst As Recordset
'Solicitamos al usuario que introduzca un DNI
nDNI = InputBox("Introduzca un DNI a buscar", "DNI")
'Creamos el filtro
filtro = "[DNI]=" & nDNI
'Clonamos el recordset
Set rst = Me.Recordset.Clone
'Manipulamos el recordset del clon para buscar un DNI a
través
'del método Recordset.FindFirst()
rst.FindFirst filtro
'Cuando lo encuentra añadimos un marcador a nuestro clon para
'"fijar" el registro que cumple con el filtro a través de la propiedad
'Recordset.Bookmark
'Traspasamos ese marcador al recordset original. De esta manera
'en pantalla nos situaremos en el registro que cumple con el filtro
Me.Bookmark = rst.Bookmark
'Cerramos el recordset clon
rst.Close
'Lo eliminamos de memoria
Set rst = Nothing
End Sub

Si probamos el botón y buscamos un DNI veremos los resultados.

Fijémonos en un par de detalles:

– Generalmente, si utilizamos el método DAO, se define el recorset con el nombre de rst


– Al declararlo como variable le decimos que va a ser un recordset: Dim rst As Recordset
– Para crearlo utilizamos la estructura SET rst = xxx. Es decir, que para asignar valor a
variables las igualábamos directamente (i.e., DNI=123), mientras que para un recordset
debemos utilizar SET (de manera similar a cómo determinábamos dbs o tbl o fld).
– Para llamar al recordset original del formulario (que es la tabla TTrabajadores)
utilizamos a nuestro viejo conocido ME. Con ello quiero decir que utilizamos el mismo sistema
que en un capítulo anterior utilizábamos para llamar a formularios y subformularios.
– Lo clonamos a través de la estructura (os indico la estructura larga):

Forms!NombreForm.Recordset.Clone

– A partir de ahí, para referirnos a nuestro clon, siempre utilizaremos rst. (rst punto). Y
ya tenemos posibilidad, con esta nomenclatura, de actuar sobre sus propiedades y métodos.
De hecho, primero actuamos sobre el método FindFirst y después actuamos sobre la propiedad
Bookmark.
– En el ejemplo, el traspaso del resultado se produce a través de la “igualación” de la
misma propiedad (Me.Bookmark ↔ rst.Bookmark)
– Para cerrar el recordset siempre utilizamos rst.Close
– Para liberar memoria le tenemos que decir a Access que el recordset es “nada”. Ello lo
conseguimos a través de la instrucción Set rst = Nothing

Siguiendo con este ejemplo, vamos a complicar el código ligeramente. ¿Qué pasa si el DNI
introducido y buscado no existe? Pues manipulamos otra propiedad del recordset. En este caso
la propiedad afectada es NoMatch. Como ya sabemos cómo actuar sobre ella (rst. +
propiedad) pues... la cosa está hecha.

4
Visítame en http://siliconproject.com.ar/neckkito/
El código nos quedaría así:


Private Sub cmdRecordsetNoExiste_Click()
'Declaramos las variables
Dim nDNI As Integer
Dim filtro As String
Dim rst As Recordset
'Solicitamos al usuario que introduzca un DNI
nDNI = InputBox("Introduzca un DNI a buscar", "DNI")
'Creamos el filtro
filtro = "[DNI]=" & nDNI
'Clonamos el recordset
Set rst = Me.Recordset.Clone
'Manipulamos el recordset del clon para buscar un DNI a través
'del método Recordset.FindFirst()
rst.FindFirst filtro
'Si no existe el DNI buscado tomamos una decisión a través de un IF...END IF
If rst.NoMatch Then
MsgBox "El DNI buscado no existe en la base de datos", vbCritical, "NO EXISTE"
Else
'Si existe traspasamos el resultado al rst original
Me.Bookmark = rst.Bookmark
End If
'Cerramos y liberamos memoria
rst.Close
Set rst = Nothing
End Sub

Creo que no hace falta realizar ningún comentario. Deberíamos ser capaces de entender el
código perfectamente con “todo lo que ya sabemos”... je, je...

UN POCO DE TEORÍA
Vamos a relajar un poco los dedos, después de tanto ejemplo, y vamos a darle “al coco” con
un poco de teoría. Os aseguro que nos será útil para más adelante.

Podemos abrir un recordset de cuatro maneras diferentes, cada una con sus características.
Vamos a verlas:

a) dbOpenTable

Este sistema nos permite abrir una tabla. La principal ventaja es que nos permite utilizar los
índices de la tabla. Con este sistema de apertura podemos añadir o modificar los datos de la
tabla (a no ser que le indiquemos que la abra en modo “sólo lectura”).

También debemos tener en cuenta que este sistema no es aplicable si lo que tenemos son
tablas vinculadas. Ello nos implicaría abrir el recordset con otro sistema.

b) dbOpenDynaset

Por Dynaset podríamos entender la idea de “hoja de respuestas dinámica”. Este método de
apertura representa el resultado de una consulta cuyos registros pueden actualizarse.

5
Visítame en http://siliconproject.com.ar/neckkito/
Dado que podemos realizar consultas sobre varias tablas una ventaja de este sistema es,
precisamente, el poder trabajar sobre los datos de varias tablas.

c) dbOpenSnapshot

Snapshot es sinónimo de “sólo lectura” (instantánea, si nos


ponemos a traducir). Su efecto es como si abriéramos una
consulta en ese modo “sólo lectura”. De lo anterior
deducimos que su ventaja es la velocidad de procesamiento
(dado que Access no se debe “preparar” para recibir
órdenes para añadir o modificar registros -por decirlo de
alguna manera-).

d) dbOpenForwardOnly

Si volvemos a utilizar el traductor veremos que eso significa “sólo hacia adelante”. Es decir, es
como si abriéramos un “snapshot” (=sólo lectura), pero sólo podemos desplazarnos por los
registros hacia adelante. Si la característica de dbSnapshot era la velocidad, pues la
característica de dbOpenForwardOnly es la velocidad multiplicada.

Es interesante saber lo anterior dado que, según la acción que necesitemos realizar sobre el
recordset (operar sobre él o sólo consultarlo), podemos optimizar el funcionamiento del código.

También debemos tener en cuenta que hay propiedades y métodos que funcionan con todos
los sistemas, pero hay otros que sólo funcionan en unos pero no en otros. No os preocupéis
pro eso porque los errores de código nos advertirán inmediatamente de que utilizamos un
“cuchillo para comer sopa”.

Y como estoy seguro de que estamos ya saturados de tanta teoría (je, je...) volvamos a
nuestra BD para poner en práctica esto que acabamos de entender.

ABRIR UN RECORDSET PARA CONSULTAR UN DATO


Vamos, en un formulario en blanco, a crear un botón que nos operará con un recordset para
mirar el nombre del primer trabajador de la tabla TTrabajadores.

El código que deberíamos emplear es el siguiente:


Private Sub cmdNombrePrimerTrabajador_Click()
'Declaramos las variables
Dim nomTrab As String
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
'Definimos las variables
Set dbs = CurrentDb
Set rst = dbs.OpenRecordset("TTrabajadores", dbOpenSnapshot)
'Nos movemos al primer registro
rst.MoveFirst
'Capturamos el nombre del trabajador
nomTrab = rst.Fields(1).Value
'Mostramos el MsgBox con la información
MsgBox "El primer trabajador se llama " & nomTrab, vbInformation, "INFO"
'Cerramos conexiones y liberamos memoria
rst.Close
dbs.Close

6
Visítame en http://siliconproject.com.ar/neckkito/
Set rst = Nothing
Set dbs = Nothing
End Sub

Vamos a ver cómo lo hacemos:

– Declaramos la variable rst como DAO.Recordset


– Al inicializarla utilizamos la siguiente estructura
Set rst = dbs.OpenRecordset(“NombreTabla”,
ModoApertura)

Recordando la teoría aprendida hace unos instantes, el modo de apertura que hemos elegido
ha sido dbOpenSnapshot, porque sólo nos interesaba “mirar” qué valor tenía ese primer campo
del primer registro.

– Para movernos al primer registro utilizamos rst.MoveFirst. Como podéis imaginar, si


hubiéramos querido mirar el nombre el último trabajador hubiéramos tenido que desplazar el
puntero del recordset al último registro, a través de rst.MoveLast.
Tras esto, tened en cuenta que podemos desplazarnos por los registros con:
• MoveFirst
• MoveLast
• MovePrevious
• MoveNext
• Move
– Para indicar el campo que queremos utilizar debemos decírselo al código. Eso lo
hacemos a través de rst.Fields(x), donde x es el número de campo.
¿Recordamos que el objeto Fields pertenecía a una colección? ¿Y recordamos que las
colecciones inician su conteo con cero, y no con uno?
Pues tras recordar esto tenemos, en la tabla TTrabajadores, lo siguiente:
– Campo [DNI] → Se correspondería con Fields(0)
– Campo [Nombre] → Se correspondería con Fields(1)
– Campo [Sexo] → Se correspondería con Fields(2)

¿Qué hacemos si no tenemos claro el número del campo? Podemos indicárselo al código
directamente por su nombre.

Así, podríamos haber escrito

nomTrab = rst.Fields(“Nombre”).Value

RECORRER UN CONJUNTO DE REGISTROS


Para recorrer un conjunto de registros se suele utilizar, generalmente, el bloque DO
UNTIL...LOOP. Para poder seguir adelante debemos antes conocer dos propiedades:

BOF

Vendría a significar Begin Of Files. Lo que está indicando es que el puntero del recordset está
antes de la primera fila del recordset.

EOF

Vendría a significar End Of Files. Nos indica que el puntero del recordset se halla después de la
última fila del recordset.

7
Visítame en http://siliconproject.com.ar/neckkito/
Si combinamos la idea del bloque DO UNTIL...LOOP con estas dos propiedades podemos ver
cómo le damos las instrucciones al código para el recorrido del recordset.

Es decir, que si queremos empezar por el primer registro y


acabar en el último debemos decir:

1.- Sitúate en el primer registro


2.- Haz estas acciones hasta que llegues a la última de las
filas.

Y lo anterior, traducido a código, sería:


rst.MoveFirst
Do Until rst.EOF
'Acciones a desarrollar
rst.MoveNext
Loop

Por el contrario, si lo que queremos es empezar en el último registro y recorrer el recordset


hasta llegar al primero lo que deberíamos escribir es:


rst.MoveLast
Do Until rst.BOF
'Acciones a desarrollar
rst.MovePrevious
Loop

Vamos a conseguir que el código nos diga los nombres de los trabajadores de nuestra tabla.
Así pues escribimos lo siguiente:


Private Sub cmdNombreTrabajadores_Click()
'Declaramos las variables
Dim nomTrab As String
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
'Definimos las variables
Set dbs = CurrentDb
Set rst = dbs.OpenRecordset("TTrabajadores", dbOpenSnapshot)
'Nos movemos al primer registro
rst.MoveFirst
'Hasta que no llegues al final del recordset, haz...
Do Until rst.EOF
nomTrab = rst.Fields(1).Value
MsgBox "Nombre trabajador: " & nomTrab, vbInformation, "NOMBRES"
'Nos movemos al siguiente registro
rst.MoveNext
If rst.EOF Then
MsgBox "Hemos llegado al final de la lista", vbExclamation, "FIN"
End If
Loop
'Cerramos conexiones y liberamos memoria

8
Visítame en http://siliconproject.com.ar/neckkito/
rst.Close
dbs.Close
Set rst = Nothing
Set dbs = Nothing
End Sub

Tras lo explicado no creo que tengáis problemas para


entender el código. Destacar que he añadido un bloque
IF...END IF, a todas luces innecesario (podríamos haber
puesto directamente ese MsgBox tras el Loop) para que
pudierais ver “en otro ambiente” cómo podemos también
manejar estas propiedades del recordset que estamos
comentando.

MODIFICANDO UN REGISTRO
Vamos a abrir directamente la tabla y vamos a modificar el nombre del último trabajador.
Vamos a cambiar “Laura” por “Laurita”.

Para modificar un registro debemos seguir la siguiente estructura:

– Primero, indicar al código que nos disponemos a realizar una edición del registro
– Segundo, indicar el campo que queremos y qué nuevo valor va a contener
– Tercero, actualizar la información modificada en la tabla.

Eso lo conseguimos a través de un bloque With...End With, de la siguiente manera:


With rst
.Edit
.Fields(x).Value = <NuevoValor>
.Update
End With

Así pues, el código para tener contenta a “Laurita” sería el siguiente:


Private Sub cmdCambiaNombre_Click()
'Declaramos las variables
Dim nomTrab As String
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
'Definimos las variables
Set dbs = CurrentDb
Set rst = dbs.OpenRecordset("TTrabajadores", dbOpenTable)
'Nos movemos al último registro
rst.MoveLast
'Editamos el campo [Nombre] y cambiamos su valor
With rst
.Edit
.Fields(1).Value = "Laurita"
.Update
End With
'Mostramos un MsgBox de confirmación

9
Visítame en http://siliconproject.com.ar/neckkito/
MsgBox "Cambios realizados correctamente", vbInformation, "OK"
'Cerramos conexiones y liberamos memoria
rst.Close
dbs.Close
Set rst = Nothing
Set dbs = Nothing
End Sub

¿Fácil, verdad? Fijaos que hemos tenido que abrir la tabla


con el sistema dbOpenTable para poder realizar la edición
de datos; si la hubiéramos abierto como dbOpenSnapshot
no nos lo hubiera permitido.

MODIFICANDO TODOS LOS REGISTROS


Si queremos modificar todos los registros deberemos recorrer todos los registros. Sólo
tenemos que aplicar lo aprendido en dos apartados anteriores y unificarlo.

Supongamos que queremos que todos los nombres de los trabajadores estén en mayúscula.
Esta “operación” ya la sabemos hacer de lo aprendido en capítulos anteriores. Así pues os
animo a que, antes de mirar el código que os pongo a continuación, intentéis programarlo
vosotros mismos, para ver dónde “fallamos” (si es que fallamos).

El código que nos haría eso sería:


Private Sub cmdPasaAMay_Click()
'Declaramos las variables
Dim nomTrab As String
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
'Definimos las variables
Set dbs = CurrentDb
Set rst = dbs.OpenRecordset("TTrabajadores", dbOpenTable)
'Nos movemos al primer registro
rst.MoveFirst
'Hasta que no llegues al final del recordset, haz...
Do Until rst.EOF
With rst
.Edit
.Fields(1).Value = UCase(.Fields(1).Value)
.Update
End With
'Nos movemos al siguiente registro
rst.MoveNext
Loop
'Avisamos de que "la operación" ha sido "todo un éxito"
MsgBox "Datos actualizados correctamente", vbInformation, "OK"
'Cerramos conexiones y liberamos memoria
rst.Close
dbs.Close
Set rst = Nothing
Set dbs = Nothing
End Sub

10
Visítame en http://siliconproject.com.ar/neckkito/
Como vemos si analizamos el código, con lo aprendido hasta ahora resulta muy fácil (¿sí?)
adaptar el código a nuestras necesidades. Ya comentaba en el capítulo anterior que “esto” es
como un mecano: con las piezas correctas podemos construir una estructura según
nuestras necesidades.

Si tuviera algo que destacar del código es la manera en que


hemos manipulado el valor del campo, lo cual hemos hecho
de forma extraordinariamente directa. No hemos hecho:
– Cojo el valor del campo
– Lo transformo a mayúsculas
– Indico el nuevo valor
sino que hemos hecho un “tres-en-uno” a través de

.Fields(1).Value = UCase(.Fields(1).Value)

De todas maneras no os preocupéis por esto, porque si habéis conseguido el resultado


esperado (cambiar los nombres a mayúsculas), pues perfecto. La estructura anterior os saldrá
sola cuando hayáis realizado tres o cuatro códigos (bueno... quizá algunos más, pero pocos ;)

Modificando todos los registros pero con más de un campo


Pongo este ejemplo a efectos sólo clarificadores, para que podáis ver cómo se hace. Pero no
hay más secreto.

Eso sí, aprovecharemos para ver el rst.Move

Vamos a modificar el valor del segundo registro, y nuestra “Eva” se va a convertir en “Rambo”.
Evidentemente, debemos cambiar también el sexo de nuestro “Rambo”, porque dejarlo como
“Mujer” sería... ¿inconveniente?

El código para ello sería:


Private Sub cmdCambiaVariosCampos_Click()
'Declaramos las variables
Dim nomTrab As String
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
'Definimos las variables
Set dbs = CurrentDb
Set rst = dbs.OpenRecordset("TTrabajadores", dbOpenTable)
'Nos movemos al segundo registro
rst.Move (1)
'Editamos el campo [Nombre] y el campo [Sexo] y cambiamos sus valores
With rst
.Edit
.Fields(1).Value = "RAMBO"
.Fields(2).Value = "Hombre"
.Update
End With
'Mostramos un MsgBox de confirmación
MsgBox "Cambios realizados correctamente", vbInformation, "OK"
'Cerramos conexiones y liberamos memoria
rst.Close
dbs.Close

11
Visítame en http://siliconproject.com.ar/neckkito/
Set rst = Nothing
Set dbs = Nothing
End Sub

Fijaos en:

'Nos movemos al segundo registro


rst.Move (1)

Supongo que no hace falta que os recuerde que estamos en


una colección, y las colecciones comienzan el recuento por
cero, ¿verdad? ;)

y fijaos en

With rst
.Edit
.Fields(1).Value = "RAMBO"
.Fields(2).Value = "Hombre"
.Update
End With

Simplemente remarcar que el .Edit edita todo lo que esté a continuación, hasta que damos la
orden de actualizar los datos (.Update). Así, todos los campos que queramos modificar irían
“ahí dentro”.

Consultando una consulta


Vamos a realizar la apertura de un recordset sobre una consulta. Para no ser tan “repetitivos”
con los ejemplos vamos a ver una utilidad que nos dirá cuántos hombres y cuántas mujeres
nos devuelve la consulta.

Sé que alguien puede pensar: “eso es un poco tontería”, pero insisto en que lo que os intento
transmitir no es “esto se hace así”, sino que con todo lo que sabemos hasta ahora podemos
realizar muchas operaciones simplemente combinando códigos o sistemas.

Si recordamos tenemos en nuestra BD una consulta llamada CTrabajadores, simple consulta de


selección que nos devuelve los mismos registros que la tabla TTrabajadores (y si no la
tuviéramos la creamos y ya está).

El código que podríamos programar para conseguir lo que queremos sería, por ejemplo:


Private Sub cmdNumHM_Click()
'Declaramos las variables
Dim nM As Integer, nH As Integer
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
'Definimos las variables
Set dbs = CurrentDb
Set rst = dbs.OpenRecordset("CTrabajadores", dbOpenDynaset)
'Inicializamos los contadores
nM = 0
nH = 0
'Nos movemos al primer registro

12
Visítame en http://siliconproject.com.ar/neckkito/
rst.MoveFirst
'Iniciamos el proceso de recuento
Do Until rst.EOF
'Miramos el valor del campo sexo y sumamos una unidad
If rst.Fields("Sexo").Value = "Hombre" Then
nH = nH + 1
Else
nM = nM + 1
End If
'Nos movemos al siguiente registro
rst.MoveNext
Loop
'Mostramos los resultados
MsgBox "Hay " & nM & " mujeres y " & nH & " hombres", vbInformation, "RECUENTO"
'Cerramos conexiones y liberamos memoria
rst.Close
dbs.Close
Set rst = Nothing
Set dbs = Nothing
End Sub

Deberíamos destacar de este código las siguientes ideas:

– Como abrimos una consulta pues debemos abrir el recordset con el método
dbOpenDynaset (hubiéramos podido utilizar un dbOpenSnapshot sin problemas). Lo que no
podríamos utilizar es el método dbOpenTable.

Si hubiéramos utilizado este último método nos saldría un “pedazo mensaje” diciéndonos:

– Recordaros que podemos hacer referencia al campo que queremos capturar o bien por
su índice o bien por su nombre. En este caso yo he utilizado el nombre
(rst.Fields("Sexo").Value)
– El bloque IF...END IF nos examina el valor del campo y suma una unidad al contador
que corresponda. Sin problemas, supongo.
– Por último, ved que he declarado las variables nH y nM como Integer. Si nuestra BD
tuviera más de 32.767 registros que cumplieran la condición el código nos daría error. En ese
caso recordad que deberíamos declarar esas variables como Long (que tiene una capacidad
mayor).

13
Visítame en http://siliconproject.com.ar/neckkito/
Consultando una consulta que aún no existe
Es en estos momentos tan “duros” de nuestro capítulo
cuando empezamos a apreciar la importancia de las
consultas SQL.

Mediante una SQL podemos crearnos una consulta según


nuestras necesidades, en tiempo de ejecución, analizarla y
actuar en consecuencia.

¿Qué ventaja obtenemos de ello? Pues que no es necesario


llenar nuestra BD de consultas como “objeto consulta” para
poder acceder a ellas.

Para hacernos una idea es como si dijéramos: “necesito esto” → Y “esto” aparece por arte de
magia en nuestra mano → “Ya no lo necesito” → Y “esto” desaparece también como por arte de
magia.

Vamos a ver una consulta SQL muy simple, que nos filtrará un trabajador por DNI solicitado.
Como resultado obtendremos un MsgBox con la información de ese trabajador/trabajadora.

El código sería:


Private Sub cmdDatosTrabSQL_Click()
'Declaramos las variables
Dim nDNI As Integer
Dim miSql As String
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
'Definimos la BD de trabajo
Set dbs = CurrentDb
'Solicitamos el DNI al usuario
nDNI = InputBox("Introduzca DNI", "DNI")
'Creamos la SQL
miSql = "SELECT TTrabajadores.* FROM TTrabajadores"
miSql = miSql & " WHERE TTrabajadores.DNI=" & nDNI
'Abrimos el recordset sobre la SQL
Set rst = dbs.OpenRecordset(miSql, dbOpenSnapshot)
'Mostramos la información
MsgBox "DNI: " & nDNI & vbCrLf & "Nombre: " & rst.Fields("Nombre").Value _
& vbCrLf & "Sexo: " & rst.Fields("Sexo").Value, vbInformation, "DATOS"
'Cerramos conexiones y liberamos memoria
rst.Close
dbs.Close
Set rst = Nothing
Set dbs = Nothing
End Sub

La mecánica es muy similar a la que hemos visto hasta ahora. Simplemente nuestro recorset
ha tomado los datos de una SQL.

Si analizamos esta SQL veremos que su estructura es:

SELECT
nombreTabla.nombreCampo

14
Visítame en http://siliconproject.com.ar/neckkito/
FROM
nombreTabla
WHERE
condición

En este caso hemos utilizado el comodín asterisco, lo cual


quiere decir que seleccionamos todos los campos de la
tabla. Si sólo nos hubieran interesado dos campos, por
ejemplo, hubiéramos tenido que escribir:

SELECT TTrabajadores.DNI, TTrabajadores.Sexo FROM


TTrabajadores

Es decir, los campos separados por comas.

En cuanto al WHERE tened en cuenta que funcionan las mismas peculiaridades, según el tipo
de campo, que comentábamos en los filtros. Es decir, en nuestro caso, al ser un dato tipo
numérico, el filtro ha sido “directo” (“ WHERE TTrabajadores.DNI =” & nDNI)

Si hubiéramos filtrado por un valor de tipo texto deberíamos haber encerrado la variable entre
comillas simples. Por ejemplo, así: “ WHERE TTrabajadores.Nombre='” & nom & “'”

Es importante que os fijéis que entre las comillas y el WHERE hay un espacio. Como podéis ver
la SQL es un String. Si no ponemos ese espacio la SQL nos quedaría escrita así (por ejemplo):
SELECT TTrabjadores.* FROM TTrabajadoresWHERE TTrabajadores.DNI = 123

Y al entender la SQL que la tabla de origen se llama TTrabajadoresWHERE nos saltaría error
de código porque, evidentemente, esa tabla no existe.

¿Error porque el valor no existe?


Si probamos nuestro código anterior y le indicamos, en el InputBox, un DNI que no está en la
tabla, ¿qué ocurre?

Pues que obtenemos este precioso


mensaje:

Eso significa que nuestra SQL está


“vacía” porque no hay registros que
cumplan la condición.

¿Cómo detectar el error? Hay varios


sistemas.

El primero es contar el número de


registros devueltos por la consulta. Si el número de registros devuelto es cero eso quiere decir
que no hay información.

La propiedad que nos indica el número de registros es Recordcount.

Así, si reescribimos nuestro código añadiendo unas líneas de código que nos controlen lo
anterior podremos evitar dicho error.

El código nos debería quedar así:

15
Visítame en http://siliconproject.com.ar/neckkito/
Private Sub cmdSQLSinDatos_Click()
'Declaramos las variables
Dim nDNI As Integer
Dim miSql As String
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
'Definimos la BD de trabajo
Set dbs = CurrentDb
'Solicitamos el DNI al usuario
nDNI = InputBox("Introduzca un DNI inexistente", "DNI")
'Creamos la SQL
miSql = "SELECT TTrabajadores.* FROM TTrabajadores"
miSql = miSql & " WHERE TTrabajadores.DNI=" & nDNI
'Abrimos el recordset sobre la SQL
Set rst = dbs.OpenRecordset(miSql, dbOpenSnapshot)
'Comprobamos que la consulta devuelve registros. Si no hay registros
'lanza un mensaje de advertencia y sale del proceso
If rst.RecordCount = 0 Then
MsgBox "No hay trabajadores con el DNI solicitado", vbCritical, "SIN DATOS"
Exit Sub
End If
'Si hay datos mostramos la información
MsgBox "DNI: " & nDNI & vbCrLf & "Nombre: " & rst.Fields("Nombre").Value _
& vbCrLf & "Sexo: " & rst.Fields("Sexo").Value, vbInformation, "DATOS"
'Cerramos conexiones y liberamos memoria
rst.Close
dbs.Close
Set rst = Nothing
Set dbs = Nothing
End Sub

Os he marcado en negrita las líneas de control para evitar sustos.

Otro sistema sería comprobar si el inicio del recordset y el final del recordset son coincidentes.
Si lo son es que no hay ningún registro entre ellos, ergo no hay información que mostrar. El
código sería el siguiente:


Private Sub cmdSQLSinDatos2_Click()
'Declaramos las variables
Dim nDNI As Integer
Dim miSql As String
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
'Definimos la BD de trabajo
Set dbs = CurrentDb
'Solicitamos el DNI al usuario
nDNI = InputBox("Introduzca un DNI inexistente", "DNI")
'Creamos la SQL
miSql = "SELECT TTrabajadores.* FROM TTrabajadores"
miSql = miSql & " WHERE TTrabajadores.DNI=" & nDNI
'Abrimos el recordset sobre la SQL
Set rst = dbs.OpenRecordset(miSql, dbOpenSnapshot)
'Comprobamos que inicio del recordset y fin del recordset son
'coincidentes. Si lo son es que no hay datos.

16
Visítame en http://siliconproject.com.ar/neckkito/
If rst.BOF And rst.EOF Then
MsgBox "No hay trabajadores con el DNI solicitado", vbCritical, "SIN DATOS"
Exit Sub
End If
'Si hay datos mostramos la información
MsgBox "DNI: " & nDNI & vbCrLf & "Nombre: " &
rst.Fields("Nombre").Value _
& vbCrLf & "Sexo: " & rst.Fields("Sexo").Value,
vbInformation, "DATOS"
'Cerramos conexiones y liberamos memoria
rst.Close
dbs.Close
Set rst = Nothing
Set dbs = Nothing
End Sub

Os he marcado en negrita cómo serían las líneas de este control.

BÚSQUEDA RÁPIDA: Seek


Cuando hablábamos de clonar el recordset hemos visto el método FindFirst. Sin embargo, si
sabemos que vamos a realizar la búsqueda sobre un campo indexado podemos utilizar el
método Seek. La velocidad de proceso a través de Seek es superior a FindFirst, por lo que
nuestras búsquedas darán resultados con mayor celeridad.

Si queremos realizar una búsqueda por DNI con este método el código sería:


Private Sub cmdSeek_Click()
'Declaramos las variables
Dim vDNI As Integer
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
'Definimos la BD de trabajo y el recordset
Set dbs = CurrentDb
Set rst = dbs.OpenRecordset("TTrabajadores", dbOpenTable)
'Solicitamos el DNI al usuario
vDNI = InputBox("Introduzca DNI", "DNI")
'Indicamos el índice actual: clave primaria
rst.Index = "PrimaryKey"
'Buscamos
rst.Seek "=", vDNI
'Si el registro no existe avisamos y salimos del proceso
If rst.NoMatch Then
MsgBox "El DNI buscado no existe", vbCritical, "SIN DATOS"
Exit Sub
End If
'Mostramos los resultados
MsgBox "El DNI " & vDNI & " corresponde a " & rst(1)
'Cerramos conexiones y liberamos memoria
rst.Close
dbs.Close
Set rst = Nothing
Set dbs = Nothing
End Sub

17
Visítame en http://siliconproject.com.ar/neckkito/

Fijaos que:
- Indicamos que la búsqueda es indexada, a través de
(rst.Index = "PrimaryKey")
– La búsqueda utilizando Seek sigue la estructura
<rst.Seek “comparador matemático”, valor a buscar (si hay
más índices definidos se colocan a continuación, separados
por comas)>.
– Hemos realizado una simplificación a la hora de
mostrar la información. En lugar de utilizar nuestro
conocido rst.Fields(1).Value lo hemos simplificado a rst(1)

Con tan pocos datos no notamos diferencia, pero con muchos registros os aseguro que se nota
la velocidad del Seek.

AÑADIENDO UN REGISTRO
Hemos visto cómo modificar un registro. Ahora vamos a ver cómo añadimos un registro a una
tabla.

La adición de un registro sigue la siguiente estructura

With rst
.AddNew
.Fields(x).Value = <NuevoValor>
.Update
End With

Vamos a crear una tabla, que llamaremos TAdiciones, que contendrá dos campos:
– [NomTrab] → De tipo texto
– [Sex] → De tipo texto, tamaño 1

Lo que haremos será recorrer los registros de la tabla TTrabajadores y copiaremos el nombre
del trabajador en TAdiciones, junto con la inicial de su sexo.

El código sería:


Private Sub cmdAdd_Click()
'Declaramos las variables
Dim dbs As DAO.Database
Dim rst As DAO.Recordset 'Recordset para TTrabajadores
Dim rstAdd As DAO.Recordset 'Recordset para TAdiciones
'Definimos la base de trabajo y abrimos los recordsets
Set dbs = CurrentDb
Set rst = dbs.OpenRecordset("TTrabajadores", dbOpenSnapshot)
Set rstAdd = dbs.OpenRecordset("TAdiciones", dbOpenTable)
'Nos situamos en el primer registro de TTrabajadores
rst.MoveFirst
'Iniciamos el recorrido de registros
Do Until rst.EOF
'Añadimos los datos del primer registro a la tabla TAdiciones
With rstAdd
.AddNew
.Fields("NomTrab").Value = rst.Fields("Nombre").Value

18
Visítame en http://siliconproject.com.ar/neckkito/
.Fields("Sex").Value = Left(rst.Fields("Sexo").Value, 1)
.Update
End With
'Nos movemos al registro siguiente
rst.MoveNext
Loop
'Abrimos la tabla para ver los resultados
DoCmd.OpenTable "TAdiciones", , acReadOnly
'Cerramos conexiones y liberamos memoria
rst.Close
rstAdd.Close
dbs.Close
Set rst = Nothing
Set rstAdd = Nothing
Set dbs = Nothing
End Sub

Como vemos, hemos utilizado un recordset dentro de otro recordset para ir añadiendo los
datos en función de los valores de origen, registro por registro.

Fijaos que rst lo hemos abierto en modo dbOpenSnapshot, porque sólo nos interesaba “mirar”
los datos; en cambio, rstAdd lo hemos tenido que abrir en modo dbOpenTable, porque
necesitábamos modificar/añadir datos en la tabla.

UNOS PREPARATIVOS NECESARIOS


Los datos que tenemos en la BD son un poco “simples”, y quizá no se pueda apreciar bien el
potencial del código. Además, como vamos a ver un ejemplo un poco más complicado vamos a
necesitar algunos datos más para operar.

Así pues vamos a hacer un alto en el camino y vamos a aumentar nuestra información dentro
de las BD's con las que vamos a operar.

1.- Vamos a añadir un campo a nuestra tabla TTrabajadores, de tipo texto, que llamaremos
[Dpto]. Añadimos unos cuantos trabajadores más y les asignamos un departamento. Nos
moveremos con tres valores: Comercial / Administración / Ventas.

Por ejemplo, los datos de mi tabla quedarían así:

2.- Vamos a añadir una tabla en nuestra BD de pruebas que nos va a recoger unos cuantos
trabajos realizados por nuestros trabajadores. A esa tabla la vamos a llamar TTrabajos. Tendrá
la siguiente estructura:

19
Visítame en http://siliconproject.com.ar/neckkito/
Tened en cuenta que el campo [IdTrab]
está creado a través del asistente,
buscando en la tabla TTrabajadores y
seleccionando los campos [DNI] y
[Nombre]. Evidentemente el valor que
nos guardará será la clave principal,
esto es, el DNI del trabajador.

Rellenamos esta tabla con algunos


datos. Por ejemplo, yo la he rellenado
así:

3.- Creamos otra tabla, la tabla TRetribuciones, con la siguiente estructura:

4.- Vamos a crearnos otra base de datos (otro archivo de Access), al que vamos a llamar
Retribuciones.mdb (o .accdb. Recordad que debéis mantener la extensión que le hayáis puesto
cuando escribamos el código). Nuestra BD de pruebas y esta otra BD estarán en el mismo
directorio.

4.- En esa nueva BD vamos a crear una tabla, que llamaremos TPrecioHora, que tendrá la
siguiente estructura:

Rellenamos la tabla con los siguientes datos:

20
Visítame en http://siliconproject.com.ar/neckkito/
Bueno, bueno... ya tenemos la “carne en el asador”.
Sigamos.

EL EJEMPLO FINAL: UN “MIX” DE TODO LO APRENDIDO.


La idea del ejemplo es construir los datos de la tabla TRetribuciones a través de código, para
saber cuánto dinero, por horas extras, les debemos pagar a nuestros empleados.

Vamos a necesitar, como SQL's:

– Una consulta que nos sume las horas realizadas por cada trabajador.
– Una consulta que nos indique el nombre del trabajador que estamos examinando en ese
momento y el departamento al que pertenece.

Estas dos consultas podrían resumirse en un solo paso, pero yo lo haré en dos para practicar el
tema de los recordset sobre SQL.

¿Qué pasa? Pues, por ahora, nuestros conocimientos de SQL son un poco “limitados”. Vamos a
hacer un poco de trampa y le diremos a Access que nos de una pista de cómo construir
nuestra SQL

Cómo sería nuestra SQL


Lo que haremos será lo siguiente:

1.- Creamos una consulta en vista diseño sobre la tabla TTrabajos. Añadimos sólo los campos
[IdTrab] y [Horas] y la convertimos en una consulta de totales. Agrupamos el campo [Horas]
por suma. Nos debería quedar una cosa así:

21
Visítame en http://siliconproject.com.ar/neckkito/
Ahora situamos esta consulta en vista SQL. Nos encontraremos con lo siguiente:

Pues ya sabemos cómo sería la SQL que nos da la suma de


las horas. Tomamos nota de este código SQL para poderlo
utilizar en nuestro código. A esta sql la llamaremos
sqlSumaHoras (en el código). Ya podemos borrar esta
consulta.

ESTRUCTURACIÓN DEL CÓDIGO


Pensad, como os comentaba al principio del capítulo, al ponerme filosófico sobre el recordset,
que a mí me va bien “ponerme en situación” e imaginarme cómo lo haría si lo tuviera que
hacer con lápiz y papel.

Es decir:

La SQL sqlSumaHoras nos proporciona dos datos: el identificador del trabajador y el número
de horas.

¿Para qué me sirve el identificador del trabajador? Pues realiza dos funciones:
– Me da el DNI para buscar el nombre del trabajador
– Me da el DNI para buscar el departamento al que pertenece el trabajador.

Y estos datos, ¿dónde los tengo? En la tabla TTrabajadores.

Conclusión, puedo utilizar el DNI para otra SQL, sqlDatos, que me devuelva esos dos datos que
voy a necesitar.

¿Para qué me sirve el número de horas? Pues para multiplicar las horas por el precio hora.
Pero no hay un solo precio hora, sino que depende del departamento. ¿Y de dónde saco el
departamento? Pues me lo va a dar sqlDatos, como comentábamos hace un momento, y si sé
el departamento sé el precio hora.

Bueno: ahora ya tengo el identificador del trabajador, su nombre, sus horas y su precio hora
según el departamento. ¿Qué hago con todos esos datos? Pues necesito escribirlos en la tabla
TRetribuciones.

Creo que, en principio, ya tenemos todos los pasos más o menos claros para meterme con el
código.

Vamos allá

DESARROLLANDO EL CÓDIGO
Os voy a poner el código extensamente comentado. Pensad que todo lo que vamos a hacer, de
una manera u otra, ya lo hemos visto en apartados anteriores. Sólo nos falta la “soltura” para
combinarlo todo... y para eso estamos, para que cojáis soltura.

22
Visítame en http://siliconproject.com.ar/neckkito/
En vez de asignaciones directas voy a pasar todos los valores por variables. Creo que de esta
manera lo veréis más claro.

El código sería:


Private Sub cmdCalculoPagoHoras_Click()
'Declaramos las variables
Dim bdExt As String 'Nos definirá la ruta, nombre y
extensión de Retribuciones.mdb
Dim sqlSumaHoras As String 'SQL que nos dará el total
de horas por trabajador
Dim sqlDatos As String 'SQL que nos dará los datos del nombre y departamento
Dim vDNI As Integer 'Variable que recoge el DNI del trabajador
Dim vNom As String 'Variable que recoge el nombre del trabajador
Dim vDep As String 'Variable que recoge el departamento del trabajador
Dim vPH As Currency 'Variable que recoge el Precio Hora
Dim vNH As Integer 'Variable que recoge el número de horas
Dim dbs As DAO.Database 'Será nuestra base actual
Dim dbsRetrib As DAO.Database 'Será nuestra base Retribuciones.mdb
Dim rstRetrib As DAO.Recordset 'Será el recordset sobre la tabla TPrecioHora
Dim rstSumaHoras As DAO.Recordset 'Será el recordset de sqlSumaHoras
Dim rstDatos As DAO.Recordset 'Será el recordset de sqlDatos
Dim rst As DAO.Recordset 'Será el recordset sobre la tabla TRetribuciones
'Definimos las dbs's
Set dbs = CurrentDb
bdExt = Application.CurrentProject.Path & "\Retribuciones.mdb"
Set dbsRetrib = DBEngine.OpenDatabase(bdExt)
'Creamos el recordset que nos debe rellenar la tabla TRetribuciones
Set rst = dbs.OpenRecordset("TRetribuciones", dbOpenTable)
'Creamos la SQL sqlSumaHoras (recordad que hemos copiado el código de la consulta
'que hemos creado y que hemos puesto en vista SQL. NO PONEMOS EL PUNTO Y COMA
FINAL!
sqlSumaHoras = "SELECT TTrabajos.IdTrab, SUM(TTrabajos.Horas) AS SumaDeHoras"
sqlSumaHoras = sqlSumaHoras & " FROM TTrabajos GROUP BY TTrabajos.IdTrab"
'Creamos el recorset sobre la sql
Set rstSumaHoras = dbs.OpenRecordset(sqlSumaHoras, dbOpenSnapshot)
'Comprobamos que haya registros. Si no hay registros salimos
If rstSumaHoras.RecordCount = 0 Then Exit Sub
'Nos movemos al primer registro
rstSumaHoras.MoveFirst
'Iniciamos el recorrido de los registros
Do Until rstSumaHoras.EOF
'Cogemos el identificador del trabajador y el total de horas realizadas
vDNI = rstSumaHoras.Fields(0).Value
vNH = rstSumaHoras.Fields(1).Value
'Ya tenemos el DNI. Vamos a crear la consulta sqlDatos que nos dé los datos
'que necesitamos, que son el nombre y el departamento. Crearemos la sql
'ya filtrada por DNI
sqlDatos = "SELECT TTrabajadores.Nombre, TTrabajadores.Dpto FROM TTrabajadores"
sqlDatos = sqlDatos & " WHERE TTrabajadores.DNI=" & vDNI
'Creamos el recordset sobre esta consulta
Set rstDatos = dbs.OpenRecordset(sqlDatos, dbOpenSnapshot)
'Cogemos los valores que necesitamos
vNom = rstDatos.Fields(0).Value
vDep = rstDatos.Fields(1).Value

23
Visítame en http://siliconproject.com.ar/neckkito/
'Ya sabemos el departamento. Vamos a buscar el precio hora de ese departamento,
'pero la búsqueda la realizaremos en la BD Retribuciones.mdb, en la tabla
'TPrecioHora. Para ello, recorreremos los registros de TPrecioHora hasta encontrar
'la coincidencia de departamentos
'Creamos el recordset
Set rstRetrib = dbsRetrib.OpenRecordset("TPrecioHora",
dbOpenSnapshot)
'Aquí podría haber un control de registros,
pero ya sabemos cómo hacerlo
'Nos situamos en el primer registro
rstRetrib.MoveFirst
'Iniciamos el recorrido de registros
Do Until rstRetrib.EOF
'Aplicamos el criterio a través de un bloque IF...END IF
If rstRetrib.Fields("Departamento").Value = vDep Then
vPH = rstRetrib("Precio").Value
Exit Do 'Ya hemos encontrado lo que buscábamos. Podemos salir
End If
'Nos movemos al siguiente registro
rstRetrib.MoveNext
Loop
'Y ya tenemos todos los elementos que necesitábamos. Vamos a crear el registro
'de resultados en nuestra tabla TRetribuciones
With rst
.AddNew
.Fields("DNI").Value = vDNI
.Fields("Nombre").Value = vNom
.Fields("ACobrar").Value = vNH * vPH
.Update
End With
'Listo para un registro de sqlSumaHoras. Vamos a analizar el siguiente
rstSumaHoras.MoveNext
Loop
'Mostramos los resultados
DoCmd.OpenTable "TRetribuciones", , acReadOnly
'Cerramos conexiones y liberamos memoria
rstRetrib.Close
rstSumaHoras.Close
rstDatos.Close
rst.Close
dbs.Close
dbsRetrib.Close
Set rstRetrib = Nothing
Set rstSumaHoras = Nothing
Set rstDatos = Nothing
Set rst = Nothing
Set dbs = Nothing
Set dbsRetrib = Nothing
End Sub

Ahora tendría que decir lo de “¿Fácil, verdad?”, pero creo que, para este ejemplo, no os lo
diré... je, je... Lo cambiaré por

¡Ánimo!

24
Visítame en http://siliconproject.com.ar/neckkito/
UNAS PALABRAS FINALES
Hasta aquí el sistema DAO. Nos queda pendiente la
colección ERROR, pero lo veremos como algo independiente
en un capítulo próximo. Creo que este capítulo ha sido
bastante “intenso” y dejaremos descansar nuestros “pobres
cerebros”.

En el siguiente capítulo ya nos meteremos con ADO.

25
Visítame en http://siliconproject.com.ar/neckkito/
CURSO DE VB

CAPÍTULO 121

Índice de contenido
OBJETOS DE ACCESO A DATOS (III).............................................................................................2
CONFESIONES ÍNTIMAS.............................................................................................................2
UN POCO DE TEORÍA..................................................................................................................2
REGISTRANDO LIBRERÍAS........................................................................................................4
PREPARANDO NUESTRA BD.....................................................................................................4
CÓDIGOS BÁSICOS INICIALES.................................................................................................6
INICIANDO LAS CONEXIONES............................................................................................6
USO DEL “NEW” PARA NUEVAS CONEXIONES................................................................7
FINALIZANDO LAS CONEXIONES......................................................................................8
CONTROLANDO LAS TRANSACCIONES................................................................................8
LOS RECORDSET........................................................................................................................10
RECORDSET SOBRE UNA TABLA......................................................................................11
RECORDSET SOBRE UNA CONSULTA SQL......................................................................12
EL OBJETO COMMAND.............................................................................................................13
ALGUNOS CÓDIGOS DE EJEMPLO.........................................................................................15
Separación de datos...................................................................................................................15
Búsqueda de registro: método SEEK........................................................................................17
Investigando los campos de nuestras tablas..............................................................................18
Y PARA FINALIZAR...................................................................................................................19

1 La BD donde están los ejemplos de este capítulo os la podéis bajar aquí.

1
Visítame en http://siliconproject.com.ar/neckkito/
OBJETOS DE ACCESO A DATOS (III)
En el capítulo anterior realizamos una amplia incursión
sobre DAO. En este capítulo vamos a echar una ojeada a
ADO.

La mecánica de este capítulo, para ser originales, va a ser


muy parecida a lo que venimos realizando en este curso.
Nos vamos a preparar una BD en blanco con una serie de
objetos para ir desarrollando teoría y práctica al mismo
tiempo. Así, iremos asimilando conceptos, funciones y
métodos al tiempo que vemos cómo se aplican en la
práctica.

Aunque este capítulo se podría estudiar de forma independiente y “saltándose” los capítulos 10
y 11 yo os recomiendo haber examinado estos dos capítulos con anterioridad, por los motivos
que apuntaré en el apartado siguiente.

CONFESIONES ÍNTIMAS
Ha llegado este “tierno” momento donde debo “desnudar mi alma” y confesaros un par de
cosas. Antes de que alguien, al llegar al final de este capítulo, diga “¿y eso es todo?”, os
comentaré un par de cosas que creo son importantes:

 Yo, prácticamente, no utilizo ADO en mis programaciones, sino que utilizo DAO. ¿Y eso, qué
significa? Que mis conocimientos sobre ADO son menores que sobre DAO. Lógicamente, al no
utilizarlo con asiduidad mis pulsaciones sobre la tecla F1 son más habituales (de hecho,
buscando información por aquí y por allá para poder escribir este capítulo estoy aprendiendo
“cosillas” que desconocía... je, je...).

 En el fondo (y supongo que alguien que sepa sobre el tema me tachará de sacrílego) DAO y
ADO son lo mismo, pero con distinta nomenclatura (ojo, esto debéis tomarlo como una opinión
muy general). Sí es posible establecer, en la mayoría de casos, una gran analogía entre ambos
“procedimientos”. Por ello os decía unas líneas más arriba lo de “examinar antes los capítulos
10 y 11”. Si los habéis estudiado veréis esas analogías que os comentaba.

 Podríamos decir que si existía una relación entre SQL y DAO dicha relación es aún más
estrecha entre SQL y ADO. Es decir, que vamos a tener que conocer un “poquito más” de
lenguaje SQL para poder extraer el jugo a nuestros códigos.

 Finalmente, y por todo lo anterior, este capítulo no será tan extenso como los anteriores, en
el sentido en que me centraré en las operaciones que pienso son más usuales (según mi
criterio, claro), dejando las “profundidades del tema” a un lado. Para quien quiera saber más le
animo a leerse la ayuda que proporciona Access y, si tan grande es su entusiasmo por ADO, a
la compra de un buen manual sobre este modelo de acceso a datos.

UN POCO DE TEORÍA
Las siglas ADO significan ActiveX Data Objects. A través de ADO podemos acceder a datos
situados en un servidor de bases de datos (ojo, no sólo Access) y manipular a nuestro antojo
dichos datos a través de lo que se denomina un “proveedor OLE DB”.

Nuestro amigo Microsoft, de hecho, recomienda el uso de ADO antes que DAO (¿será por eso

2
Visítame en http://siliconproject.com.ar/neckkito/
por lo que no lo utilizo? ;) ). Entre las ventajas que destaca de ADO tenemos:

– Es fácil de usar
– Utiliza poca memoria
– Utiliza poco espacio en el disco
– Tiene un buen rendimiento
– Ofrece funcionalidades básicas que permiten crear
aplicaciones cliente/servidor y aplicaciones web.

En fin... Vosotros, al final del capítulo, os habréis podido


formar un criterio (o eso espero) de qué sistema es el que
más os gusta.

Hemos hablado de un “proveedor OLE DB”. ¿Y qué es esto? Pues no es más que una tecnología
que nos permite tener acceso a prácticamente cualquier fuente de información. Y cuando digo
“prácticamente a cualquier fuente de información” me refiero a una acepción amplia del
término: cualquier “lugar” donde haya datos... ahí llega ADO. No me centro, pues, sólo en
bases de datos, sino que debemos pensar “a lo grande”: bases de datos (relacionales o no),
sistemas de archivos, correo electrónico, etc.

Hasta donde yo sé, podríamos acceder a los siguientes elementos a través de ADO, ya que
están basados en la tecnología OLE DB (no entraré en explicaciones porque no le veo mucho
sentido. Simplemente os lo apuntaré para que veáis el elenco de posibilidades):

• Bases ODBD → PDOLEDB2: MSADSQL


• SQL Server → PDOLDB: SQLOLE DB
• Bases Oracle → PDOLDB:MSDAORA
• Bases de Access 2007 → PDOLDB: Microsoft Office 12.0 OLE DB (14.0 para Access
2010. Para versiones anteriores tenemos Microsoft Jet 4.0 OLE DB Provider)
• Index Server → PDOLDB:MSIDXS
• Active Directory → PDOLDB: Objeto ADSDSO
• AS 400 IMS-CICS VSAM → PDOLDB: SNA Server

Si tuviéramos que jerarquizar el modelo ADO podríamos obtener una cosa así:

OBJETO COLECCIÓN
CONNECTION
…................................................... ERRORS
…................................................... PROPERTIES
COMMAND
…................................................... PARAMETERS
…................................................... PROPERTIES
RECORDSET
…................................................... FIELDS
…................................................... PROPERTIES
RECORD
…................................................... FIELDS
STREAM

2 PDOLEDB: Proveedor de datos OLE DB

3
Visítame en http://siliconproject.com.ar/neckkito/
Finalmente, si tuviéramos que esquematizar las “cosas” que hace ADO, podríamos hablar de
que “ADO nos permite” (en términos generales):

– Establecer una conexión (lógico, puesto que necesitamos


“conectar con los datos”) a bases de datos ODBC u OLE DB
– Esa conexión la establecemos a través del objeto
Connection
– Es habitual utilizar la palabra “transacción” para referirnos a
la “actividad que ADO está realizando”. ADO nos permite,
iniciada una transacción, la posibilidad de aceptar o
rechazar las “operaciones” que se han realizado durante
dicha transacción.
– Como DAO, y una vez se ha accedido a los datos, podemos utilizar SQL para
modificarlos, actualizarlos, añadir nuevos, consultarlos... Y todo ello enviando
comandos, manipulando las propiedades de los objetos, utilizando funciones y
métodos...

En fin, que ya podéis haceros una primera idea de “vida y milagros” de ADO

REGISTRANDO LIBRERÍAS
Para poder utilizar ADO debemos registrar la librería correspondiente. Tenemos dos librerías
disponibles para ello: la hermana rica y la hermana pobre... ji, ji...

La hermana rica es la librería “Microsoft ADO Ext 2.x for DLL and Security”. El archivo dll que
se corresponde con esa librería se llama MSADOX.DLL. ¿Y por qué es “rica”? Porque, además
de funcionalidades ADO, esta librería permite acceder a objetos complementarios, como, por
ejemplo, users, views, catalogs..., que también son manipulables a través de ADO.

La hermana pobre es la librería “Microsoft ActiveX Data Objects 2.x Library”, que contiene los
elementos imprescindibles para poder trabajar con ADO.

En ambos casos esas DLL's, en mi ordenador, están en <C:\Archivos de programa\Archivos


comunes\System\ado>. En los vuestros no creo que se hayan ido demasiado lejos.

Por si alguien se ha “despistado” recordad que para registrar la librería debemos, en el VBE,
irnos a Menú Herramientas → Referencias..., buscar una de esas bibliotecas, marcar su check y
aceptar.

PREPARANDO NUESTRA BD
Llegados a este punto vamos a prepararnos el terreno para este capítulo. Vamos a crear una
BD en blanco y vamos a crear:

– Una tabla, que llamaremos TAerop, que tendrá la siguiente estructura.

Rellenamos con algunos datos la siguiente tabla. Pero, ojo, vamos a cometer unos errores a

4
Visítame en http://siliconproject.com.ar/neckkito/
propósito: vamos a escribir mal el nombre de dos aeropuertos. Por ejemplo, escribiremos
“Madri” en lugar de Madrid y “Barselona” en lugar de Barcelona. Yo he puesto los siguientes
valores:

– Vamos a crear una segunda tabla, que llamaremos TCias, con la siguiente estructura.
Además, la rellenamos con algunos valores (no tengo ni idea de si las abreviaturas que
he puesto se corresponden con la realidad, puesto que tengo “escasos conocimientos”
del tema de aeropuertos).

Finalmente, vamos a crear la última tabla, que tendrá por nombre TVuelos, que tendrá la
siguiente estructura:

Teniendo en cuenta lo siguiente:

– Campo [Cia]: hemos utilizado, como tipo de datos, el asistente para búsqueda, que nos
ha buscado la información en la tabla TCias. Por tanto, el valor que almacena es el
identificador la compañía.
– Campos [AeOr] y [AeDest]: hemos utilizado, como tipo de datos, el asistente para
búsqueda, que nos ha buscado la información en el tabla TAerop. Por tanto, el valor que
almacena es el identificador del aeropuerto.

Rellenamos la tabla con los datos que queramos. Por ejemplo, yo he utilizado los siguientes
datos:

5
Visítame en http://siliconproject.com.ar/neckkito/
Y ya tenemos nuestro “abono” preparado. Vamos a meternos de lleno ya en ADO.

CÓDIGOS BÁSICOS INICIALES

INICIANDO LAS CONEXIONES


Lo primero que debemos hacer, como es habitual, es declarar las variables. Vamos a ver cómo
sería la declaración de variables en ADO. Ya os avanzo que los nombres de las variables que
vamos a utilizar son las que normalmente se utilizan con ADO, y que son las que yo también
voy a utilizar. Os pongo las Dim más habituales, pero no significa que en cada código las
necesitemos todas:

...
Dim cnn as ADODB.Connection → Nos servirá para establecer la conexión con la fuente de
datos.

Dim cmd as ADODB.Command → Nos servirá para asignar un comando que podamos crear.

Dim rs as ADOB.Recordset → Nos servirá para asignar un recordset.


Ahora debemos definir las variables. Aquí nos encontramos con dos situaciones posibles:

1ª: queremos establecer una conexión con la base de datos en la que estamos trabajando
2ª: queremos establecer una conexión con una fuente de datos externa (es decir, con un
proveedor de OLE DB).

Vamos a ver cómo conectaríamos con la base de datos actual:


Set cnn = CurrentProject.Connection

Y la conexión a una fuente externa sería (ojo, que esto no es estándar: dependerá de las
características del proveedor OLE al que queramos conectarnos):

...
Set cnn = NEW ADOB.Connection

6
Visítame en http://siliconproject.com.ar/neckkito/
cnn.Open “Provider=<NombreDelProveedorOLE>;<Argumentos(siLosHay)>;<OrigenDatos>;<Otros>;”

Como lo anterior quizá pueda ser un poco “lioso” vamos a


poner tres ejemplos, simplemente para que podáis ver la
mecánica de la conexión:

Ejemplo 1: nos queremos conectar a un proveedor SQL


Server, cuya fuente de datos está en el servidor miSVR y
los datos están en la tabla DatosFin


Dim cnn As ADODB.Connection
Set cnn = NEW ADODB.Connection
cnn.Open “Provider=SQLOLEDB.1; Data Source= miSVR; Initial Catalog=DatosFin” _
& “Integrated Security=SSPI; Persist Security Info=False;”

Ejemplo 2: nos queremos conectar a una base de datos Access llamada “Cafes.mdb”, versión
2003, que está en local, en el directorio “C:\Productos\”.

...
Dim cnn As ADODB.Connection
Set cnn = NEW ADODB.Connection
cnn.Open “Provider=Microsoft.Jet.OLEDB.4.0.; Data Source=C:\Productos\Cafes.mdb;”

Fijaos que en esta conexión, por ejemplo, no he manipulado elementos opcionales.

Ejemplo 3: ¿y si queremos abrir la misma BD que en ejemplo 2, pero el archivo es


“Cafes.accdb” (es decir, Access 2007)? Pues la abriríamos así:


Dim cnn As ADODB.Connection
Set cnn = NEW ADODB.Connection
cnn.Open “Provider=Microsoft.ACE.OLEDB.12.0.; Data Source=C:\Productos\Cafes.accdb;” _
& “Persist Security Info=False;”
...

Y, en este caso, por ejemplo, sí que he utilizado elementos opcionales.

Fijaos en que es muy importante no olvidarse de los punto y coma (;) en la declaración de
apertura de la conexión.

USO DEL “NEW” PARA NUEVAS CONEXIONES


Si os habéis fijado en los códigos anteriores hemos indicado que la conexión es una “nueva
conexión” a través de la palabra NEW.

Tenemos dos posibilidades para utilizar la palabra NEW: en la declaración de variables o en la


definición de variables. O utilizamos una o utilizamos otra, pero nunca las dos a la vez.

Aunque ya lo hemos visto, para mayor claridad vamos a ver las estructuras:

1ª manera: seguiría la estructura siguiente:

7
Visítame en http://siliconproject.com.ar/neckkito/
– La variable cnn es una conexión ADO
– Establecemos cnn como una nueva conexión

Lo anterior, traducido a código, sería:


Dim cnn As ADODB.Connection
Set cnn = NEW ADODB.Connection

Y lo mismo podríamos hacer para establecer el resto de


variables.

2ª manera: declaración directa de que la conexión es ADO y que además es una nueva
conexión:

Seguiría la siguiente estructura:

– La variable es una nueva conexión ADO

Y traducido a código sería:


Dim cnn As NEW ADODB.Connection

¿Qué sistema es el mejor? Pues, realmente, yo no lo sé. Si me preguntáis qué uso yo, pues os
diré que generalmente utilizo la primera estructura, pero simplemente por “preferencias
personales”. Quizá alguien que siga el curso y encuentre en algún sitio alguna explicación
sobre este tema pueda escribirme y darme algún tipo de explicación ;)

FINALIZANDO LAS CONEXIONES


Como ya vimos en DAO, podemos finalizar las conexiones y liberar memoria. El código para
realizar eso ya nos es conocido, y sería:


rs.Close
cnn.Close
Set rs = Nothing
Set cnn = nothing

Sin más problemas.

CONTROLANDO LAS TRANSACCIONES


Si estamos seguros de que las acciones que vamos a realizar sobre los datos, a través de
nuestro código deben realizarse imperativamente no es necesario un control de transacciones.
Sin embargo, si queremos controlar, en el momento final, que se apliquen los cambios que
hemos estado efectuando o no, podemos establecer un control de transacciones.

Las palabras “mágicas” para ello son:

– BeginTrans → Inicia la transacción

8
Visítame en http://siliconproject.com.ar/neckkito/
– CommitTrans → Guarda la transacción
– RollbackTrans → Cancela la transacción

Vamos a realizar un ejemplo sobre nuestra BD.


¿Recordamos que en la tabla TAerop escribimos “Madri” en
vez de “Madrid”, y “Barselona” en lugar de “Barcelona”? En
este caso hablamos de dos registros, pero imaginemos que
tenemos 2.000 registros con errores tipográficos. Vamos a
ver cómo podemos cambiarlos todos de golpe.

Si creamos un formulario en blanco en nuestra BD y añadimos un


botón de comando podríamos asignar el siguiente código:


Private Sub cmdMadri_Click()
'Declaramos las variables
Dim cnn As ADODB.Connection
Dim miSql As String, resp As String
'Realizamos la conexión con la BD actual
Set cnn = CurrentProject.Connection
'Controlamos el inicio de la transacción
cnn.BeginTrans
'Creamos la SQL para actualizar "Barselona"
miSql = "UPDATE TAerop SET Aerop = 'Barcelona' WHERE Aerop = 'Barselona'"
'Ejecutamos la SQL
cnn.Execute miSql
'Creamos la segunda SQL para actualizar "Madri"
miSql = "UPDATE TAerop SET Aerop = 'Madrid' WHERE Aerop = 'Madri'"
'Ejecutamos la SQL
cnn.Execute miSql
'Solicitamos confirmación para validar las actualizaciones. Si responde que SÍ
'aplicamos los cambios; si responde que NO no los aplicamos
resp = MsgBox("Actualizaciones correctas. ¿Desea aplicarlas?", vbQuestion + vbYesNo,
"AVISO")
If resp = vbYes Then
cnn.CommitTrans
Else
cnn.RollbackTrans
End If
'Cerramos conexiones y liberamos memoria
cnn.Close
Set cnn = Nothing
End Sub

Destacar los siguientes detalles:

– Hemos establecido la conexión a través de la variable cnn. Si os fijáis veréis que en el


resto del código continuamente hacemos referencia a la conexión a través de “cnn.” +
método/propiedad (por ejemplo, cnn.BeginTrans, cnn.Execute, …).
– Para manipular los datos hemos utilizado una SQL que se correspondería con una
consulta de actualización. En el capítulo anterior ya os expliqué cómo utilizar las
consultas de Access para que nos diera una pista de cómo podría ser la SQL. A ese
“truquillo” os remito.
– Una vez establecida la conexión con la base de datos actual, y a continuación,
empezamos a controlar las acciones que se realizarán en el código a través de

9
Visítame en http://siliconproject.com.ar/neckkito/
<cnn.BeginTrans>
– Realizadas ya las operaciones nos quedamos a punto de
aplicarlas. Es cuando solicitamos al usuario que dé su
conformidad o no. Si acepta utilizamos
<cnn.CommitTrans>; si no acepta utilizamos
<cnn.RollbackTrans>
– Para ejecutar una SQL ved que utilizamos cnn.Execute
<nombreSQL>

Creo que, a través de este ejemplo, vemos que ADO tiene


sus peculiaridades, pero la similitud con DAO empieza a ser
un poco “evidente”. Lo veremos sobre todo con el tema de
los recordset... ¿y por qué esperar más? Vamos a por los
recordset.

LOS RECORDSET
Para abrir un recordset de una tabla o consulta, entendida la consulta como objeto de Access,
debemos, en el código:

– Declarar el recordset, como ya hemos visto a través de Dim rs As ADODB.Recordset


– Crear el recorset mediante Set rs = NEW ADODB.Recordset
– Abrir el recorset mediante rs.Open
– Y ya podemos realizar las operaciones necesarias.

La estructura de rs.Open sería la siguiente:

rs.Open <Origen>, <conexión activa>, <tipo de cursor>, <tipo de bloqueo>, <opciones>

Vamos a ver algunos de estos argumentos (todos menos las <opciones>):

 Origen: se correspondería con el nombre de la tabla o consulta de las cuales queremos


crear el recordset
 Conexión activa: sería la conexión activa que hemos definido; es decir, nuestro cnn
 Tipo de cursor: nos indicaría cómo debe comportarse el puntero que recorre el
recordset. Tenemos diferentes opciones (no vamos a entrar en demasiados
tecnicismos):
◦ adOpenDynamic: es decir, un cursor dinámico. Para que nos entendamos, podemos
ir adelante y atrás en el recordset, y además ver los registros que se han añadido,
modificado o eliminado por otros usuarios
◦ adOpenKeyset: es igual que el dinámico, con la excepción que no se pueden ver los
registros que otros usuarios han agregado. Se denomina “Keyset” porque utiliza un
conjunto de claves.
◦ adOpenStatic: se correspondería con la idea de “Snapshot” que veíamos en
capítulos anteriores.
◦ adOpenForwardOnly: igual al estático, pero sólo permite el movimiento hacia
adelante. Es el valor predeterminado si no se especifica este argumento.
 Tipo de bloqueo: se refiere al bloqueo que se aplica a los registros cuando utilizamos el
recordset. Tenemos varios tipos de bloqueo:
◦ adLockOptimistic: indica un bloqueo optimista.
◦ adLockPessimistic: indica un bloqueo pesimista.
◦ adLockReadOnly: sólo lectura, lo que implica que no es posible modificar datos.
◦ adLockBatchOptimistic: bloqueo optimista, pero por lotes.

¿Y ahora me pediréis: “qué es un bloqueo”? Os copio lo que dice Access sobre el tema de los

10
Visítame en http://siliconproject.com.ar/neckkito/
bloqueos, porque está más o menos claro. De todas maneras debéis quedaros con la idea de
que un bloqueo es una acción que se establece sobre un registro o conjunto de registros para
evitar que dos o más usuarios puedan modificar o actualizar, a la vez, el mismo registro (o
conjunto de registros). Ahí va lo que “opina” Access sobre el tema:

<<El bloqueo es el proceso mediante el cual un sistema de


administración de bases de datos (DBMS) restringe el
acceso a una fila en un entorno multiusuario. Cuando una
fila o una columna se bloquean de forma exclusiva, el
acceso a los datos bloqueados queda cerrado mientras no
se libere el bloqueo. De este modo, se garantiza que dos
usuarios no puedan actualizar simultáneamente la misma
columna de una fila.
Los bloqueos pueden ser muy costosos en cuanto a recursos y, por tanto, sólo se deben
utilizar cuando sea necesario para preservar la integridad de los datos. En una base de datos
en la que cientos o miles de usuarios podrían estar intentando obtener acceso a un registro
cada segundo (por ejemplo, una base de datos en Internet), el uso de bloqueos innecesarios
podría reducir el rendimiento de la aplicación.
La forma en que el origen de datos y la biblioteca de cursores ADO supervisan la concurrencia
se puede controlar si elige la opción de bloqueo apropiada.
Establezca la propiedad LockType antes de abrir un objeto Recordset para especificar qué
tipo de bloqueo debe utilizar el proveedor al abrirlo. Lea la propiedad para obtener el tipo de
bloqueo utilizado en un objeto abierto de este tipo.
Puede que los proveedores no admitan todos los tipos de bloqueo. Si un proveedor no admite
la opción LockType solicitada, la sustituirá por otro tipo de bloqueo. Utilice el método
Supports con adUpdate y adUpdateBatch para determinar la funcionalidad real de bloqueo
disponible en un objeto Recordset.
El valor adLockPessimistic no se admite si la propiedad CursorLocation se establece en
adUseClient. Si se establece un valor no admitido, no se producirá ningún error; en su lugar,
se utilizará el LockType admitido más próximo.
La propiedad LockType es de lectura y escritura cuando el objeto Recordset está cerrado, y
de sólo lectura cuando está abierto.>>

RECORDSET SOBRE UNA TABLA


Vamos pues a crear un recordset que nos recorrerá los registros de la tabla TCias y nos dirá
qué compañías tenemos de alta. El código sería el siguiente:


Private Sub cmdVerCias_Click()
'Declaramos las variables
Dim cnn As ADODB.Connection
Dim rs As ADODB.Recordset
'Realizamos la conexión
Set cnn = CurrentProject.Connection
'Creamos el recordset
Set rs = New ADODB.Recordset
'Abrimos el recordset
rs.Open "TCias", cnn, adOpenKeyset, adLockReadOnly, adCmdTableDirect
'Inicamos el recorrido de registros
rs.MoveFirst
Do Until rs.EOF
MsgBox rs(2).Value, vbInformation, "COMPAÑÍA"

11
Visítame en http://siliconproject.com.ar/neckkito/
rs.MoveNext
Loop
'Cerramos conexiones y liberamos memoria
rs.Close
cnn.Close
Set rs = Nothing
Set cnn = Nothing
End Sub

Como podemos ver, el comportamiento es muy similar a lo que ya


conocíamos, evidentemente con las peculiaridades propias de
ADO.

RECORDSET SOBRE UNA CONSULTA SQL


Vamos a ver una variación sobre el tema: vamos a abrir un recordset sobre una SQL que
habremos creado previamente.

Por ejemplo, el procedimiento, muy parecido al anterior, nos va a decir cuántos pasajeros
volaron entre los días 5 y 6 de febrero (ojo, según los datos que yo he utilizado en la BD de
ejemplo. Si vosotros habéis utilizado otros deberéis ajustar las fechas a vuestros datos).

El código podría ser el siguiente:


Private Sub cmdOrigYDest_Click()
'Declaramos las variables
Dim miSQL As String
Dim numPas As Long
Dim cnn As ADODB.Connection
Dim rs As ADODB.Recordset
'Establecemos la conexion con la base activa. Creamos el recordset
Set cnn = CurrentProject.Connection
Set rs = New ADODB.Recordset
'Inicializamos la variable numPas
numPas = 0
'Creamos la SQL
miSQL = "SELECT TVuelos.NPas FROM TVuelos"
miSQL = miSQL & " WHERE TVuelos.Fecha BETWEEN #02/05/12# AND #02/06/12#"
'Abrimos el recordset
rs.Open miSQL, cnn, adOpenDynamic, adLockReadOnly
'Iniciamos el recorrido de registros
Do Until rs.EOF
numPas = numPas + rs(0).Value
rs.MoveNext
Loop
'Mostramos el resultado
MsgBox "Entre el 5 y el 6 de febrero volaron " & numPas & " personas", vbInformation,
"DATOS"
'Cerramos conexiones y liberamos memoria
rs.Close
cnn.Close
Set rs = Nothing
Set cnn = Nothing
End Sub

12
Visítame en http://siliconproject.com.ar/neckkito/

Fijaos que, en la construcción de la SQL, hemos tenido que


utilizar las fechas en formato inglés (mm/dd/aa). Tened
esto en cuenta cuando vayáis a trabajar con fechas dentro
de un recordset.

EL OBJETO COMMAND
Vamos a aprovechar este apartado para abrir una conexión con
una base de datos externa. Para ello vamos a tener que:

– Creamos una BD, que llamaremos DatosIB.xxx, donde xxx será o bien la extensión mdb
o accdb (yo lo haré sobre mdb por compatibilidad). Guardaremos esa BD en el mismo
directorio donde tenemos nuestra BD de pruebas.
– En esa BD creamos una tabla, que llamaremos TCargos, con la estructura siguiente:

Construida la tabla vamos a meter algunos datos. Por ejemplo, yo he introducido los
siguientes:

Vamos a crear una tabla en nuestra BD de pruebas con los comandantes de la compañía IB.
Esa tabla se llamará TComandIB. Veamos cómo sería el código, que estructuraremos en
diversas fases3:


Private Sub cmdCreaTComandIB_Click()
'-----FASE 1--------------------------------------------
'Eliminamos la tabla si existiera
'Declaramos las variables de esta fase
Const nomTbl As String = "TComandIB"
Dim tbl As Object
'Eliminamos la tabla, si existiera
For Each tbl In CurrentData.AllTables
If tbl.Name = nomTbl Then
DoCmd.DeleteObject acTable, nomTbl

3 Para usuarios de Access 2003: si no os funciona el código deberéis registrar la librería “Microsoft DAO 3.6 Object Library”

13
Visítame en http://siliconproject.com.ar/neckkito/
Exit For
End If
Next tbl
'-----FASE 2--------------------------------------------
'Creamos la estructura de la tabla a través de una
SQL
'Declaramos y definimos las variables de esta fase
Dim creaTbl As String
Dim dbs As Database
Set dbs = CurrentDb
creaTbl = "CREATE TABLE " & nomTbl & " (Nom
STRING(25), Cargo STRING(15), Cia STRING(2))"
'Ejecutamos la SQL para que se nos cree la tabla
dbs.Execute creaTbl
dbs.Close
Set dbs = Nothing
'-----FASE 3--------------------------------------------
'Creamos las conexiones y comandos para rellenar la tabla
'Declaramos las variables de esta fase
Const Comp As String = "IB"
Dim cnnExt As New ADODB.Connection
Dim cnn As ADODB.Connection
Dim cmd As New ADODB.Command
Dim rsExt As New ADODB.Recordset
Dim rs As New ADODB.Recordset
Dim ruta As String
'Definimos la ruta a la BD DatosIB
ruta = Application.CurrentProject.Path & "\DatosIB.mdb"
'Abrimos las conexiones
cnnExt.Open "Provider=Microsoft.Jet.OLEDB.4.0.;" _
& "Data Source=" & ruta & ";"
Set cnn = CurrentProject.Connection
'Definimos la conexión del Command y sus propiedades
Set cmd.ActiveConnection = cnnExt
cmd.CommandType = adCmdText
cmd.CommandText = "SELECT * FROM TCargos WHERE TCargos.Cargo='Comandante'"
'Creamos el recordset sobre la SQL definida en Command
Set rsExt = cmd.Execute
'Creamos el recordset que actuará sobre la tabla TCargosIB
rs.Open nomTbl, cnn, adOpenDynamic, adLockOptimistic
'Si no hubiera registros salimos del proceso
If rsExt.RecordCount = 0 Then Exit Sub
'Nos movemos al primer registro
With rsExt
.MoveFirst
Do Until .EOF
rs.AddNew
rs.Fields(0) = .Fields(1)
rs.Fields(1) = .Fields(2)
rs.Fields(2) = Comp
rs.Update
.MoveNext
Loop
End With
'Cerramos conexiones y liberamos memoria
rsExt.Close

14
Visítame en http://siliconproject.com.ar/neckkito/
rs.Close
cnn.Close
Set rsExt = Nothing
Set rs = Nothing
Set cmd = Nothing
Set cnn = Nothing
End Sub

Como podéis ver en el código, el proceso de utilización del


command (en este ejemplo) pasa por:

– Declarar el Command (Dim cmd As New ADODB.Command)


– Definir su conexión (Set cmd.ActiveConnection = cnnExt)
– Especificar sus propiedades (cmd.CommandType = adCmdText)
– Crear el texto de la SQL (cmd.CommandText = "SELECT * FROM TCargos WHERE
TCargos.Cargo='Comandante'")
– Ejecutar el recordset basado en el command que hemos definido (Set rsExt =
cmd.Execute)

Recordad que si habéis utilizado la extensión .accdb (Access 2007 ó 2010) debéis indicar, para
cnnExt, el Proveedor OLEDB correcto. Revisad el apartado, en este capítulo, “Iniciando las
conexiones”.

Yo, personalmente, encuentro el uso del Command particularmente “complicado”. Os remito a


la ayuda de Access si queréis profundizar un poco en el tema.

ALGUNOS CÓDIGOS DE EJEMPLO


Vamos a ver algunos códigos de ejemplo para algunas situaciones “posibles”. Aprovecharemos
para repasar “cosillas” que hemos visto en capítulos anteriores porque así, además de repasar,
podemos ver utilidades varias que quizá podamos aplicar en nuestras bases de datos.

Separación de datos
Imaginemos que hemos importado un Excel a una tabla (que yo he llamado TExcel), que nos
recoge países de origen y destino de los vuelos. Lamentablemente, los datos vienen reflejados
en un solo campo donde se hallan separados por guiones.

Yo me he creado la tabla que recrearía esta circunstancia, e introducido, a modo de ejemplo,


los siguientes datos:

¿Cómo podríamos automatizar la separación de estos datos en dos campos, [Origen] y

15
Visítame en http://siliconproject.com.ar/neckkito/
[Destino]?

Vamos a ver cómo conseguirlo:

1.- Situamos TExcel en vista diseño y añadimos el campo


[Origen] y el campo [Destino], de tipo texto ambos. Para
mayor claridad debería quedarnos así:

2.- En un formulario cualquiera añadimos un


botón de comando, que llamaremos
cmdSepara, y le añadimos el siguiente
código:


Private Sub cmdSepara_Click()
'Declaramos las variables
Dim cnn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim largoPais As Integer, i As Integer
Dim vChar As String
Dim vOr As String, vDest As String
'Definimos las conexiones
Set cnn = CurrentProject.Connection
Set rs = New ADODB.Recordset
'Creamos el recordset
rs.Open "TExcel", cnn, adOpenKeyset, adLockOptimistic, adCmdTableDirect
'Nos movemos al primer registro
rs.MoveFirst
'Iniciamos el recorrido de registros
Do Until rs.EOF
'Cogemos la longitud del campo [Paises]
largoPais = Len(rs.Fields("Paises").Value)
'Recorremos uno a uno los caracteres del valor hasta encontrar el guión
For i = 1 To largoPais
If Mid(rs.Fields("Paises").Value, i, 1) = "-" Then
'Cuando encuentra el guión coge el número de caracteres -1 a la izquierda
'para sacar el valor del país de origen
vOr = Left(rs.Fields("Paises").Value, i - 1)
'Y sacamos el valor para el destino a través del número total de caracteres
'menos los caracteres del valor de origen -1
vDest = Right(rs.Fields("Paises"), largoPais - Len(vOr) - 1)
'Actualizamos los campos origen y destino
With rs
.Fields("Origen").Value = vOr
.Fields("Destino").Value = vDest
.Update
End With
'Salimos el FOR porque ya hemos encontrado el guión
Exit For
End If
Next i

16
Visítame en http://siliconproject.com.ar/neckkito/
'Nos movemos al siguiente registro
rs.MoveNext
Loop
'Cerramos conexiones y liberamos memoria
rs.Close
cnn.Close
Set rs = Nothing
Set cnn = Nothing
End Sub

Si habéis seguido los capítulos anteriores a este, y dado que el código está ampliamente
comentado, no deberías tener problema para entender cada una de las líneas de código
(¡espero!). Y si se os escapa algo os animo a analizarlo tranquilamente para descubrir el
“intringulis”...

Os animo a que, partiendo de los campos [Origen] y [Destino], os creéis un código para
conseguir, en otro campo llamado [DatosUnidos], el mismo valor que en el campo [Paises]. En
la BD de ejemplo tenéis la solución de cómo podría ser un posible código. ¡Ánimo!

Búsqueda de registro: método SEEK


Vamos a ver cómo sería el método Seek para la búsqueda de un registro.

Supongamos que nos llega la información codificada por números de aeropuerto, y en un


momento determinado queremos saber un número a qué aeropuerto pertenece. Podríamos
programar un botón que nos diera esa información. El código sería:


Private Sub cmdCodAerop_Click()
'Declaramos las variables
Dim cnn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim vAero As Variant
'Efectuamos la conexión
Set cnn = CurrentProject.Connection
'Definimos el recordset
Set rs = New ADODB.Recordset
'Solicitamos la información al usuario
vAero = InputBox("¿Código aeropuerto?", "AEROPUERTOS")
'Abrimos el recordset
rs.Open "TAerop", cnn, adOpenKeyset, adLockReadOnly, adCmdTableDirect
With rs
.Index = "PrimaryKey"
.Seek vAero, adSeekFirstEQ
'Si no encontramos el registro...
If .EOF Then
MsgBox "No existe aeropuerto con ese código", vbCritical, "SIN DATOS"
Else
'Si lo encontramos mostramos la información
MsgBox "Aeropuerto: " & .Fields("Aerop"), vbInformation, "RESULTADO BÚSQUEDA"
End If
End With
'Cerramos conexiones y liberamos memoria
rs.Close

17
Visítame en http://siliconproject.com.ar/neckkito/
cnn.Close
Set rs = Nothing
Set cnn = Nothing
End Sub

El código está comentado y no requiere, creo, más


explicaciones.

Investigando los campos de nuestras tablas


Supongamos que, como administradores de la base de datos,
queremos hacer una comprobación rápida de qué campos tiene
una tabla, y de qué tipo son, sin necesidad de abrir la tabla. Para
ello podemos actuar sobre el objeto Field.

Por ejemplo, vamos a echarle un vistazo a las “entrañas” de nuestra tabla TVuelos sin abrirla.

El código sería el siguiente:


Private Sub cmdCamposTVuelos_Click()
'Declaramos las variables
Dim cnn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim fld As ADODB.Field
'Realizamos la conexión y creamos el recordset
Set cnn = CurrentProject.Connection
Set rs = New ADODB.Recordset
'Abrimos el recordset
rs.Open "TVuelos", cnn, adOpenKeyset, adLockReadOnly, adCmdTableDirect
'Recorremos los campos y mostramos su información
For Each fld In rs.Fields
MsgBox "Nombre campo: " & fld.Name & vbCrLf & vbCrLf _
& "Tipo campo: " & fld.Type, vbInformation, "DATOS"
Next fld
'Cerramos conexiones y liberamos memoria
rs.Close
cnn.Close
Set rs = Nothing
Set cnn = Nothing
End Sub

Hay que tener en cuenta que fld.Type nos devolverá un entero, que se corresponde con una
constante de VB, que, lógicamente, se corresponde con un tipo de campo. Os indico aquí los
más usuales:

CÓDIGO NUMÉRICO TIPO DE CAMPO


202 Texto
3 Entero largo (Long)
2 Entero (Integer)
5 Doble (Double)
7 Fecha/hora (Date)

18
Visítame en http://siliconproject.com.ar/neckkito/
6 Moneda (Currency)

11 Sí/No (Boolean)
203 Memo / Hipervínculo
205 Objeto OLE

Y PARA FINALIZAR...
Y con esto damos por finalizado este capítulo. Ya habréis podido, espero, “captar” la mecánica
básica del funcionamiento de ADO. Como ya os comentaba al principio, yo no estoy muy
habituado a utilizar este sistema, por lo que, si alguno está interesado, le animo a profundizar
por su cuenta en búsqueda de información, ya sea a través de la red o a través de la compra
de algún manual que “machaque” este sistema. La idea es que al menos podáis entender más
claramente la base de los códigos que encontréis o intentéis poner en práctica.

Espero que os sea útil lo explicado en este capítulo.

¡Suerte!

19
Visítame en http://siliconproject.com.ar/neckkito/
CURSO DE VB

CAPÍTULO 131

Índice de contenido
USO DE ETIQUETAS / CONTROL DE ERRORES .........................................................................2
PREPARANDO NUESTRA BD DE PRUEBAS............................................................................2
ETIQUETAS....................................................................................................................................2
ESTRUCTURA DE LAS ETIQUETAS. PROCESO DEL CÓDIGO........................................3
ETIQUETAS DENTRO DE UN BLOQUE...............................................................................6
CONTROL DE ERRORES.............................................................................................................7
MOSTRAR INFORMACIÓN DEL ERROR.............................................................................8
MANIPULANDO LOS ERRORES...........................................................................................9
UN ERROR HABITUAL QUE SE SOLUCIONA... HACIENDO NADA.............................11
LA PROPIEDAD SOURCE.....................................................................................................13
ON ERROR RESUME NEXT......................................................................................................15
GENERANDO NUESTROS PROPIOS ERRORES....................................................................16
ALGO MÁS SOBRE ERRORES..................................................................................................18
TIPOS DE ERRORES..............................................................................................................18
COMPILANDO EL CÓDIGO..................................................................................................21
DEPURACIÓN DE ERRORES................................................................................................22
UNOS BREVES TRUCOS.......................................................................................................22
PARA FINALIZAR.......................................................................................................................23

1 La BD donde están los ejemplos de este capítulo os la podéis bajar aquí.

1
Visítame en http://siliconproject.com.ar/neckkito/
USO DE ETIQUETAS / CONTROL DE
ERRORES
Este capítulo estará dedicado, en primera instancia, al uso
de etiquetas para poder “dar saltos interestelares” a través
del código, y a continuación hablaremos de cómo controlar
los errores de código (y ver qué utilidades les podemos
dar).

Como siempre, vamos a prepararnos nuestra BD de


ejemplo.

PREPARANDO NUESTRA BD DE PRUEBAS


Vamos a crear una tabla, que llamaremos TClientes, que tendrá la estructura siguiente:

Rellenamos la tabla con algunos registros. Por ejemplo, yo la he rellenado así:

Vamos a crear otra tabla, que llamaremos TCientesNo. Para crearla vamos a copiar la tabla
TClientes, pero sólo pegamos su estructura (no los datos), y la pegamos con el nombre
propuesto.

En principio no necesitamos mucho más (y, si no, ya lo iremos añadiendo sobre la marcha).

ETIQUETAS
Las etiquetas son, por llamarlo de alguna manera, como “estaciones de servicio” en una
autopista. Es decir, lo normal sería ir conduciendo por dicha autopista, y hacer caso omiso a las
estaciones de servicio que van apareciendo. Sin embargo, si se produce algún evento en el
conducir (tenemos hambre, nos quedamos sin gasolina, queremos hacer un “pipí”...) pues
tenemos que pararnos en una estación de servicio.

Siguiendo con el símil, si programáramos lo anterior podríamos tener un esquema así (en
general):

2
Visítame en http://siliconproject.com.ar/neckkito/

Private Sub...
Empieza a conducir
Ve por la autopista
Si <quedamos sin gasolina> entonces <Parar
en estación de servicio>
Llegamos a destino
Exit Sub
Parar:
Repostar gasolina
Seguir conduciendo
Llegamos a destino
End Sub

Para los que os suene algo de código, sería una especie de “GoSub” (que existe, pero como
hace el código difícil de leer prácticamente no se utiliza).

Evidentemente, si podemos “solventar” la condición dentro del propio código mejor hacerlo de
esta manera. Debemos recurrir a las etiquetas sólo cuando no tengamos otra solución mejor.

Ello implica que en el código anterior hubiera sido mejor escribir:


Private Sub...
Empieza a conducir
Ve por la autopista
Si <quedamos sin gasolina> entonces
<Parar en estación de servicio>
Continuar camino
End Si
Llegamos a destino
End Sub

Pensad que, en este capítulo, no voy a analizar si es mejor utilizar uno u otro sistema. Utilizaré
las etiquetas porque, obviamente, es lo que vamos a explicar.

También, para agudizar vuestro ingenio, no pondré los códigos comentados. Si habéis seguido
el curso ordenadamente hasta aquí deberíais ser capaces de entender perfectamente las
líneas. ¡Venga, que un poco de estímulo no está mal! ;)

ESTRUCTURA DE LAS ETIQUETAS. PROCESO DEL CÓDIGO


La estructura de las etiquetas es muy simple. Una etiqueta viene representada simplemente
por una palabra que a nosotros nos guste.

Para llamar a la etiqueta simplemente escribimos esa palabra elegida

Para indicar que es la etiqueta escribimos la palabra elegida seguida de dos puntos (:)

El código que debe ejecutarse, asociado a la etiqueta, lo escribimos a continuación de la


definición de la etiqueta.

Por ejemplo, supongamos que creamos la etiqueta “Cerrar_proceso”

3
Visítame en http://siliconproject.com.ar/neckkito/
El código, en abstracto, sería:


Private Sub...
'Código
'Condición: si se cumple → GoTo
Cerrar_proceso
'Código
Cerrar_proceso:
'Código
End Sub

En cuanto al proceso, dentro del código, es un proceso lineal (como habréis podido intuir a lo
largo de todos estos capítulos anteriores). Podemos introducir un punto de “escape” al código
antes de llegar a la etiqueta, si nos interesa. Si no introducimos ese punto de “escape” el
código llegará a la etiqueta y seguirá procesándose.

Es decir, si yo escribo:


Private Sub ...
Const vNum as Byte = 25
Dim vProd as Integer SENTIDO DE
vProd = vNum * 2 EJECUCIÓN DEL
MsgBox "El producto es: " & vProd CÓDIGO
If vProd < 50 Goto Salgo
Salgo:
MsgBox "El producto es inferior a 50"
End Sub

Me voy a encontrar con:

– Me sale el MsgBox dándome el producto


– No se cumple If
– Me sale el MsgBos diciéndome que el producto es inferior a 50

Vemos que, en este caso, el código me da un resultado erróneo, lo que indica que no puedo
permitir que el código se ejecute más allá del If. Para ello modificamos ligeramente el código,
así:


Private Sub ... SENTIDO DE
Const vNum as Byte = 25 EJECUCIÓN DEL
Dim vProd as Integer CÓDIGO
vProd = vNum * 2
MsgBox "El producto es: " & vProd
If vProd < 50 Goto Salgo
Exit Sub INTERRUPCIÓN
Salgo:
MsgBox "El producto es inferior a 50"
End Sub

Ahora el código me dirá:

4
Visítame en http://siliconproject.com.ar/neckkito/
Si vNum >= 25 Si vNum < 25
El valor del producto El valor del producto
----INTERRUPCIÓN Y SALIDA---- El mensaje de que el producto es inferior a 50

Y, lógicamente, no se estará equivocando.

Vamos a ver otro ejemplo aplicado sobre nuestra BD de pruebas.

En un formulario en blanco añadimos un botón de comando. En el evento “Al hacer click”, el


siguiente código2:


Private Sub cmdEtiqueta_Click()
Dim rst As Recordset
Set rst = CurrentDb.OpenRecordset("TClientesNo", dbOpenSnapshot)
If rst.RecordCount = 0 Then GoTo Salida
rst.MoveFirst
MsgBox "El primer cliente de la tabla es " & rst.Fields(0).Value
GoTo Salida2
Salida:
MsgBox "La tabla está vacía", vbInformation, "SIN DATOS"
Salida2:
rst.Close
Set rst = Nothing
End Sub

Este código nos da el código del primer cliente de la tabla TClientesNo. Si no hay registros
salta a la etiqueta Salida.

¿Qué problema tenemos aquí con el punto de interrupción? Evidentemente podríamos haber
escrito un Exit Sub tras la línea de código: MsgBox "El primer cliente de la tabla es " &
rst.Fields(0).Value

Pero como queremos hacer las cosas bien queremos cerrar el recordset y liberar memoria. Si
dejamos que el código continúe con la etiqueta Salida nos saldrá un mensaje que, en caso de
haber registros, no será correcto.

¿Solución? He creado una segunda etiqueta que hace “saltar” el código por encima del
mensaje de que no hay registros y nos cierra el recordset y libera memoria.

De esta manera, haya o no haya registros, nos aseguramos que al final cerramos el recordset

2 Si utilizáis Access 2003 y el código os da error utilizad DAO

5
Visítame en http://siliconproject.com.ar/neckkito/
y liberamos memoria.

¿Pillamos la mecánica?

ETIQUETAS DENTRO DE UN BLOQUE


No debemos pensar que las etiquetas sólo se utilizan para
“soluciones finales” del código. Podemos utilizar etiquetas
dentro de un bloque para evaluar condiciones y “saltar” a
un sitio u otro.

Vamos a crearnos una tabla, que llamaremos TContactos, con la siguiente estructura 3:

Rellenamos la tabla con algunos datos, pero debemos asegurarnos que dejamos alguno de los
contactos en blanco. Por ejemplo, yo la he rellenado así:

Vamos a crearnos un código en el evento “Al hacer click” de un botón de comando que nos
indicará, a través de un Msgbox, el nombre de la empresa y el contacto.

El código sería:


Private Sub cmdContactos_Click()
Dim rst As Recordset
Set rst = CurrentDb.OpenRecordset("TContactos", dbOpenSnapshot)
With rst
.MoveFirst
Do Until .EOF
If IsNull(.Fields("Contacto").Value) Then GoTo Siguiente
MsgBox "Código empresa: " & .Fields("Empresa").Value & vbCrLf _
& "Contacto: " & .Fields("Contacto").Value
Siguiente:
.MoveNext
Loop
End With
rst.Close
Set rst = Nothing
End Sub

3 El campo [Empresa] es numérico porque almacena el código de la empresa, y está creado a través del asistente para búsquedas.

6
Visítame en http://siliconproject.com.ar/neckkito/
Como podemos ver en el código, la condición que se evalúa
es si el campo [Contacto] esta vacío (IsNull). Si se cumple
esta condición lo que hacemos es “saltar” al registro
siguiente, a través de la etiqueta Siguiente:, a fin de no
obtener un mensaje de error por “uso no válido de null”.

Y también, como podéis ver, la etiqueta está inmersa dentro


del bloque DO UNTIL...LOOP.

CONTROL DE ERRORES
La estructura de un control de errores es extremadamente simple. Su inicio es el siguiente:


Private Sub …
On error GoTo <etiqueta>

Pensad que debemos situar lo anterior siempre en el principio del código, inmediatamente
después del Sub (o Function).

Lo que hace es que, en cualquier momento de la ejecución del código, cuando se produce un
error, lo reconduce a la etiqueta que nosotros hayamos definido.

Si ampliamos el código, en abstracto, nos encontraríamos la siguiente estructura:


Private Sub …
On Error GoTo sol_err
'Código
Exit Sub
sol_err:
'Código para gestionar el error
End Sub

Es decir, que a través del código de la etiqueta gestionamos el error y tomamos las medidas
oportunas.

Finalmente, de manera habitual se suele remitir el final del código de control del error a lo que
sería el final “normal” del código, y eso se hace a través de otra etiqueta. La palabra “mágica”
para remitir de nuevo la ejecución del código a lo que he llamado el “final normal” de código es
RESUME (para quien quiera palabras más técnicas, RESUME es una instrucción).

En definitiva, si intentáramos esquematizarlo en abstracto el resultado nos quedaría una cosa


así:


Private Sub …
On Error GoTo sol_err
'Código
Salida:
'Código para salir (por ejemplo, un simple Exit Sub)
sol_err:
'Código para gestionar el error

7
Visítame en http://siliconproject.com.ar/neckkito/
Resume Salida
End Sub

¿Fácil, no?

MOSTRAR INFORMACIÓN DEL ERROR


Una manera de gestionar el error es, simplemente, mostrar
qué error se produce. Para capturar el error utilizamos el
objeto ERR

Las propiedades que utilizaremos para ello serán:

– NUMBER
– DESCRIPTION

Vamos a verlo con un ejemplo.

Sabemos que, en nuestra BD de pruebas, en la tabla TClientesNo no hay registros. Vamos a


crearnos un código en un botón de comando que intente leer los datos de un registro.
Evidentemente, si no hay registros activos, eso nos provocará un error.

El código sería:


Private Sub cmdErrorTClientesNo_Click()
Dim rst As Recordset
Set rst = CurrentDb.OpenRecordset("TClientesNo", dbOpenSnapshot)
rst.MoveFirst
MsgBox rst.Fields(1).Value
rst.Close
Set rst = Nothing
End Sub

y el error que devuelve Access es el siguiente:

Vamos a añadirle pues un control de errores, de manera que nos saldrá un mensaje
advirtiéndonos del error, pero el usuario ya no tendrá opción de pulsar el botón <Depurar> y
así no podrá acceder al código VB.

El nuevo código debería quedarnos así:


Private Sub cmdErrorTClientesNo_Click()

8
Visítame en http://siliconproject.com.ar/neckkito/
On Error GoTo sol_err
Dim rst As Recordset
Set rst = CurrentDb.OpenRecordset("TClientesNo",
dbOpenSnapshot)
rst.MoveFirst
MsgBox rst.Fields(1).Value
Salida:
rst.Close
Set rst = Nothing
Exit Sub
sol_err:
MsgBox "Error número: " & Err.Number & vbCrLf & vbCrLf & _
"Descripción: " & Err.Description, vbCritical, "ERROR"
Resume Salida
End Sub

Ahora, el mensaje que obtendremos tendrá esta “pinta”:

Y, como vemos, no nos da ningún botón de acceso al código VB.

Fijaos cómo hemos manejado el error: a través de: Err.Number y de Err.Description

MANIPULANDO LOS ERRORES


Si tenemos el nombre del error y su número de error podemos manipular ese error en
concreto a nuestro antojo. Vamos a modificar el código para que nos salga un mensaje
“personalizado” para ese error. Por ejemplo, un usuario de Access que no tenga mucha idea
puede ser que no entienda qué significa esto de “registro activo”. Por ello vamos a poner un
mensaje algo más “legible” para este usuario.

El código que deberíamos escribir sería:


Private Sub cmdManipuloErrorTClientesNo_Click()
On Error GoTo sol_err
Dim rst As Recordset
Set rst = CurrentDb.OpenRecordset("TClientesNo", dbOpenSnapshot)
rst.MoveFirst
MsgBox rst.Fields(1).Value
Salida:
rst.Close
Set rst = Nothing
Exit Sub
sol_err:
If Err.Number = 3021 Then
MsgBox "No existe ningún dato dado de alta todavía. No se puede mostrar" _

9
Visítame en http://siliconproject.com.ar/neckkito/
& " la información solicitada", vbInformation, "SIN DATOS"
End If
Resume Salida
End Sub

Ahora creo que el usuario sí sabrá por qué no obtiene


ningún dato, ¿verdad?

Profundicemos un poco más en este tema.

Vamos a copiar nuestra tabla TClientesNo y la vamos a pegar como


TClientesNo2. Vamos a añadir un registro, pero “olvidándonos” de
introducir el CIF del cliente. Por ejemplo, algo así:

En un botón de comando escribimos el anterior código, a ver qué resultados nos da


(adaptándolo, claro, al nuevo nombre de tabla).

En definitiva, escribimos:


Private Sub cmdErrorTClientesNo2_Click()
On Error GoTo sol_err
Dim rst As Recordset
Set rst = CurrentDb.OpenRecordset("TClientesNo2", dbOpenSnapshot)
rst.MoveFirst
MsgBox rst.Fields(1).Value
Salida:
rst.Close
Set rst = Nothing
Exit Sub
sol_err:
If Err.Number = 3021 Then
MsgBox "No existe ningún dato dado de alta todavía. No se puede mostrar" _
& " la información solicitada", vbInformation, "SIN DATOS"
End If
Resume Salida
End Sub

Si ahora ejecutamos el código, ¿qué pasa?

Pues que no nos salta ningún error, pero tampoco obtenemos ningún MsgBox con la
información solicitada.

Conclusión: que debemos ir con sumo cuidado a la hora de gestionar el error, puesto que no
tendría que pasarnos lo de “por arreglar una cosa espachurramos otra”.

Si modificamos el código de la siguiente manera veremos los nuevos resultados:


Private Sub cmdErrorTClientesNo2_2_Click()

10
Visítame en http://siliconproject.com.ar/neckkito/
On Error GoTo sol_err
Dim rst As Recordset
Set rst = CurrentDb.OpenRecordset("TClientesNo2",
dbOpenSnapshot)
rst.MoveFirst
MsgBox rst.Fields(1).Value
Salida:
rst.Close
Set rst = Nothing
Exit Sub
sol_err:
If Err.Number = 3021 Then
MsgBox "No existe ningún dato dado de alta todavía. No se puede mostrar" _
& " la información solicitada", vbInformation, "SIN DATOS"
Else
MsgBox "Error número: " & Err.Number & vbCrLf & vbCrLf & _
"Descripción: " & Err.Description, vbCritical, "ERROR"
End If
Resume Salida
End Sub

Ahora sí recibiremos una notificación de por qué no nos funcionaba el código: se producía otro
número de error.

Una vez vayamos detectando los diferentes errores que se puedan producir en nuestro código
podremos ir gestionándolos a nuestro gusto. Os recuerdo que si queremos gestionar más de
un error (por ejemplo, en el caso que acabamos de explicar) tenéis el bloque SELECT CASE
para ir identificando los errores y manipulándolos.

UN ERROR HABITUAL QUE SE SOLUCIONA... HACIENDO NADA


Como curiosidad os comentaré que hay una tipología de errores que responden a una
caracterización muy similar entre ellos y que la mejor solución es, precisamente, no hacer
nada.

Entended esto como una sugerencia, y no como una solución “única y cerrada” al problema.
Evidentemente ya sabemos cómo manipular los errores y cómo adaptarlos a nuestras
necesidades.

Me estoy refiriendo a aquellos errores que se producen al intentar ejecutar alguna acción que
depende de un parámetro, y que cuando se nos solicita el parámetro nos lo pensamos mejor y
cancelamos.

Vamos a ver dos ejemplos de este tipo (aunque en realidad serían “los mismos perros con
distintos collares”).

Vamos a crear una consulta CClientes parametrizada sobre la tabla TClientes. La estructura

11
Visítame en http://siliconproject.com.ar/neckkito/
podría ser la siguiente:

Ahora queremos abrir esa consulta desde un


botón de comando. El código simple que
normalmente pondríamos a ese botón sería:


Private Sub cmdAbreCClientes_Click()
DoCmd.OpenQuery "CClientes"
End Sub

Si hacemos click sobre el botón nos saldrá la ventana con la petición del valor del parámetro.
¿Qué pasa si cancelamos?

Pues que obtenemos el siguiente mensaje:

Bastante molesto, por cierto.

La solución pasa por introducir, en el código, un control de errores que no haga nada. Es decir,
que el código nos quedaría así:


Private Sub cmdAbreCClientesNoError_Click()
On Error GoTo sol_err
DoCmd.OpenQuery "CClientes"
sol_err:
Exit Sub
End Sub

Si ahora hacemos click y cancelamos la introducción del parámetro... pues el comportamiento


de Access parece “normal”.

12
Visítame en http://siliconproject.com.ar/neckkito/
Vamos a realizar un segundo ejemplo: creamos un informe, que llamaremos RClientes, basado
en TClientes. Una vez creado situamos el informe en vista diseño y añadimos un cuadro de
texto en la cabecera, de manera que nos quede así:

El código “normal” de un botón de comando para abrir el informe sería:


Private Sub cmdAbreRClientes_Click()
DoCmd.OpenReport "RClientes", acViewPreview
End Sub

Nos pedirá la información del solicitante y... cancelamos. Nos saltará un mensaje así:

Como en el caso anterior, modificamos nuestro código para abrir el informe de la siguiente
manera:


Private Sub cmdAbreRClientesNoError_Click()
On Error GoTo sol_err
DoCmd.OpenReport "RClientes", acViewPreview
sol_err:
Exit Sub
End Sub

Si volvemos a probar el botón, y cancelamos, de nuevo ahora el comportamiento de Access


parecerá el lógico.

LA PROPIEDAD SOURCE
Si estamos utilizando varias bases de datos a la vez, gestionadas desde una sola aplicación, es
posible que nos encontremos con la aparición de un error pero no sepamos de qué BD
proviene (lo que implica que nos tendríamos que poner a buscar su origen).

Para facilitarnos esa labor tenemos la propiedad Source.

13
Visítame en http://siliconproject.com.ar/neckkito/
El resultado de esa propiedad es la devolución del nombre del proyecto en el que estamos
trabajando. ¿Y cuál es ese nombre de proyecto? Buena pregunta...

Si abrimos, en nuestra aplicación, el editor de VB, y nos


fijamos en la ventana Proyecto, veremos ahí identificado
susodicho nombre. Por ejemplo, en la BD en la que estoy
trabajando ahora es “Nuevo Microsoft Office Access 2007
Base de datos”. Aunque no lo veáis entero, lo podéis intuir
en la imagen:

¡Qué nombre más feo! Vamos a cambiarlo. Clickamos con el botón de la derecha sobre el
nombre del proyecto, y en el menú emergente seleccionamos la opción “Propiedades de xxx”,
donde xxx es el nombre del proyecto. Nos aparecerá una ventana así:

Y ya podemos cambiarle el nombre del proyecto, además de configurar otras opciones que no
explicaremos aquí (aunque son bastante obvias).

Yo, de nombre, le he puesto CursoVB. Recordemos ese nombre porque vamos a ver cómo
funciona la propiedad Source a través de código.

Creamos un botón de comando y le asignamos este código, que intentará abrir un formulario
que no existe:


Private Sub cmdSource_Click()
On Error GoTo sol_err
DoCmd.OpenForm "NoExiste"
Exit Sub
sol_err:
MsgBox Err.Source
End Sub

14
Visítame en http://siliconproject.com.ar/neckkito/

Si clickamos sobre ese botón obtendremos un MsgBox con


el nombre del proyecto que ha generado el error, y que,
lógicamente, en mi caso, será CursoVB. Algo así:

Claro, ¿verdad?

ON ERROR RESUME NEXT


Puede ocurrirnos que se produzca un error, pero que queramos hacer caso omiso de él, y que
queramos que el código se siga ejecutando.

Para ello tenemos la opción de escribir ON ERROR RESUME NEXT.

Lo que hace la anterior sentencia es decirle, como habréis intuido, que si se produce un error
que no le haga caso y siga con la siguiente línea de código.

Vamos a obtener una lista de los contactos de la tabla TContactos. Os pongo la línea del
control de error convertida en comentario para que no actúe. El código sería:


Private Sub cmdResumeNext_Click()
'On Error Resume Next
Dim rst As Recordset
Set rst = CurrentDb.OpenRecordset("TContactos", dbOpenSnapshot)
With rst
.MoveFirst
Do Until .EOF
MsgBox .Fields("Contacto").Value
.MoveNext
Loop
End With
MsgBox "Finalizó la lista de contactos", vbInformation, "FIN"
rst.Close
Set rst = Nothing
End Sub

Si ejecutamos el código veremos el “precioso” error que nos salta.

Si ahora “activamos” el control de errores y volvemos a ejecutar el código veremos la


diferencia de comportamiento.

Por si acaso, el código quedaría así:


Private Sub cmdResumeNext_Click()
On Error Resume Next
Dim rst As Recordset
Set rst = CurrentDb.OpenRecordset("TContactos", dbOpenSnapshot)

15
Visítame en http://siliconproject.com.ar/neckkito/
With rst
.MoveFirst
Do Until .EOF
MsgBox .Fields("Contacto").Value
.MoveNext
Loop
End With
MsgBox "Finalizó la lista de contactos", vbInformation, "FIN"
rst.Close
Set rst = Nothing
End Sub

En definitiva, que si vemos que en nuestro código se producen errores que no “bloquean” el
código (por decirlo de alguna manera) haciéndolo “inservible”, sino que son errores que no
tienen una influencia importante sobre el mismo, y que si los obviamos conseguimos sin
problemas los resultados esperados, podemos recurrir al ON ERROR RESUME NEXT.

GENERANDO NUESTROS PROPIOS ERRORES


Hemos visto que los errores que nos lanzaba Access iban identificados por un número (que
capturábamos con Err.Number). En un alarde de altruismo Access nos deja libres los números
que van del 513 al 65535 para que podamos asignarles un “error personalizado” (del 0 al 512
están reservados para los errores de sistema). Para ello podremos utilizar un método
denominado RAISE.

Por ejemplo, imaginemos que lo que queremos es lo contrarío de lo que queríamos hace unas
páginas antes: que ante una determinada circunstancia nos salga un mensaje de error con la
posibilidad de depurar el código.

Como tenemos los números de error reservados que os comentaba antes imaginemos que,
como programadores de la BD, nos hacemos una categorización de errores. Algo así como:

– Error 700: recordset vacío


– Error 705: valor nulo
– Error 710: etc...

He cogido el error 700 como hubiera podido coger el error 800... esto ha sido porque me gusta
el 7... je, je...

La idea subyacente no es “esperar que se produzca el error”, sino prever dónde se puede
producir el error con alguna probabilidad y establecer un control, sin resolverlo. Con
posterioridad ya aplicaremos las medidas oportunas para corregirlo como nos plazca (esto ya
sería un tema de “gustos propios sobre programación”).

Dicho lo anterior, y antes de ver el ejemplo con código, os indicaré que la sintaxis de RAISE es:

Err.Raise <NúmeroError>, <Source>, <TextoDelError>, <RutaDelFicheroDeAyuda>,


<IdentificadorDeAyuda>.

Lo que os he marcado en negrita constituye la expresión mínima obligatoria; el resto de


argumentos son opcionales.

Si aprovechamos para ir construyendo mi error escribiríamos: Err.Raise 700

<Source> ya os debería “sonar”... Es la misma “Source” que veíamos en el apartado anterior.

16
Visítame en http://siliconproject.com.ar/neckkito/
Y mi error se completaría con: Err.Raise 700, “CursoVB”

<TextoDelError> es una simple descripción del error, que


aparecerá en pantalla.

Seguimos: Err.Raise 700, “CursoVB”, “Recordset vacío”

Los otros dos argumentos se refieren a si hemos construido


un fichero de ayuda (un *.chm), y nos sirve para poder
mostrar una ayuda más detallada del error. Indicaríamos la
ruta del fichero de ayuda y un identificador, que es
simplemente un marcador que, dentro del fichero de ayuda,
nos lleva al apartado donde se trata ese error.

Como podemos ver, la posibilidad de incluir un fichero de ayuda con la descripción del error
aumenta las posibilidades de “mostrar más información” y más personalizada que si se tratara
de un error “normal” propio de Access.

Y por fin vamos al ejemplo... Reutilizaremos el código que, en este ejemplo, yo empleaba para
manipular el error devuelto por TClientesNo, al no haber registros en la tabla.

Vamos a ver el mismo código, pero con “dos caras”.

El primer código sería el siguiente:


Private Sub cmdRaise_Click()
'On Error GoTo sol_err
Dim rst As Recordset
Set rst = CurrentDb.OpenRecordset("TClientesNo", dbOpenSnapshot)
'-----PREVEO QUE PUEDA NO HABER REGISTROS----
If rst.RecordCount = 0 Then
Err.Raise 700, "CursoVB", "Recordset sin registros. Contacte con el administrador"
Else
rst.MoveFirst
'-----PREVEO QUE PUEDA HABER VALORES VACÍOS
If IsNull(rst.Fields(1).Value) Then
Err.Raise 705, "CursoVB", "Campo vacío. Contacte con el administrador"
Else
MsgBox rst.Fields(1).Value
End If
End If
'Salida:
rst.Close
Set rst = Nothing
Exit Sub
'sol_err:
' MsgBox Err.Number & " - " & Err.Description
' Resume Salida
End Sub

Fijaos que he convertido en comentario todas las líneas referentes al control de errores
“normal”. En definitiva, es como si hubiera escrito el código sin control de errores.

17
Visítame en http://siliconproject.com.ar/neckkito/
Os animo a ejecutarlo, para que veáis el comportamiento del mensaje de error.

A continuación volvemos al código y eliminamos los


comentarios de las líneas correspondientes al control de
errores. Es decir, que el código debería quedarnos así:


Private Sub cmdRaise2_Click()
On Error GoTo sol_err
Dim rst As Recordset
Set rst = CurrentDb.OpenRecordset("TClientesNo",
dbOpenSnapshot)
'-----PREVEO QUE PUEDA NO HABER REGISTROS----
If rst.RecordCount = 0 Then
Err.Raise 700, "CursoVB", "Recordset sin registros. Contacte con el administrador"
Else
rst.MoveFirst
'-----PREVEO QUE PUEDA HABER VALORES VACÍOS
If IsNull(rst.Fields(1).Value) Then
Err.Raise 705, "CursoVB", "Campo vacío. Contacte con el administrador"
Else
MsgBox rst.Fields(1).Value
End If
End If
Salida:
rst.Close
Set rst = Nothing
Exit Sub
sol_err:
MsgBox Err.Number & " - " & Err.Description
Resume Salida
End Sub

Y, si lo ejecutamos ahora, podréis ver la diferencia de respuesta del mensaje.

Pues... en función de nuestras necesidades ya sabemos cómo elegir uno u otro.

ALGO MÁS SOBRE ERRORES

TIPOS DE ERRORES
Podemos encontrarnos diversos tipos de error, y cada uno tiene un tipo diferente de
tratamiento.

Los diferentes tipos de error que podemos encontrarnos son:

– Errores en tiempo de diseño


– Errores en tiempo de diseño que se detectan en tiempo de ejecución
– Errores en tiempo de ejecución
– Errores de programación

Los errores en tiempo de diseño son muy fáciles de detectar porque es el propio Access quien
nos indica el error.

18
Visítame en http://siliconproject.com.ar/neckkito/
Por ejemplo, si en cualquier procedimiento de los que hemos estado utilizando escribimos lo
siguiente:

If variable=0

y pulsamos Enter para acceder a una nueva línea, nos


saldrá lo siguiente:

Así, es el propio Access quien nos indica que hay error de “diseño”, y además nos proporciona
alguna sugerencia sobre lo que hemos omitido al escribir la línea de codigo.

¿Cómo se solventan? Fácil: escribiendo correctamente la línea de código en cuestión.

Los errores de diseño que se detectan en tiempo de ejecución de producen cuando se está
ejecutando el código. Es decir, que sintácticamente el código está bien escrito, pero hay
errores precisamente de diseño que imposibilitan la ejecución del código.

Por ejemplo, si escribimos lo siguiente en un procedimiento:


Private Sub cmdErrorDiseño_Click()
MsgBox vCliente
End Sub

El diseño de este procedimiento es, en principio correcto a nivel sintáctico, pero si intentamos
ejecutarlo nos saldrá el siguiente mensaje (doy por supuesto que hemos activado la opción
“Requerir declaración de variables”).

¿Cómo se solventan? Fácil, también: escribiendo la línea o líneas de código que corrigen el
error.

Los errores en tiempo de ejecución son los que sintácticamente están totalmente correctos,
pero que, al ejecutarse el código, se derivan del mismo una serie de “situaciones” que generan
errores por expresiones o valores incorrectos.

Son en definitiva, los que hemos estado viendo en los apartados precedentes.

Por ejemplo, si yo escribo el siguiente código:


Private Sub cmdErrorEjecucion_Click()
Const vNumerador As Integer = 10

19
Visítame en http://siliconproject.com.ar/neckkito/
Const vDenominador As Integer = 0
Dim vResultado As Double
vResultado = vNumerador / vDenominador
MsgBox vResultado
End Sub

Sintácticamente es correcto, todas las variables están bien


declaradas y definidas... y, al ejecutarlo, nos dará un error.
¿Por qué? Porque derivado del código se obtiene una
expresión incorrecta, que es intentar obtener un valor de
una división entre cero.

¿Solución? La solución más obvia es establecer un control de errores. Sin embargo, hay que
tener en cuenta que, en general, estos errores son más difíciles de solucionar, porque aquí
entra en juego nuestra capacidad de “ponernos en lugar del usuario” e imaginar todas las
opciones posibles que puede realizar.

Porque si el código anterior fuera bastante más complicado, y vDenominador dependiera de un


valor introducido por el usuario que además se originara por operaciones aritméticas
posteriores, o somos capaces de “intuir” que vDenominador puede llegar a coger el valor cero
(que podría ser nunca) o en la trigésima ejecución por parte del usuario... ¡zas! Error al canto.

Finalmente, los errores de programación ya sí dependen totalmente de nosotros, porque no


hay errores de diseño ni errores en tiempo de ejecución y, sin embargo, no obtenemos los
resultados esperados.

Por ejemplo, si escribimos este código:


Private Sub cmdErrorProgramacion_Click()
Dim rst As Recordset
Set rst = CurrentDb.OpenRecordset("TContactos", dbOpenSnapshot)
MsgBox "Lista de contactos"
With rst
.MoveFirst
Do Until .EOF
MsgBox .Fields(1).Value
.MoveNext
Loop
End With
End Sub

20
Visítame en http://siliconproject.com.ar/neckkito/
Es un código “maravilloso”, sin errores de ninguna clase... hasta que lo ejecutamos, y vemos
que la información que nos muestra no tiene mucho sentido.

¿Dónde está el error? Pues descubrimos que nos hemos


equivocado en el índice de Fields, y que el campo donde
está el nombre del contacto no tiene el índice 1, sino que
tiene el índice 2.

¿Cómo solucionamos este tipo de errores? Quizá llevando


alguna vela a un santo... je, je... Si el código es muy
complicado no nos quedará más remedio que ir probando y
probando (a no ser que la solución sea muy evidente, claro)
hasta ir acotando el código hasta descubrir qué es lo que
hemos programado “mal” para que nos dé la información
incorrecta, y así poder subsanar el error.

COMPILANDO EL CÓDIGO
Si os habéis fijado en las imágenes que os he puesto para los errores de diseño y de ejecución,
veréis que Access nos indica que el error es un error “de compilación”.

Algunas veces podemos escribir un código (o muchos) y no testearlo inmediatamente. Para


estar seguros de que no tenemos errores podemos compilar el código.

Para ello:

1.- Nos situamos en el VBE

2.- Nos vamos a menú Depuración → Compilar xxx (donde xxx será el nombre de nuestro
proyecto)

3.- Si hay algún error de diseño (ojo, que los errores en tiempo de ejecución nos saltarán al
ejecutar el código, no al compilar), Access nos marcará el tipo de error y su ubicación.

4.- Corregimos los errores que nos vayan apareciendo. Cuando ya no haya más errores la
opción del menú mencionado nos saldrá atenuada en gris.

¡Y código compilado!

21
Visítame en http://siliconproject.com.ar/neckkito/
DEPURACIÓN DE ERRORES
El editor de VB nos proporciona otras herramientas para
depurar el código. Prácticamente todas las podemos
encontrar en menú Depuración.

Sin embargo no las vamos a explicar aquí, porque no es


objeto del curso.

Si queréis más información en el menú de ayuda del VBE escribid


“menú Depuración” y ahí encontraréis todos los resultados para las
diferentes opciones de dicho menú.

UNOS BREVES TRUCOS


A veces nos salta un error insistentemente y no vemos el origen del mismo. Os explico un par
de trucos que yo utilizo para depurar errores:

a) Comentarios

Si en el VBE nos vamos menú Ver → Barras de herramientas → Edición nos aparecerá dicha
barra de herramientas. En ella encontraremos dos botones muy útiles:

De esta manera, si “sospechamos” que un bloque de código es el que nos causa problemas,
podemos seleccionarlo entero y clickar sobre el botón que aplica comentarios en bloque.
Ejecutamos el código y si se sigue produciendo el error querrá decir que nuestras sospechas
eran erróneas (o al revés); lógicamente teniendo en cuenta siempre que la “supresión
temporal” de ese bloque de código no imposibilite la ejecución del código, claro.

b) La función MsgBox

Cuando creemos que una variable no está obteniendo el valor deseado, o cuando una cadena
de texto no está devolviendo los valores esperados, podemos realizar una comprobación rápida
con un MsgBox. E incluso, para obviar el error, lo podemos combinar con un Exit Sub para
evitar que se siga ejecutando el código.

22
Visítame en http://siliconproject.com.ar/neckkito/
Por ejemplo, si nuestra variable vVar nos da problemas podríamos hacer lo siguiente:


Private Sub...
'Código
Msgbox vVar
Exit Sub
'Código
End Sub

Al ejecutar el código nos saldrá un mensaje con el valor de la


variable, y así podremos comprobar qué valor ha cogido.

O, por ejemplo, si una Sql nos da error, podemos escribir lo siguiente:


Private Sub...
'Código
sql = “SELECT * FROM Tabla WHERE Tabla.var1=” & vVar1 & “ AND Tabla.var2=” & vVar2
Msgbox sql
Exit sub
'Código
End Sub

El resultado podría ser, por ejemplo:

SELECT * FROM Tabla WHERE Tabla.var1 = 12/05/11 AND Tabla.var2 = 25

E inmediatamente deberíamos darnos cuenta que la fecha debería ir entre almohadillas.

Para quienes les guste utilizar el VBE el MsgBox sería una especie de “sucedáneo” de

Debug.Print

Que nos mostraría lo mismo, pero en la ventana Inmediato del VBE.

PARA FINALIZAR
Hemos dado, en este capítulo, la mecánica para utilizar etiquetas, más un buen “repasón” al
tema de los errores, y hemos añadido, al final, algo que podríamos considerar un poco de
teoría sobre el tema.

Espero que, tras leer/estudiar este capítulo, en vuestros códigos “no se os escape ni uno”... je,
je...

Un saludo, y...

¡suerte!

23
Visítame en http://siliconproject.com.ar/neckkito/
CURSO DE VB

CAPÍTULO 141

Índice de contenido
AUTOMATIZACIÓN..........................................................................................................................2
INTRODUCCIÓN...........................................................................................................................2
NECESITO UN DICCIONARIO....................................................................................................2
PREPARANDO NUESTROS ELEMENTOS DE PRUEBAS.......................................................3
ACCESS......................................................................................................................................3
EXCEL........................................................................................................................................4
PROTOCOLO DDE........................................................................................................................4
FASE DE INICIO: DDEInitiate..................................................................................................4
RECOGER DATOS: DDERequest.............................................................................................6
ENVIAR DATOS: DDEPoke.....................................................................................................8
ENVÍO DE COMANDOS: DDEExecute...................................................................................9
FINALIZAR CONEXIÓN: DDETerminate / DDETerminateAll...............................................9
AUTOMATIZACIÓN: INDICACIONES INICIALES................................................................10
Registrar la referencia...............................................................................................................10
Abrir la aplicación servidor.......................................................................................................10
Abrir un archivo existente.........................................................................................................10
Métodos, propiedades y demás malas hierbas..........................................................................11
ACCESS – WORD........................................................................................................................11
ACCESS – EXCEL.......................................................................................................................14
ACCESS – OUTLOOK.................................................................................................................17
ACCESS – POWERPOINT...........................................................................................................18
FUNCIONES CREATEOBJECT() Y GETOBJECT()..................................................................19
PARA FINALIZAR.......................................................................................................................20

1 La BD donde están los ejemplos de este capítulo os la podéis bajar aquí.

1
Visítame en http://siliconproject.com.ar/neckkito/
AUTOMATIZACIÓN

INTRODUCCIÓN
Y la pregunta del millón es... ¿qué significa
“automatización”?

Y si yo os pregunto: ¿os suena la palabrita OLE?

Supongo que todos me diréis... “Claro, OLE... Object


Linking and Embedding... ¿Quién es que no sabe eso?”

Pues claro claro... no sé si queda... je, je...

La automatización (o automatización OLE) no es más que una tecnología que permite manejar
datos de una aplicación directamente desde otra aplicación.

Así pues, podemos distinguir dos elementos en un proceso de automatización:

– Un cliente (o controlador)
– Un servidor OLE

El cliente sería pues la aplicación que estamos utilizando, y que en un momento dado requiere
de los “servicios” de un servidor, por lo que el cliente se encarga de realizar las operaciones
sobre el servidor OLE de manera automática.

Dicho en pocas palabras, si estamos en Access y gestionamos (lógicamente desde Access) un


Word nuestro Access sería el cliente, y Word sería el servidor OLE.

NECESITO UN DICCIONARIO
Por ahora creo que la cosa ya se ha clarificado bastante, y ya entendemos qué es esto de
“automatización”.

Muy bien... Yo soy Neckkito Access, y, como cliente, voy a manejar Wordito Word... Y me
encuentro la primera sorpresa: ¡Wordito habla en chino! Así, lógicamente, no hay quien se
entienda...

Para solventar este tema necesito un diccionario... que en nuestro caso no es otra cosa que
una referencia o biblioteca propia de Word.

Y para “adquirir” esa referencia no tengo otro remedio que irme, en el menú el VBE, a menú
Herramientas → Referencias, y buscar el “diccionario” que corresponda al idioma que la
aplicación servidor entiende (en este ejemplo sería “Microsoft Word x.y Object Library”, donde
x.y es la versión que tengamos instalada en nuestro PC).

Resumiendo: si manejo una aplicación servidor debo antes registrar la referencia adecuada
para que pueda establecerse una comunicación “entendible” entre ambas aplicaciones.

2
Visítame en http://siliconproject.com.ar/neckkito/
PREPARANDO NUESTROS ELEMENTOS DE
PRUEBAS

ACCESS
Antes de seguir en materia vamos a prepararnos una
pequeña BD de pruebas para ir desarrollando los ejemplos
de este capítulo. Para ello, en una nueva aplicación de
Access, creamos una tabla, la tabla TDatos, que tendrá esta
simple estructura:

A continuación creamos la tabla TAct, que sólo contendrá un campo:

El tipo de datos de [Factor] será Doble, con 2 decimales.

Finalmente vamos a crearnos una consulta, que llamaremos CDatosAct, que tendrá la siguiente
estructura:

Por si no se ve bien el campo calculado la expresión es:

CapitalAct: [Capital]*[Factor]

Sacamos las propiedades de ese campo calculado y le definimos su formato como Moneda.

3
Visítame en http://siliconproject.com.ar/neckkito/
EXCEL
En el mismo directorio donde tenemos la BD vamos a
crearnos un Excel, que llamaremos InfoOrigen.xlsx (o xls).

1.- Renombramos la HOJA1 a DATOS


2.- Respetando las celdas que veréis en las ilustraciones,
montamos el Excel así (en la ilustración de la izquierda, los
valores; en la de la derecha, las fórmulas):

En principio con esto nos basta para empezar con el primer epígrafe.

La idea de lo que vamos a desarrollar es la siguiente: nos pasan (o utilizamos) un Excel que
contiene unos valores que se van actualizando, y necesitamos de esos valores actualizados
para obtener información fidedigna de nuestra base de datos.

PROTOCOLO DDE
Hay una forma muy “directa” de establecer comunicación entre las aplicaciones de Microsoft
Office. Esa forma es el protocolo Dynamic Data Interchange, o protocolo DDE. Los elementos
de este protocolo son:

– Un cliente (o Destino)
– Un Servidor (u Origen)
– Un proceso de inicio, donde se abre un canal de comunicación entre cliente y servidor
– Un proceso de comunicación, donde se establece un intercambio de datos entre
cliente y servidor
– Un proceso de cierre, donde se cierra el canal de comunicación.

 Importante: con este protocolo ambas aplicaciones deben estar abiertas al mismo
tiempo. Si se cierra una de ellas el canal de comunicación se cierra automáticamente.

Teniendo en cuenta lo anterior vamos a ver cómo podemos aprovecharnos de este protocolo
DDE.

FASE DE INICIO: DDEInitiate


Vamos a abrir nuestro Excel, y después abriremos nuestra base de datos. Nos vamos a crear
un formulario en blanco y añadiremos un botón de comando. Lo programaremos después.
Ahora vamos a ver la sistemática de cómo abrimos un canal de comunicación.

La estructura para ello es la siguiente:

DDEInitiate (<aplicacion>, <tema>)

4
Visítame en http://siliconproject.com.ar/neckkito/
Ambos argumentos son requeridos. Y

– <aplicacion> será el nombre del Servidor


– <tema> será el identificador de donde cogemos los datos.

Nuestro amigo Access define <tema> como asunto de una


conversación de intercambio de datos dinámicos (DDE)
entre dos aplicaciones. Para la mayoría de las aplicaciones
que utilizan archivos, el tema es un nombre de archivo2.

En fin... como veis, a veces la ayuda de Access parece


escrita para nigromantes.

Siguiendo con nuestra explicación, DDEInitiate lo que hace es devolver un valor, de tipo Long,
que representa el canal que se ha establecido entre ambas aplicaciones.

Vamos a comprobarlo.

En ese botón que comentábamos al principio de este epígrafe vamos a programar el siguiente
código (recordad que Access y Excel deben estar abiertas simultáneamente para que
funcione):


Private Sub cmdDDEInitiate_Click()
Dim numCanal As Long
numCanal = DDEInitiate("excel", "DATOS")
MsgBox "El número de canal que se ha abierto entre Access y Excel es: " _
& numCanal, vbInformation, "CANAL"
DDETerminate (numCanal)
End Sub

Como vemos, nuestros argumentos han sido:

– <aplicación> = “excel”
– <tema> = “DATOS” → Que se corresponde con el nombre de la hoja de Excel donde
hemos introducido los datos.

Si hacemos click sobre ese botón obtendremos un número, que se corresponderá con el
número de canal.

En mi caso:

Para evitarnos sorpresas vamos a hacer una cosa:

– Cerramos Excel
– Volvemos a hacer click sobre el botón

2 Extraído de la ayuda de Access

5
Visítame en http://siliconproject.com.ar/neckkito/
Y nos encontramos con el error 282, que dice así:

Por ello, y como tras el capítulo 13 ya sabemos gestionar errores, podríamos poner remedio a
esta situación tan “embarazosa” (no lo haremos aquí, porque no es el objetivo del capítulo,
claro).

Debemos tener en cuenta que este proceso consume recursos del sistema. Es conveniente
cerrar siempre el canal a través de DDETerminate. Y, como habréis podido comprobar en el
código, DDETerminate es tan simple como poner el método y, como argumento, el número de
canal que queremos cerrar. Fácil.

RECOGER DATOS: DDERequest


Vamos a ver cómo podemos recoger datos de la aplicación servidor. Para ello utilizaremos el
método DDERequest.

La estructura de DDERequest es la siguiente:

DDERequest (<canal>, <elemento>)

Por ejemplo, supongamos que queremos mostrar información sobre el valor del Euribor en un
MsgBox. Para ello, en un botón, podríamos programar el siguiente código:


Private Sub cmdDDEMsgBox_Click()
'Declaramos las variables
Dim CanalDDE As Long
Dim vEurib As Variant
'Abrimos el canal
CanalDDE = DDEInitiate("excel", "DATOS")
'Establecemos la comunicación y sacamos el elemento (valor) de la
'casilla B2 de nuestro Excel
vEurib = DDERequest(CanalDDE, "F2C2")
MsgBox “El valor del Euribor es: “ & vEurib, vbInformation, “EURIBOR”
'Cerramos el canal
DDETerminate (CanalDDE)
End Sub

Un par de cosillas:

.- Podemos encontrarnos problemas con el argumento <elemento> de DDERequest debido a


las diferentes versiones de Excel, más los diferentes idiomas. Como veis, debemos utilizar el
sistema F1C1 para definir las celdas. Y eso significa que quizá, en nuestras versiones de Excel,

6
Visítame en http://siliconproject.com.ar/neckkito/
ese sistema sea R1C1, o L1C1... Tendréis que saber qué sistema utiliza vuestro Excel para
identificar las casillas (eso lo podéis buscar dentro de las opciones de Excel, buscando el “estilo
de referencia”).

.- Otra solución pasaría por nombrar las casillas objetivo de


Excel. Es decir, si yo me sitúo en la celda A2 veremos, en el
cuadro de nombres, que me indica que esa celda se llama
“A2”.

Si ahora nos situamos en la celda B2, sobre el “cuadro de nombres”, y reemplazo “B2” por
“Euribor”, lo cual nos dará una cosa así:

podremos “reprogramar” nuestro código haciendo referencia a ese nombre de celda. Es decir,
que nuestra línea:

vEurib = DDERequest(CanalDDE, "F2C2")

nos quedaría así:

vEurib = DDERequest(CanalDDE, "Euribor")

Y nos evitamos el tener que utilizar el sistema de referencias de nuestro Excel.

Vamos a complicar el ejemplo. Lo que queremos es obtener el valor actualizado de la inversión


de nuestros inversionistas. Para ello:

1.- Añadimos algunos registros a nuestra tabla TDatos.

2.- Añadimos un factor de 1 a nuestra tabla TAct, simplemente para tener un registro

3.- Programamos un botón con el siguiente código, que sacará el valor de la celda B3,

7
Visítame en http://siliconproject.com.ar/neckkito/
actualizará la tabla TAct y nos mostrará la consulta con las inversiones actualizadas 3:


Private Sub cmdInvActualizadas_Click()
'Declaramos las variables
Dim canalDDE As Long
Dim vFact As Double
Dim rst As DAO.Recordset
'Abrimos el canal
canalDDE = DDEInitiate("excel", "DATOS")
'Establecemos la comunicación y sacamos el valor de B3
vFact = DDERequest(canalDDE, "F3C2")
'Abrimos el recordset
Set rst = CurrentDb.OpenRecordset("TAct", dbOpenTable)
With rst
'Nos movemos al primer registro
.MoveFirst
'Actualizamos el valor
.Edit
.Fields(0).Value = vFact
.Update
End With
'Mostramos la consulta con los valores actualizados
DoCmd.OpenQuery "CDatosAct", , acReadOnly
'Cerramos el canal
DDETerminate (canalDDE)
'Cerramos conexiones y liberamos memoria
rst.Close
Set rst = Nothing
End Sub

Sin problemas, ¿verdad?

ENVIAR DATOS: DDEPoke


Para remitir datos desde el destinatario al origen utilizamos DDEPoke. Su estructura es la
siguiente:

DDEPoke <canal>, <elemento>, <dato>

Para poder probar este método tendremos que hacer algunas pequeñas modificaciones en
nuestras aplicaciones.

1.- En Excel, en la celda A5, escribimos: “Capturado Access:”

2.- En Access, en nuestro formulario de pruebas, añadimos un cuadro de texto, al que


pondremos de nombre txtCapt.

3.- Ahora ya sí podemos programar un botón para pasar la información que introduzcamos en
txtCapt a la celda B5. El código sería el siguiente:

3 Si no la tenemos registrada debemos registrar la librería “Microsoft DAO 3.6 Object Library”

8
Visítame en http://siliconproject.com.ar/neckkito/

Private Sub cmdDDEPoke_Click()
'Declaramos las variables
Dim canalDDE As Long
Dim vCapt As String
'Capturamos el valor de txtCapt
vCapt = Me.txtCapt.Value
'Abrimos el canal
canalDDE = DDEInitiate("excel", "DATOS")
'Pasamos el valor a la celda B5
DDEPoke canalDDE, "F5C2", vCapt
'Cerramos el canal
DDETerminate (canalDDE)
End Sub

Fijaos que en DDEPoke no hemos utilizado paréntesis porque utilizamos directamente el


método.

Sin más comentarios.

ENVÍO DE COMANDOS: DDEExecute


Podría interesarnos realizar algún tipo de acción con el origen. Por ello, podríamos enviarle un
comando (que reconozca lógicamente la aplicación origen) y automatizar esa tarea.

Para ello utilizaremos el método DDEExecute.

Su estructura es la siguiente:

DDEExecute <canal>, <comando>

Por ejemplo, podría interesarnos crear un nuevo libro de Excel. Supongamos

El código que nos permitiría hacer eso sería:


Private Sub cmdDDEExecute_Click()
'Declaramos las variables
Dim canalDDE As Long
'Abrimos el canal
canalDDE = DDEInitiate("excel", "DATOS")
'Creamos un nuevo Excel
DDEExecute canalDDE, "[New(1)]"
'Cerramos el canal
DDETerminate canalDDE
End Sub

FINALIZAR CONEXIÓN: DDETerminate / DDETerminateAll


Como ya hemos visto, para optimizar recursos del sistema es recomendable cerrar los canales
que hayamos abierto.

Para ello tenemos:

9
Visítame en http://siliconproject.com.ar/neckkito/
DDETerminate <canal>

para cerrar canales individualizadamente, o bien

DDETerminateAll

para cerrar de manera global todos los canales abiertos.

AUTOMATIZACIÓN: INDICACIONES INICIALES


Vamos a ver, antes de entrar de lleno en algunas de las
aplicaciones de Office, algunos elementos comunes que
debemos tener siempre en cuenta.

Registrar la referencia
Como ya comentábamos unas páginas más arriba, es necesario proporcionar a Access ese
“diccionario” para que pueda manejar el lenguaje de la aplicación servidor.

Al registrar la referencia no sólo hacemos posible la comunicación entre aplicaciones, sino que,
cuando vayamos escribiendo código, la ayuda contextual del VBE nos proporcionará las pistas
necesarias para ver qué elementos “caben” dentro de la expresión.

Abrir la aplicación servidor


En general, podemos decir que debemos abrir una instancia de la aplicación, para después
crear uno o varios nuevos elementos propios de la aplicación abierta.

Es decir, por poner un ejemplo con Word:

– Abrimos una instancia de Word → Equivaldría a abrir un Word nuevo, pero sin ningún
documento en él
– Abrimos un nuevo documento y lo enmarcamos dentro de esa instancia → Equivaldría al
propio documento (“ese espacio en blanco donde podemos escribir”)

Tras lo anterior podremos manipular la instancia de Word, sin referirnos al documento, o


podremos manipular el propio documento.

Sé que esto puede parecer un poco confuso al principio, pero con el ejemplo del epígrafe
ACCESS – WORD creo que lo entenderemos perfectamente.

Abrir un archivo existente


Para abrir un archivo existente la sistemática sería muy parecida a lo que acabamos de
comentar, pero, lógicamente, no debemos añadir un elemento nuevo porque ya existe (si no
no lo podríamos abrir, ¿no?).

Es decir, siguiendo con el ejemplo de Word:

– Creamos una instancia de Word


– Abrimos el documento guardado, asignándolo a la instancia.

10
Visítame en http://siliconproject.com.ar/neckkito/
Igual que lo que os comentaba, parece un poco confuso pero a través del ejemplo espero que
lo entendamos perfectamente.

Métodos, propiedades y demás malas


hierbas
Cada una de las aplicaciones de Office tiene sus propias
características, y, evidentemente, son muchísimas.

La idea de los epígrafes que siguen a continuación no es


hacer un análisis exhaustivo de cada una de las
aplicaciones, sino que la pretensión es simplemente poner
un “ejemplo más o menos representativo” de cómo sería la
mecánica para una automatización entre Access y las
aplicaciones más “usuales” de la suite Office.

Por suerte para todos, en Internet hay múltiples ejemplos para realizar diversas acciones, por
lo que a una búsqueda, en el caso de que necesitéis algo en concreto, os remito. La finalidad
de lo que explicaré es que seáis capaces de “entender”, encontrado el ejemplo, qué estáis
haciendo, o si lo podéis adaptar a las particularidades de vuestra BD.

ACCESS – WORD
Vamos a realizar el ejemplo en dos fases:

La primera fase, crearemos un nuevo Word y lo guardaremos en la carpeta donde tengamos la


base de datos. En él escribiremos los datos que tenemos en la tabla TDatos.

La segunda fase, abriremos ese Word y pondremos toda la información que tengamos ahí en
negrita.

Vamos a ver cómo podemos hacer eso. Como siempre, os pondré el código ampliamente
comentado.

Recordad que en el editor de VBE debemos registrar la referencia “Microsoft Word x.y Object
Library”

En un botón de comando escribimos el siguiente código:


Private Sub cmdCreaWord_Click()
'Declaramos las variables
Dim Wrd As New Word.Application
Dim DocWrd As New Word.Document
Dim rst As DAO.Recordset
Dim miArchivo As String
'Determinamos la ruta y el nombre de archivo que se va a guardar
miArchivo = Application.CurrentProject.Path & "\MiWord.doc"
'Hacemos visible el Word
Wrd.Visible = True
'Añadimos un documento en blanco
Set DocWrd = Wrd.Documents.Add
'Activamos el documento
Wrd.ActiveDocument.Select
'Creamos el recordset

11
Visítame en http://siliconproject.com.ar/neckkito/
Set rst = CurrentDb.OpenRecordset("TDatos", dbOpenSnapshot)
'Nos situamos en el primer registro
rst.MoveFirst
'Iniciamos el recorrido de registros
Do Until rst.EOF
'Escribimos los datos del registro
With Wrd.Selection
.TypeText (rst.Fields(0).Value)
.TypeParagraph
.TypeText (rst.Fields(1).Value)
.TypeParagraph
.TypeText (rst.Fields(2).Value)
.TypeParagraph
End With
'Nos movemos al siguiente registro
rst.MoveNext
Loop
'Guardamos el documento
Wrd.ActiveDocument.SaveAs FileName:=miArchivo, FileFormat:=wdFormatDocument
'Cerramos el documento y Word
DocWrd.Close
Wrd.Quit
'Cerramos conexiones y liberamos memoria
rst.Close
Set rst = Nothing
Set Wrd = Nothing
End Sub

Un par de comentarios:

.- Tengamos en cuenta que una cosa es el Word como aplicación y otra cosa es una hoja de
Word (el documento). Son dos elementos diferentes, y en el código deben tratarse, pues,
como objetos diferentes (os comentaba esto en el epígrafe anterior).

Es por ello por lo que lo que es la aplicación la definimos como el objeto Wrd, y la hoja de
Word la definimos como el objeto DocWord

.- Debe haber una equivalencia entre la extensión del documento y el formato con el que
guardamos el documento. Es decir, que si, en ejemplo, indicamos que se va a guardar un
archivo .doc (miArchivo = Application.CurrentProject.Path & "\MiWord.doc") en la línea
(Wrd.ActiveDocument.SaveAs FileName:=miArchivo, FileFormat:=wdFormatDocument)
debemos decirle que el formato es wdFormatDocument

Extraído de la ayuda de Access os indico aquí los formatos posibles:

Nombre Valor Descripción


wdFormatDocument 0 Formato de Microsoft Office Word.
wdFormatDOSText 4 Formato de texto de Microsoft DOS.
Formato de texto de Microsoft DOS con saltos
wdFormatDOSTextLineBreaks 5
de línea.
wdFormatEncodedText 7 Formato de texto codificado.
wdFormatFilteredHTML 10 Formato HTML filtrado.

12
Visítame en http://siliconproject.com.ar/neckkito/
wdFormatHTML 8 Formato HTML estándar.
wdFormatRTF 6 Formato RTF.

wdFormatTemplate 1 Formato de plantilla de Word.


wdFormatText 2 Formato de texto de Microsoft Windows.
Formato de texto de Windows con saltos de
wdFormatTextLineBreaks 3
línea.
wdFormatUnicodeText 7 Formato de texto Unicode.
wdFormatWebArchive 9 Formato de archivo Web.
Formato XML (lenguaje de marcado
wdFormatXML 11
extensible).
wdFormatDocument97 0 Formato de documento de Microsoft Word 97.
Formato predeterminado de archivo de
wdFormatDocumentDefault 16 documento de Word. En el caso de Microsoft
Office Word 2007, es el formato DOCX.
wdFormatPDF 17 Formato PDF.
wdFormatTemplate97 1 Formato de plantilla de Word 97.
wdFormatXMLDocument 12 Formato de documento XML.
Formato de documento XML con las macros
wdFormatXMLDocumentMacroEnabled 13
habilitadas.
wdFormatXMLTemplate 14 Formato de plantilla XML.
Formato de plantilla XML con las macros
wdFormatXMLTemplateMacroEnabled 15
habilitadas.
wdFormatXPS 18 Formato XPS.

.- Relacionado con lo anterior, estamos utilizando la versión 2007 (o posterior) no se haría


necesario indicar el formato de documento, puesto que el formato se nos pondría ya por
defecto. Es decir, que si escribimos:

miArchivo = Application.CurrentProject.Path & "\MiWord.docx"

podemos escribir sólo

Wrd.ActiveDocument.SaveAs FileName:=miArchivo

sin indicarle formato (para vuestra información os diré que el formato es


wdFormatXMLDocument).

.- Si no especificamos el formato correcto o bien obtendremos error al ejecutar el código o bien


obtendremos error al abrir el archivo Word que hemos creado.

13
Visítame en http://siliconproject.com.ar/neckkito/
Fase 2

Ahora ya tenemos nuestro archivo de Word creado. Vamos a abrirlo para poder poner todo el
texto en negrita.

El código que deberíamos emplear sería el siguiente:


Private Sub cmdAbreWord_Click()
'Declaramos las variables
Dim miArchivo As String
Dim Wrd As New Word.Application
'Creamos la ruta del archivo con su nombre y
extensión
miArchivo = Application.CurrentProject.Path & "\MiWord.doc"
'Abrimos el documento
Wrd.Documents.Open miArchivo
'Hacemos visible el Word
Wrd.Visible = True
'Seleccionamos el documento de Word
Wrd.ActiveDocument.Select
With Wrd.Selection
'Seleccionamos todo el contenido del documento
.WholeStory
'Pasamos el contenido a negrita
.Font.Bold = True
End With
'Cerramos el documento guardando los cambios
Wrd.ActiveDocument.Close wdSaveChanges
'Cerramos el Word
Wrd.Quit
Set Wrd = Nothing
End Sub

Fijaos en que:

.- En este caso no es necesario “crear” un nuevo documento, puesto que al abrirse el archivo
MiWord.doc ya tenemos el documento creado.

.- Para acceder a él sólo hay que decirle que la instancia de Word lo “abra”, a través de la línea

Wrd.Documents.Open miArchivo

Bueno, como habéis podido apreciar hay que saber “algunas cosillas” de cómo se escribe el
código cuando hablamos de Word (y veremos lo mismo con el resto de aplicaciones). Eso sí
que es un trabajo autónomo, y habrá que recurrir a la ayuda del VBE de Access (puesto que al
registrar la biblioteca algunos de estos elementos que hemos visto se incorporan a la ayuda) o
a la ayuda del propio Word (la búsqueda por Internet ya la doy por supuesta).

ACCESS – EXCEL
Cuando trabajamos con Excel debemos tener en cuenta los siguientes elementos:

– La instancia de Excel
– El libro activo

14
Visítame en http://siliconproject.com.ar/neckkito/
– La hoja activa

Debemos, pues, controlar estos tres elementos desde nuestro código.

Vamos a:

1.- Crear un nuevo Excel, que llamaremos Inversores.xls


2.- En la hoja1 escribiremos los datos de los inversores
cuya aportación de capital sea superior o igual a 2.500
euros
3.- En la hoja2 escribiremos los datos de los inversores
cuya aportación de capital sea inferior a 2.500 euros

Recordad que debemos registrar la referencia “Microsoft Excel x.y Object Library”

El código que nos permitiría hacer lo anterior sería:


Private Sub cmdCreaExcel_Click()
'Declaramos las variables
Dim miSql As String
Dim miExcel As String
Dim rst As DAO.Recordset
Dim fld As DAO.Field
Dim Exc As Excel.Application 'Aplicación
Dim LibExc As Excel.Workbook 'Libro
Dim HojExc As Excel.Worksheet 'Hoja
Dim numFila As Integer, numColumna As Integer
'Definimos las variables
'Creamos la ruta y nombre de archivo
miExcel = Application.CurrentProject.Path & "\Inversores.xls"
'Creamos una nueva instancia de Excel
Set Exc = New Excel.Application
'Añadimos el libro en nuestra instancia
Set LibExc = Exc.Workbooks.Add
'Fijamos la hoja activa
Set HojExc = Exc.ActiveSheet
'Hacemos visible nuestro Excel
Exc.Visible = True
'-----CÓDIGO PARA RELLENAR LA HOJA1-------------------------------------------
'Definimos la SQL para que nos filtre los registros superiores
'a 2.500 euros
miSql = "SELECT * FROM TDatos WHERE Capital>=2500"
'Creamos el recordset sobre la SQL
Set rst = CurrentDb.OpenRecordset(miSql, dbOpenSnapshot)
'Inicializamos el número de fila y de columna
numFila = 1
numColumna = 1
'Creamos la cabecera con los campos
For Each fld In rst.Fields
HojExc.Cells(1, numColumna).Value = fld.Name
numColumna = numColumna + 1
Next fld
'Preparamos la fila
numFila = 2
'Reinicializamos la columna
numColumna = 1
'Nos movemos al primer registro
rst.MoveFirst
'Iniciamos el proceso
Do Until rst.EOF
'En la fila 2 escribimos los valores obtenidos en la SQL
With HojExc
.Cells(numFila, numColumna).Value = rst.Fields(0).Value
numColumna = numColumna + 1
.Cells(numFila, numColumna).Value = rst.Fields(1).Value

15
Visítame en http://siliconproject.com.ar/neckkito/
numColumna = numColumna + 1
.Cells(numFila, numColumna).Value = rst.Fields(2).Value
End With
'Reinicializamos la columna
numColumna = 1
'Pasamos a la siguiente fila
numFila = numFila + 1
'Nos movemos al siguiente registo
rst.MoveNext
Loop
'-----CÓDIGO PARA RELLENAR LA
HOJA2-------------------------------------------
'Cambiamos la hoja activa a la hoja2
Exc.Worksheets("Hoja2").Select
'Reinicializamos la hoja activa (que ahora es la Hoja2)
Set HojExc = Exc.ActiveSheet
'Creamos la SQL para menores de 2500
miSql = "SELECT * FROM TDatos WHERE Capital<2500"
'A partir de aquí, hasta la rutina de cierre de proceso, es un copy-paste
'del código anterior
'Abrimos el recordset
Set rst = CurrentDb.OpenRecordset(miSql, dbOpenSnapshot)
'Inicializamos el número de fila y de columna
numFila = 1
numColumna = 1
'Creamos la cabecera con los campos
For Each fld In rst.Fields
HojExc.Cells(1, numColumna).Value = fld.Name
numColumna = numColumna + 1
Next fld
'Preparamos la fila
numFila = 2
'Reinicializamos la columna
numColumna = 1
'Nos movemos al primer registro
rst.MoveFirst
'Iniciamos el proceso
Do Until rst.EOF
'En la fila 2 escribimos los valores obtenidos en la SQL
With HojExc
.Cells(numFila, numColumna).Value = rst.Fields(0).Value
numColumna = numColumna + 1
.Cells(numFila, numColumna).Value = rst.Fields(1).Value
numColumna = numColumna + 1
.Cells(numFila, numColumna).Value = rst.Fields(2).Value
End With
'Reinicializamos la columna
numColumna = 1
'Pasamos a la siguiente fila
numFila = numFila + 1
'Nos movemos al siguiente registo
rst.MoveNext
Loop
'-----CERRAMOS EL PROCESO GUARDANDO EL EXCEL
Exc.ActiveWorkbook.SaveAs FileName:=miExcel, FileFormat:=xlExcel8
Exc.Quit
'Cerramos conexiones y liberamos memoria
rst.Close
Set rst = Nothing
Set Exc = Nothing
End Sub

Para destacar algunos elementos del código comentaré que:

.- Fijaos que, para moverme por las celdas, utilizo CELLS(numFila, numColumna). Eso me
permite manipular las variables numFila y numColumna “a mi antojo”, para llevar los valores a
las celdas que me interesan.

16
Visítame en http://siliconproject.com.ar/neckkito/
.- De nuevo podéis ver que manipulamos primero la instancia de Excel (variable Exc), el libro
activo (variable LibrExc) y la hoja activa (variable HojExc).

.- Igual que en el caso del Word, debemos especificar el


formato de salida, que debe corresponderse con la
extensión de archivo que hemos indicado. Para obtener un
listado entero de los formatos posibles podéis situaros en la
ayuda de Excel y buscar por <xlFileFormat>. De las
opciones que os aparezcan seleccionad la “enumeración”.

Y poca cosa más sobre Excel. Insistir en lo que os he estado


comentando a lo largo de este capítulo: si queremos que nos salga
algo en concreto en Excel deberemos buscar el “vocabulario”
adecuado para indicárselo a Access.

ACCESS – OUTLOOK
No me entretendré demasiado con Outlook aquí, porque, básicamente, la mecánica es la
misma: tengo que definir una instancia de Outlook y referenciar los objetos de Outlook (un
mensaje, un contacto, etc.).

Vamos a programar un ejemplo relativamente sencillo con Outlook. No olvidéis registrar la


librería correspondiente a Microsoft Outlook.

El código para enviar un mail sería el siguiente4:


Private Sub cmdCreaOutlook_Click()
'Declaramos las variables
Dim mailDestinatario As String
Dim mailAsunto As String
Dim mailMensaje As String
Dim Olk As Outlook.Application
Dim OlkMsg As Outlook.MailItem
Dim OlkDestinatario As Outlook.Recipient
'Solicitamos la dirección de correo electrónico
mailDestinatario = InputBox("¿Mail del destinatario?", "E-MAIL")
'Solicitamos el asunto
mailAsunto = InputBox("¿Asunto?", "ASUNTO")
'Solicitamos el texto del mensaje
mailMensaje = InputBox("¿Mensaje?", "MENSAJE")
'Creamos una instancia de Outlook
Set Olk = CreateObject("Outlook.Application")
'Creamos un nuevo mensaje de Outlook
Set OlkMsg = Olk.CreateItem(olMailItem)
'Creamos la información del mail
With OlkMsg
Set OlkDestinatario = .Recipients.Add(mailDestinatario)
OlkDestinatario.Type = olTo
.Subject = mailAsunto
.Body = mailMensaje
'Display muestra el correo antes de enviarse
.Display
'Si queremos que se envíe directamente debemos sustituir .Display

4 En ocasiones el código no me funciona correctamente si no tengo el Outlook previamente abierto. Si a vosotros os pasa lo mismo
probadlo abriendo Outlook antes de ejecutar el código.

17
Visítame en http://siliconproject.com.ar/neckkito/
'por .Send
End With
'Eliminamos la instancia
Set Olk = Nothing
Set OlkMsg = Nothing
Set OlkDestinatario = Nothing
End Sub

Si queréis profundizar en el código referente a la


automatización con Outlook os invito a que echéis un
vistazo a estos dos ejemplos que están en la web y que
hacen referencia a esta aplicación. Estoy seguro de que si lo
analizáis deberíais entender perfectamente lo que se está
haciendo.

Los ejemplos los podéis encontrar en:

1.- Y dos de mail


2.- Exportar citas a Outlook

ACCESS – POWERPOINT
Aunque, personalmente, nunca he utilizado la automatización con PowerPoint, haremos un
pequeño ejemplo muy simple aquí, a fin de que quede “para la posteridad”.

No olvidéis registrar la referencia “Microsoft PowerPoint x.y Object Library”

Vamos a crearnos un PowerPoint con una diapositiva que nos dará la información del factor de
actualización.

El código que nos permitiría hacer lo anterior sería:


Private Sub cmdCreaPowerPoint_Click()
'Declaramos las variables
Dim miPresent As String
Dim PWP As PowerPoint.Application
Dim PWPDiapo As PowerPoint.Slide
'Definimos la ruta y el nombre de la presentación
miPresent = Application.CurrentProject.Path & "\Factor.pps"
'Creamos la instancia del PWP
Set PWP = New PowerPoint.Application
'La convertimos en visible
PWP.Visible = True
'Añadimos una diapositiva. Debemos identificar el índice de
'la diapositiva y el tipo (argumentos entre paréntesis)
Set PWPDiapo = PWP.Presentations.Add.Slides.Add(1, ppLayoutTitle)
'Como hemos elegido una diapositiva con título y subtítulo (ppLayoutTitle)
'rellenamos título y subtítulo
With PWPDiapo
'Este es el título
.Shapes.Title.TextFrame.TextRange = "FACTOR ACTUALIZACION"
'Este es el subtítulo. Buscamos el valor del factor en la tabla TAct a
'través de un DLookup
.Shapes.Placeholders(2).TextFrame.TextRange = DLookup("[Factor]", "TAct")

18
Visítame en http://siliconproject.com.ar/neckkito/
End With
'Guardamos nuestra presentación en el directorio de la base de datos
PWP.ActivePresentation.SaveAs miPresent,
ppSaveAsDefault
'Cerramos la presentación
PWP.Quit
Set PWPDiapo = Nothing
Set PWP = Nothing
End Sub

Como veis el código está ampliamente comentado. Como curiosidad os diré que, inicialmente,
no tenía ni idea de cómo programar el ejemplo (ya os comentaba al principio de este apartado
que no utilizo la automatización con PowerPoint), por lo que me he pasado un buen rato
“buceando” por la ayuda. Pero, como podéis comprobar, al final la cosa, más bien o más mal,
acaba saliendo. ;)

FUNCIONES CREATEOBJECT() Y GETOBJECT()


En los ejemplos anteriores no hemos utilizado las funciones CreateObject() y GetObject().
Podríamos haberlas utilizado sin problemas.

La idea principal para utilizar esas dos funciones radica en dos pasos:

– Creamos una variable que sea un objeto


– Si:
– Queremos crear una nueva aplicación utilizamos CreateObject
– Queremos acceder a una aplicación existente utilizamos GetObject

Por ejemplo, si quisiéramos crear un nuevo Excel escribiríamos el siguiente código:


Private Sub cmdCreateObject_Click()
'Declaramos las variables
Dim miExcel As Object 'o también miExcel As Excel.Application
Dim miLibro As Object 'o también miLibro As Excel.Workbook
'Creamos el archivo a través de CreateObject()
Set miExcel = CreateObject("Excel.Application")
'Añadimos un libro a la aplicación
Set miLibro = miExcel.Workbooks.Add
'Lo hacemos visible
miExcel.Visible = True
End Sub

Estas dos funciones, sin embargo, pueden servirnos para acceder a los objetos del sistema
(Windows), o a otras determinadas aplicaciones, y no sólo para la automatización con las
aplicaciones de la suite Office.

Por ejemplo, en la ayuda de Access (en el VBE) se muestran ejemplos de cómo acceder a
archivos *.CAD, o a archivos *.DRW

Si queremos acceder a los objetos del sistema utilizaremos el objeto FileSystemObject

19
Visítame en http://siliconproject.com.ar/neckkito/
No entraremos en este capítulo a explicar las características de este último objeto que hemos
mencionado. Me limito a mencionarlo por si a alguien “le pica el gusanillo” y quiere dedicarse a
estudiarlo por su cuenta.

PARA FINALIZAR
Y, en principio, eso es todo por lo que respecta al
tema de la automatización. Sé que hay más
aplicaciones incluidas en la suite Office, pero en el
fondo, si hemos comprendido la mecánica de cómo
“manejar” los elementos de automatización, sólo nos
queda la “difícil tarea” de investigar cómo son las
llamadas al código propio de la aplicación servidor.

¡Suerte!

20
Visítame en http://siliconproject.com.ar/neckkito/
CURSO DE VB

CAPÍTULO 151

Índice de contenido
UN POCO DE XML Y XSL................................................................................................................2
INTRODUCCIÓN...........................................................................................................................2
¿QUÉ ES XML Y XSL?..................................................................................................................2
PREPARANDO NUESTRA BASE DE DATOS.............................................................................3
EXPORTANDO A XML..................................................................................................................4
PROCEDIMIENTO....................................................................................................................4
ARCHIVOS DE RESULTADO..................................................................................................6
MANIPULANDO EL ARCHIVO XML....................................................................................8
EXPORTANDO UN FORMULARIO........................................................................................8
TRASPASANDO LOS DATOS A UNA APLICACIÓN EXTERNA...........................................10
IMPORTANDO XML...................................................................................................................10
PARA FINALIZAR.......................................................................................................................11

1 La BD donde están los ejemplos de este capítulo os la podéis bajar aquí.

1
Visítame en http://siliconproject.com.ar/neckkito/
UN POCO DE XML Y XSL

INTRODUCCIÓN
¿Por qué le dedico un capítulo a XML y XSL? Básicamente
porque se trata de un lenguaje ampliamente extendido que,
quizás, en algún momento nos pueda sacar de un apuro.

Imaginemos que tenemos una base de datos con


información. Por los motivos que sean necesitamos trabajar
con una aplicación XXX

¿Qué ocurre? Que nuestra aplicación XXX no puede leer datos directamente de Access.
Lógicamente, Access tampoco puede leer datos de XXX.

¿Y cómo podríamos poner un “parche” a la anterior situación? Pues la respuesta podría ser:
“utilicemos un lenguaje que entiendan ambas aplicaciones”.

Y, curiosamente, el lenguaje XML es un lenguaje ampliamente extendido que suele ser


reconocido por un gran número de programas.

Por cierto, cuando hablo de una aplicación XXX no me refiero a una súper-aplicación que nos
haya costado montones de dinero (que también podría serlo, claro). Me refiero a algo tan
común como nuestro querido Internet Explorer, por ejemplo.

Debo deciros que, aunque “chapurreo” un poco XML, no es un lenguaje que “controle”. En
definitiva, que la idea que deberíais sacar de este capítulo es que se puede, con Access,
interactuar con archivos XML, y eso puede sernos de utilidad. Al que le guste mucho el sistema
pues no tendrá más remedio que estudiar por su cuenta “esto de XML”. Quizá, quien se anime,
pueda darme, en un futuro no muy lejano, clases a mí... de manera gratuita, claro... je, je,
je...

Entremos en materia.

¿QUÉ ES XML Y XSL?


XML corresponde a las palabras eXtensible Markup Language, con una espectacular traducción
mía que es “lenguaje estructurado por marcas”.

XML está muy relacionado con Internet, y constituye uno de los lenguajes más estandarizados
de intercambio de datos a través de la Red.

¿Y por qué digo “por marcas”? Porque, asombrosamente (ji, ji...), XML utiliza marcas en su
desarrollo.

¿Seguimos confusos? Imagino que todo el mundo ha visto código html. HTML utiliza también
marcas, que son las que definen lo que son los metadatos de la página web, lo que es la
cabecera, el cuerpo, si una frase que aparece está en negrita, o en itálica, etc. Esas marcas
vienen delimitadas por los símbolos (<) y (>).

Por ejemplo, y hablando de htlm, en el siguiente código podemos ver las marcas head, tittle, y
body:

2
Visítame en http://siliconproject.com.ar/neckkito/
<html>
<head>
<tittle>ESTO ES EL TÍTULO</tittle>
</head>
<body>Esto es el cuerpo</body>
</html>

Este es un lenguaje estructurado porque, por decirlo de


alguna manera, se utiliza una estructura que delimita
claramente el inicio y el fin de cada marca.

<head> → Empieza la cabecera, y


</head> → Finaliza la cabecera.

Todo lo que hay entre estas dos etiquetas pertenece a la cabecera. Por ello, el título pertenece
a la cabecera, y, a su vez, la frase “ESTO ES EL TÍTULO” pertenece al título, porque tenemos
una etiqueta de apertura <tittle> y otra de cierre </tittle>, y nuestra frase se halla entre
ambas.

He utilizado html porque creo que todo el mundo ha visto alguna vez en su vida un código
html, y resulta muy fácil de entender.

Pues XML sigue el mismo patrón. No vamos a detenernos a examinar los requisitos de XML
porque no es objecto de este curso, pero os comentaré que, a efectos de este capítulo:

– La primera línea debe indicar que se trata de XML y qué tipo de codificación se va a
emplear. Eso se consigue a través de:

<?xml version = “1.0” enconding = “UTF-8” ?>

– La segunda línea debe llamar a la información de presentación (si queremos utilizarla,


porque no es obligatoria), es decir, al archivo XSL (lo que lo hace “bonito” a la vista, en
definitiva). Veremos cómo lo llamamos un poco más adelante.

En uno de los ejemplos que veremos más adelante nos detendremos a comentar el tema de
las marcas de un archivo XML.

Visto lo anterior, podremos deducir que XSL es una instancia de XML que permite gestionar la
presentación de cómo veremos los documentos XML

Para finalizar comentaros que para editar y manipular un archivo XML necesitamos un potente
programa llamado “Bloc de notas” (je, je...), aunque nos serviría cualquier editor de texto (yo
os recomiendo el Notepad++, que además es portable).

PREPARANDO NUESTRA BASE DE DATOS


Vamos a crearnos una BD en blanco para desarrollar este capítulo. En ella introduciremos una
tabla, que llamaremos TDatos, que tendrá la siguiente estructura:

3
Visítame en http://siliconproject.com.ar/neckkito/
Añadimos unos pocos registros (pocos para no tener un
XML muy largo). Por ejemplo, yo he añadido los siguientes
(los datos son inventados):

Ahora nos creamos un formulario en blanco. Será el que nos servirá para hacer pruebas. Por
ejemplo, yo lo he llamado FMenu (para variar, claro).

Vamos a crearnos un formulario basado en la tabla TDatos, pero en vista de formularios


continuos2. Lo llamaremos FDatos. Debería quedarnos una cosa así:

Finalmente, en la carpeta donde tenemos la BD, crearemos una nueva carpeta, a la que
llamaremos ArchivosXML. Ahí guardaremos todos nuestros archivos xml.

EXPORTANDO A XML

PROCEDIMIENTO
Para exportar a XML vamos a utilizar el método EXPORTXML, que tiene la estructura siguiente:

Application.ExportXML <TipoObjeto>, <OrigenDatos>, <DestinoDatos>, <DestinoEsquema>,


<DestinoPresentacion>, <DestinoImagenes>, <Codificacion>, <OtrosArgumentos>, <Condicion>,
<InformaciónAdicional>

<TipoObjecto> → si queremos exportar los datos de una tabla, de una consulta, de un

2 Para usuarios de Access 2007 y posterior tenemos un botón específico para ello, que es “Varios elementos”. Los sufridos usuarios de
Access 2003 deberán confeccionarse el formulario a medida, sacar sus propiedades → Formato → Vista predeterminada:
Formularios continuos.

4
Visítame en http://siliconproject.com.ar/neckkito/
formulario...

<OrigenDatos> → nombre del objeto que vamos a exportar

Estos dos argumentos son de entrada obligatoria. Los que


siguen son opcionales.

<DestinoDatos> → Ruta, nombre y extensión (*.xml) de los


datos exportados

<DestinoEsquema> → Ruta, nombre y extensión del


esquema exportado.

<DestinoPresentacion> → Ruta, nombre y extensión (*.xsl) de la presentación de los datos

<DestinoImagenes> → Ruta de las imágenes exportadas (si las hubiera)

<Codificación> → Codificación del archivo XML

<OtrosArgumentos> → Máscara de bits que especifica otros comportamientos asociados a la


exportación a XML

<Condicion> → Condición si queremos filtrar un conjunto de registros

<InformacionAdicional> → Especifica tablas adicionales que se van a exportar.

Tras esta intensa explicación teórica vamos a realizar un ejemplo, donde sólo utilizaremos
algunos de los argumentos de los que hemos visto.

La idea es exportar la tabla TDatos a un fichero en la carpeta ArchivosXML que llamaremos


“misDatos”.

En nuestro FMenu crearemos un botón de comando (cmdExportXML) y le asignaremos, en el


evento “Al hacer click”, el siguiente código:


Private Sub cmdExportXML_Click()
'Declaramos las variables
Dim archivoDestino As String
Dim nombreArchivo As String
'Definimos las variables
nombreArchivo = "misDatos"
archivoDestino = Application.CurrentProject.Path & "\ArchivosXML\" & nombreArchivo
'Exportamos la tabla
Application.ExportXML acExportTable, "TDatos", archivoDestino & ".xml", _
, archivoDestino & ".xsl", , acUTF8
MsgBox "Exportación realizada correctamente", vbInformation, "OK"
End Sub

Como vemos, hemos indicado, en la exportación:

– Que se trataba de una tabla (acExportTable)


– Que la tabla era TDatos
– Que el archivo destino era misDatos, con la extensión xml
– Que el archivo destino de la presentación era misDatos, con la extensión xsl

5
Visítame en http://siliconproject.com.ar/neckkito/
– Que la codificación era UTF-8

ARCHIVOS DE RESULTADO
Si ejecutamos este procedimiento y nos vamos a la carpeta
ArchivosXML veremos que se nos han creado 3 archivos:

– El archivo misDatos.xml (que contiene los datos)


– El archivo misDatos.xsl (que contiene información de
la presentación)
– El archivo misDatos.htm (que contiene la mezcla de
los dos anteriores para poder ser vista en el navegador).

Si tenemos Firefox o Chrome e intentamos abrir el archivo misDatos.htm nos encontraremos


con una agradable sorpresa. Os invito a probarlo (si tenéis estos navegadores instalados,
claro).

¿Y qué sorpresa es? Que la página del navegador sale en blanco. ¿Y por qué? ¿No será que
Firefox o Chrome no son de Microsoft?

Intentemos abrir misDatos.htm, pero esta vez con Internet Explorer. ¿Qué nos sale?

Pues nos aparece, nada más y nada menos, que lo siguiente:

¿Curioso, verdad?

Moraleja: debemos abrir el archivo htm con IE.

Vamos a abrir el archivo misDatos.xml con el bloc de notas 3. Nos encontraremos con lo
siguiente:

3 Entiendo que todos sabemos cómo hacer eso, pero por si hay algún/a despistado/a debemos: hacer click con el botón derecho
sobre el archivo → Abrir con... → Seleccionamos el bloc de notas . Si no nos sale en la lista de programas le damos a la opción
Elegir Programa... → Seleccionamos el bloc de notas, y si aún así no nos sale buscamos el archivo Notepad.exe

6
Visítame en http://siliconproject.com.ar/neckkito/
Como vemos, la primera y la segunda líneas se corresponden con valores por defecto que
siempre crea la exportación, y que proporcionan información variada sobre el tipo y contenido
del archivo.

La primera etiqueta se corresponde con el nombre de nuestra tabla <TDatos>. Dentro de la


tabla TDatos tenemos los campos, que vienen recogidos registro a registro con la estructura
NombreCampo – Valor – Cierre NombreCampo.

De ahí que veamos:


<Id>1</Id>
<Peli>Matrix</Peli>
etc.

Si queremos verlo más “legible” podemos abrir el archivo misDatos.xml con IE o con el editor
XML de Microsoft. Obtendremos unas delimitaciones más claras y con “colorines". Algo así:

7
Visítame en http://siliconproject.com.ar/neckkito/
Y, finalmente, si abrimos el archivo misDatos.xsl con el bloc de notas o con el XML editor de
Microsoft podemos prepararnos para llevarnos un susto...

Evidentemente no entraremos en explicar los detalles de este archivo (no creo que yo fuera
capaz ;-) ) Simplemente, como podemos intuir al verlo, vemos que marca los “estilos” que
configurarán el documento.

MANIPULANDO EL ARCHIVO XML


Hemos comentado que el archivo *.htm combinaba datos y estilos. Sin embargo, podría
interesarnos que el fichero *.xml “supiera” dónde está su “amigo *.xsl”. Para hacer la prueba
vamos a hacer lo siguiente:

– Copiamos el archivo misDatos.xml


– Lo pegamos en la misma carpeta con el nombre misDatos2.xml
– Editamos, con el bloc de notas, misDatos2.xml y añadimos la siguiente línea de código
debajo de la primera:

<?xml-stylesheet type="text/xsl" href="misDatos.xsl"?>

De manera que nos quede ubicado así:

En la ilustración podréis ver el primer fragmento del código que ya conocíamos con la línea
insertada. Para vuestra información esto es lo que veríais si utilizáis Notepad++

¿Y para qué nos sirve lo anterior? Pues para no depender de un archivo htm. Lo veremos con
más claridad en el próximo apartado.

EXPORTANDO UN FORMULARIO
En nuestro FMenu vamos a añadir un botón de comando, al que llamaremos
cmdExportFormXML, y le generaremos el siguiente código:

8
Visítame en http://siliconproject.com.ar/neckkito/

Private Sub cmdExportFormXML_Click()
'Declaramos las variable
Dim archivoDestino As String
Dim nombreArchivo As String
'Definimos las variables
nombreArchivo = "misDatosForm"
archivoDestino = Application.CurrentProject.Path &
"\ArchivosXML\" & nombreArchivo
'Exportamos el formulario
Application.ExportXML acExportForm, "FDatos",
archivoDestino & ".xml", _
, archivoDestino & ".xsl", , acUTF8
MsgBox "Exportación realizada correctamente",
vbInformation, "OK"
End Sub

Como veis es prácticamente idéntico al anterior, sólo que adaptado a un formulario (os he
marcado en negrita las diferencias).

Si ejecutamos ese código veremos que en nuestra carpeta ArchivosXML nos han aparecido,
como mínimo, tres archivos más (si hemos dejado el logo -Access 2007 y sup.- nos habrá
aparecido otro archivo gráfico).

Si abrimos con IE el archivo misDatosForm.htm veremos que nos aparece lo siguiente:

Lo cual ya es más bonito, ¿verdad?

Vamos a copiar nuestro archivo misDatosForm.xml y lo vamos a pegar con el nombre de


misDatosForm2.xml. Abrimos este último con el bloc de notas y añadimos la línea que
comentábamos antes, para indicarle que existe un archivo *.xsl.

Su cabecera nos debería quedar así:

Recapitulando:

9
Visítame en http://siliconproject.com.ar/neckkito/
– misDatosForm.xml no “sabe” que hay un archivo de presentación
– misDatosForm2.xml sí “sabe” que hay un archivo de presentación

Si abrimos ambos archivos con el IE obtendremos lo


siguiente:

misDatosForm2.xml

misDatosForm.xml

Como vemos, si no hay estilo IE sólo puede mostrar el código, mientras que si hay estilo IE no
muestra los datos con el estilo correspondiente (y nos evita depender de un archivo htm).

TRASPASANDO LOS DATOS A UNA APLICACIÓN EXTERNA


Como comentábamos al inicio de este capítulo, vamos a imaginarnos que tenemos una
aplicación que puede leer datos desde archivos XML.

Para este ejemplo utilizaremos como aplicación externa Microsoft Excel, básicamente porque
doy por supuesto que todos disponemos de este programa.

Así pues, nos situamos sobre el archivo misDatos.xml y lo abrimos con Excel. Al abrirse nos
pedirá cómo deseamos abrir el archivo. Le indicamos la opción “Como tabla XML”.

Al final deberíamos tener un resultado parecido a este:

En principio la idea que apuntábamos al principio, que era poder pasar datos desde Access a
una aplicación que no leyera directamente los datos en Access, ya está conseguida.

IMPORTANDO XML
Para realizar la importación en Access utilizaremos el método IMPORTXML, que tiene la
siguiente estructura:

Application.ImportXML <DatosOrigen>, <OpcionesImportacion>

El primer argumento es obligatorio; el segundo opcional.

10
Visítame en http://siliconproject.com.ar/neckkito/
<DatosOrigen> → archivo xml donde están los datos.

<OpcionesImportación> → Podemos elegir entre tres


opciones:
– Anexar los datos a una tabla
– Crear una tabla con estructura y datos
– Crear una tabla con sólo estructura

Vamos a copiar el archivo misDatos.xml y lo pegaremos con


el nombre de datosExternos.xml. Lo editamos con el bloc de
notas y lo dejamos así:

Haremos un ejercicio de imaginación y supondremos que este archivo es el que hemos


obtenido de esa aplicación externa al exportar sus datos a xml.

En nuestro FMenu vamos a crear el botón de comando cmdImportXML y le asignaremos el


siguiente código:


Private Sub cmdImportXML_Click()
'Declaramos la variable
Dim archivoAImportar As String
'Indicamos la ruta del archivo
archivoAImportar = Application.CurrentProject.Path & "\ArchivosXML\datosExternos.xml"
'Importamos el archivo a la tabla TDatosExcel
Application.ImportXML archivoAImportar, acStructureAndData
MsgBox "Importación realizada correctamente", vbInformation, "OK"
End Sub

Al ejecutarlo se nos creará la tabla AppExterna con la información del archivo xml.

¿Fácil, verdad?

Y ya tenemos la segunda parte del proceso, que era poder incluir datos de una aplicación
externa a nuestro Access.

PARA FINALIZAR...
Bueno... creo que eso es todo sobre el tema de XML. Espero que este capítulo sirva para

11
Visítame en http://siliconproject.com.ar/neckkito/
“incrementar los conocimientos de los recursos” de que disponemos para manejar datos entre
aplicaciones.

Un saludo, y...

¡suerte!

12
Visítame en http://siliconproject.com.ar/neckkito/
CURSO DE VB

CAPÍTULO 161

Índice de contenido
MANEJO, A TODAS HORAS, DIRECTORIOS, FICHEROS E IMPRESORAS... .........................2
INTRODUCCIÓN...........................................................................................................................2
PREPARANDO EL TERRENO......................................................................................................2
COMBO CON FILTRO POR NOMBRE DE INFORME..............................................................3
IMPRIMIR UNA PÁGINA EN CONCRETO / RANGO DE PÁGINAS.......................................4
LA COLECCIÓN IMPRESORAS..................................................................................................7
RECORRIENDO LA COLECCIÓN..........................................................................................7
LA IMPRESORA PREDETERMINADA... QUE NOSOTROS QUEREMOS.........................7
IMPRESIÓN A TRAVÉS DEL CUADRO DE DIÁLOGO DE IMPRESORAS.......................8
ALGUNAS IDEAS DE APLICACIÓN..........................................................................................9
SELECCIÓN MEDIANTE CUADRO DE LISTA................................................................9
SELECCIÓN MEDIANTE UN MARCO DE OPCIONES.................................................10
UTILIZANDO LA IMPRESORA VIRTUAL “PDFCREATOR”.................................................11
MANIPULACIÓN DE DIRECTORIOS Y FICHEROS....................................................................12
SACANDO INFORMACIÓN DE NUESTRA APLICACIÓN................................................12
TRABAJANDO CON DIRECTORIOS........................................................................................14
CREANDO DIRECTORIOS: MkDir.......................................................................................14
BORRANDO DIRECTORIOS: RmDir....................................................................................16
EXAMINANDO CARPETAS Y FICHEROS...............................................................................18
LA FUNCIÓN Dir()..................................................................................................................18
“MATANDO” ARCHIVOS: Kill...................................................................................................20
OTRAS “CURIOSIDADES” SOBRE GESTIÓN DE CARPETAS.............................................23
CURDIR().................................................................................................................................23
CHDIR()....................................................................................................................................24
CHDRIVE()..............................................................................................................................25
PARA FINALIZAR EL CAPÍTULO.................................................................................................26

1 La BD donde están los ejemplos de este capítulo os la podéis bajar aquí.

1
Visítame en http://siliconproject.com.ar/neckkito/
MANEJO, A TODAS HORAS,
DIRECTORIOS, FICHEROS E
IMPRESORAS...

INTRODUCCIÓN
Ahora ya hemos llegado a un punto donde si yo os hablo de
colecciones, al menos en teoría, deberíamos saber a qué
me estoy refiriendo, ¿verdad? (contestad que sí, please...).

Vamos a ver en este capítulo cómo podemos manipular la colección “impresoras”, es decir, las
impresoras que tenemos instaladas en nuestro ordenador. Aprovecharemos para explicar un
pequeño truco para crearnos una “colección” (esta vez la palabra “colección” sólo tiene el
sentido de “conjunto de” sin implicación a lo que VB considera colecciones) de informes, para
hacer más fácil la vida al usuario de nuestra aplicación.

Os proporcionaré también una reseña a un código que nos permitirá manejar la impresora
PDFCreator directamente desde VBA, sin utilizar el panel de control de la impresora.

Finalmente, empezaremos a ver cómo podemos operar con los directorios y ficheros de
nuestro sistema operativo, para poder realizar “cuatro cositas” con ellos.

Dicho lo anterior, procedamos.

PREPARANDO EL TERRENO
Vamos a crearnos una BD de ejemplo muy sencilla. Crearemos una tabla, que llamaremos
TDatos, con la siguiente estructura:

Introducimos algunos datos simplemente para tener “material” para que nuestro futuro
informe tenga algo que mostrar.

Para poder ver el efecto del “truco” que os comentaba en la introducción vamos a realizar un
informe sobre TDatos. Una vez hecho lo vamos a copiar-pegar para tener cuatro informes, de
tal manera que:

– Al informe “original” lo llamaremos RDatos (para no perder las costumbres, claro).

2
Visítame en http://siliconproject.com.ar/neckkito/
– A la primera copia la llamaremos, por ejemplo, Nck-R1
– A la segunda copia la llamaremos, por ejemplo, Nck-R2
– A la tercera copia la llamaremos, por ejemplo, Nck-R3

Para distinguirlos, a la hora de sacarlos en vista previa o


imprimirlos, podemos cambiar la etiqueta del encabezado
de informe.

¿Por qué hacemos esto? Imaginemos que en nuestra BD


tenemos unos informes que pueden seleccionar e imprimir
unos usuarios, y otros digamos que sólo pueden ser vistos
o impresos por usuarios con más nivel.

Podríamos gestionar lo anterior con temas de permisos de usuarios (v.gr., podéis analizar el
fabuloso ejemplo de la web... je, je...), pero también podríamos permitir que los usuarios
eligieran los informes que sí pueden imprimir de una lista. Y, para distinguir dichos informes,
les ponemos un prefijo a todos ellos (por ejemplo, Nck, que nosotros hemos utilizado en este
ejemplo, podría ser, perfectamente, una palabra como “Nivel1”, “Nivel2”), de manera que nos
permita hacer un filtro a través del nombre que le hayamos puesto al informe.

Como esto puede sonar un poco complicado, dado que os lo estaba explicando a nivel teórico,
vamos a verlo con un ejemplo.

COMBO CON FILTRO POR NOMBRE DE INFORME


Vamos a crearnos un formulario en blanco, al que pondremos de nombre FSelReport.
Añadimos un cuadro combinado, al que pondremos de nombre cboSelReport.

Insertamos también un botón de comando, que llamaremos cmdImprimeReport.

Más o menos una cosa así:

Vamos a crear el origen de la fila del combo. Para ello, vamos a generar el siguiente código en
el evento “Al recibir el enfoque del combo”:


Private Sub cboSelReport_GotFocus()
'Declaramos las variables
Dim miOrigen As String
Dim miReport As Object
'Inicializamos la variable miOrigen como una cadena vacía
miOrigen = ""
'1.- Recorremos la colección "Informes"
'2.- Si encontramos el prefijo "Nck" lo añadimos a la lista
For Each miReport In CurrentProject.AllReports

3
Visítame en http://siliconproject.com.ar/neckkito/
If miReport.Name Like "Nck*" Then
miOrigen = miOrigen & miReport.Name & ";"
End If
Next miReport
'Quitamos el último punto y coma final de la lista que nos
habrá creado
miOrigen = Left(miOrigen, Len(miOrigen) - 1)
'Vamos a manipular algunas propiedades del combo
With Me.cboSelReport
'Manipulamos la propiedad "Limitar a la lista"
.LimitToList = True
'Manipularmos la propiedad "Permitir ediciones de lista"
.AllowValueListEdits = False
'Manipulamos el tipo de origen de la fila del combo
.RowSourceType = "Lista de valores"
'Manipulamos el origen de la fila
.RowSource = miOrigen
End With
End Sub

Las partes más interesante del código, si os fijáis, son las siguientes:

– Recorremos la colección Reports a través de un FOR EACH... NEXT


– Sacamos el nombre a través del comparador LIKE, utilizando el carácter comodín “*”
– Manipulamos varias propiedades del combo a través del bloque WITH... END WITH,
realizando llamadas a las propiedades y estableciendo sus valores
(.nomPropiedad=valorPropiedad).

Si ahora situamos nuestro formulario en vista formulario y desplegamos el combo veremos que
nos muestra sólo aquellos informes que comienzan por “Nck”, que son en definitiva aquellos a
los que nos interesaba que el usuario pudiera tener acceso.

Finalmente nos queda programar el botón de comando para que imprima el informe
seleccionado. Esto ya deberíamos saber realizarlo nosotros, pero para “esas dudas
existenciales que nos pueden asaltar” os indico a continuación cómo debería ser dicho código
del evento “Al hacer click” del botón (os pongo ya el código sin comentarios):


Private Sub cmdImprimeReport_Click()
Dim vSel As String
vSel = Nz(Me.cboSelReport.Value, "")
If vSel = "" Then Exit Sub
DoCmd.OpenReport vSel, acViewPreview
End Sub

Y listo 

IMPRIMIR UNA PÁGINA EN CONCRETO / RANGO DE PÁGINAS


Vamos a añadir bastantes registros a nuestra tabla TDatos. Yo he añadido datos hasta que me
salen 3 hojas de informe en vista previa.

En un formulario añadiremos un botón que nos permitirá imprimir o bien un rango concreto de
páginas o bien la totalidad del informe. A ese botón lo llamaremos cmdImprimeRango.

4
Visítame en http://siliconproject.com.ar/neckkito/
¿Cuál va a ser la operativa (es decir, qué vamos a aprender)?

•“Sin que nadie nos vea” vamos a abrir el informe para saber
cuántas páginas devuelve ese informe.
• Un InputBox nos va a pedir la página inferior, o introducir
cero en números si queremos todo el informe.
◦ Introduciremos un control para detectar la pulsación
de cancelar y para detectar si se ha introducido un
número. Controlaremos también que el valor
introducido no supere el número de páginas del
informe.
• Si no queremos todo el informe nos saldrá otro InputBox,
que nos pedirá el rango superior.
◦ Introduciremos un control para detectar la pulsación
de cancelar, para detectar si se ha introducido un
número, para detectar si ese número no supera el
número máximo de páginas del informe y para
detectar si el valor superior es efectivamente mayor
que el inferior (bueno... creo que por controles no
estaremos, ¿verdad?)
• Imprimiremos el informe con las condiciones pedidas.

 ¡Ojo! Tened en cuenta que el informe se imprime a la impresora (no hay vista previa).
Personalmente os diré que yo tengo la impresora PDFCreator como impresora predeterminada,
por lo que el informe se me imprime a PDF. Cuidado si hacéis pruebas y no tenéis una
impresora virtual instalada, porque os saldrá por la impresora que tengáis configurada por
defecto.

Tened en cuenta que los InputBox se podrían cambiar, por ejemplo, por TextBox. La mecánica
sería la misma, sólo que cambiando el método de entrada del rango.

El código que nos haría lo anterior sería:


Private Sub cmdImprimeRango_Click()
'Declaramos las variables
Dim vInf As Variant, vSup As Variant
Dim nPag As Integer
'Abrimos el informe en vista previa, pero oculto, para
'poder coger el número de páginas, y lo volvemos a cerrar
DoCmd.OpenReport "RDatos", acViewPreview, , , acHidden
nPag = Reports!RDatos.Pages
DoCmd.Close acReport, "RDatos"
Rango_inferior:
'Solicitamos el rango inferior, o cero para el informe completo
vInf = InputBox("¿Página inferior? -0 para todo el informe-", "RANGO INFERIOR", 0)
'Detectamos la pulsación del botón cancelar
If StrPtr(vInf) = 0 Then Exit Sub
'Detectamos si el valor introducido es un valor numérico
If Not IsNumeric(vInf) Then
MsgBox "El valor introducido no es válido", vbExclamation, "ERROR"
GoTo Rango_inferior
End If
'Detectamos qué valor numérico se ha introducido
If vInf = 0 Then

5
Visítame en http://siliconproject.com.ar/neckkito/
'Imprimimos la totalidad del informe
DoCmd.OpenReport "RDatos"
Exit Sub
Else
'Comprobamos que el valor introducido no supera
el máximo de páginas
If vInf > nPag Then
MsgBox "El valor introducido no es válido. El
informe sólo tiene " & nPag _
& " páginas", vbInformation, "VALOR
INCORRECTO"
'Volvemos a la introducción del rango inferior
GoTo Rango_inferior
End If
End If
Rango_superior:
'Solicitamos el rango superior
vSup = InputBox("¿Página superior?", "RANGO SUPERIOR", 0)
'Detectamos la pulsación del botón cancelar
If StrPtr(vSup) = 0 Then Exit Sub
'Detectamos si el valor introducido es un valor numérico
If Not IsNumeric(vInf) Then
MsgBox "El valor introducido no es válido", vbExclamation, "ERROR"
GoTo Rango_superior
End If
'Comprobamos que el valor no supera el número de páginas
If vSup > nPag Then
MsgBox "El valor introducido no es válido. El informe sólo tiene " & nPag _
& " páginas", vbInformation, "VALOR INCORRECTO"
'Volvemos a la introducción del rango superior
GoTo Rango_inferior
End If
'Comprobamos que el valor no es inferior al rango inferior
If vSup < vInf Then
MsgBox "La página inferior no puede ser mayor que la página superior", _
vbExclamation, "ERROR"
GoTo Rango_superior
End If
'Abrimos el informe en vista diseño,sacamos a la impresora las páginas acotadas.
'Tras eso cerramos el informe
DoCmd.OpenReport "RDatos", acViewPreview
DoCmd.PrintOut acPages, vInf, vSup
DoCmd.Close acReport, "RDatos"
End Sub

El código está ampliamente comentado y, si “pilláis” la mecánica, no tiene mayor dificultad.


Sólo dos pequeños comentarios:

- Es necesario abrir el informe para poder imprimir las páginas seleccionadas, de ahí que esté
escrito este bloque:

DoCmd.OpenReport "RDatos", acViewPreview


DoCmd.PrintOut acPages, vInf, vSup
DoCmd.Close acReport, "RDatos"

6
Visítame en http://siliconproject.com.ar/neckkito/
- Ved que hemos utilizado las etiquetas (que vimos en el capítulo dedicado al control de
errores) para, si nuestro usuario no “supera” nuestros controles, volver a la ejecución del
código en el “momento preciso”.

- El método DoCmd.PrintOut tiene una serie de


argumentos. Uno de ellos es el número de copias. Nosotros
no lo hemos utilizado en el código, pero lógicamente
podríais añadir una nueva variable para solicitar el número
de copias. Para más información, en el VBE, podemos
buscar en la ayuda escribiendo “DoCmd.PrintOut”, y
seleccionamos la opción “DoCmd.PrintOut (método)”. Ahí
encontraréis todos los detalles para utilizar este método.

LA COLECCIÓN IMPRESORAS
Cuando abrimos nuestra carpeta de impresoras, ¿qué nos encontramos? Pues nada más y
nada menos que una colección... de impresoras. Pues, utilicemos esa colección para nuestros
“malévolos” fines... je, je...

RECORRIENDO LA COLECCIÓN
Vamos a crearnos un botón que nos dará la información sobre nuestra colección de
impresoras. Debemos recordar que, como toda colección, cada una tendrá un índice, y que,
también como toda colección, la numeración de los índices empieza por cero.

El código que nos dará esta información es el siguiente:


Private Sub cmdColImpr_Click()
'Declaramos las variables
Dim miImpr As Printer
Dim i As Byte
'Recorremos la colección de impresoras, mostrando
'un MsgBox con la información sobre cada una de ellas
For Each miImpr In Application.Printers
With miImpr
MsgBox "Nombre de la impresora: " & .DeviceName & vbCrLf _
& "Índice: " & i & vbCrLf _
& "Controlador: " & .DriverName & vbCrLf _
& "Puerto: " & .Port
i=i+1
End With
Next miImpr
End Sub

LA IMPRESORA PREDETERMINADA... QUE NOSOTROS QUEREMOS


Con la anterior información ya sabemos qué índice tiene la impresora que nos interesa.
Podemos aprovechar esa información para determinar por qué impresora puede salir nuestro
informe.

Para ello basta indicar en el código que queremos establecer una impresora predeterminada a

7
Visítame en http://siliconproject.com.ar/neckkito/
través de su índice.

Es decir, que para imprimir RDatos con la impresora que queramos (por ejemplo, la impresora
de índice 1) nuestro código debería quedar así, :


Private Sub cmdImpr1_Click()
On Error GoTo sol_err
'Establecemos la impresora predeterminada a la que
'tiene el índice 1
Set Application.Printer = Application.Printers(1)
'Imprimimos el informe
DoCmd.OpenReport "RDatos", acViewPreview
DoCmd.PrintOut acSelection
Salida:
DoCmd.Close acReport, "RDatos"
Exit Sub
sol_err:
If Err.Number = 2501 Then
MsgBox "Canceló la impresión", vbExclamation, "CANCELADO"
Else
MsgBox "Se ha producido el error: " & Err.Number & " - " & Err.Description
End If
Resume Salida
End Sub

Cuando cancelamos la impresión se produce el error 2501. Es por ello por lo que hemos
añadido un control de errores para poder manejar este error en el caso de que el usuario
cancele la impresión.

Ved que establecemos la impresora predeterminada de la aplicación a través de la línea

Set Application.Printer = Application.Printers(1)

Eso significa que para Access, a partir de ahora, la impresora predeterminada será la que
tenga el índice 1. Debemos tener esto en cuenta para no llevarnos una sorpresa. Si por
casualidad la impresora “de siempre” es la que tiene el índice cero podríamos devolver el
control de impresión a dicha impresora redefiniendo la impresora de la aplicación al final del
código. Es decir, que tendríamos que modificar nuestro código así (os pongo sólo el fragmento
afectado):


Salida:
DoCmd.Close acReport, "Rdatos"
Set Application.Printer = Application.Printers(0)
Exit Sub

IMPRESIÓN A TRAVÉS DEL CUADRO DE DIÁLOGO DE IMPRESORAS


Podría suceder también que queremos elegir, en ese momento, a través de qué impresora
queremos imprimir nuestro informe. Necesitamos pues que nos aparezca una ventana que nos
deje elegir la impresora, además de otras opciones relacionadas con la misma.

Para conseguir lo anterior debemos recurrir a un DoCmd.RunCommand

8
Visítame en http://siliconproject.com.ar/neckkito/
Nuestro código nos podría quedar así:

Private Sub cmdCuadroDialogoImpresoras_Click()
On Error GoTo sol_err
'Abrimos el informe que queremos imprimir
DoCmd.OpenReport "RDatos", acViewPreview
'Abrimos el cuadro de diálogo de las impresoras
DoCmd.RunCommand acCmdPrint
Salida:
'Cerramos el informe
DoCmd.Close acReport, "RDatos"
Exit Sub
sol_err:
If Err.Number = 2501 Then
MsgBox "Canceló la impresión", vbExclamation, "CANCELADO"
Else
MsgBox "Se ha producido el error: " & Err.Number & " - " & Err.Description
End If
Resume Salida
End Sub

Como podéis ver, aquí también hemos utilizado el mismo control de errores para detectar si se
cancela la impresión, y dar una “salida elegante” a nuestro código.

ALGUNAS IDEAS DE APLICACIÓN


Con todo lo que hemos visto podemos hacer algunas cosas “chulas” en nuestra aplicación. Os
voy a presentar dos ideas, pero el límite a lo que se explica aquí lo debería poner nuestra
imaginación.

SELECCIÓN MEDIANTE CUADRO DE LISTA


¿Nos acordamos de lo que hemos visto en el apartado “Combo por filtro nombre de informe”?
Pues podemos realizar un proceso semejante, pero utilizando las impresoras. Evidentemente
podemos aplicarlo a un combo, pero para ver un poco más de variedad esta vez lo haremos
con un cuadro de lista.

En un formulario insertamos un cuadro de lista. Lo llamaremos lstSelImpr.

En el evento “Al recibir el enfoque de lstSelImpr generamos el siguiente código:


Private Sub lstSelImpr_GotFocus()
'Declaramos las variables
Dim miOrigen As String
Dim miImpr As Printer
'Inicializamos miOrigen con una cadena vacía
miOrigen = ""
'Recorremos la lista del impresoras del sistema
For Each miImpr In Application.Printers
'Vamos creando la lista de valores
miOrigen = miOrigen & miImpr.DeviceName & ";"

9
Visítame en http://siliconproject.com.ar/neckkito/
Next miImpr
'Eliminamos el último punto y coma de la lista de valores
miOrigen = Left(miOrigen, Len(miOrigen) - 1)
'Asignamos el origen al cuadro de lista
Me.lstSelImpr.RowSourceType = "Lista de valores"
Me.lstSelImpr.RowSource = miOrigen
End Sub

Con esto ya tenemos la lista para que el usuario pueda


seleccionar una impresora. Ahora nos hace falta un botón
que nos imprima. Así pues, en el botón cmdImprConSel
asignamos este código al evento “Al hacer click”:


Private Sub cmdImprConSel_Click()
On Error GoTo sol_err
'Declaramos las variables
Dim indiceImpr As Long
'Obtenemos el índice del elemento seleccionado, que nos
'coincidirá con el índice de impresora
indiceImpr = Me.lstSelImpr.ListIndex
'Si el valor devuelto es -1 es que no se ha seleccionado impresora
If indiceImpr = -1 Then
MsgBox "Debe seleccionar una impresora", vbCritical, "SIN SELECCIÓN"
Exit Sub
End If
'Establecemos la impresora seleccionada como la impresora
'por la que se debe imprimir
Set Application.Printer = Application.Printers(indiceImpr)
'Imprimimos el informe RDatos
DoCmd.OpenReport "RDatos"
Salida:
Exit Sub
sol_err:
If Err.Number = 2501 Then
MsgBox "Canceló la impresión", vbExclamation, "CANCELADO"
Else
MsgBox "Se ha producido el error: " & Err.Number & " - " & Err.Description
End If
Resume Salida
End Sub

Creo que con los comentarios del código la cosa queda clara (espero).

Tened en cuenta que no siempre nos actuará el control del error 2501. Según el controlador
de la impresora que tengamos es posible que él ya se encargue de “ver” que se ha cancelado
la impresión y no se produzca ninguna interrupción en el código, lo que implica que no
devuelve error y por tanto no nos sale nuestro mensaje.

SELECCIÓN MEDIANTE UN MARCO DE OPCIONES


Esta opción es mucho más restrictiva, porque quizá, para evitar errores, podría no interesarnos
que el usuario vea todas las impresoras del sistema.

10
Visítame en http://siliconproject.com.ar/neckkito/
Realizaremos el ejemplo con dos opciones, pero, una vez entendida la mecánica, no hay
problema para añadir cuantas impresoras queramos. Supongamos que queremos permitir la
impresión a través de dos impresoras, de las cuales sabemos que sus índices son el cero y el
cuatro. ¿Cómo haríamos eso?

Podemos utilizar pues un marco de opciones. Os explico


cómo podríamos configurarlo.

1.- Insertamos un marco de opciones en un formulario. Nos


saldrá el asistente.
2.- En la primera ventana escribimos el nombre de las
impresoras que queremos que se utilicen → Siguiente
3.- Podemos establecer como opción predeterminada la que
queramos → Siguiente
4.- Para la primera impresora especificamos el valor 0 (cero), puesto que sabemos que ese es
su índice. Para la segunda establecemos el valor en 4, porque, evidentemente, ese es su índice
→ Siguiente
5.- Damos el formato que queramos → Siguiente
6.- Como título podemos escribir algo como “Seleccione impresora” → Finalizar
7.- A ese marco de opciones le ponemos de nombre mrcSelImpr

Ahora sólo nos queda añadir un botón de comando para realizar la impresión. El código
asociado a ese botón sería el siguiente:


Private Sub cmdImprConSel_Click()
On Error GoTo sol_err
'Declaramos las variables
Dim indiceImpr As Long
'Obtenemos el índice del elemento seleccionado (que será el valor
'que hemos asignado en la configuración del marco de opciones)
indiceImpr = Me.mrcSelImpr.Value
'Establecemos la impresora seleccionada como la impresora
'por la que se debe imprimir
Set Application.Printer = Application.Printers(indiceImpr)
'Imprimimos el informe RDatos
DoCmd.OpenReport "RDatos"
Salida:
Exit Sub
sol_err:
If Err.Number = 2501 Then
MsgBox "Canceló la impresión", vbExclamation, "CANCELADO"
Else
MsgBox "Se ha producido el error: " & Err.Number & " - " & Err.Description
End If
Resume Salida
End Sub

Y con esto ya tenemos “limitado” el conjunto de impresoras por las que el usuario puede
imprimir. ¿Fácil, no?

UTILIZANDO LA IMPRESORA VIRTUAL “PDFCREATOR”


No vamos a desarrollar un ejemplo de cómo utilizar esta impresora virtual. Sólo deciros que lo
que hace el código es enviar el archivo a la impresora e imprimir directamente sin tener que

11
Visítame en http://siliconproject.com.ar/neckkito/
manipular el panel de control de la impresora, dado que las opciones las manipulamos a través
de VBA.

En definitiva, que os dejo indicados aquí el enlace donde podéis encontrar el código
que hace este “trabajo”.

Imprimir en PDFCreator:
http://www.excelguru.ca/content.php?192

Creo que con los conocimientos que ahora ya tenemos no


tendremos ningún problema en entender los códigos.

 Importante: el anterior ejemplo requiere que registremos la referencia llamada


“PDFCreator” para que el código funcione correctamente.

MANIPULACIÓN DE DIRECTORIOS Y FICHEROS


¿No sería interesante poder ordenar nuestros archivos desde Access, adaptándose a la
información que estamos manipulando en el momento?

Con lo anterior quiero decir que si exportamos un informe de unas características, ¿por qué no
crear una carpeta específica para él (o ellos) desde Access? Que tenemos un informe con otras
características... pues nada, creamos otra carpeta y listo.

En definitiva, lo anterior será la idea que va a “guiar” la explicación de cómo podemos


manipular directorios desde Access. Lógicamente no es la única utilidad que se puede obtener,
pero si entendéis la mecánica no tendréis problemas para acoplarlo a vuestras necesidades.

Vamos pues, en nuestra BD de ejemplo, a crearnos una consulta parametrizada sobre la tabla
TDatos. A esa consulta la llamaremos CDatos. La construimos con la siguiente estructura:

Ahora creamos un informe rápido sobre dicha consulta, al que llamaremos REdad.

Vamos a adentrarnos en el “fascinante mundo” de los directorios.

SACANDO INFORMACIÓN DE NUESTRA APLICACIÓN


A lo largo de todos estos capítulos habréis visto, en los códigos, que para sacar la ruta del
directorio donde está nuestra BD hemos utilizado:

12
Visítame en http://siliconproject.com.ar/neckkito/
Application.CurrentProject.Path

Tenemos algunas opciones más para extraer algo más de información sobre nuestra BD
(hablando de directorios y ficheros, claro).

Si en un código escribimos lo siguiente:


Private Sub cmdInfoBD_Click()
'Declaramos las variables
Dim ruta As String
Dim rutaFull As String
Dim nombreBD As String
Dim formatoBD As String
'Asignamos valores a las variables
ruta = Application.CurrentProject.Path
rutaFull = Application.CurrentProject.FullName
nombreBD = Application.CurrentProject.Name
formatoBD = Application.CurrentProject.FileFormat
'Determinamos qué formato es
Select Case formatoBD
Case 2
formatoBD = "Microsoft Access 2"
Case 7
formatoBD = "Microsoft Access 95"
Case 8
formatoBD = "Microsoft Access 97"
Case 9
formatoBD = "Microsoft Access 2000"
Case 10
formatoBD = "Access 2002 - 2003"
Case 12
formatoBD = "Microsoft Access 2007"
Case 14
formatoBD = "Microsoft Access 2010"
End Select
'Mostramos la información al usuario
MsgBox "La ruta de la BD es " & ruta, vbInformation, "RUTA"
MsgBox "La ruta completa de la BD es " & rutaFull, vbInformation, "RUTA COMPLETA"
MsgBox "El nombre de la BD es " & nombreBD, vbInformation, "NOMBRE"
MsgBox "El formato de la BD es " & formatoBD, vbInformation, "FORMATO"
End Sub

Fijaos que os he marcado en negrita los códigos para obtener la información de nuestra BD, de
manera que:

– La primera (Path) nos indica la ruta de directorios donde está la BD


– La segunda (FullName) nos indica la ruta, nombre de archivo y extensión
– La tercera (Name) nos indica el nombre de archivo y extensión
– La cuarta (Format) nos indica el tipo de versión de Access que estamos utilizando.

Y supongo que alguien ahora me dirá: “¿Y si sólo quiero el nombre de la BD, pero sin la
extensión?” Vaya, vaya... Yo diría: “Uhmm... Me gusta que me haga esa pregunta...”.

Podríamos sacar “manualmente” el nombre sin extensión utilizando el punto que separa el

13
Visítame en http://siliconproject.com.ar/neckkito/
nombre de la extensión como delimitador (que de hecho, lo es) a través del siguiente código,
que añado al código anterior para tener el “panorama completo” (los nuevos elementos están
en negrita):


Private Sub cmdInfoBD2_Click()
'Declaramos las variables
Dim ruta As String
Dim rutaFull As String
Dim nombreBD As String
Dim formatoBD As String
Dim nombreSinExtension As String
Dim numCaracteres As Long
'Asignamos valores a las variables
ruta = Application.CurrentProject.Path
rutaFull = Application.CurrentProject.FullName
nombreBD = Application.CurrentProject.Name
formatoBD = Application.CurrentProject.FileFormat
'Sacamos el nombre de la BD sin la extensión
'Devuelve el número de caracteres desde el principio hasta el punto inclusive
numCaracteres = InStr(nombreBD, ".")
'Cogemos la izquierda de la cadena de texto, quitándole el punto (-1)
nombreSinExtension = Left(nombreBD, numCaracteres - 1)
'Determinamos qué formato es
Select Case formatoBD
Case 2
formatoBD = "Microsoft Access 2"
Case 7
formatoBD = "Microsoft Access 95"
Case 8
formatoBD = "Microsoft Access 97"
Case 9
formatoBD = "Microsoft Access 2000"
Case 10
formatoBD = "Access 2002 - 2003"
Case 12
formatoBD = "Microsoft Access 2007"
Case 14
formatoBD = "Microsoft Access 2010"
End Select
'Mostramos la información al usuario
MsgBox "La ruta de la BD es " & ruta, vbInformation, "RUTA"
MsgBox "La ruta completa de la BD es " & rutaFull, vbInformation, "RUTA COMPLETA"
MsgBox "El nombre de la BD es " & nombreBD, vbInformation, "NOMBRE"
MsgBox "El nombre sin extensión de la BD es " & nombreSinExtension,
vbInformation, "NOMBRE"
MsgBox "El formato de la BD es " & formatoBD, vbInformation, "FORMATO"
End Sub

Y con esto... Pues ya tenemos toda la información que queríamos.

14
Visítame en http://siliconproject.com.ar/neckkito/
TRABAJANDO CON DIRECTORIOS

CREANDO DIRECTORIOS: MkDir


Para crear directorios podemos utilizar la instrucción MkDir,
que tiene la siguiente estructura:

MkDir ruta

Imaginemos que queremos sacar un archivo snapshot sobre


el informe REdad, pero además queremos guardarlo en un
directorio específico de nuestro disco duro. En este caso
será en una subcarpeta de la carpeta donde tenemos
nuestra BD.

 Nota: lo sacaremos en snapshot porque los usuarios de Access 2003 no tienen la opción de
sacarlo a pdf. En el ejemplo podré comentado también cómo sacarlo a pdf para usuarios de
Access 2007 y posterior, teniendo en cuenta que, en este caso, deberemos tener instalado el
plugin para exportar a PDF y a XPS.

El código que nos podría hacer lo anterior sería:


Private Sub REdadSnp_Click()
On Error GoTo sol_err
'Declaramos las variables
Dim miRuta As String
Dim miCarpeta As Variant
Dim miNewRuta As String
'Cogemos la ruta de la BD y añadimos la contrabarra
miRuta = Application.CurrentProject.Path & "\"
'Solicitamos el nombre de la nueva carpeta
miCarpeta = InputBox("¿Nombre de la carpeta donde se guardará el archivo?", "CARPETA")
'Detectamos la pulsación del botón cancelar
If StrPtr(miCarpeta) = 0 Then Exit Sub
'Detectamos que se haya escrito algún valor
If miCarpeta = Null Or miCarpeta = "" Then
MsgBox "No ha especificado un nombre de carpeta", vbExclamation, "SIN DATOS"
Exit Sub
End If
'Asignamos valor a la variable miNewRuta
miNewRuta = miRuta & miCarpeta
'Creamos la carpeta
MkDir miNewRuta
Continua_error_75: 'El error 75 aparece cuando la carpeta ya exite
'Exportamos el informe a snapshot a la carpeta creada
DoCmd.OutputTo acOutputReport, "REdad", "SnapshotFormat(*.snp)", miNewRuta &
"\Edad.snp"
'----------------------------------------------------------------------------------------------
' 'La siguiente línea exporta el informe a pdf
' DoCmd.OutputTo acOutputReport, "REdad", "pdf", miNewRuta & "\Edad.pdf"
'----------------------------------------------------------------------------------------------
Salida:
Exit Sub
sol_err:

15
Visítame en http://siliconproject.com.ar/neckkito/
If Err.Number = 75 Then
GoTo Continua_error_75
Else
MsgBox "Se ha producido el error " & Err.Number & " -
" & Err.Description
End If
Resume Salida
End Sub

Como notas destacables remarcar:

– Hemos construido la ruta de la carpeta a través de variables, en este caso a través de


un InputBox. Si tuviéramos un TextBox, por ejemplo, con pasar su valor a la variable
miCarpeta, conseguiríamos lo mismo. Simplemente remarco este punto para que veáis
que hay muchas más posibilidades.
– Si la carpeta ya existe nos salta el error número 75. Lo que hacemos con este código es
gestionar este error en concreto, “obligando” al código a seguir con todos los procesos
a pesar del error, haciendo que “salte” a la etiqueta <Continua_error-75:>

BORRANDO DIRECTORIOS: RmDir


Así como hemos creado un directorio podemos eliminarlo. Y para ello utilizamos la instrucción
RmDir, cuya estructura es la siguiente:

RmDir ruta

El procedimiento es sencillo, pero, ¡ojo!, el directorio debe estar vacío de archivos. Si


intentamos borrar un directorio con archivos en su interior nos saltará un error (que podemos
gestionar, claro).

Vamos a crear, manualmente, una carpeta en el directorio donde tenemos la BD. Por ejemplo,
creamos la carpeta “Borrar”.

Analizaremos las distintas situaciones que pueden producirse:

1.- La carpeta existe y está vacía.

Nuestro código para borrarla sería:


Private Sub cmdBorraCarpeta_Click()
'Declaramos las variables
Dim miRuta As String, miCarpeta As String
Dim rutaCompleta As String
'Damos valor a las variables
miRuta = Application.CurrentProject.Path
miCarpeta = "\Borrar"
'Creamos la ruta completa
rutaCompleta = miRuta & miCarpeta
'Borramos la carpeta
RmDir rutaCompleta
'Lanzamos un mensaje de confirmación
MsgBox "La carpeta ha sido borrada", vbExclamation, "OK"
End Sub

16
Visítame en http://siliconproject.com.ar/neckkito/
2.- La carpeta no existe

En este caso, si la carpeta no se encuentra, se produce el error 76. Nuestro código


simplemente debería gestionar dicho error:


Private Sub cmdBorraCarpetaInexistente_Click()
On Error GoTo sol_err
'Declaramos las variables
Dim miRuta As String, miCarpeta As String
Dim rutaCompleta As String
'Damos valor a las variables
miRuta = Application.CurrentProject.Path
miCarpeta = "\NoExiste"
'Creamos la ruta completa
rutaCompleta = miRuta & miCarpeta
'Borramos la carpeta
RmDir rutaCompleta
'Lanzamos un mensaje de confirmación
MsgBox "La carpeta ha sido borrada", vbExclamation, "OK"
Salida:
Exit Sub
sol_err:
If Err.Number = 76 Then
MsgBox "La carpeta no se puede borrar porque no existe", vbCritical, "NO EXISTE"
Else
MsgBox "Se ha producido el error " & Err.Number & " - " & Err.Description
End If
Resume Salida
End Sub

3.- La carpeta no está vacía

En este caso el error que se devuelve vuelve a ser el número 75. Como ya hemos aprendido,
gestionamos el error:


Private Sub cmdBorraCarpetaConArchivos_Click()
On Error GoTo sol_err
'Declaramos las variables
Dim miRuta As String, miCarpeta As String
Dim rutaCompleta As String
'Damos valor a las variables
miRuta = Application.CurrentProject.Path
miCarpeta = "\Edad"
'Creamos la ruta completa
rutaCompleta = miRuta & miCarpeta
'Borramos la carpeta
RmDir rutaCompleta
'Lanzamos un mensaje de confirmación
MsgBox "La carpeta ha sido borrada", vbExclamation, "OK"
Salida:
Exit Sub
sol_err:

17
Visítame en http://siliconproject.com.ar/neckkito/
If Err.Number = 75 Then
MsgBox "La carpeta no se puede borrar porque contiene archivos", vbCritical, "NO VACÍA"
Else
MsgBox "Se ha producido el error " & Err.Number & " -
" & Err.Description
End If
Resume Salida
End Sub

EXAMINANDO CARPETAS Y FICHEROS


Hemos visto que al intentar eliminar una carpeta que no existía nos salía el error 76, y lo
hemos gestionado para nuestros propósitos. Pero la pregunta que quizá alguien pueda
plantearse es: “¿No hay manera de examinar si la carpeta existe, y no tener que recurrir a la
gestión del error 76?”

Por otra parte, con este último ejemplo hemos visto que se nos avisaba de que no se podía
borrar la carpeta porque no estaba vacía. La pregunta evidente que nos hacemos a
continuación es: “¿Qué diantres guardé yo en esa carpeta?”

Pues para responder a esas preguntaa tenemos...

LA FUNCIÓN Dir()
La estructura de la función Dir() es la siguiente:

Dir(nombreRuta, atributos)

donde <atributos> es un argumento opcional.

¿Y para qué nos sirve Dir()? Pues según Access << Devuelve un tipo String que representa el
nombre de un archivo, directorio o carpeta que coincide con el patrón o atributo de archivo
especificado, o la etiqueta de volumen de una unidad de disco>>

¿Y cómo la podemos utilizar para saber si no existe la carpeta (o el archivo)? Porque Dir()
devuelve una cadena vacía si no encuentra dicha carpeta o archivo.

En definitiva, que nuestro código podría haber quedado así:


Private Sub cmdBorrarCarpetaNoExisteDir_Click()
'Declaramos las variables
Dim miRuta As String, miCarpeta As String
Dim rutaCompleta As String
'Damos valor a las variables
miRuta = Application.CurrentProject.Path
miCarpeta = "\NoExiste"
'Creamos la ruta completa
rutaCompleta = miRuta & miCarpeta
'Comprobamos si existe antes de borrarla.
'La carpeta no existe
If Dir(rutaCompleta, vbDirectory) = "" Then
MsgBox "La carpeta especificada no existe", vbInformation, "NO EXISTE"
Exit Sub

18
Visítame en http://siliconproject.com.ar/neckkito/
Else
'La carpeta existe: la borramos
RmDir rutaCompleta
End If
'Lanzamos un mensaje de confirmación
MsgBox "La carpeta ha sido borrada", vbExclamation,
"OK"
End Sub

Fijaos que, como estamos hablando de una carpeta, hemos


tenido que decirle al código que, precisamente, buscábamos
una carpeta, y eso lo hemos hecho a través del argumento
vbDirectory (If Dir(rutaCompleta, vbDirectory) = "" Then)

Volviendo a la segunda de nuestras “preguntas existenciales”, ¿qué archivos hay dentro de


nuestra carpeta que no podemos borrar por no estar vacía?

Para que nos funcione el siguiente código debemos comprobar que en la carpeta “Borrar” hay
al menos un archivo.

Para resolver nuestro dilema debemos crearnos una colección, que recogerá todos los ficheros.
Debemos recorrer esa colección hasta que Dir() nos devuelva una cadena vacía, lo que nos
indicará que ha acabado de recorrer todos los archivos de la carpeta.

Una vez creada nuestra colección mostramos los elementos a través de un MsgBox, por
ejemplo, recorriendo de nuevo la colección (siguiendo el mismo sistema con el que recorríamos
la lista de impresoras de nuestro PC).

En definitiva, que nuestro código podría ser el siguiente:


Private Sub cmdArchivosEnCarpeta_Click()
'Declaramos las variables
Dim miRuta As String, miCarpeta As String
Dim rutaCompleta As String, misArchivos As String
Dim colArchivos As New Collection
Dim i As Long
'Damos valor a las variables
miRuta = Application.CurrentProject.Path
miCarpeta = "\Borrar"
rutaCompleta = miRuta & miCarpeta
misArchivos = Dir(rutaCompleta & "\*.*")
'Si no hay archivos salimos del proceso
If misArchivos = "" Then
Exit Sub
Else
'Si hay archivos recorremos los archivos uno a uno y los vamos
'añadiendo a la colección
Do Until misArchivos = ""
colArchivos.Add misArchivos
misArchivos = Dir()
Loop
End If
'Ahora recorremos la colección, mostrando los archivos encontrados
For i = colArchivos.Count To 1 Step -1

19
Visítame en http://siliconproject.com.ar/neckkito/
MsgBox colArchivos(i), vbInformation, "LISTA ARCHIVOS"
Next i
'Eliminamos la colección
Set colArchivos = Nothing
End Sub

Ni que decir tiene que hemos utilizado el comodín asterisco


para devolver todos los archivos. Si, por ejemplo, sólo
quisiéramos ver los pdf's, tendríamos que haber escrito

misArchivos = Dir(rutaCompleta & "\*.pdf")

y lo mismo para archivos gráficos (*.jpg, *.gif, *.tif, etc.).

También podemos utilizar todos los comodines que ya conocemos y que hemos visto en
capítulos anteriores. Por poner un ejemplo para “refrescar memorias”, si quisiéramos sacar
todos los archivos que empiezan por “p”, de cuatro letras y sea cual sea su extensión,
escribiríamos

misArchivos = Dir(rutaCompleta & "\p???.*")

Finalmente, podemos comentar que en el argumento <atributos> de Dir() se pueden utilizar


las siguientes constantes de vb:

Constante VB Valor devuelto Sirve para especificar:


vbNormal 0 Archivos sin atributos (es la opción predeterminada)
vbReadOnly 1 Archivos de sólo lectura
vbHidden 2 Archivos ocultos
vbSystem 4 Archivos de sistema
vbVolume 8 La etiqueta del volumen
vbDirectory 16 Directorios y carpetas

“MATANDO” ARCHIVOS: Kill


Sigamos con nuestros “problemas filosóficos”: no podemos borrar la carpeta que hemos
seleccionado porque no está vacía. ¿Qué hacemos?

Una solución sería abrir, manualmente, la carpeta a través del explorador de Windows,
seleccionar todos los archivos de la carpeta y borrarlos. ¿Fácil, no?

Sin embargo alguien podría objetar que eso no es muy “elegante”, cosa con la que yo estaría
totalmente de acuerdo.

Solución: enviar a un sicario para que haga “el trabajo sucio” por nosotros.

Y ese sicario se llama... KILL

La instrucción KILL tiene la siguiente estructura:

Kill ruta

20
Visítame en http://siliconproject.com.ar/neckkito/
Por si hemos perdido un poco “el hilo”, vamos a asegurarnos que tenemos una carpeta, en el
mismo directorio donde tenemos la BD, llamada “Borrar”, y que dentro de ella hay al menos un
archivo.

Nuestro código para eliminar los archivos podría quedar así:


Private Sub cmdEliminaArchivosEnCarpeta_Click()
'Declaramos las variables
Dim miRuta As String, miCarpeta As String
Dim rutaCompleta As String
Dim resp As Integer
'Damos valor a las variables
miRuta = Application.CurrentProject.Path
miCarpeta = "\Borrar"
'Creamos la ruta completa
rutaCompleta = miRuta & miCarpeta
'Comprobamos si existe antes de borrarla.
'La carpeta no existe
If Dir(rutaCompleta, vbDirectory) = "" Then
MsgBox "La carpeta especificada no existe", vbInformation, "NO EXISTE"
Exit Sub
End If
'Comprobamos si la carpeta contiene archivos. Debemos añadir la contrabarra
'para "entrar" dentro de la carpeta
If Dir(rutaCompleta & "\") <> "" Then
resp = MsgBox("La carpeta especificada contiene archivos. ¿Desea continuar con" _
& " el borrado de archivos?", vbQuestion + vbYesNo, "COFIRMACIÓN")
'Si se responde que no salimos del procedimiento
If resp = vbNo Then
Exit Sub
Else
'En caso contrario borramos los archivos...
Kill rutaCompleta & "\*.*"
End If
Else
MsgBox "La carpeta no contiene archivos que borrar", vbInformation, "SIN ARCHIVOS"
Exit Sub
End If
'Lanzamos un mensaje de aviso
MsgBox "Archivos eliminados correctamente", vbInformation, "CORRECTO"
End Sub

Ni que decir tiene que podemos realizar un “borrado selectivo” a través de la utilización de
comodines (tal y como veíamos en el apartado anterior). Si queremos borrar sólo los archivos
de Word, por ejemplo, escribiríamos:

Kill rutaCompleta & "\*.doc*"

Añado el asterisco después de la extensión para borrar archivos tanto de la versión 2003 como
de versiones posteriores 

Siguiendo con el ejemplo, vamos a borrar archivos y carpeta a la vez. Sin embargo, nos
encontramos con un problema: al borrar los archivos la carpeta queda temporalmente
bloqueada, lo que hace que el código nos devuelva el error 75.

21
Visítame en http://siliconproject.com.ar/neckkito/
Con franqueza os diré que no he conseguido encontrar un
sistema para saltarme este bloqueo. Ello implica que el
sistema que os indicaré a continuación implica tener que
volver a pulsar el botón de borrado. Evidentemente si
alguien “descubre” la manera de saltarse ese bloqueo
estaré más que encantado de que me lo comente.

Tras lo anterior, el código anterior podría ser modificado de


la siguiente manera (os marco las diferencias en negrita):


Private Sub cmdEliminaArchivosYCarpeta_Click()
On Error GoTo sol_err
'Declaramos las variables
Dim miRuta As String, miCarpeta As String
Dim rutaCompleta As String
Dim resp As Integer
'Damos valor a las variables
miRuta = Application.CurrentProject.Path
miCarpeta = "\Borrar"
'Creamos la ruta completa
rutaCompleta = miRuta & miCarpeta
'Comprobamos si existe antes de borrarla.
'La carpeta no existe
If Dir(rutaCompleta, vbDirectory) = "" Then
MsgBox "La carpeta especificada no existe", vbInformation, "NO EXISTE"
Exit Sub
End If
'Comprobamos si la carpeta contiene archivos. Debemos añadir la contrabarra
'para "entrar" dentro de la carpeta
If Dir(rutaCompleta & "\") <> "" Then
resp = MsgBox("La carpeta especificada contiene archivos. ¿Desea continuar con" _
& " el borrado de archivos?", vbQuestion + vbYesNo, "COFIRMACIÓN")
'Si se responde que no salimos del procedimiento
If resp = vbNo Then
Exit Sub
Else
'En caso contrario borramos los archivos...
Kill rutaCompleta & "\*.*"
End If
Else
GoTo Borrado_carpeta
End If
'Lanzamos un mensaje de aviso
MsgBox "Archivos eliminados correctamente", vbInformation, "CORRECTO"
Borrado_carpeta:
'Borramos la carpeta
RmDir rutaCompleta
'Lanzamos un mensaje de aviso
MsgBox "Carpeta eliminada correctamente", vbInformation, "CORRECTO"
Salida:
Exit Sub
sol_err:
If Err.Number = 75 Then
MsgBox "Carpeta temporalmente bloqueada. Inténtelo de nuevo",

22
Visítame en http://siliconproject.com.ar/neckkito/
vbInformation, "BLOQUEO"
Else
MsgBox "Se ha producido el error " & Err.Number & " - " & Err.Description
End If
Resume Salida
End Sub

En definitiva, que es la mejor solución que se me ha


ocurrido.

OTRAS “CURIOSIDADES” SOBRE GESTIÓN DE CARPETAS


Nuestro PC tiene lo que se denomina “ruta actual predeterminada”, que en numerosos casos es
la carpeta “Mis Documentos”. Eso, con Access, no es una excepción.

¿Cuándo notamos lo anterior? Imaginemos una aplicación externa (que no sea Access, quiero
decir) y le damos al botón “Guardar” con un archivo no guardado aún, o “Guardar como”. Si
esa aplicación externa no lleva establecida una ruta actual y respeta la del sistema, cuando se
nos abre el cuadro para guardar el archivo, ¿dónde se nos abre? Pues generalmente se abre en
nuestra carpeta “Mis documentos”, ¿verdad?

Lo anterior hace referencia a la unidad donde tenemos instalado el sistema operativo


(usualmente “C:). La ruta actual predeterminada del resto de unidades suele ser el directorio
raíz.

¿Cómo podemos saber cuál es nuestra carpeta predeterminada? Pues utilizando...

CURDIR()
Es decir, que podemos pedirle a Access que nos muestre cuál es nuestra ruta actual
predeterminada, y además que nos lo diga de una unidad de disco específica.

Su estructura es

CurDir(“Unidad”)

Es decir, que si escribimos el siguiente código en un botón de comando nos devolverá la ruta
actual de nuestro PC:


Private Sub cmdRutaPredeterminada_Click()
Dim rutaActual As String
rutaActual = CurDir("C")
MsgBox rutaActual
End Sub

Suponiendo que tengamos una unidad D:\, si cambiamos la línea de CurDir() por la siguiente:

rutaActual = CurDir("D")

nos devolverá la ruta actual de D, que con toda probabilidad será el directorio raíz.

Si escribimos

23
Visítame en http://siliconproject.com.ar/neckkito/
rutaActual = CurDir()

nos devolverá la ruta actual predeterminada de C:\

¿Y para qué lo anterior? Porque necesitábamos esos


conocimientos para poder explicar...

CHDIR()
La estructura de ChDir() es

ChDir “miRutaActual”

Como habréis podido adivinar, ChDir() nos permite cambiar la ruta actual predeterminada por
la que nosotros queramos.

Por ejemplo, imaginemos que queremos sacar muchos informes temporales para ir
analizándolos, o exportando datos a Excel de manera temporal para coger algunas casillas de
resultados.

En ese caso podría interesarnos tener una carpeta temporal en C:\, o en otra ruta, donde
llevar toda esa información. Supongamos que lo que queremos es llevarlos a “C:\AccessTemp”.
Lo que debemos hacer pues es:

1.- Creamos nuestra carpeta “AccessTemp” (ya sea “a mano” o a través de los códigos que
hemos visto) y le decimos a Access que queremos que, para nuestra BD, esa sea la carpeta
temporal.

2.- Cambiar la ruta actual predeterminada (por ejemplo, asignando el código al evento “Al
cargar” del formulario que se nos abra al iniciar Access).

Este ejemplo lo desarrollaremos con el código asignado a un botón de comando. Lo


complicaremos “un poco” para que, en el caso de que no exista nuestra carpeta “AccessTemp”,
nos la cree.

El código sería el siguiente:


Private Sub cmdCreaRutaAccessTemp_Click()
'Declaramos las variables
Dim miCarpeta As String, miRutaActual As String
miCarpeta = "AccessTemp"
miRutaActual = "c:\" & miCarpeta
'Comprobamos que la ruta existe
If Dir(miRutaActual, vbDirectory) = "" Then
'Si no existe la creamos
MkDir miRutaActual
End If
'La definimos como la ruta actual predeterminada
ChDir miRutaActual
'A efectos de este ejemplo, sacamos un msgbox que nos
'dirá cuál es la ruta actual predeterminada a partir de ahora
MsgBox "La ruta actual predeterminada es " & CurDir("C"), vbInformation, "RAP"
End Sub

24
Visítame en http://siliconproject.com.ar/neckkito/
¿Recordamos, hace unas cuantas páginas, que hablábamos de
“Application.CurrentProject.Path”? Pues ahora podemos dar la misma utilidad a CurDir().

Por ejemplo, exportamos TDatos a nuestra carpeta


temporal:


Private Sub cmdExportaTDatosARAP_Click()
'Declaramos las variables
Dim miRuta As String
Dim miExcel As String
'Inicializamos las variables
miRuta = CurDir()
miExcel = miRuta & "\ExcelDatos.xls"
'Sacamos TDatos a Excel
DoCmd.TransferSpreadsheet acExport, acSpreadsheetTypeExcel8, "TDatos", miExcel
'Lanzamos un mensaje de exportación correcta
MsgBox "La exportación se ha realizado correctamente a la carpeta: " _
& miRuta, vbInformation, "OK"
End Sub

También tened en cuenta de la instrucción MkDir() coge la ruta actual predeterminada si no


indicamos específicamente la ruta.

Por ejemplo, si escribimos:


Private Sub cmdCreaCarpetaSinRuta_Click()
MkDir "SinRuta"
MsgBox "Se ha creado la carpeta 'SinRuta' en " & CurDir(), vbInformation, "CREADA"
End Sub

Si tenemos claro cuál es la ruta actual predeterminada podemos utilizar MkDir() sin tener que
recurrir cada vez a determinar la ruta completa, lo que facilita la velocidad de escritura de
código (eso sí, debemos tener muy claro qué valor devuelve CurDir()).

Finalmente, para vuestra información, os diré que también existe...

CHDRIVE()
Su sintaxis es:

ChDrive “unidad”

Lo que hace es cambiar la unidad predeterminada por la que nosotros queramos.

Si queremos cambiar la unidad predeterminada a D:\, simplemente deberíamos escribir:

ChDrive “D”

Un par de curiosidades:

25
Visítame en http://siliconproject.com.ar/neckkito/
– Hay que tener en cuenta que si la unidad no existe se devuelve el error 68. Ya tenemos
suficientes conocimientos de saber cómo podemos gestionar ese error, pero en el
siguiente punto podemos ver cómo lo he gestionado yo.
– Si cambiamos ChDrive estamos cambiando la ruta de
acceso predeterminada. Por ello, si escribimos:


Private Sub cmdChDrive_Click()
On Error GoTo sol_err
ChDrive "D"
MsgBox CurDir()
Salida:
Exit Sub
sol_err:
If Err.Number = 68 Then
MsgBox "La unidad especificada no existe", vbCritical, "NO EXISTE"
Else
MsgBox "Se ha producido el error " & Err.Number & " - " & Err.Description
End If
Resume Salida
End Sub

El mensaje que nos saldrá será que CurDir() es “D:\” (si no salta el error 68, claro)

– Si escribimos lo siguiente: ChDrive “Frodo acarició el Anillo”, ¿qué pasa? Pues que
ChDrive coge sólo el primer carácter de la cadena, por lo que CurDir() sería “F:\”
– Si lo dejamos en blanco (es decir, escribimos una cadena vacía con comillas dobles
(“”) ChDrive no realiza ningún cambio, y se queda como predeterminada la unidad
que ya estaba predeterminada.

PARA FINALIZAR EL CAPÍTULO


Hemos visto, en este capítulo, cómo podemos movernos por dos “mundos”: el de las
impresoras y el de las “carpetas/archivos”.

La verdad es que ha salido un capítulo un poco largo, pero, en este caso, me he encontrado
con aquello de “ya que esamos podría explicar...”.

De todas maneras, hay más material sobre manejo de archivos, así que no debemos
“desesperarnos”... je, je...

Espero poder desarrollar algunas cosillas más en un próximo capítulo.

Y, si habéis podido aprender algo nuevo, pues mejor que mejor. 

Un saludo y...

¡suerte!

26
Visítame en http://siliconproject.com.ar/neckkito/
CURSO DE VB

CAPÍTULO 171

Índice de contenido
SIGAMOS UN POCO MÁS CON DIRECTORIOS Y ARCHIVOS... ..............................................2
INTRODUCCIÓN...........................................................................................................................2
UTILIZANDO UNA HERRAMIENTA DE OFFICE: FileDialog..................................................2
CARACTERÍSTICAS COMUNES............................................................................................2
ELEGIR UNA CARPETA..........................................................................................................3
ELEGIR UN ARCHIVO.............................................................................................................4
ALGUNAS IDEAS QUE PUEDEN RESULTAR ÚTILES.......................................................7
Ruta “temporal” de trabajo de la BD......................................................................................7
Ruta “permanente” de trabajo de la BD.................................................................................7
Trabajo con diferentes tipos de archivo..................................................................................8
TRABAJAR CON ARCHIVOS DE TEXTO (I): VISUAL BASIC...............................................9
LEER DATOS DE UN TXT.......................................................................................................9
ESCRIBIR DATOS EN UN TXT.............................................................................................11
TRABAJAR CON ARCHIVOS DE TEXTO (II): FileSystemObject (fso)..................................12
LEER DATOS DE UN TXT.....................................................................................................13
ESCRIBIR DATOS EN UN TXT.............................................................................................14
FILESYSTEMOBJECT.................................................................................................................15
TRABAJAR CON UNIDADES DE DISCO............................................................................15
Obtener el número de serie de una unidad de disco.............................................................16
Obtenemos el nombre de la unidad de disco........................................................................16
Obtenemos el tipo de unidad (extraíble o fija).....................................................................17
Obtenemos el total de espacio y el espacio disponible........................................................17
Obtenemos la disponibilidad de la unidad...........................................................................18
Otras propiedades que podemos utilizar..............................................................................19
TRABAJAR CON CARPETAS...............................................................................................19
CREANDO UNA CARPETA (Y ALGUNAS COSILLAS MÁS)......................................19
ELIMINANDO UNA CARPETA........................................................................................20
OTRAS PROPIEDADES QUE PODEMOS UTILIZAR....................................................21
TRABAJAR CON ARCHIVOS...............................................................................................22
DOS BAGATELAS.......................................................................................................................23
CREAR NUESTRO “DICCIONARIO”...................................................................................23
CREAR UN BACKUP DE LA BD EN TIEMPO DE EJECUCIÓN.......................................25
PARA FINALIZAR ESTE CAPÍTULO........................................................................................27

1 La BD donde están los ejemplos de este capítulo os la podéis bajar aquí.

1
Visítame en http://siliconproject.com.ar/neckkito/
SIGAMOS UN POCO MÁS CON
DIRECTORIOS Y ARCHIVOS...

INTRODUCCIÓN
En el capítulo anterior vimos una sistemática para “operar”
con archivos y carpetas, pero no es la única.

Vamos, en este capítulo, a ver algunas cosas más concernientes al


mundo del manejo de estos elementos de nuestro PC.

La BD de ejemplo que vamos a manejar será la misma que vimos en el capítulo 16. Es decir,
que, si hicisteis vuestra propia BD, o bien la reutilizáis o bien creáis una BD nueva importando
la tabla TDatos (en el ejemplo que yo preparé).

UTILIZANDO UNA HERRAMIENTA DE OFFICE: FileDialog


Microsoft Office pone a nuestra disposición una herramienta que nos permite trabajar con
archivos y carpetas de una manera muy específica. Esa herramienta es el objeto FileDialog.

Como quizá hayáis intuido, para que Access pueda “comunicarse” con el objeto FileDialog es
necesario registrar una referencia, que en este caso es “Microsoft Office xx.x Object Library”,
donde xx.x es el número de versión, que dependerá de la versión que tengamos instalada en
nuestro PC.

El objeto FileDialog nos permite abrir una ventana de diálogo al estilo de Windows y solicitar al
usuario que elija la carpeta o archivo que desee, o que abra o guarde un archivo.

De lo anterior podemos deducir que tenemos cuatro opciones:

– Elegir una carpeta


– Elegir un archivo
– Abrir un archivo
– Guardar un archivo

A efectos de este capítulo nosotros sólo veremos las dos primeras. Si estáis interesados en las
dos segundas os remito a la propia ayuda de Access. Tened en cuenta que para utilizarlas
debéis combinarlas con el método Execute, pero eso está explicado en la propia ayuda, así
que, entendida la mecánica, no deberíais tener ningún problema.

CARACTERÍSTICAS COMUNES
Como siempre, al ser un objeto tenemos que definirlo como tal al declarar las variables. Una
vez declarado dicho objeto, lo asignamos a un FileDialog a través del SET.

Es decir, que lo anterior se resumiría así:

Dim fd As Object 'fd = FileDialog


Set fd = Application.FileDialog(<argumentoFileDialogType>)

Ya tenemos nuestro objeto y, además , ya “existe” para Access. Como todo objeto, tiene sus
propiedades. Vamos a echar un repaso a las más comunes:

2
Visítame en http://siliconproject.com.ar/neckkito/
– Título (Title): nos permite escribir el título que
aparecerá en la parte superior de la ventana.
– Nombre del botón (ButtonName): nos permite
escribir el nombre del botón de acción.
– Vista inicial (InitialView): nos permite aplicar la
constante de msoFileDialogView para ver carpetas y
archivos. Es decir, nos permite utilizar estas opciones de la
ilustración de la izquierda (si sabemos un poco de inglés las
entenderemos perfectamente):

– Ruta inicial o Archivo inicial (InitialFileName): representa la ruta o el fichero que se


abrirá por defecto en nuestra ventana.
– Filtros de archivos (Filters): si queremos que la selección sea de unos tipos de archivos
en concreto (por ejemplo, sólo Words) podríamos utilizar esta propiedad, junto con el método
Add.
– Selección múltiple de archivos (AllowMultiSelect): si el cuadro de diálogo es de
selección de archivos nos permite elegir más de un archivo. Para ello basta situar su valor en
TRUE.

Una vez tenemos el “diseño” de nuestra ventana configurado debemos detectar si el usuario ha
seleccionado “algo” y pulsado el botón de selección. Para detectar la acción del usuario
debemos recurrir la método Show. ¿Qué botón ha pulsado el usuario? Pues si se ha hecho click
sobre el botón de selección el valor devuelto es -1, mientras que si se ha pulsado “Cancelar” el
valor devuelto es 0 (cero).

Para saber qué elemento se ha seleccionado manejaremos las propiedades SelectedItems e


Item.

Pero extrapolemos la metáfora “una imagen vale más que mil palabras” y vamos a ver nuestro
código en acción, en diferentes situaciones.

ELEGIR UNA CARPETA


Para aplicar el FileDialog realizaremos las siguientes acciones en nuestra BD:

1.- En un formulario (en la BD de ejemplo se utiliza FMenu) crearemos un botón de comando,


al que llamaremos cmdFileDialogCarpeta.
2.- Crearemos también un cuadro de texto, que nos recogerá la opción seleccionada. A este
control lo llamaremos txtCarpeta.
3.- Crearemos un nuevo módulo, al que pondremos por nombre mdlFileDialog. Ahí
escribiremos los diferentes códigos que veremos a continuación.

Hecho lo anterior veamos el código que escribiremos en mdlFileDialog para seleccionar una
carpeta:

3
Visítame en http://siliconproject.com.ar/neckkito/

Public Function selectCarpeta() As String
'Creamos un control de errores
On Error GoTo sol_err
'Declaramos las variables
Dim fd As Object 'fd = FileDialog
Dim rutaIni As String
'Definimos la ruta inicial
rutaIni = Application.CurrentProject.Path
'Creamos el objeto FileDialog
Set fd = Application.FileDialog(msoFileDialogFolderPicker)
'Configuramos las características de nuestra ventana de
diálogo
With fd
.Title = "SELECCIONE LA CARPETA QUE MÁS LE GUSTE"
.ButtonName = "QUIERO ESTA"
.InitialView = msoFileDialogViewList
.InitialFileName = rutaIni
'Detectamos el botón pulsado por el usuario
If .Show = -1 Then
'Asignamos a la función la carpeta seleccionada, convirtiéndola
'a un valor de tipo String
selectCarpeta = CStr(.SelectedItems.Item(1))
Else
'Si se pulsa cancelar avisamos y salimos
MsgBox "¡HA CANCELADO LA SELECCIÓN! ¡¿POR QUÉ?!", vbExclamation,
"CANCELADO"
Exit Function
End If
End With

Salida:
Exit Function
sol_err:
MsgBox "Se ha producido el error: " & Err.Number & " - " & Err.Description
Resume Salida
End Function

Fijaos que, como argumento el el SET, hemos utilizado msoFileDialogFolderPicker, es decir,


“recogedor de carpetas”.

Ahora ya sólo nos resta programar nuestro botón, que hará una llamada a la función para
obtener el String de la carpeta que hayamos seleccionado. El código del botón será,
simplemente:


Private Sub cmdFileDialogCarpeta_Click()
Me.txtCarpeta.Value = selectCarpeta()
End Sub

ELEGIR UN ARCHIVO
Para elegir un archivo vamos a realizar un proceso similar en nuestra BD. Crearemos:

4
Visítame en http://siliconproject.com.ar/neckkito/
1.- Un botón, al que pondremos de nombre cmdFileDialogArchivo
2.- Un cuadro de texto que llamaremos txtArchivo

Vamos a suponer que queremos recoger documentos de


Word (que será la opción predeterminada) y de Excel, y, por
seguridad, vamos a dejarnos una selección de “todos los
archivos”. Ello nos permitirá utilizar la propiedad Filters.

3.- En nuestro módulo añadiremos una nueva función con el


siguiente código:


Public Function selectArchivo() As String
'Creamos un control de errores
On Error GoTo sol_err

'Declaramos las variables


Dim fd As Object 'fd = FileDialog
Dim rutaIni As String
'Definimos la ruta inicial
rutaIni = Application.CurrentProject.Path
'Creamos el objeto FileDialog
Set fd = Application.FileDialog(msoFileDialogFilePicker)
'Configuramos las características de nuestra ventana de diálogo
With fd
.Title = "SELECCIONE EL ARCHIVO A SACRIFICAR"
.ButtonName = "TE TOCÓ"
.InitialView = msoFileDialogViewSmallIcons
.InitialFileName = rutaIni
.Filters.Add "Esclavos Word", "*.doc; *.docx"
.Filters.Add "Esclavos Excel", "*.xls; *.xlsx"
.Filters.Add "Todos los Esclavos", "*.*"
'Detectamos el botón pulsado por el usuario
If .Show = -1 Then
'Asignamos a la función el archivo seleccionado, convirtiéndola
'a un valor de tipo String
selectArchivo = CStr(.SelectedItems.Item(1))
Else
'Si se pulsa cancelar avisamos y salimos
MsgBox "¡HA CANCELADO LA SELECCIÓN! ¡¿POR QUÉ?!", vbExclamation,
"CANCELADO"
Exit Function
End If
End With

Salida:
Exit Function
sol_err:
MsgBox "Se ha producido el error: " & Err.Number & " - " & Err.Description
Resume Salida
End Function

Y el código de nuestro botón sería:

5
Visítame en http://siliconproject.com.ar/neckkito/
Private Sub cmdFileDialogArchivo_Click()
Me.txtArchivo.Value = selectArchivo()
End Sub

Fijaos, en la función, los cambios mínimos que hemos


realizado en relación a la función selectCarpeta:

• Para recoger un archivo hemos utilizado, en el SET:


msoFileDialogFilePicker
• Para añadir los tipos de archivo hemos utilizado
.Filters.Add, siendo el primero el valor
predeterminado.
• La estructura de Filters.Add es:

fd.Filters.Add “Descriptor”, “*.extensión; *.extensión; …; *.extension”

Ni que decir tiene que el valor de “Descriptor” lo ponemos nosotros a nuestro gusto.

Finalmente, y suponiendo que el archivo que debiéramos recoger fuera el mismo la mayoría de
veces, podríamos predeterminarlo.

Por ejemplo, vamos a crear, en la carpeta donde tenemos la BD, un Word que se llama
“WdPredeterminado.doc” (o docx).

Nuestro código cambiaría mínimamente así (lo escribo en una nueva función):


Public Function selectArchivoPredet() As String
'Creamos un control de errores
On Error GoTo sol_err

'Declaramos las variables


Dim fd As Object 'fd = FileDialog
Dim rutaIni As String
Dim archivoPredet As String
'Definimos la ruta inicial
rutaIni = Application.CurrentProject.Path
'Definimos el archivo predeterminado
archivoPredet = "WdPredeterminado.doc"
'Creamos el objeto FileDialog
Set fd = Application.FileDialog(msoFileDialogFilePicker)
'Configuramos las características de nuestra ventana de diálogo
With fd
.Title = "SELECCIONE EL ARCHIVO A SACRIFICAR"
.ButtonName = "TE TOCÓ"
.InitialView = msoFileDialogViewSmallIcons
.InitialFileName = rutaIni & "\" & archivoPredet
.Filters.Add "Esclavos Word", "*.doc; *.docx"
.Filters.Add "Esclavos Excel", "*.xls; *.xlsx"
.Filters.Add "Todos los esclavos", "*.*"
'Detectamos el botón pulsado por el usuario
If .Show = -1 Then
'Asignamos a la función el archivo seleccionado, convirtiéndola
'a un valor de tipo String
selectArchivoPredet = CStr(.SelectedItems.Item(1))

6
Visítame en http://siliconproject.com.ar/neckkito/
Else
'Si se pulsa cancelar avisamos y salimos
MsgBox "¡HA CANCELADO LA SELECCIÓN! ¡¿POR
QUÉ?!", vbExclamation, "CANCELADO"
Exit Function
End If
End With

Salida:
Exit Function
sol_err:
MsgBox "Se ha producido el error: " & Err.Number & " - "
& Err.Description
Resume Salida
End Function

Remarcar los siguientes puntos:

– Definimos el archivo predeterminado a través de una variable (archivoPredet)


– Al manipular la propiedad InitialFileName realizamos la concatenación de la ruta con el
archivo predeterminado, debiendo añadir entre ambos la barra inclinada (rutaIni & "\" &
archivoPredet)

Y ya está 

ALGUNAS IDEAS QUE PUEDEN RESULTAR ÚTILES


Os presento aquí algunas ideas que quizá os puedan resultar útiles para vuestras aplicaciones.
Sólo apuntaré las ideas, pero no las desarrollaré. Si habéis seguido el curso no deberíais tener
problemas para implementarlas.

Ruta “temporal” de trabajo de la BD


¿Nos acordamos, en el capítulo anterior, de CurDir() y ChDir()?

Podríamos establecer una ruta de trabajo predeterminada al inicio de la aplicación (por


ejemplo, al cargar un formulario de bienvenida) a través de ChDir(). Si lo hacemos así en
todos nuestros códigos podríamos, para obtener la ruta de trabajo, utilizar CurDir().

¿Qué pasa si, para esa sesión, queremos utilizar otra ruta de trabajo, y que dicha ruta se
mantenga para toda la sesión?

Pues podríamos crear un formulario para que el usuario, a través del FileDialog (para selección
de carpetas), eligiera la ruta de trabajo para esa sesión. Una vez elegida volveríamos a
redefinir ChDir() y todos nuestros códigos con CurDir() tomarían como valor la nueva ruta.

Lógicamente, al cerrar la BD dicha ruta de trabajo “se esfumaría”.

Ruta “permanente” de trabajo de la BD


¿Qué pasaría si queremos una ruta para varias sesiones, y no tener que cambiar cada vez de
ruta predeterminada?

7
Visítame en http://siliconproject.com.ar/neckkito/
Pues la mecánica sería la misma, pero operando con tablas.

Hagamos memoria... ¿Recordamos la función DLookup()?


¿Recordamos los capítulos dedicados a DAO y ADO? ¿Sí? (y
si la respuesta es no... pues... ¡a repasar! )

Siguiendo la sistemática anterior (lo que os comentaba de


cargar la ruta de trabajo al abrir un formulario de
bienvenida o de inicio), podríamos complementarlo con la
creación de una tabla que nos almacene la ruta de trabajo
que queremos utilizar. De esta manera dicha ruta no sería
“temporal” para esa sesión, sino que sería permanente
hasta que el usuario la modificase.

La sistemática sería la siguiente:

1a.- Utilizamos DLookup (sin criterio) para coger el valor de la ruta (lógicamente estoy dando
por supuesto que sólo hay un registro en esa tabla. Si quisiéramos almacenar varias rutas -por
ejemplo, una ruta para “capturar” archivos y otra para “guardarlos”- nuestra tabla podría tener
dos campos, uno con el tipo de trabajo y otro con la ruta, y utilizaríamos en este caso el
criterio con DLookup). Podríamos incluir un IF...END IF para controlar si aún no hay ningún
registro en la tabla.

1b.- Podríamos hacer lo mismo utilizando un DAO.Recordset o un ADODB.Recordset.

2.- Sentamos el valor devuelto como el directorio de trabajo.

3.- El usuario tendría un formulario con un FileDialog para seleccionar una carpeta como nueva
ruta de trabajo

4.- A través de un DAO.Recordset o de un ADODB.Recordset reescribiríamos el campo de la


tabla con la nueva ruta seleccionada por el usuario.

De esta manera, el último valor seleccionado por el usuario sería el que se cargaría siempre al
abrir la aplicación.

Lógicamente, podemos complicar un poco más “el asunto” si queremos que cada usuario tenga
su ruta de trabajo. La sistemática sería muy similar a lo explicado anteriormente, pero nuestra
tabla con las rutas debería tener también indicado el nombre de usuario (lo que implica que
nuestra aplicación debería pedir, al iniciarse, nombre de usuario, o bien utilizar la función
“Environ()” (si esto último os ha pillado desprevenidos echad un vistazo a este artículo).

Trabajo con diferentes tipos de archivo


Imaginemos que trabajamos con diferentes tipos de archivo (imágenes, pdf's, Words, etc.). Y
como somos personas muy ordenadas tenemos carpetas diferentes para categorías de archivo
diferentes.

Es decir, que tendríamos una carpeta “madre” (v.gr. “C:\MisArchivos”) y diferentes subcarpetas
(“C:\MisArchivos\Imagenes”, “C:\MisArchivos\PDFs”, etc.).

Podríamos crear un FileDialog que apuntara a la carpeta “madre”, y el usuario ya se encargaría


de entrar en la subcarpeta correspondiente.

Sin embargo, también podríamos poner, por ejemplo, un marco de opciones, donde el usuario
eligiera qué tipo de archivo desea seleccionar”. Tras eso podríamos utilizar un SELECT CASE

8
Visítame en http://siliconproject.com.ar/neckkito/
para definir las diferentes rutas.

Además, la opción elegida la podríamos pasar como argumento de la función.

En definitiva, que el código podría ser algo así (extracto de


código):


Public Function selectCarpeta(vOpcion As Integer) As String
'Código
'Examinamos la opción elegida por el usuario
Select Case vOpcion
Case 1 'Imágenes
rutaIni = "C:\MisArchivos\Imagenes\"
Case 2 'PDF's
rutaIni = "C:\MisArchivos\PDFs\"
Case 3 'Words
rutaIni = "C:\MisArchivos\Words\"
Case Else
rutaIni = "C:\MisArchivos\"
End Select
'Resto de código
End Function

Y en el botón de comando:


Private Sub...
Me.txtCarpeta.Value = selectCarpeta(Me.mrcOpciones.Value)
End Sub

Podríamos poner un par más de ejemplos, pero en el fondo espero que se haya entendido la
sistemática. Los límites a lo anterior los pone la propia imaginación de cada uno .

TRABAJAR CON ARCHIVOS DE TEXTO (I): VISUAL BASIC


Un caso especial de trabajar con archivos es trabajar con archivos de texto. Veamos, en este
apartado, algunas características de esta sistemática utilizando los comandos propios de VB.

No vamos a entrar en mucho detalle sobre este tema porque en la actualidad el uso de
archivos de texto para almacenar datos es minoritario. Digamos que podemos considerarlo un
sistema “obsoleto” (por favor, poned la anterior frase entre comillas).

LEER DATOS DE UN TXT


Vamos a crearnos, en el directorio donde tenemos nuestra BD, un archivo de texto, que
llamaremos DatosTxt.txt

En dicho archivo escribiremos lo siguiente (no es mi intención ser machista... es lo que es):

9
Visítame en http://siliconproject.com.ar/neckkito/
Vamos a crearnos, en un formulario, un cuadro de texto,
que será el que nos servirá para recoger el contenido del
archivo txt. A ese cuadro de texto lo llamaremos
txtContenidoTxt.

Muy bien. Ahora veamos:

1.- Para abrir un archivo de texto utilizamos la instrucción OPEN

2.- La estructura del open, para lectura, es:

Open <rutaTxt> FOR INPUT AS #<numero>

donde <numero> es, precisamente, el número que queramos, y sirve para distinguir el archivo
txt que hemos abierto.

3.- El recorrido del contenido del txt lo realizamos a través de un bloque DO UNTIL
EOF(<numero>)...LOOP, o también DO WHILE NOT EOF(<numero>)

4.- Para guardar las líneas del txt lo podemos hacer asignándolos a una variable, con una
estructura que veremos en el código de ejemplo.

5.- Para cerrar el archivo de texto utilizamos la instrucción CLOSE

En definitiva, que nuestro código debería ser:


Private Sub cmdRecuperaContenidoTxt_Click()
'Declaramos las variables
Dim miTxt As String
Dim lineaTxt As String
Dim textoTxt As String
'Borramos el contenido de nuestro cuadro de texto, por si lo hubiera
Me.txtContenidoTxt.Value = Null
'Definimos dónde está nuestro TXT
miTxt = Application.CurrentProject.Path & "\DatosTxt.txt"
'Abrimos el txt y le asignamos el número 1
Open miTxt For Input As #1
'Empezamos a recorrer las líneas
Do Until EOF(1)
'Guardamos cada línea en lineaTxt
Input #1, lineaTxt
'Vamos creando el contenido del txt en textoTxt, creando los saltos
'de línea al llegar al final de línea
textoTxt = textoTxt & IIf(textoTxt <> "", vbCrLf, "") & lineaTxt
Loop
'Cerramos el archivo de texto
Close #1

10
Visítame en http://siliconproject.com.ar/neckkito/
'Escribimos en nuestro cuadro de texto el contenido obtenido
Me.txtContenidoTxt.Value = textoTxt
End Sub

ESCRIBIR DATOS EN UN TXT


Para ver los resultados de nuestro código vamos a crear
una consulta a través de SQL que nos va a filtrar aquellos
elementos de TDatos cuya edad esté comprendida entre 20
y 30 años. Ese es pues nuestro objetivo.

Vamos a crear, en el mismo directorio donde tenemos la base de datos, un archivo de texto. Lo
llamaremos Edades.txt.

El método para hacer lo anterior es:

1.- Abrimos el fichero de texto a través de la instrucción OPEN

2.- La estructura del open, para lectura, es:

Open <rutaTxt> FOR OUTPUT AS #<numero>

donde <numero> es, precisamente, el número que queramos, y sirve para distinguir el archivo
txt que hemos abierto.

3.- Almacenamos lo que queremos escribir en una variable (variableTxt), y utilizamos la


instrucción PRINT de la siguiente manera:

PRINT #1, <variableTxt>

4.- Para añadir líneas en blanco utilizamos la primera parte de la estructura del punto 3, es
decir:

PRINT #1,

En definitiva nuestro código debería quedar así:


Private Sub cmdEscribeEdadesTxt_Click()
'Declaramos las variables
Dim lineaTxt As String
Dim miTxt As String
Dim miSql As String
Dim rst As DAO.Recordset
'Definimos nuestro archivo Txt
miTxt = Application.CurrentProject.Path & "\Edades.txt"
'Abrimos el txt para poder escribir una cabecera
Open miTxt For Output As #1
'Escribimos la cabecera
Print #1, "Edades comprendidas entre 20 y 30 años"
Print #1, "--------------------------------------"
Print #1,
'Creamos la SQL
miSql = "SELECT TDatos.Nombre, TDatos.Edad FROM TDatos" _
& " WHERE TDatos.Edad BETWEEN 20 AND 30"

11
Visítame en http://siliconproject.com.ar/neckkito/
'Creamos el recordset sobre la SQL
Set rst = CurrentDb.OpenRecordset(miSql)
'Iniciamos el recorrido de registros
With rst
.MoveFirst
Do Until .EOF
'La línea recogerá el nombre y la edad
lineaTxt = .Fields(0).Value & vbTab & .Fields(1).Value
'Escribimos la línea en el archivo de texto
Print #1, lineaTxt
'Volvemos a dejar lineaTxt en blanco
lineaTxt = ""
'Nos movemos al registro siguiente
.MoveNext
Loop
End With
'Escribimos el cierre del fichero txt
Print #1,
Print #1, "--------------------------------------"
Print #1, "FIN DE LOS DATOS"
'Cerramos el txt
Close #1
'Lanzamos un mensaje de confirmación
MsgBox "El fichero 'Edades.txt' se ha creado correctamente", vbInformation, "OK"
'Cerramos conexiones y liberamos memoria
rst.Close
Set rst = Nothing
End Sub

 Truco: si abrimos el archivo de texto veremos que las edades nos salen “desalineadas”.
Una manera de corregir lo anterior sería utilizar un Iif a la hora de escribir la línea de texto,
analizando la longitud del campo [Nombre] y dando una o dos tabulaciones. En definitiva,
nuestra línea de código debería quedarnos así:
lineaTxt = .Fields(0).Value _
& IIf(Len(.Fields(0).Value) > 7, vbTab, vbTab & vbTab) & .Fields(1).Value

TRABAJAR CON ARCHIVOS DE TEXTO (II): FileSystemObject (fso)


Existe por ahí una biblioteca que se denomina FileSystemObject, que también nos permite
trabajar con archivos y carpetas.

A modo de introducción sobre esta biblioteca vamos a ver cómo podemos utilizarla para
manipular archivos de texto.

 Para poder trabajar con FileSystemObject debemos registrar la referencia “Microsoft


Scripting Runtime”

En FileSystemObject nos encontramos con un objeto llamado TextStream. Es este objeto el


que nos permitirá operar sobre nuestro txt. Veréis que la operativa es “diferente” al sistema de
VB, y, de hecho, yo diría que más sencillo.

¿Y cómo hacemos aparecer un fso de la nada? Lo veremos en los códigos, pero las “palabras
mágicas” son:

12
Visítame en http://siliconproject.com.ar/neckkito/

Dim fso As Scripting.FileSystemObject
Set fso = CreateObject(“Scripting.FileSystemObject”)

LEER DATOS DE UN TXT


Realicemos la misma acción que desarrollamos en el
apartado donde utilizábamos VB; es decir, pasaremos el
contenido de un archivo de texto (nuestros súper-refranes)
a un cuadro de texto en un formulario.

Utilizaremos los mismos nombres que usamos para el cuadro de texto y el botón.
Lógicamente, el código del botón será el que nos variará.

¿Qué debemos saber?

• Que debemos declarar no sólo un objeto fso, sino que, como indicábamos un poco más
arriba, necesitaremos declarar un objeto TextStream
• Que para abrir el archivo de texto utilizaremos el método OpenTextFile()
• Los argumentos de OpenTextFile son (<archivoTxt>, <tipoDeOperación>, <blnCrear>,
<formato>), de manera que:
● archivoTxt: ruta, nombre de archivo y extensión
● tipoDeOperación: es el método de entrada/salida (IO), que puede ser escritura,
lectura o anexión
● blnCrear: valor TRUE/FALSE que permite crear el archivo si no existe. Si su valor es
False (predeterminado) el txt no se crea
● formato: se refiere a si el archivo se creará como unicode o ASCII, o dejamos que se
cree con el formato predeterminado en el sistema.

• Que tenemos un método que nos lee todo el archivo de texto: ReadAll2
• Que una vez realizadas las operaciones cerramos el archivo con el método Close

Con lo anterior en mente vamos a ver cómo sería nuestro código:


Private Sub cmdRecuperaContenidoTxt_Click()
'Borramos el contenido del cuadro de texto
Me.txtContenidoTxt.Value = Null

'Declaramos las variables


Dim fso As Scripting.FileSystemObject
Dim contenidoTxt As String
Dim miTxt As String
'Definimos miTxt
miTxt = Application.CurrentProject.Path & "\DatosTxt.txt"
'Declaramos el objeto TextStream
Dim ots As Scripting.TextStream 'ots = Objeto TextStream
'Creamos fso
Set fso = CreateObject("Scripting.FileSystemObject")
'Creamos ots con los argumentos mínimos
Set ots = fso.OpenTextFile(miTxt, ForReading)
'Leemos todos los datos del archivo

2 Según aconseja la propia ayuda de Access, si el archivo de texto es muy grande la utilización de ReadAll desperdicia recursos de
memoria. Se aconseja utilizar otras técnicas, como la lectura del archivo de texto línea por línea.

13
Visítame en http://siliconproject.com.ar/neckkito/
contenidoTxt = ots.ReadAll
'Escribimos el contenido en el cuadro de texto del
formulario
Me.txtContenidoTxt.Value = contenidoTxt
'Cerramos el archivo
ots.Close
End Sub

ESCRIBIR DATOS EN UN TXT


Y, para seguir viendo diferencias entre un método y otro, vamos a crear el archivo “Edades.txt”
utilizando fso.

Objetivo: conseguir un txt con la información de TDatos con las edades comprendidas entre 20
y 30 años.

Los elementos del código que vamos a necesitar serán los siguientes:

• Un objeto fso
• Un objeto Scripting.TextStream
• Para crear el archivo de texto → utilizar el método CreateTextFile()
• Pare escribir el texto → utilizar el método WriteLine
• Para escribir líneas en blanco → utilizar el método WriteBlankLines

Y todos los anteriores ingredientes, bien cocinados, nos permitirían escribir lo siguiente:


Private Sub cmdEscribeEdadesTxtFso_Click()
'Declaramos las variables
Dim fso As Scripting.FileSystemObject
Dim miTexto As Scripting.TextStream
Dim miSql As String
Dim miArchivoTxt As String
Dim rst As DAO.Recordset
'Definimos miArchivoTxt
miArchivoTxt = Application.CurrentProject.Path & "\EdadesFso.txt"
'Creamos el objeto fso
Set fso = CreateObject("Scripting.FileSystemObject")
'Creamos miTexto
Set miTexto = fso.CreateTextFile(miArchivoTxt, True)
'Definimos la SQL
miSql = "SELECT TDatos.Nombre, TDatos.Edad FROM TDatos" _
& " WHERE TDatos.Edad BETWEEN 20 AND 30"
'Escribimos la cabecera de nuestro txt
miTexto.WriteLine ("Listado de datos con edades entre 20 y 30 años")
miTexto.WriteLine ("----------------------------------------------")
'Escribimos dos líneas en blanco
miTexto.WriteBlankLines (2)
'Creamos el recordset
Set rst = CurrentDb.OpenRecordset(miSql)
'Iniciamos el proceso de escritura
rst.MoveFirst
Do Until rst.EOF
'Escribimos la primera línea
miTexto.WriteLine (rst.Fields(0).Value & vbTab & rst.Fields(1).Value)

14
Visítame en http://siliconproject.com.ar/neckkito/
'Nos movemos al siguiente registro
rst.MoveNext
Loop
'Escribimos el final del documento
miTexto.WriteBlankLines (1)
miTexto.WriteLine ("----------------------------------------------")
miTexto.WriteLine ("FIN DEL DOCUMENTO")
'Lanzamos un mensaje de OK
MsgBox "Archivo de texto creado correctamente",
vbInformation, "OK"
'Cerramos conexiones y liberamos memoria
rst.Close
Set rst = Nothing
End Sub

Simplemente remarcar un par de cosas del código:

✔ Podemos escribir las líneas en blanco que queramos indicándoselo al método


WriteBlankLines(númeroDeLineas)
✔ Al definir miTexto (Set miTexto = fso.CreateTextFile(miArchivoTxt, True)) hemos
indicado que, si el archivo existe, lo sobrescriba a través del TRUE. Si hubiéramos
puesto FALSE el archivo no hubiera sido sobrescrito. Si el archivo existe nos hubiera
dado el error 58. En ese caso necesitaríamos programar un control de errores, cosa que
doy por supuesto que ya sabemos hacer sin problemas. 

 Truco: si abrimos el archivo de texto veremos que las edades nos salen “desalineadas”.
Una manera de corregir lo anterior sería utilizar un Iif a la hora de escribir la línea de texto,
analizando la longitud del campo [Nombre] y dando una o dos tabulaciones. En definitiva,
nuestra línea de código debería quedarnos así:

miTexto.WriteLine (rst.Fields(0).Value _
& IIf(Len(rst.Fields(0)) > 7, vbTab, vbTab & vbTab) & rst.Fields(1).Value)

FILESYSTEMOBJECT
Acabamos de ver qué podemos hacer con FileSystemObject con un fichero de texto, pero sus
“virtudes” no se quedan ahí. No debemos olvidar que el objetivo de este capítulo es el manejo
de archivos y carpetas.

Así pues, con FileSystemObject disponemos de un conjunto de herramientas sumamente útiles


para lograr la consecución de nuestro objetivo.

FileSystemObject se engloba en lo que se denomina “Script Runtime”. Los elementos de “Script


Runtime” se hallan situados en la referencia “Microsfot Scripting Runtime”, y, en consecuencia
(como ya habíamos comentado al trabajar con archivos de texto), es necesario registrar dicha
librería para poder utilizar el código.

Analicemos algunas “cosas” que nos permite realizar dicho objeto.

TRABAJAR CON UNIDADES DE DISCO


Podemos utilizar FSO para trabajar con algunas características o “informaciones” que
provienen de nuestras unidades de disco. Para no hacer la explicación demasiado ardua nos
centraremos en las que yo personalmente creo que son más interesantes, pero dejaré

15
Visítame en http://siliconproject.com.ar/neckkito/
apuntadas otras disponibles. La mecánica, una vez entendida, es “simple”, por lo que no
deberíamos tener problemas para operar con unas o con otras.

La estructura sería:

• Definimos un objeto fso


• Definimos un objeto drv (que nos identifica la unidad
sobre la que queremos trabajar)
• Operamos sobre la propiedad que queremos

Obtener el número de serie de una unidad de disco


Para obtener el número de serie de una unidad de disco debemos utilizar la propiedad
SerialNumber.

Nuestro código podría quedarnos así:


Private Sub cmdSerialC_Click()
'Declaramos las variables
Dim fso As Scripting.FileSystemObject
Dim drv As Scripting.Drive
Dim vSerial As String
'Creamos el objeto fso
Set fso = CreateObject("Scripting.FileSystemObject")
'Creamos el objeto drv
Set drv = fso.GetDrive("C:\")
'Obtenemos el número de serie de la unidad "C:"
vSerial = drv.SerialNumber
'Informamos del número de serie
MsgBox "El número de serie de la unidad 'C:' es " & vSerial, vbInformation, "INFO"
End Sub

¿Por qué considero esta propiedad muy interesante? Por temas de protección de nuestra
aplicación.

Como me gusta ser “misterioso” podría dejarlo aquí y no seguir con esta frase... 

Pero no os voy a dejar en la estacada, aunque sólo lanzaré la idea: ¿y si hubiera algún sistema
que, al abrir la BD, nos “mirara” el número de serie de la unidad sobre la que se está
ejecutando la misma, y, si no coincidiera con el “nuestro”, nos cerrara Access, no sin antes
mostrar un hermoso mensaje parecido a “esta copia no puede ejecutarse en este equipo”? Ji,
ji...

Os animo a que echéis un vistazo a este ejemplo.

Obtenemos el nombre de la unidad de disco


Nuestro código sería exactamente igual al anterior, sólo que deberíamos actuar sobre otra
propiedad. Creo que por comodidad vuestra es mejor poner el código entero, marcando en
negrita la diferencia:


Private Sub cmdNombreC_Click()
'Declaramos las variables

16
Visítame en http://siliconproject.com.ar/neckkito/
Dim fso As Scripting.FileSystemObject
Dim drv As Scripting.Drive
Dim vNombre As String
'Creamos el objeto fso
Set fso = CreateObject("Scripting.FileSystemObject")
'Creamos el objeto drv
Set drv = fso.GetDrive("C:\")
'Obtenemos el nombre de la unidad "C:"
vNombre = drv.VolumeName
'Informamos del nombre de la unidad
MsgBox "El nombre de la unidad 'C:' es " & vNombre,
vbInformation, "INFO"
End Sub

Obtenemos el tipo de unidad (extraíble o fija)


El código sería:


Private Sub cmdTipoUnidad_Click()
'Declaramos las variables
Dim fso As Scripting.FileSystemObject
Dim drv As Scripting.Drive
Dim vTipoDrv As Byte
Dim vTipo As String
'Creamos el objeto fso
Set fso = CreateObject("Scripting.FileSystemObject")
'Creamos el objeto drv
Set drv = fso.GetDrive("C:\")
'Obtenemos el tipo de la unidad "C:"
vTipoDrv = drv.DriveType
'Analizamos el tipo
Select Case vTipoDrv
Case 0
vTipo = "Desconocido"
Case 1
vTipo = "Unidad extraíble"
Case 2
vTipo = "Unidad fija"
Case 3
vTipo = "Unidad de red"
Case 4
vTipo = "Unidad de CD-ROM"
Case 5
vTipo = "Disco RAM"
End Select
'Informamos del tipo
MsgBox "El tipo de la unidad 'C:' es: " & vTipo, vbInformation, "INFO"
End Sub

Fijaos que a través del SELECT CASE podemos ver qué valor devuelve vTipoDrv, y cuál es su
correspondencia.

17
Visítame en http://siliconproject.com.ar/neckkito/
Obtenemos el total de espacio y el espacio disponible
El código que nos informaría de lo anterior sería:


Private Sub cmdTamaños_Click()
'Declaramos las variables
Dim fso As Scripting.FileSystemObject
Dim drv As Scripting.Drive
Dim vTotal As Long, vDisponible As Long, vLibre As Long
'Creamos el objeto fso
Set fso = CreateObject("Scripting.FileSystemObject")
'Creamos el objeto drv
Set drv = fso.GetDrive("C:\")
'Obtenemos el tamaño total de la unidad "C:"
vTotal = FormatNumber(drv.TotalSize / 1024, 0)
'Obtenemos el tamaño disponible de la unidad
vDisponible = FormatNumber(drv.AvailableSpace / 1024, 0)
'Obtenemos el tamaño libre de la unidad
vLibre = FormatNumber(drv.FreeSpace / 1024, 0)
'Informamos de los tamaños
MsgBox "El tamaño total de la unidad 'C:' es " & vTotal & " Kb" & vbCrLf & vbCrLf _
& "El tamaño disponible es " & vDisponible & " Kb" & vbCrLf & vbCrLf _
& "El tamaño libre es " & vLibre & " Kb", vbInformation, "INFO"
End Sub

He aprovechado para mostrar, en el código, las propiedades AvailableSpace y FreeSpace. A


nuestros efectos son propiedades sinónimas, por lo que bastaría haber empleado una de las
dos.

Obtenemos la disponibilidad de la unidad


Para este apartado supondremos que queremos saber la disponibilidad de una hipotética
unidad “J:\”. Podemos encontrarnos con tres situaciones:

1.- Que la unidad exista y esté disponible


2.- Que la unidad exista pero no esté disponible (porque esté bloqueada por algún motivo)
3.- Que la unidad no exista

Si la unidad no existe se produce el error número 68 en tiempo de ejecución. Nuestro código


deberá llevar, en consecuencia, un control de gestión de errores. Dicho código podría ser:


Private Sub cmdUnidadDisponible_Click()
On Error GoTo sol_err
'Declaramos las variables
Dim fso As Scripting.FileSystemObject
Dim drv As Scripting.Drive
Dim vDisponible As Boolean
Dim vDisp As String
'Creamos el objeto fso
Set fso = CreateObject("Scripting.FileSystemObject")
'Creamos el objeto drv
Set drv = fso.GetDrive("J:\")
'Obtenemos la disponibilidad de la unidad "C:"

18
Visítame en http://siliconproject.com.ar/neckkito/
vDisponible = drv.IsReady
'Analizamos si IsReady es TRUE o FALSE
If vDisponible = True Then
vDisp = "Unidad disponible"
Else
vDisp = "Unidad no disponible"
End If
Mensaje:
'Informamos del estado de la unidad
MsgBox "La situación de la unidad 'J:' es: " & vDisp,
vbInformation, "INFO"
Salida:
Exit Sub
sol_err:
If Err.Number = 68 Then
vDisp = "Unidad no existente"
Resume Mensaje
Else
MsgBox "Se ha producido el error " & Err.Number & " - " & Err.Description
Resume Salida
End If
End Sub

Otras propiedades que podemos utilizar


Para obtener otro tipo de información de la unidad con la que queremos trabajar podemos
utilizar las siguientes propiedades:

• Para saber la letra de la unidad → DriveLetter


• Para saber el sistema de archivos (FAT, NTFS...) → FileSystem
• Para saber el nombre del recurso compartido → ShareName
• Para saber la ruta de la unidad → RootFolder, o también Path

TRABAJAR CON CARPETAS


Para trabajar con carpetas necesitamos otro objeto fso, que es FOLDER. La mecánica es
bastante similar a lo que hemos visto en el apartado anterior: necesitamos definir un objeto
fso y un objeto Folder.

CREANDO UNA CARPETA (Y ALGUNAS COSILLAS MÁS)


Veamos lo anterior a través de un ejemplo. Vamos a crearnos una carpeta donde exportaremos
los datos de TDatos a Excel. La carpeta que crearemos se llamará ExcelTDatos.

El código, por decirlo de alguna manera, no será el “óptimo”, porque lo que interesa es que
podáis ver cómo (con qué propiedades y/o métodos) podemos trabajar con carpetas. Por eso
habrá algunas líneas que, de tratarse de un supuesto para la “vida real”, no tendrían
demasiado sentido. Espero pues que entendáis esta licencia que me permito de no escribir un
código óptimo.

Además, veréis comentarios en casi cada línea, con lo que no creo que tengáis demasiados
problemas en entender qué hace cada una de ellas.

19
Visítame en http://siliconproject.com.ar/neckkito/
El resultado sería el siguiente:


Private Sub cmdCreaCarpetas_Click()
On Error GoTo sol_err
'Declaramos las variables
Dim fso As Scripting.FileSystemObject
Dim cpt As Scripting.Folder 'cpt = carpeta
Dim miRuta As String, miCpt As String, miExcel As String
'Obtenemos la ruta de la BD
miRuta = Application.CurrentProject.Path
'Definimos fso
Set fso = CreateObject("Scripting.FileSystemObject")
'Definimos la carpeta contenedora
miCpt = miRuta & "\ExcelTDatos"
'Creamos la carpeta. Si ya existiera se generaría el error 58. Lo gestionamos
'a través del control de errores
fso.CreateFolder (miCpt)
'--------------El siguiente código es para que veáis el manejo de algunas propiedades y
'--------------métodos de trabajo con carpetas
'-----Obtener la ruta de la carpeta:
Set cpt = fso.GetFolder(miCpt)
MsgBox "La ruta de mi carpeta es " & cpt, vbInformation, "INFO"
'-----Obtener la ruta de la carpeta contenedora de CptInf50
MsgBox "La carpeta se encuentra dentro de " & fso.GetParentFolderName(miCpt), _
vbInformation, "INFO"
'-----Obtener el nombre de la carpeta
MsgBox "El nombre de la carpeta creada es " & fso.GetBaseName(miCpt), _
vbInformation, "INFO"
'-----Obtener en qué unidad está ubicada la carpeta
MsgBox "La carpeta está en la unidad " & cpt.Drive, vbInformation, "INFO"
'-----------------------------------------------------------------------------------------
'Exportamos la tabla a Excel
miExcel = miCpt & "\Edades.xls"
DoCmd.TransferSpreadsheet acExport, acSpreadsheetTypeExcel8, "TDatos", miExcel
'Lanzamos un mensaje de confirmación
MsgBox "Excel creado satisfactoriamente", vbInformation, "OK"
Salida:
Exit Sub
sol_err:
If Err.Number = 58 Then
'Omitimos el error y seguimos con el código
Resume Next
Else
MsgBox "Se ha producido el error " & Err.Number & " - " & Err.Description
Resume Salida
End If
End Sub

ELIMINANDO UNA CARPETA


Para eliminar una carpeta deberemos utilizar el método DeleteFolder. Si intentamos eliminar
una carpeta que no existe nos saltará el error 76, el cual gestionaremos a través de un control
de errores.

20
Visítame en http://siliconproject.com.ar/neckkito/
Importante: fso borra la carpeta con todo su contenido, sin avisarnos de ello. Es por ello
por lo que debemos proceder con mucha precaución a la hora de utilizar DeleteFolder.

En el directorio donde tengamos la BD creamos, manualmente, una carpeta llamada


“Borrame”. A continuación el código que nos borrará esa carpeta sería el siguiente:


Private Sub cmdBorraCarpeta_Click()
On Error GoTo sol_err
'Declaramos las variables
Dim fso As Scripting.FileSystemObject
Dim cpt As Scripting.Folder 'cpt = carpeta
Dim miRuta As String, miCpt As String
'Obtenemos la ruta de la BD
miRuta = Application.CurrentProject.Path
'Definimos fso
Set fso = CreateObject("Scripting.FileSystemObject")
'Definimos la carpeta que queremos borrar
miCpt = miRuta & "\Borrame"
'Borramos la carpeta. Si ya existiera se generaría el error 67. Lo gestionamos
'a través del control de errores
fso.DeleteFolder (miCpt)
'Lanzamos un mensaje de confirmación
MsgBox "La carpeta ha sido borrada correctamente", vbInformation, "OK"
Salida:
Exit Sub
sol_err:
If Err.Number = 76 Then
'Lanzamos un mensaje de advertencia
MsgBox "La carpeta no se puede borrar porque no existe", vbCritical, "NO EXISTE"
Else
MsgBox "Se ha producido el error " & Err.Number & " - " & Err.Description
End If
Resume Salida
End Sub

OTRAS PROPIEDADES QUE PODEMOS UTILIZAR


Algunas otras propiedades que nos permiten sacar información sobre carpetas son:

• Para saber la ruta de la carpeta → cpt.Path


• Para saber el nombre de la carpeta → cpt.Name

21
Visítame en http://siliconproject.com.ar/neckkito/
• Para saber la fecha de creación → cpt.DateCreated
• Para saber la fecha de último acceso a la carpeta → cpt.DateLastAccessed
• Para saber la fecha de la última modificación →
cpt.DataLastModified
• Para saber el tamaño de la carpeta → cpt.Size
• Para mover la carpeta → cpt.MoveFolder
• Para copiar la carpeta → cpt.CopyFolder

TRABAJAR CON ARCHIVOS


Finalmente, podemos trabajar con archivos a través de
FILE.

Vamos a crearnos, en el directorio donde tenemos la BD, un Word, que llamaremos


“Prueba.doc”. Veamos qué operaciones podemos realizar sobre dicho archivo.

Os pongo un código con diferentes opciones, para que veáis alguna de las características de
fso.File


Private Sub cmdTrabajarConArchivos_Click()
On Error GoTo sol_err
'Declaramos las variables
Dim fso As Scripting.FileSystemObject
Dim arch 'As Scripting.File 'arch = archivo
Dim miRuta As String, miArch As String
Dim miAtrib As Integer
'Cogemos la ruta del word (debemos añadir una contrabarra a la ruta)
miRuta = Application.CurrentProject.Path & "\"
'Definimos el Word sobre el que operaremos
miArch = miRuta & "Prueba.doc"
'Creamos el objeto fso
Set fso = CreateObject("Scripting.FileSystemObject")
'Obtenemos el archivo de trabajo. Si no existe obtenemos el error 53.
'Lo gestionamos a través del control de errores
Set arch = fso.GetFile(miArch)
'--------Sacamos algunos datos de nuestro Word
MsgBox "Nombre: " & arch.Name & vbCrLf _
& "Ruta: " & arch.Path & vbCrLf _
& "Tamaño: " & arch.Size & "bytes" & vbCrLf _
& "Fecha creacion: " & arch.DateCreated & vbCrLf _
& "Fecha último acceso: " & arch.DateLastAccessed & vbCrLf _
& "Fecha última modificación: " & arch.DateLastModified, _
vbInformation, "DATOS ARCHIVO"
'--------Obtenemos los atributos del Word
miAtrib = arch.Attributes
Select Case miAtrib - 32
Case 0
MsgBox "Atributo: Normal", vbInformation, "ATRIBUTOS"
Case 1
MsgBox "Atributo: Sólo lectura", vbInformation, "ATRIBUTOS"
Case 2
MsgBox "Atributo: Oculto", vbInformation, "ATRIBUTOS"
Case 3
MsgBox "Atributo: Oculto-Sólo lectura", vbInformation, "ATRIBUTOS"
Case 4

22
Visítame en http://siliconproject.com.ar/neckkito/
MsgBox "Atributo: Archivo de Sistema", vbInformation, "ATRIBUTOS"
Case 32
MsgBox "Atributo: Archivo", vbInformation, "ATRIBUTOS"
Case Else
MsgBox "Atributo: ¿" & miAtrib - 32 & "?",
vbInformation, "ATRIBUTOS"
End Select
'--------Hacemos una copia de nuestro Word
arch.Copy (miRuta & "CopiaPrueba.doc")
'Si en lugar de hacer una copia lo hubiéramos
querido mover emplearíamos
'arch.Move(nuevaRuta & "NuevoNombre.doc")
'--------Eliminamos el archivo
fso.DeleteFile (miArch)
Salida:
Exit Sub
sol_err:
If Err.Number = 53 Then
MsgBox "El archivo " & miArch & " no existe", vbCritical, "NO EXISTE"
Else
MsgBox "Se ha producido el error " & Err.Number & " - " & Err.Description
End If
Resume Salida
End Sub

Resumiendo, en el anterior código podemos ver cómo:

• Sacar datos sobre el archivo


• Sacar los atributos del archivo
• Copiar/Mover el archivo
• Eliminar el archivo

DOS BAGATELAS
Os indico aquí dos pequeños ejemplos de utilidades que quizá os sean prácticas para vuestras
aplicaciones. Ambas se basan en cosas que hemos visto en estos dos capítulos sobre el
manejo de archivos y carpetas, y también descubriréis algunas curiosidades nuevas.

CREAR NUESTRO “DICCIONARIO”


Imaginemos que nuestra BD utiliza abreviaturas en algún campo. Hasta aquí muy bien.
Podríamos tener una tabla con las abreviaturas y su significado. Así, cuando alguien, en un
formulario, deja el puntero del ratón sobre el campo con la abreviatura saldría el “tip” que nos
indicaría qué significa dicha abreviatura.

Pero, en este caso, somos un poco quisquillosos y no queremos que alguien pueda acceder a la
tabla y cambiarnos el significado de las abreviaturas, ni siquiera un administrador de la BD.
Sólo el programador debe tener acceso a esas abreviaturas.

Pues el Scripting Runtime nos proporciona un sistema (que él denomina diccionario), que nos
permitiría conseguir lo anterior.

Veamos cómo podemos hacerlo.

23
Visítame en http://siliconproject.com.ar/neckkito/
1.- Creamos una tabla en la BD que llamaremos TAbrev, con un sólo campo, que llamaremos
[Abrev], de tipo texto y de longitud máxima 4. Podemos dejar que sea clave principal.

La rellenamos con algunas abreviaturas. Por ejemplo, yo he


utilizado las siguientes:

2.- Creamos otra tabla, que llamaremos TInfo. La idea de esta tabla es que tenga los campos
que necesitemos para introducir información en la BD, y donde, lógicamente, habrá un campo,
que a efectos de este ejemplo llamaremos [InfoAbrev], que sacará los datos de TAbrev. Es
decir, que en su tipo de datos elegiremos el asistente para búsquedas y lo configuraremos de
manera que busque, en la tabla TAbrev, los datos del campo [Abrev]. Por ejemplo, mi tabla ha
quedado así:

3.- Nos creamos un formulario, que llamaremos FInfo, basado en la tabla TInfo. A mí me ha
quedado así:

4.- Seleccionamos el campo [InfoAbrev] y en el evento “Al hacer doble click” le generamos el
siguiente código:


Private Sub InfoAbrev_DblClick(Cancel As Integer)
'Declaramos la variable
Dim vAbrev As String
'Asignamos valor a la variable

24
Visítame en http://siliconproject.com.ar/neckkito/
vAbrev = Nz(Me.InfoAbrev.Value, "")
'Si no hay valor salimos del proceso
If vAbrev = "" Then Exit Sub
'Lanzamos un mensaje con el significado
MsgBox fncMiTip(vAbrev), vbInformation,
"SIGNIFICADO"
End Sub

Como vemos, al hacer doble click sobre el campo, si


contiene valor, obtendremos un MsgBox con el significado
de la abreviatura, y ese significado nos viene dado por la
función fncMiTip().

5.- Vamos a crearnos la función. En el editor de VB insertamos un nuevo módulo, que


guardaremos con el nombre de mdlMisAbreviaturas. En él escribimos nuestra función:


Public Function fncMiTip(vAbreviado As String) As String
'Es necesario registrar la referencia "Microsoft Scripting Runtime"
'Declaramos las variables
Dim abv As Object
'Creamos el objeto abv
Set abv = CreateObject("Scripting.Dictionary")
'Añadimos los significados de las abreviaturas
abv.Add "DDCG", "Dar de comer al gato"
abv.Add "DDCP", "Dar de comer al perro"
abv.Add "FP", "Fregar los platos"
abv.Add "LYP", "Lavar y planchar"
abv.Add "SB", "Sacar la basura"
'Examinamos la abreviatura que contiene el campo y asignamos su equivalencia
'como resultado de la función
fncMiTip = abv.Item(vAbreviado)
End Function

Fijémonos que el proceso es el siguiente:

• Creamos un objeto denominado Scripting.Dictionary


• A través del método Add vamos añadiendo los significados de las abreviaturas, según la
estructura: <objeto.Add “abreviatura”, “significado”>
• A través de la propiedad Item() examinamos la abreviatura en cuestión, y obtenemos
su significado, que a su vez asignamos como resultado de la función.

Sé que este ejemplo puede resultar un poco “tonto”, pero quizá su mecánica de
funcionamiento os pueda ser útil en otro contexto.

CREAR UN BACKUP DE LA BD EN TIEMPO DE EJECUCIÓN


Finalmente, vamos a ver cómo podemos realizar un backup de nuestra BD en tiempo de
ejecución.

En un formulario vamos a crearnos un botón de comando. Le asignamos el siguiente código:


Private Sub cmdBackup_Click()

25
Visítame en http://siliconproject.com.ar/neckkito/
Call creaBackup
End Sub

Es decir, vamos a llamar a un procedimiento para que


realice el backup.

En el editor de VB insertamos un nuevo módulo, que


llamaremos mdlBackup, y generaremos el siguiente código:


Public Sub creaBackup()
'-----CREADO POR NECKKITO EL 07/05/12-----
'----------Requiere registrar la librería "Microsoft Scripting
Runtime"-----

On Error GoTo sol_err


'Declaramos las variables
Dim fsc As Scripting.Folder 'fsc=filesystem-Carpeta
Dim fsa As Scripting.File 'fsa=filesystem-Archivo
Dim fso As FileSystemObject ' fso=filesystem-Objeto
Dim rutaFullBD As String, nombreBD As String
Dim rutaBackup As String, nombreBackup As String
Dim fullBackup As String
Dim resp As Integer
Dim yaExisteBackup As Boolean

'Cogemos la información de la BD actual


rutaFullBD = Application.CurrentProject.FullName
'Cogemos el nombre de la BD
nombreBD = Application.CurrentProject.Name
'Definimos la carpeta "Backup", dentro del directorio donde está la BD
'actual, como la ruta predefinida para guardar el backup
rutaBackup = Application.CurrentProject.Path & "\Backups\"
'Definimos el nombre del backup, que será la fecha del sistmea más el nombre de la BD
'A la fecha le damos formato con separador por puntos porque la barra nos
'daría un nombre de archivo no válido. Ponemos antes el mes para conseguir una
'ordenación correcta por fecha
nombreBackup = Format(Date, "mm.dd.yy") & "-" & nombreBD
'Creamos el fullBackup (ruta+nombre+extension)
fullBackup = rutaBackup & nombreBackup
'Lanzamos un aviso diciendo dónde se va a crear la copia de seguridad y qué
'nombre tendrá. Solicitamos confirmación al usuario.
resp = MsgBox("Se va a realizar una copia de seguridad con la siguiente ruta y" _
& " nombre de archivo:" & vbCrLf & vbCrLf & fullBackup _
& vbCrLf & vbCrLf & vbTab & "¿Continuar?", vbQuestion + vbYesNo, "BACKUP")
'Si la respuesta es NO salimos del proceso
If resp = vbNo Then Exit Sub
'Creamos el FileSystemObject
Set fso = CreateObject("Scripting.FileSystemObject")
'Accedemos a la carpeta de copias de seguridad. Si la carpeta no existe
'nos saltará el error 76. Lo gestionamos desde el control de errores
Set fsc = fso.GetFolder(rutaBackup)
'Establecemos la hipótesis de que ya existe una copia de la BD con el mismo nombre
yaExisteBackup = True
'Comprobamos si efectivamente ya existe una copiacon el mismo nombre.

26
Visítame en http://siliconproject.com.ar/neckkito/
'Si el archivo no existe nos saltará el error 53. Lo gestionamos desde
'el control de errores
Set fsa = fso.GetFile(fullBackup)
'Si ya existe el backup informamos y pedimos al
usuario qué desea hacer
If yaExisteBackup = True Then
resp = MsgBox("Ya existe un backup con el mismo
nombre. ¿Desea sobrescribirlo?", _
vbQuestion + vbYesNo, "COPIA EXISTENTE")
'Si el usuario no desea sobreescribir salimos del proceso
If resp = vbNo Then Exit Sub
End If
'Finalmente, guardamos la copia de seguridad
fso.CopyFile rutaFullBD, fullBackup
'Lanzamos un mensaje de aviso
MsgBox "Copia de seguridad realizada correctamente", vbInformation, "CORRECTO"
Salida:
Exit Sub
sol_err:
'Gestionamos los errores 76, 53 y otros.
Select Case Err.Number
Case 76 'La carpeta no existe
'Creamos la carpeta
fso.CreateFolder (rutaBackup)
'Seguimos con la ejecución del código en el punto de interrupción
Resume Next
Case 53 'El archivo no existe
'Cambiamos el valor de yaExisteBackup
yaExisteBackup = False
'Seguimos con la ejecución del código en el punto de interrupción
Resume Next
Case Else 'Si se produce otro error
MsgBox "Se ha producido el error: " & Err.Number & " - " & Err.Description
Resume Salida
End Select
End Sub

Con todo lo que hemos aprendido en este capítulo no deberíamos tener problemas para
entender el código.

Fijaos que el código crea el backup en un subdirectorio llamado Backups de la carpeta donde
tenemos la BD. Eso lo podríamos cambiar cambiando la ruta de la variable rutaBackup,
indicándole la ruta donde queremos las copias de seguridad.

Por lo demás me remito a lo dicho anteriormente: si habéis “hecho los deberes” la


comprensión del código no debería suponer problema alguno.

PARA FINALIZAR ESTE CAPÍTULO


Bueno... Creo que ya somos unos “profesionales” del manejo de unidades de disco, carpetas y
ficheros de nuestro sistema operativo a través de Access. Si queréis profundizar en algunos
elementos que, bajo mi criterio, no he considerado importantes mencionar en el capítulo, en la
ayuda del VBE podéis escribir “FileSystemObject” y hacer click sobre el resultado
“FileSystemObject (método)”, y a partir de ahí, si hacemos click sobre los enlaces de cabecera
(“Vea también”, “Propiedades” y “Métodos”), descubriremos “un maravilloso mundo de luz y

27
Visítame en http://siliconproject.com.ar/neckkito/
color”... je, je...

Un saludo y...

¡suerte!

28
Visítame en http://siliconproject.com.ar/neckkito/
CURSO DE VB

CAPÍTULO 181

Índice de contenido
TIPOS, COLECCIONES Y CLASES..................................................................................................2
INTRODUCCIÓN...........................................................................................................................2
TIPOS..............................................................................................................................................2
COLECCIONES..............................................................................................................................6
DEFINIR UNA COLECCIÓN....................................................................................................6
AÑADIR ELEMENTOS A UNA COLECCIÓN........................................................................7
CONTAR ELEMENTOS DE UNA COLECCIÓN....................................................................7
ELIMINAR ELEMENTOS DE UNA COLECCIÓN.................................................................7
RECORRER LOS ELEMENTOS DE UNA COLECCIÓN.......................................................8
UN EJEMPLO CON TODO LO ANTERIOR............................................................................8
CLASES...........................................................................................................................................9
TRABAJANDO CON LAS CLASES...........................................................................................11
EXPORTANDO EL MÓDULO DE CLASE............................................................................12
SEGUIMOS CON EL TRABAJO CON CLASES...................................................................13
UTILIZANDO LAS PROPIEDADES: PROCEDIMIENTO PROPERTY..............................16
PROPERTY LET..................................................................................................................16
PROPERTY GET.................................................................................................................16
PROPERTY SET..................................................................................................................17
APLIQUEMOS EL PROCEDIMIENTO PROPERTY........................................................17
UNAS BREVES EXPLICACIONES TEÓRICAS BASADAS EN TODO LO ANTERIOR. 21
UN EJEMPLO INTEGRÁNDOLO CON ACCESS.....................................................................23
PARA FINALIZAR ESTE CAPÍTULO........................................................................................31

1 La BD donde están los ejemplos de este capítulo os la podéis bajar aquí.

1
Visítame en http://siliconproject.com.ar/neckkito/
TIPOS, COLECCIONES Y CLASES

INTRODUCCIÓN
El objetivo de este capítulo es llegar a comprender las
clases. Sin embargo, para ello es necesario asimilar dos
conceptos clave: los tipos y las colecciones. Si bien no son
lo mismo (lógicamente) las ideas que subyacen en cada uno
de ellos nos allanarán el camino para llegar a las clases.

Es por esto por lo que empezaremos desarrollando los que son los tipos, cómo se definen, etc.,
y haremos lo mismo con las colecciones (que, además, ya nos debería “sonar de algo” si
hemos seguido todo este curso hasta aquí).

Como utilizaremos los conceptos explicados cuando hablábamos de matrices, si notáis que
tenéis un momento de “debilidad memorística”, quizá sería interesante que tuvierais a mano el
capítulo 9, donde se habla de ellas. 

Os advierto que este capítulo quizá pueda resultaros algo complejo, sobre todo en la parte
final del mismo y en especial en el apartado “Un ejemplo integrándolo con Access”,
básicamente porque se mezclan no sólo cosas de este capítulo sino elementos que hemos ido
explicando (y aprendiendo, supongo ) durante este curso. Pensad que la finalidad es
podernos construir nuestro propio módulo de clase (todo un “lujazo” para nuestra aplicación).

Empecemos pues.

TIPOS
Nuestro buen amigo Access nos dice, refiriéndose a la instrucción Type, lo siguiente: <<Se usa
en el nivel de módulo para definir un tipo de datos definido por el usuario que contiene uno o
más elementos>>.

Vayamos por partes:

• “Se usa a nivel de módulo”. Pues vaya, ya tenemos una primera pista.
• “Tipo de datos”. Sabemos que hay datos numéricos, booleanos, de texto... ¿Qué tipo de
datos será?
• “Definido por el usuario”. Ergo no es un tipo de datos llamémosle “estándar”, sino que
es el usuario el que se lo “saca de la manga”.
• “Contiene uno o más elementos”. Eso me suena a matrices, ¿verdad?

Sigamos. La estructura para definir un tipo es la siguiente:

PUBLIC/PRIVATE TYPE nombreTipo


nombreElemento1 AS tipoDato

nombreElementoN AS tipoDato
END TYPE

OK. Ya tenemos la estructura para definir un tipo. ¿Y los elementos?

2
Visítame en http://siliconproject.com.ar/neckkito/
Pues los elementos deben ser definidos dentro de un procedimiento.

Lo anterior nos lleva a tener que remarcar dos puntos:

• La declaración del tipo NO va dentro de un procedimiento,


sino que debemos situarlo en la cabecera del módulo.
• La declaración de los elementos SÍ va dentro de un
procedimiento.

Vamos a realizar un pequeño ejercicio muy simple para


empezar a entender lo anterior. Después complicaremos un
poco el asunto.

En una BD en blanco vamos a insertar un módulo estándar, que llamaremos mdlDefinoTipos.


Vamos a crearnos un tipo personalizado, que llamaremos CodigoColor. La idea es que nuestra
empresa tiene, como mecanismo de seguridad, un código de colores: a cada color le
corresponde un grado de peligrosidad.

Como hemos comentado antes, la definición de los tipos debe ir en la cabecera del módulo
(debajo de Option Compare Database / Option Explicit). Así, los elementos que componen
nuestro tipo serán dos:

1.- Nombre del color


2.- Significado de peligro

Deberíamos pues definirlo así:


Public Type CodigoColor
nombre As String * 10
significado As String * 25
End Type

Como veis, las variables que indican la definición de los elementos son, en este caso, de tipo
texto String. Además, hemos indicado su longitud máxima (a través de “* 10” y “* 25”).

Segunda fase: rellenar los elementos.

Como es el primer ejemplo “fácil” sólo vamos a rellenar los datos de un color. Como
comentaba antes, una vez entendido lo que hace nuestro código pondremos un ejemplo más
complejo.

Para rellenar los elementos del tipo vamos a utilizar un procedimiento público, que llamaremos
rellenaCodigos. Lo escribiríamos así, inmediatamente debajo de nuestra definición de tipo
personalizado CodigoColor:


Public Sub rellenaCodigos()
'Declaramos una variable, diciéndole que es de nuestro tipo personalizado
Dim vColor As CodigoColor
'Rellenamos los valores
vColor.nombre = "Verde"
vColor.significado = "Sin peligro"

3
Visítame en http://siliconproject.com.ar/neckkito/
'Mostramos los resultados
MsgBox vColor.nombre & vColor.significado, vbInformation, "DATOS"
End Sub

Fijaos en que:

Debo declarar la variable como mi tipo personalizado


Debo indicar a qué elemento me refiero, y rellenarlo,
utilizando la estructura: <variable.elemento = valor>

Si nos situamos en nuestro procedimiento y pulsamos F5


veremos qué nos muestra el MsgBox.

Compliquemos un poco más la cosa. Supongamos que tenemos un listado de países con sus
monedas. El listado que utilizaremos será el siguiente:

1- USA - Dólar
2- España - Euro
3- Japón - Yen

Nos crearemos, en nuestro módulo, un nuevo tipo, al que llamaremos Monedas, de la manera
siguiente:


Public Type tipoMoneda
nombre As String * 10
Moneda As String * 10
End Type

Y nos crearemos un procedimiento llamado usaMoneda, así:


Public Sub usaMoneda(ByVal indice As Integer)
'Declaramos una variable como nuestro tipo, pero en forma de matriz
Dim paisMoneda(1 To 3) As tipoMoneda
'Rellenamos los datos
paisMoneda(1).nombre = "USA"
paisMoneda(2).nombre = "España"
paisMoneda(3).nombre = "Japón"
paisMoneda(1).moneda = "Dólar"
paisMoneda(2).moneda = "Euro"
paisMoneda(3).moneda = "Yen"
'Devolvemos un MsgBox con la información
MsgBox "El país " & paisMoneda(indice).nombre & " usa la moneda " &
paisMoneda(indice).moneda, _
vbInformation, "RESULTADO"
End Sub

Finalmente, nos creamos un formulario en blanco y, en un botón de comando, escribimos el


siguiente código2:

2 El tema de control de errores os lo dejo para vosotros 

4
Visítame en http://siliconproject.com.ar/neckkito/
Private Sub cmdProcedimientousaMoneda_Click()
'Declaramos las variables
Dim vCod As Variant
'Solicitamos al usuario un código de país
vCod = InputBox("Introduzca código del país (número
del 1 al 3)", "CÓDIGO PAÍS", "1")
'Llamamos al procedimiento usaMoneda()
Call usaMoneda(vCod)
End Sub

Pensad que la “gracia” del ejemplo está en entender la


mecánica de cómo funciona la definición de tipos
personalizados.

También pensad que podemos “reutilizar” nuestra definición de tipo para otras acciones (si lo
planificamos bien nos puede salir una definición de tipos bastante “universal”).

Por ejemplo, si creamos otro procedimiento para saber en qué moneda debemos pagar a un
cliente, sabiendo su código de cliente, sólo deberíamos escribir un procedimiento específico
para ello, sin tener que volver a definir otro tipo.

Es decir, en nuestro módulo podemos escribir lo siguiente:


Public Sub clienteMoneda(ByVal Indice As Integer)
Dim cliMon(1 To 3) As tipoMoneda
cliMon(1).nombre = "Meals Co"
cliMon(2).nombre = "Verduras SA"
cliMon(3).nombre = "Arigato"
cliMon(1).moneda = "Dólar"
cliMon(2).moneda = "Euro"
cliMon(3).moneda = "Yen"
MsgBox "Para " & cliMon(Indice).nombre & " debemos pagar usando el " &
cliMon(Indice).moneda, _
vbInformation, "MONEDA DE PAGO"
End Sub

Y el código de nuestro botón sería:


Private Sub cmdProcedimientoclienteMoneda_Click()
'Declaramos las variables
Dim vCod As Variant
'Solicitamos al usuario un código de cliente
vCod = InputBox("Introduzca código del cliente (número del 1 al 3)", "CÓDIGO CLIENTE",
"1")
'Llamamos al procedimiento clienteMoneda()
Call clienteMoneda(vCod)
End Sub

Para finalizar, tened en cuenta que hemos utilizado matrices estáticas para definir los índices
de los datos. No habría ningún problema en utilizar matrices dinámicas si no conocemos, de
antemano, el número de elementos que va a haber en la matriz.

5
Visítame en http://siliconproject.com.ar/neckkito/
COLECCIONES
Durante todo el curso hemos estado viendo, en
determinados capítulos, distintas colecciones. A modo de
recordatorio, hemos visto la colección Fields() de un
recordset, o la colección Properties de los controles. En
definitiva, que si nos paramos a pensar un poco podremos
llegar a afirmar aquella frase de “en ocasiones veo
colecciones”.

¿Y qué es una colección? Nuestro amigo Access la define


así, añadiendo unos comentarios:

<<Un objeto Collection es un conjunto ordenado de elementos a los que se puede hacer
referencia como una unidad.

Comentarios
El objeto Collection permite hacer referencia de forma sencilla a un grupo de elementos
relacionados como un único objeto. Los elementos o miembros de una colección sólo tienen
que estar relacionados por el hecho de existir en la colección. Los miembros de una colección
no tienen que compartir el mismo tipo de dato>>.

Veamos:

• Es un conjunto ordenado de elementos → Ya sabemos que puede contener varios


elementos, de manera ordenada.
• Se puede hacer referencia a los mismos como una unidad → Para entendernos, yo
puedo tener un Porshe, un Mercedes, un Ferrari y un Lexus (por si alguien tenía dudas
esto es un ejemplo, y no tengo ninguno de los cuatro ). Pues bien, cuando quiero
referirme a todos ellos no los cito uno a uno, sino que digo “mis coches”. Luego,
“misCoches” es mi unidad, mi colección.
• Los elementos sólo tienen que estar relacionados por el hecho de existir en la colección
→ Es decir, si yo digo “misCoches”, ¿dónde queda mi Harley Davidson? No es un
elemento porque no he creado ninguna relación con la colección misCoches. Ahora bien,
si yo utilizo otra colección, y la llamo misMediosDeTransporte, ya hemos establecido
relación, por lo que ahora sí estoy citando misCoches y misMotos a la vez.
• Los miembros no tienen que compartir el mismo tipo de dato → Fácil: en mi colección
misMediosDeTransporte mi Lexus es un dato tipo “coche”, pero mi Suzuki es un dato
tipo “moto”, y mi “Boeing” es un dato tipo “avión”... y no olvidemos que sí están todos
en mi colección.

Con todo lo anterior ya vemos que podemos crearnos nuestras propias colecciones.

DEFINIR UNA COLECCIÓN


Para crear una colección debemos definirla como, precisamente, una colección, mediante la
palabra COLLECTION.

En definitiva, que si queremos crearnos la colección misCoches deberíamos declararla así:

Public misCoches As Collection

y definirla así:

Set misCoches = New Collection

6
Visítame en http://siliconproject.com.ar/neckkito/
Nos debería sonar también la idea de que esos dos pasos podemos
resumirlos en uno, declarando la colección de la siguiente manera:

Public misCoches As New Collection

AÑADIR ELEMENTOS A UNA


COLECCIÓN
Para añadir elementos debemos utilizar la palabra ADD.

La estructura sería:

nomColección.Add “Elemento”

o bien

nomColeccion.Add “Elemento”, “Clave”

Por ejemplo, si yo quiero añadir mi coche “Porshe Carrera” a la colección escribiría lo siguiente:

misCoches.Add “Porshe Carrera”

Si, además, quisiera identificar ese coche con una palabra clave, podría añadirla también con
el ADD. Por ejemplo, supongamos que mi clave para el “Porshe Carrera” es “PorCar”; luego
escribiría:

misCoches.Add “Porshe Carrera”, “PorCar”

CONTAR ELEMENTOS DE UNA COLECCIÓN


Para contar los elementos de una colección utilizamos la palabra COUNT.

La estructura sería muy sencilla, como imagino que ya habremos intuido:

nomColeccion.Count

Es decir:

misCoches.Count

ELIMINAR ELEMENTOS DE UNA COLECCIÓN


Para eliminar elementos utilizamos la palabra REMOVE.

Los elementos de una colección tienen todos un índice, que vendría a ser el identificador de la
posición que ocupan en la colección. Sabiendo esto, y también sabiendo que podemos eliminar
un elemento por su palabra clave, la estructura para eliminar sería:

nomColeccion.Remove (índice)

o bien

7
Visítame en http://siliconproject.com.ar/neckkito/
nomColeccion.Remove (“clave”)

Es decir, que si mi “Porshe Carrera” tuviera el índice 6, podría eliminarlo así:

misCoches.Remove (6)

o así

misCoches.Remove (“PorCar”)

RECORRER LOS ELEMENTOS DE UNA


COLECCIÓN
Para recorrer los elementos de una colección debemos utilizar el bucle FOR EACH...NEXT. En
este curso hemos visto en algunos ejemplos cómo se utiliza dicho bucle. Su estructura es:

FOR EACH <elemento> IN <coleccion>


'Código
NEXT <elemento>

Como vamos a ver un ejemplo en el siguiente apartado no insisto más aquí.

UN EJEMPLO CON TODO LO ANTERIOR


Vamos a realizar un pequeño ejercicio con todo lo que acabamos de aprender. Vamos a
crearnos un nuevo módulo, al que llamaremos mdlColeccionCoches.


Public Sub colecCoches()
'Declaramos las variables
Dim cocheBorrado As String
'Declaramos la colección
Dim misCoches As Collection
'Definimos la colección
Set misCoches = New Collection
'Declaramos el contenedor de elementos de la colección
Dim elCoche As Variant
'Añadimos los elementos de la colección
With misCoches
.Add "Porshe Carrera", "PorCar"
.Add "Ford Mustang", "ForMus"
.Add "Mazda RX5", "MazRx5"
.Add "Toyota Celica", "ToyCel"
End With
'Mostramos cuantos coches tenemos
MsgBox "Tengo " & misCoches.Count & " cochazos", vbExclamation, "#COCHES"
'Recorremos la colección
For Each elCoche In misCoches
MsgBox "Un coche es un " & elCoche, vbInformation, "MIS COCHES"
Next elCoche
'Cogemos el valor del coche que vamos a borrar
cocheBorrado = misCoches("PorCar")
'----------------------------------------------
'Esta expresión sería equivalente a la anterior
'cocheBorrado = misCoches.Item(1)

8
Visítame en http://siliconproject.com.ar/neckkito/
'----------------------------------------------
'Borramos el primer coche
misCoches.Remove (1)
MsgBox "¡Oh! No recordaba que el primer coche lo vendí
por un pastón" _
& vbCrLf & vbCrLf & "Ahora sólo tengo " &
misCoches.Count & " 'cochecitos'", _
vbExclamation, "#COCHES"
MsgBox "Quiero decir, que el coche que vendí fue el " &
cocheBorrado, vbInformation, "VENDIDO"
End Sub

Y sólo nos queda, en cualquier formulario, añadirle este código a un botón de comando:


Private Sub cmdLlamaColecCoches_Click()
Call colecCoches
End Sub

Si analizáis el primer código veréis cómo he manipulado la colección y sus propiedades según
lo que os he estado explicando en apartados anteriores. No creo que tenga mayor dificultad
para nuestros ya entrenados cerebros 

Sólo quisiera llamaros la atención sobre algo que no habíamos comentado aún, y que es la
propiedad ITEM. Fijaos que cuando asigno valor a la variable <cocheBorrado> lo hago a través
de su clave, así:

cocheBorrado = misCoches("PorCar")

Pero si no tuviera clave, o mi “guía” fuese el índice del elemento en la colección, podría utilizar
esta otra expresión:

cocheBorrado = misCoches.Item(1)

para hacer referencia al elemento (item) número 1 de la colección.

CLASES
Nuestro amigo Access nos define una clase como <<Definición formal de un objeto. La clase
actúa como plantilla desde la que se crea una instancia de un objeto en tiempo de ejecución.
La clase define las propiedades del objeto y los métodos utilizados para controlar su
comportamiento>>.

Veamos:

• Define un objeto. Eso significaría que lo que nos permite crear son objetos, y en los
capítulos anteriores hemos visto algunas de las muchas cosas que se pueden hacer con
los objetos.

• Actúa como plantilla desde la que se crea una instancia de un objeto. Ya sabemos que
lo que nos estamos creando es una plantilla, y que gracias a esa plantilla vamos a crear
una instancia del objeto. ¿Y qué significa esto? Que nosotros no vamos a trabajar con la
plantilla directamente cuando creemos un objeto, sino que vamos a trabajar con una

9
Visítame en http://siliconproject.com.ar/neckkito/
instancia.

Quizá lo anterior pueda resultar un poco confuso, pero un


ejemplo gráfico creo que nos ayudará: supongamos que yo
creo una clase que se llama “rectángulo”. Cuando necesito
un objeto “rectángulo” llamo a la clase y creo el objeto. En
realidad creo una instancia del objeto: todo el trabajo que
realice se hará sobre esa instancia, no sobre la plantilla. Es
decir:

• Se crea una instancia del objeto en tiempo de ejecución. Lo que significa, para
entendernos, que se crea “mientras tenemos Access en marcha”.

• La clase define las propiedades del objeto y los métodos utilizados para controlar su
comportamiento. Ya sabemos pues que el objeto tendrá propiedades y métodos, y que
podremos establecer sus valores para que la instancia se comporte según nuestros
deseos.

Aunque no hayamos sido conscientes con los ejemplos que hemos ido aprendiendo durante
este curso estábamos realizando este proceso. Por poner un ejemplo “fresco”, ¿recordamos el
capítulo dedicado al “Scripting Runtime”, en concreto el ejemplo de nuestro “diccionario”
(capítulo 17)?

Pues bien, cuando escribíamos:


'Declaramos las variables
Dim abv As Object
'Creamos el objeto abv
Set abv = CreateObject("Scripting.Dictionary")
'Añadimos los significados de las abreviaturas
abv.Add "DDCG", "Dar de comer al gato"

fncMiTip = abv.Item(vAbreviado)

10
Visítame en http://siliconproject.com.ar/neckkito/
Mediante la línea del <Set abv...> estábamos creando la
instancia del objeto de la clase Scripting.Dictionary. La
estábamos creando, evidentemente, en tiempo de ejecución
(no “parábamos” Access para poder crear dicho objeto) y, a
continuación, manipulábamos, en este caso, un método de
la clase, que era ADD, y una propiedad, en este caso, ITEM.

Espero que con lo anterior tengamos meridianamente claro


qué es una clase y los elementos que la componen, porque
es básico para poder comprender lo que veremos en los
apartados siguientes 

TRABAJANDO CON LAS CLASES


Vamos a ver cómo podemos trabajar con una clase personalizada a través de un ejemplo.
Vamos a planificar primero las bases de nuestro proceso:

 La clase se llamará clsVehic, y nos recogerá los vehículos de que disponemos.


 Tendremos tres tipos de vehículos: coches, motos y camiones.
 Cada vehículo tendrá unas características determinadas, que recogeremos a través de
variables públicas.
 Con la información introducida, más información que introduciremos a futuro, en tiempo de
ejecución, nuestro módulo de clase realizará una serie de operaciones.

Voy a explicar el proceso relacionándolo con los puntos anteriores, para que podáis seguir
mejor el hilo de la explicación.

Para cumplir el punto  nos vamos al editor de VB y en el menú Insertar añadimos un módulo
de clase. Lo guardamos como clsVehic.

Para cumplir los puntos  y  vamos a definir las características que necesitaremos. Para ello
escribimos en nuestro módulo de clase lo siguiente:


Option Compare Database
Option Explicit

Public claseVehic As String 'Si es coche, moto o camión


Public nombre As String 'Nombre del vehículo (v.gr., Porshe)
Public tipo As String 'Tipo del vehículo (v.gr., Carrera)
Public cilindrada As Long 'Cilindrada
Public capacDep As Double 'Capacidad del depósito, en litros

Para cumplir con el punto  vamos a necesitar unas funciones que nos calcularan los
resultados. La primera función nos unirá el nombre con su tipo; la segunda función nos
calculará cuánto costaría llenar el depósito en el momento actual (es decir, que el proceso nos
pedirá el importe del litro de combustible en el momento en que necesitemos ese dato).

Así, a continuación de lo anterior escribimos estas dos funciones:


Public Function nomVehic() As String
nomVehic = nombre & " " & tipo
End Function

11
Visítame en http://siliconproject.com.ar/neckkito/
Public Function costeDeposito(ByVal precioLitro As Double) As Currency
costeDeposito = capacDep * precioLitro
If costeDeposito > 150 Then
MsgBox "¡Qué caro! Mejor dejar el vehículo
aparcado!", vbExclamation, "UFFF"
End If
End Function

La primera función nos devuelve una simple concatenación


de valores, separados por un espacio en blanco. Sin
problemas.

En la segunda función se produce una operación matemática de multiplicación. He añadido,


además, que se lance un MsgBox en función del valor obtenido, simplemente para que veáis
que se pueden realizar varias “operaciones” en una misma función.

Ya tenemos pues nuestro módulo de clase creado con todas las características que
necesitábamos. Ese módulo de clase lo podríamos exportar para poder utilizarlo en diferentes
bases de datos que tuviéramos sobre vehículos.

EXPORTANDO EL MÓDULO DE CLASE


Haremos un inciso y aprovecharemos para explicar cómo exportar el módulo de clase. Nos
vamos para ello al VBE y seleccionamos el módulo clsVehic. Hacemos click con el botón
derecho y seleccionamos la opción “Exportar archivo...”

Se nos abrirá una ventana que nos pedirá dónde queremos guardarlo. Como es un módulo de
clase tendrá la extensión cls.

12
Visítame en http://siliconproject.com.ar/neckkito/
Si quisiéramos realizar una importación de clase el proceso sería el mismo (situándonos en
cualquier espacio en blanco en la ventana de proyecto): haríamos click derecho y elegiríamos
la opción “Importar archivo...”

SEGUIMOS CON EL TRABAJO CON CLASES


Prosigamos. Lo explicado hasta ahora de clsVehic estoy seguro que nos recuerda a lo que, al
principio de este capítulo, explicábamos sobre “Definición de tipos”, ¿verdad? Pues ahora, para
poder continuar, vamos a pasar a utilizar nuestro siguiente elemento: las colecciones,
combinándolo esta vez con el módulo de clase.

En nuestro formulario de pruebas añadimos un botón de comando, que llamaremos


cmdListaVehiculos, que llamará al procedimiento misVehiculos(), que crearemos en breve. El
código de ese botón sería, simplemente:


Private Sub cmdListaVehiculos_Click()
Call misVehiculos
End Sub

Ahora sí nos creamos un módulo estándar, al que llamaremos mdlVehic. En él vamos a


crearnos una colección con nuestros vehículos. Así pues, escribiremos el siguiente código 3 (ojo
con el código, que es “intenso” ):


Public Sub misVehiculos()
'Definimos la colección
Set colMisVehic = New Collection
'Declaramos un objeto perteneciente a nuestra clase
Dim unVehiculo As clsVehic
'Declaramos una variable que recogerá los elementos de la colección, y
'que también pertenecerá a nuestra clase
Dim elemVehic As clsVehic
'Creamos una instancia del objeto unVehiculo
Set unVehiculo = New clsVehic
'Rellenamos los datos que necesitamos
With unVehiculo
.claseVehic = "Coche"
.nombre = "Porshe"
.tipo = "Carrera"

3 No tengo ni idea de las características técnicas de los vehículos que voy a introducir. De hecho es posible que introduzca alguna
“salvajada”. En definitiva, que me lo voy inventando a medida que escribo. Por ello, no toméis estos valores como referencias
reales.

13
Visítame en http://siliconproject.com.ar/neckkito/
.cilindrada = 3000
.capacDep = 80.5
End With
'Lo añadimos a nuestra colección
colMisVehic.Add unVehiculo, "PorCar"
'Rellenamos el siguiente vehículo
Set unVehiculo = New clsVehic
With unVehiculo
.claseVehic = "Coche"
.nombre = "Mercedes"
.tipo = "CLK"
.cilindrada = 2800
.capacDep = 85.7
End With
colMisVehic.Add unVehiculo, "MerCLK"
'Rellenamos el siguiente vehículo
Set unVehiculo = New clsVehic
With unVehiculo
.claseVehic = "Moto"
.nombre = "Suzuki"
.tipo = "Bandid"
.cilindrada = 650
.capacDep = 30.3
End With
colMisVehic.Add unVehiculo, "SuzBan"
'Rellenamos el siguiente vehículo
Set unVehiculo = New clsVehic
With unVehiculo
.claseVehic = "Moto"
.nombre = "Ducati"
.tipo = "Storm"
.cilindrada = 1200
.capacDep = 46.2
End With
colMisVehic.Add unVehiculo, "DucSto"
'Rellenamos el siguiente vehículo
Set unVehiculo = New clsVehic
With unVehiculo
.claseVehic = "Camión"
.nombre = "Mercedes"
.tipo = "Pegaso"
.cilindrada = 4000
.capacDep = 120.7
End With
colMisVehic.Add unVehiculo, "MerPeg"
'Ya podemos mostrar la información de la colección a petición del usuario
'No comento esta parte de código porque deberíamos entenderla ya perfectamente
Dim vClaseVehic As Variant
Dim vPrecioCombustible As Variant
Peticion:
vClaseVehic = InputBox("Seleccione qué información desea: 1-Coches; 2-Motos;" _
& " 3-Camiones; 0-Todos", "INFORMACIÓN VEHÍCULOS", 0)
If StrPtr(vClaseVehic) = 0 Then GoTo BorraColeccion
If Not IsNumeric(vClaseVehic) Then
MsgBox "El valor introducido no es válido", vbCritical, "INCORRECTO"
GoTo Peticion
End If
If vClaseVehic < 0 Or vClaseVehic > 3 Then
MsgBox "El valor introducido no es válido", vbCritical, "INCORRECTO"
GoTo Peticion
End If
'Por simplificar suponemos que todos utilizan el mismo tipo de combustible
vPrecioCombustible = InputBox("Introduzca el precio por litro de combustible", "PRECIO")
'También, por simplificar, no introduzco elementos de control para el valor introducido
'Si quisiérais utilizarlos os podéis guiar por los elementos de control que he utilizado
'en el primer InputBox
If StrPtr(vPrecioCombustible) = 0 Then GoTo BorraColeccion
Select Case vClaseVehic
Case 0
For Each elemVehic In colMisVehic
MsgBox "El vehículo tiene las siguientes características:" & vbCrLf & vbCrLf _

14
Visítame en http://siliconproject.com.ar/neckkito/
& "Clase: " & elemVehic.claseVehic & vbCrLf _
& "Nombre: " & elemVehic.nomVehic & vbCrLf _
& "Cilindrada: " & elemVehic.cilindrada & vbCrLf _
& "Capacidad del depósito: " & elemVehic.capacDep & vbCrLf _
& "Precio de llenado del depósito: " & elemVehic.costeDeposito(vPrecioCombustible), _
vbInformation, "DATOS"
Next elemVehic
Case 1
For Each elemVehic In colMisVehic
If elemVehic.claseVehic = "Coche" Then
MsgBox "El vehículo tiene las siguientes características:" & vbCrLf & vbCrLf _
& "Clase: " & elemVehic.claseVehic & vbCrLf _
& "Nombre: " & elemVehic.nomVehic & vbCrLf _
& "Cilindrada: " & elemVehic.cilindrada & vbCrLf _
& "Capacidad del depósito: " & elemVehic.capacDep & vbCrLf _
& "Precio de llenado del depósito: " & elemVehic.costeDeposito(vPrecioCombustible), _
vbInformation, "DATOS"
End If
Next elemVehic
Case 2
For Each elemVehic In colMisVehic
If elemVehic.claseVehic = "Moto" Then
MsgBox "El vehículo tiene las siguientes características:" & vbCrLf & vbCrLf _
& "Clase: " & elemVehic.claseVehic & vbCrLf _
& "Nombre: " & elemVehic.nomVehic & vbCrLf _
& "Cilindrada: " & elemVehic.cilindrada & vbCrLf _
& "Capacidad del depósito: " & elemVehic.capacDep & vbCrLf _
& "Precio de llenado del depósito: " & elemVehic.costeDeposito(vPrecioCombustible), _
vbInformation, "DATOS"
End If
Next elemVehic
Case 3
For Each elemVehic In colMisVehic
If elemVehic.claseVehic = "Camión" Then
MsgBox "El vehículo tiene las siguientes características:" & vbCrLf & vbCrLf _
& "Clase: " & elemVehic.claseVehic & vbCrLf _
& "Nombre: " & elemVehic.nomVehic & vbCrLf _
& "Cilindrada: " & elemVehic.cilindrada & vbCrLf _
& "Capacidad del depósito: " & elemVehic.capacDep & vbCrLf _
& "Precio de llenado del depósito: " & elemVehic.costeDeposito(vPrecioCombustible), _
vbInformation, "DATOS"
End If
Next elemVehic
End Select
'Borramos los elementos de la colección
BorraColeccion:
Dim i As Long
For i = colMisVehic.Count To 1 Step -1 'Realizamos una cuenta atrás
colMisVehic.Remove (i)
Next i
End Sub

Si vemos “desde arriba” el código veremos claramente (¡eso espero!) su estructura:

1.- Definimos las colecciones y los objetos, y los relacionamos, mediante dicha definición, con
el módulo de clase que hemos creado.
2.- Rellenamos los datos del elementos y lo añadimos a la colección.
3.- Repetimos el proceso de rellenado con todos los elementos que queramos.
4.- Solicitamos la información al usuario
5.- En función de lo que quiere ver el usuario (coche, moto o camión), para lo cual utilizamos
un SELECT CASE...END SELECT, recorremos los elementos de la colección a través de un
bloque FOR EACH...NEXT
6.- En dicho bloque operamos con las propiedades de la clase que hemos creado (es decir, las
variables de nuestro tipo) y con los métodos (es decir, con las dos funciones que habíamos
definido en el módulo de clase). En este caso la operación es mostrar un MsgBox con la
información pertinente.

15
Visítame en http://siliconproject.com.ar/neckkito/
7.- Finalizado el proceso, borramos todos los elementos de la colección para dejarla vacía.

Si hubiéramos querido incluso eliminar la colección en sí


hubiéramos añadido, justo antes del “End Sub”, la línea de
código:

Set colMisVehic = Nothing

¿Fácil, no? Je, je... (Ánimo, que con un poco de práctica


esto está hecho!).

UTILIZANDO LAS PROPIEDADES: PROCEDIMIENTO PROPERTY


Y ahora que os he “machacado” con un montón de código es cuando os digo: “vamos a
modificarlo con una cosa nueva” 

La pregunta que nos podría dar origen a esta explicación es: ¿qué pasa si el valor asignado a
capacidad del depósito de combustible no es correcta? Es decir, que, por error, introducimos
una capacidad de 10000 litros (que yo sepa, no hay ningún vehículo, dentro de los tipos de
vehículos sobre los que estamos trabajando, que pueda tener un depósito así: ¡habría más
depósito que vehículo!).

Para realizar un control de este elemento podríamos utilizar el procedimiento PROPERTY

Para que nos entendamos, y hablando en términos generales, un procedimiento PROPERTY


vendría a ser un procedimiento que gestiona las características de una propiedad de nuestra
clase y, por extensión, gestiona los errores (en el sentido de “introducciones erróneas de
valor”) que pudieran producirse.

También nos permite que una propiedad de una clase adopte un comportamiento determinado
en función de los parámetros que reciba.

PROPERTY LET
Mediante la instrucción PROPERTY LET generamos el código encargado de gestionar las
características de la propiedad del elemento definido en la clase.

En abstracto, lo que haría esta instrucción sería:


Public Property Let …
'Si pasa esto haz esto
'Si pasa aquello haz esto otro
'Si pasa eso haz eso otro
End Property

Como vemos, podríamos equipararlo a un procedimiento SUB por su estructura.

PROPERTY GET
Para poder obtener el valor asignado a la propiedad utilizaremos la instrucción PROPERTY GET.

16
Visítame en http://siliconproject.com.ar/neckkito/
Hablando en abstracto, podríamos comparar un PROPERTY GET con una función, dado que
devuelve un valor.

Su estructura sería:


Public Property Get nombre(argumentos) As <tipo>
'Codigo
End Property

PROPERTY SET
Podemos asignar una propiedad mediante el property set. En este caso me remitiré a la ayuda
de Access, que indica que utilizamos el PROPERTY SET para asignar una referencia a un objeto.

Podríamos hacerlo siguiendo la siguiente estructura:


Public Property Set nombre(variableX As Objeto)
Set variableY = variableX
End Property

APLIQUEMOS EL PROCEDIMIENTO PROPERTY


A efectos de ser más claro voy a realizar de nuevo todo el proceso anterior, bautizándolo
ahora, para que nos entendamos, como clsVehic2. Si queréis modificar vuestro código anterior,
pues perfecto, y si queréis crearlo de nuevo, pues también perfecto: así practicaremos y
afianzaremos el estudio de las clases.

Manos a la obra...

Vamos a suponer que no hay vehículo (de los nuestros) que pueda tener un depósito superior
a los 500 litros. Aprovecharemos para comprobar también que no se nos haya “colado” un
depósito con capacidad negativa. El resto de elementos del código serán los mismos.

Creamos un módulo de clase, y lo llamaremos clsVehic2.

En ese nuevo módulo de clase escribimos el siguiente código (ojo, marco en negrita lo nuevo;
el resto es como ya lo habíamos escrito en el ejemplo del apartado anterior):


Option Compare Database
Option Explicit

Const capacidadMax As Double = 500 'Defino la capacidad máxima


Private p_capacDep As Double 'Nueva variable que hace referencia a la propiedad
'que gestionaremos con Property Let. Ojo que es <Private>
Public claseVehic As String 'Si es coche, moto o camión
Public nombre As String 'Nombre del vehículo (v.gr., Porshe)
Public tipo As String 'Tipo del vehículo (v.gr., Carrera)
Public cilindrada As Long 'Cilindrada

Public Property Let capacDep(ByVal capacidad As Double)


'Analizamos los supuestos

17
Visítame en http://siliconproject.com.ar/neckkito/
Select Case capacidad
'Si la capacidad es superior a 500 litros
Case Is > capacidadMax
Err.Raise 666, "clsVehic2", "La capacidad del depósito de
combustible no es real"
'Si la capacidad es negativa
Case Is < 0
Err.Raise 667, "clsVehic2", "La capacidad del depósito no
puede ser menor que cero"
Case Else
'Si no el valor es considerado correcto
p_capacDep = capacidad
End Select
End Property

Public Property Get capacDep() As Double


capacDep = p_capacDep
End Property

Public Function nomVehic() As String


nomVehic = nombre & " " & tipo
End Function

Public Function costeDeposito(ByVal precioLitro As Double) As Currency


costeDeposito = capacDep * precioLitro
If costeDeposito > 150 Then
MsgBox "¡Qué caro! Mejor dejar el vehículo aparcado!", vbExclamation, "UFFF"
End If
End Function

Fijémonos en las principales diferencias:

• La propiedad capacDep ahora ya no es una variable (Public capacDep As Double


'Capacidad del depósito, en litros), sino que viene gestionada por nuestro procedimiento
Property (Public Property Let capacDep(ByVal capacidad As Double))

• La variable que nos hace de “puente” entre Property Let y Property Get la hemos
llamado p_capacDep, pero, atención, la hemos definido como Private, para que su
ámbito de actuación sea sólo a nivel de clase (es decir, que no es accesible desde otros
módulos).

• La operación “puente” es la siguiente:


◦ Property Let gestiona el valor de la variable <capacidad>, pasada como argumento
en el propio procedimiento ((ByVal capacidad As Double)). Si no hay motivo de
error p_capacDep toma el valor de <capacidad> ('Si no el valor es considerado
correcto
p_capacDep = capacidad)
◦ Property Get asigna el valor de nuestra propiedad de clase a p_capacDep, de
manera que si esta tiene valor significa que todas las comprobaciones han sido
correctas (capacDep = p_capacDep)

Y fijémonos también que, si se incumple alguna de las condiciones, generamos nuestros


propios errores a través de4

• Err.Raise 666, "clsVehic2", "La capacidad del depósito de combustible no es real"


• Err.Raise 667, "clsVehic2", "La capacidad del depósito no puede ser menor que cero"

Sigamos: debemos crearnos un nuevo módulo estándar para generar la colección que nos dará
la información de nuestros vehículos. A este nuevo módulo lo llamaremos mdlVehic2. Para

4 Para “refrescar memorias” podéis consultar el capítulo 13 de este curso

18
Visítame en http://siliconproject.com.ar/neckkito/
simplificar sólo rellenaremos un vehículo por tipo y los listaremos, sin pedir otra cosa al
usuario que el precio del combustible.

El código de ese nuevo módulo debería ser:


Option Compare Database
Option Explicit

'Declaramos una colección


Public colMisVehic As Collection

Public Sub misVehiculos2()


'Definimos la colección
Set colMisVehic = New Collection
'Declaramos un objeto perteneciente a nuestra clase
Dim unVehiculo As clsVehic2
'Declaramos una variable que recogerá los elementos de la colección, y
'que también pertenecerá a nuestra clase
Dim elemVehic As clsVehic2
'Creamos una instancia del objeto unVehiculo
Set unVehiculo = New clsVehic2
'Rellenamos los datos que necesitamos
With unVehiculo
.claseVehic = "Coche"
.nombre = "Porshe"
.tipo = "Carrera"
.cilindrada = 3000
.capacDep = 80.5
End With
'Lo añadimos a nuestra colección
colMisVehic.Add unVehiculo, "PorCar"
'Rellenamos el siguiente vehículo
Set unVehiculo = New clsVehic2
With unVehiculo
.claseVehic = "Moto"
.nombre = "Suzuki"
.tipo = "Bandid"
.cilindrada = 650
.capacDep = 30.3
End With
colMisVehic.Add unVehiculo, "SuzBan"
'Rellenamos el siguiente vehículo
Set unVehiculo = New clsVehic2
With unVehiculo
.claseVehic = "Camión"
.nombre = "Mercedes"
.tipo = "Pegaso"
.cilindrada = 4000
.capacDep = 120.7
End With
colMisVehic.Add unVehiculo, "MerPeg"
'Por simplificar suponemos que todos utilizan el mismo tipo de combustible
Dim vPrecioCombustible As Variant
vPrecioCombustible = InputBox("Introduzca el precio por litro de combustible", "PRECIO")
'También, por simplificar, no introduzco elementos de control para el valor introducido
'Si quisiérais utilizarlos os podéis guiar por los elementos de control que he utilizado
'en el primer InputBox
If StrPtr(vPrecioCombustible) = 0 Then GoTo BorraColeccion
'Procedemos a la enumeración de los vehículos
For Each elemVehic In colMisVehic
MsgBox "El vehículo tiene las siguientes características:" & vbCrLf & vbCrLf _
& "Clase: " & elemVehic.claseVehic & vbCrLf _
& "Nombre: " & elemVehic.nomVehic & vbCrLf _
& "Cilindrada: " & elemVehic.cilindrada & vbCrLf _
& "Capacidad del depósito: " & elemVehic.capacDep & vbCrLf _
& "Precio de llenado del depósito: " & elemVehic.costeDeposito(vPrecioCombustible), _
vbInformation, "DATOS"
Next elemVehic

19
Visítame en http://siliconproject.com.ar/neckkito/
'Borramos los elementos de la colección
BorraColeccion:
Dim i As Long
For i = colMisVehic.Count To 1 Step -1 'Realizamos una cuenta atrás
colMisVehic.Remove (i)
Next i
End Sub

Si finalmente creamos en un formulario un botón de


comando deberemos asignarle una llamada al
procedimiento misVehiculos2 con este simple código:


Private Sub cmdListaVehicProperty_Click()
Call misVehiculos2
End Sub

Si hacemos click sobre ese botón (con el formulario en vista formulario) se nos solicitará el
precio del litro de combustible y nos enumerará, a través de MsgBox, nuestros vehículos con
sus características.

Si ahora modificamos la capacidad del depósito de nuestro Porshe (la idea es que nos
equivocamos involuntariamente al determinar dicha capacidad) y escribimos:


With unVehiculo
.claseVehic = "Coche"
.nombre = "Porshe"
.tipo = "Carrera"
.cilindrada = 3000
.capacDep = 800.5
End With

la ejecución de nuestro código nos devolverá lo siguiente:

De la misma manera, si escribimos


.capacDep = -80.5

Obtendremos el siguiente mensaje:

20
Visítame en http://siliconproject.com.ar/neckkito/
UNAS BREVES EXPLICACIONES TEÓRICAS BASADAS EN TODO LO
ANTERIOR
Para profundizar en el marco teórico en base al ejemplo anterior vamos a ponernos metafísicos
y vamos a hablar del alma y del cuerpo. Me vais a permitir esta pequeña licencia porque me
viene “que ni pintada” para que podamos entender lo que os quiero transmitir. Además, ¿quién
os iba a decir que la religión serviría para explicar VBA?5

Supongamos que existe una “esencia universal” (llamémosle Dios, el Uno, el Todo...), y
supongamos que una persona humana, al nacer, está compuesto por un cuerpo físico y un
alma que, en realidad, es una parte de dicha “esencia universal”. Es decir, que dicha alma es
individualizable pero al mismo tiempo es parte de un Todo.

Pues bien, nuestra clase sería esa “esencia universal”. A partir de ahí podemos crear las
“personas” (=objetos) que necesitemos. Si yo creo el objeto <miVehiculo> y creo el objeto
<tuVehiculo> el “cuerpo físico” de <miVehiculo> es diferente y diferenciable de <tuVehiculo>,
pero sus almas, aunque diferentes, son partes de una “esencia universal” (= clase).

<miVehiculo> y <tuVehiculo> se denominan, en jerga informática, ejemplares de clase.

Bajemos un nivel: mi código VB y yo tomamos una gran decisión: vamos a tener un hijo. A
este hijo le vamos a poner de nombre <elHijoVehículo>.

La pregunta que nos hacemos en este punto es: ¿existe <elHijoVehículo>? Y la respuesta es:
sí existe “a nivel mental”, porque mi código y yo así lo hemos decidido, pero aún no existe a
nivel físico porque aún no ha sido concebido.

Y por fin vamos a poner sobre la mesa y en conjunción todo este planteamiento con lo que
acabamos de programar en el ejemplo anterior:

1.- Creamos la “esencia universal” → Eso se refleja en la creación de la clase (creación del
módulo de clase).

2.- Decidimos tener un hijo → Eso se refleja en la declaración de la variable.

Dim elHijoVehiculo As clsVehic

En este momento <elHijoVehiculo> no es nada. Siguiendo el símil, es “la expresión de un


deseo”.

5 Os ruego que toméis esta explicación únicamente como lo que es: una explicación. En ningún momento pretendo entrar en
valoraciones, ni críticas, ni desprecios, ni comparaciones. Al contrario, estos temas merecen todo mi respeto. Por favor, que nadie
le busque “cinco pies al gato” porque lejos de mí está actuar con un ánimo negativo, sino ser lo más claro posible en la explicación.

21
Visítame en http://siliconproject.com.ar/neckkito/
3.- Concebimos <elHijoVehículo> → Eso se produce cuando hacemos

Set elHijoVehiculo = New clsVehic

Pero mucho cuidado: si en ese momento nos mezclan


nuestro recién nacido con otros bebés no vamos a ser
capaces de decir “¡Este es el mío!”. Nos hace falta “algo”,
¿verdad?

4.- Nos fijamos en los rasgos de nuestro “hijo” para saber


que es nuestro → Eso se produce cuando definimos las
propiedades (me vais a permitir la licencia de inventarme
dichas propiedades simplemente para que “queden mejor”
en el ejemplo).

With elHijoVehiculo
.Pelo = “Pelón”
.Nariz = “Como la del padre”
.Peso = “3 Kg”
.Lloro = “Muy llorón”
End With

En definitiva, que a través de todo lo anterior ya deberíamos tener muy claro todo el proceso
y, si nos falla algo, tendremos una “guía de pasos” para “detectar” en qué fase podría estar el
error.

Sigamos un poco más. Si os fijáis en el código, en la parte “repetitiva” con el comentario


'Rellenamos el siguiente vehículo
Set unVehiculo = New clsVehic
With unVehiculo

Veréis que en cada adición estamos escribiendo la línea de código:

Set unVehiculo = New clsVehic

¿Por qué es necesario escribirla?

Imaginemos que yo tengo una clase, clsBebe, y defino un ejemplar de clase así:

Set miBebe = New clsBebe

y le asigno valores a las propiedades:

With miBebe
.Caracter = “Dulce”
.Ojos = “Llorones”
End with

Ya tenemos a miBebe definido. A continuación creo otro miBebe:

Caso A: rellenando directamente las propiedades (sin el Set miBebe = New clsBebe)

With miBebe

22
Visítame en http://siliconproject.com.ar/neckkito/
.Caracter = “Gruñón”
.Ojos = “Desafiantes”
End with

Caso B: rellenando las propiedades pero antes pasando por


el Set

Set miBebe = New clsBebe


With miBebe
.Caracter = “Gruñón”
.Ojos = “Desafiantes”
End with

¿Qué hemos obtenido en realidad según el caso?

Caso A → NO hemos obtenido otro bebé, sino que lo que hemos hecho es mostrar mi bebé en
su adolescencia

Caso B → Sí hemos obtenido otro bebé.

Es decir, que en el caso A no hemos creado uno nuevo, sino que, en el mismo bebé, hemos
cambiado sus propiedades.

En el caso B teníamos un bebé; a continuación este ya ha dejado de existir para ser sustituido
por otro miBebe, con otras propiedades diferentes.

¿Vemos la diferencia?

Para mayor abundamiento os propongo que en el código anterior (en el procedimiento “Public
Sub misVehiculos”) convirtáis en comentarios todos los <Set unVehiculo = New clsVehic>,
exceptuando el primero, y ejecutéis el código. Así podréis ver qué grata sorpresa nos llevamos.

UN EJEMPLO INTEGRÁNDOLO CON ACCESS


Desarrollemos un ejemplo con todo lo anterior, integrándolo con información que tengamos
almacenada en Access. En este ejemplo no utilizaré las colecciones porque creo que con lo
explicado anteriormente deberíamos tenerlo claro. Digamos que “le daré un enfoque diferente”.

Dado que quizá ya entremos en terrenos algo complejos os recomiendo que no sólo analicéis
los códigos que vendrán, sino también cómo se realiza el planteamiento del problema. Ni que
decir tiene que si el código es técnicamente perfecto pero el problema no está bien planteado y
planificado los resultados no van a ser satisfactorios.

He aquí el problema:

<<Nuestra empresa es una empresa de alquiler de coches. En nuestra flota de automóviles


tenemos una serie de modelos que se adaptan a unas características que nosotros hemos
generalizado. La idea es que nuestro cliente que venga a alquilar un coche pueda elegir una
combinación de esas características y, en un momento, podamos darle a escoger una serie de
vehículos que cumplan dichas características, a la vez que le proporcionamos información
complementaria actualizada que puede ser de mucho interés>>

Vayamos analizando los elementos de que disponemos al tiempo que creamos nuestras tablas
en Access.

En primer lugar, crearemos una tabla que nos recogerá los modelos y las características que

23
Visítame en http://siliconproject.com.ar/neckkito/
necesitamos. A esa tabla la llamaremos TAutos. Tendrá la siguiente estructura (algunos
campos se podrían haber desglosado en dos o más, pero los he unificado a efectos de
simplificar al máximo y no hacer el ejemplo engorroso en demasía):

Fijaos que para los campos [GamaAuto], [TerrenoAuto] y [TipoCombAuto] lo ideal hubiera sido
crearnos tablas auxiliares con la información, y que nuestra TAuto hubiera “chupado” los datos
de dichas tablas auxiliares. Sin embargo, a efectos de simplificación, nosotros escribiremos los
valores directamente.

Apunto lo anterior para que lo tengáis en cuenta de cara a vuestras BD's.

Rellenamos TAutos con algunos datos6; por ejemplo, así:

Hagamos notar que el consumo medio se refiere al consumo en litros cada 100 kilómetros (por
si no quedaba claro qué significaba ese valor).

Avancemos un poco los acontecimientos:

• Nuestro cliente va a decidir qué gama de coche desea → Recurriremos a [GamaAuto]


• Nuestro cliente va a decidir en qué tipo de terreno va a utilizar el coche → Recurriremos
a [TerrenoAuto]
• Nuestro cliente nos va a informar de cuántos kilómetros prevé realizar → Sabemos qué
combustible utiliza el coche ([TipoCombAuto]) y cuál es su consumo medio por
kilómetro ([ConsumMedAuto]) → Recurriremos a una tabla auxiliar donde informaremos
del precio por litro de combustible, y algún trabajador nuestro actualizará la tabla en
cuanto se produzcan modificaciones en dichos precios.
• El precio de alquiler incluye ya seguro. Sin embargo, ofreceremos al cliente la
posibilidad de efectuar el pago de una sobreprima de seguro, que irá en función de la
gama del auto, y que ofrecerá coberturas extras (que la política de empresa decidirá en
función de lo que haya negociado con la compañía aseguradora) → Requeriremos de
una tabla auxiliar que nos relacione la gama con el precio de la sobreprima.

6 Los datos que veréis son totalmente inventados. No toméis estos datos como datos “de la vida real”.

24
Visítame en http://siliconproject.com.ar/neckkito/
• El precio del alquiler irá en función de la gama del vehículo y de los días de alquiler.
Nuestro cliente nos indicará cuántos días va a necesitar el vehículo → Requeriremos de
una tabla auxiliar que nos dé los precios por día, según la gama, y que un trabajador
nuestro actualizará tan pronto como la empresa decida modificar esos
precios.

Con lo anterior ya podemos empezar a vislumbrar qué


necesidades tenemos, ¿verdad? Empecemos pues por
crearnos nuestras tablas auxiliares.

Crearemos la tabla TPrecioComb, que tendrá la siguiente


estructura:

Y la rellenaremos con los siguientes datos:

Crearemos la tabla TPrimas con la siguiente estructura:

Y la rellenaremos con los siguientes datos:

Crearemos la tabla TPrecios con la siguiente estructura:

Y rellenaremos los datos así:

25
Visítame en http://siliconproject.com.ar/neckkito/
Espero que, hasta aquí, no nos hayamos perdido... je, je...

Vamos a la siguiente fase, que será crearnos la clase.


Insertamos pues un nuevo módulo de clase que llamaremos
clsAutos.

Veréis que he programado un procedimiento PROPERTY para comprobar que no pueda haber
un consumo medio negativo o igual a cero.
Eso lo podría haber controlado a través del campo [ConsumMedioAuto] de la tabla TAutos, con
una regla de validación, pero como la idea es practicar las clases lo controlaremos desde aquí.

Las explicaciones están como comentarios en el propio código.

Y, por fin, el código de clsAuto nos queda así:


Option Compare Database
Option Explicit

'Declaramos las variables


Private p_consumMedio As Double
Public nombreCoche As String
Public gamaCoche As String
Public terrenoCoche As String
Public tipoCombCoche As String

'Controlamos que el consumo medio no sea negativo ni cero


Public Property Let consumMedioCoche(ByVal consumoMedio As Double)
'Analizamos los supuestos
If consumoMedio <= 0 Then
Err.Raise 666, "clsAuto", "El consumo medio no puede ser negativo o cero"
Else
p_consumMedio = consumoMedio
End If
End Property

'Obtenemos el valor del valor puente ya depurado


Public Property Get consumMedioCoche() As Double
consumMedioCoche = p_consumMedio
End Property

'Establecemos el coste del alquiler según los días requeridos


Public Function alquilerCoche(numDias As Integer, precioDiario As Currency) As Currency
alquilerCoche = numDias * precioDiario
End Function

'Establecemos el coste de la sobreprima


Public Function sobreprimaCoche(importePrima As Currency, numDias As Integer) As Currency
sobreprimaCoche = (importePrima * numDias) * 10 / 365
End Function

'Establecemos el coste previsto en gasolina


Public Function gasolinaCoche(numKilometros As Long, precioComb As Currency) As Currency
gasolinaCoche = (p_consumMedio / 100) * numKilometros * precioComb
End Function

Como vemos, nuestra clase recogerá toda la información que queremos proporcionar al cliente.

26
Visítame en http://siliconproject.com.ar/neckkito/
Para clarificar ideas digamos que:

• El procedimiento Property (Let y Get) comprueba


consumMedioCoche
• alquilerCoche nos proporciona el precio total del alquiler
• sobreprimaCoche nos proporciona el importe de esa prima
adicional. Obviamente la fórmula me la he inventado, no es
ninguna “receta mágica” 
• gasolinaCoche nos proporciona el consumo en función del
consumo por kilómetro, del número de kilómetros y el
precio por litro de carburante.

Siguiente fase: creemos un módulo estándar. A ese módulo estandar lo llamaremos mdlAuto.
¿Qué va a hacer ese módulo? Lo que va a hacer es un doble proceso, adornado con algunas
operaciones adicionales:

Uno: va a recoger toda la información de nuestra tabla TAutos y nos va a rellenar los datos
correspondientes a la clase

Dos: va a plasmar esos datos en una tabla temporal, para tener una base donde filtrar según
las necesidades del cliente y a través de la cual podremos preparar un informe para
entregárselo a nuestro seguro consumidor.

El código de ese módulo será el siguiente:


Option Compare Database
Option Explicit

'Requiere tener registrada la referencia "Microsoft DAO 3.6 Object Library" o


'módulo equivalente----------------------------------------------------------

'Declaramos las variables del módulo


Private Const nomTablaAux As String = "TAuxInfoCliente"
Private dbs As DAO.Database
Private rstIN As DAO.Recordset
Private rstOUT As DAO.Recordset
Private tbl As Object
Private miSql As String
Private miCoche As clsAuto
Private buscoPrecioComb As Currency
Private buscoPrima As Currency
Private buscoPrecioAlquiler As Currency

'Creamos el procedimiento que nos generará la tabla


Public Sub creoTAux(numKm As Long, diasAlq As Integer)

'Creo un control de errores por si se produce algún error inesperado


On Error GoTo sol_err

'Defino la BD de trabajo
Set dbs = CurrentDb
'En primer lugar debemos comprobar si la tabla ya existe. Si existe debemos
'borrarla para que no nos dé error de código
For Each tbl In CurrentData.AllTables
If tbl.Name = nomTablaAux Then
DoCmd.DeleteObject acTable, nomTablaAux
Exit For
End If
Next tbl
'Creamos la estructura de la tabla a través de una SQL. La escribo estructurada
'en líneas para que apreciemos perfectamente los campos que se crearán
'Para relacionarlo con el código posterior, tened en cuenta que:

27
Visítame en http://siliconproject.com.ar/neckkito/
'Nombre -> Será el Field(0); Gama -> Será el Field(1); ... ; PrecioAlquiler ->
'Será el Field(6)
miSql = "CREATE TABLE " & nomTablaAux & " (" _
& "Nombre STRING," _
& "Gama STRING," _
& "Terreno STRING," _
& "TipoCombustible STRING," _
& "PrevGtoComb CURRENCY," _
& "Sobreprima CURRENCY," _
& "PrecioAlquiler CURRENCY" _
& ")"
'Creamos la tabla a través de la ejecución de la SQL
dbs.Execute miSql

'Defino los recordset


Set rstIN = dbs.OpenRecordset("TAutos", dbOpenForwardOnly)
Set rstOUT = dbs.OpenRecordset("TAuxInfoCliente", dbOpenTable)
'Aquí el proceso va a ser doble: vamos a recorrer los registros de TAutos. Situados
'en un registro concreto, vamos a crear los elementos de la clase. Una vez creados
'los vamos a traspasar a nuestra tabla auxiliar
With rstIN
Do Until .EOF
'Creo un ejemplar de clase
Set miCoche = New clsAuto
'Relleno los datos 'fijos' con la información del registro
miCoche.nombreCoche = .Fields("NomAuto").Value
miCoche.gamaCoche = .Fields("GamaAuto").Value
miCoche.terrenoCoche = .Fields("TerrenoAuto").Value
miCoche.tipoCombCoche = .Fields("TipoCombAuto").Value
miCoche.consumMedioCoche = .Fields("ConsumMedAuto").Value
'Obtengo el origen de los datos variables buscando en las tablas auxiliares
buscoPrecioComb = DLookup("[ImpComb]", "TPrecioComb", "[TipoComb]='" _
& miCoche.tipoCombCoche & "'")
buscoPrima = DLookup("[Prima]", "TPrimas", "[GamaPrima]='" _
& miCoche.gamaCoche & "'")
buscoPrecioAlquiler = DLookup("[Precio]", "TPrecios", "[GamaPrec]='" _
& miCoche.gamaCoche & "'")
'Paso los datos a la tabla auxiliar
rstOUT.AddNew
rstOUT.Fields(0).Value = miCoche.nombreCoche
rstOUT.Fields(1).Value = miCoche.gamaCoche
rstOUT.Fields(2).Value = miCoche.terrenoCoche
rstOUT.Fields(3).Value = miCoche.tipoCombCoche
rstOUT.Fields(4).Value = miCoche.gasolinaCoche(numKm, buscoPrecioComb)
rstOUT.Fields(5).Value = miCoche.sobreprimaCoche(buscoPrima, diasAlq)
rstOUT.Fields(6).Value = miCoche.alquilerCoche(diasAlq, buscoPrecioAlquiler)
rstOUT.Update
.MoveNext
Loop
End With
Salida:
'Cerramos conexiones y liberamos memoria
rstIN.Close
rstOUT.Close
dbs.Close
Set rstIN = Nothing
Set rstOUT = Nothing
Set dbs = Nothing
Exit Sub
sol_err:
MsgBox "Se ha producido el error " & Err.Number & " - " & Err.Description & vbCrLf & vbCrLf _
& "El origen del error está en " & Err.Source
Resume Salida
End Sub

Fijaos en cómo se me ha ocurrido articular el proceso: como un obrero en medio de dos cintas
de montaje: por una de las cintas (rstIN) llega un paquete (la tabla TAutos), nuestro obrero (la
clase) le pone los “lacitos” para dejarlo “guapo” y lo deposita sobre la otra cinta (rstOUT) listo

28
Visítame en http://siliconproject.com.ar/neckkito/
para servir.

Como curiosidad cabe remarcar que en el control de errores


he añadido que el mensaje nos muestre el origen del error
a través de Err.Source, dado que se manejan bastantes
elementos. Ello nos ayudará a depurar el error, si se
produce, con mayor facilidad.

Como podemos ver, nuestro módulo se ha “simplificado”


bastante en cuanto a cálculos porque dichos cálculos ya los
hemos establecido a través de métodos en nuestra clase.
Así, hemos podido realizar las siguientes acciones:

...
'Relleno los datos 'fijos' con la información del registro
...

para dichos datos 'fijos', y con los datos variables hemos necesitado, simplemente recuperar el
valor de los argumentos a través de las variables buscoXXX y pasárselos al método, a través
de las líneas


'Obtengo el origen de los datos variables buscando en las tablas auxiliares
buscoPrecioComb = DLookup("[ImpComb]", "TPrecioComb", "[TipoComb]='" _
& miCoche.tipoCombCoche & "'")
buscoPrima = DLookup("[Prima]", "TPrimas", "[GamaPrima]='" _
& miCoche.gamaCoche & "'")
buscoPrecioAlquiler = DLookup("[Precio]", "TPrecios", "[GamaPrec]='" _
& miCoche.gamaCoche & "'")


rstOUT.Fields(4).Value = miCoche.gasolinaCoche(numKm, buscoPrecioComb)
rstOUT.Fields(5).Value = miCoche.sobreprimaCoche(buscoPrima)
rstOUT.Fields(6).Value = miCoche.alquilerCoche(diasAlq, buscoPrecioAlquiler)

También fijaos en que los datos “externos”, que deben ser proporcionados por nuestro cliente
(previsión de kilómetros a recorrer y días que quiere alquilar el vehículo) y que, lógicamente,
no sabemos a priori, los paso como argumentos del procedimiento, a través de


Public Sub creoTAux(numKm As Long, diasAlq As Integer)

Sigamos: para el proceso que sigue vamos a necesitar que la tabla auxiliar TAuxInfoCliente
esté creada. Para ello creamos, en el mismo módulo de trabajo mdlAuto, un procedimiento que
eliminaremos tras su ejecución (o, si no queremos eliminarlo, podemos convertir en
comentario), y que será el mismo del código anterior con unas ligerísimas modificaciones
(prácticamente es un copy-paste de un fragmento del código anterior; os marco en negrita lo
que se debe cambiar, que está hacia el final del código):

29
Visítame en http://siliconproject.com.ar/neckkito/
Private Sub creoTablaPorPrimeraVez()
'Creamos la estructura de la tabla a través de una SQL.
miSql = "CREATE TABLE " & nomTablaAux & " (" _
& "Nombre STRING," _
& "Gama STRING," _
& "Terreno STRING," _
& "TipoCombustible STRING," _
& "PrevGtoComb CURRENCY," _
& "Sobreprima CURRENCY," _
& "PrecioAlquiler CURRENCY" _
& ")"
'Creamos la tabla a través de la ejecución de la SQL
CurrentDb.Execute miSql
End Sub

Nos situamos sobre ese Sub y pulsamos F5. Nuestra tabla se creará. Ya podemos borrar el
código, o convertirlo en comentario. Tened en cuenta que:

– Si la tabla ya existe nos dará error de código


– Puede ser que no veamos la tabla creada en Access. Si es así seleccionad cualquier
tabla y pulsad F5 para actualizar. La tabla recién creada nos aparecerá “de la nada”.

Vamos a crearnos un informe sobre la tabla TAuxInfoCliente, y lo llamaremos RAuxInfoCliente.


Eso no tiene mayor complicación ya para nosotros.

Y ya llegamos a la fase en el que el usuario tendrá una participación activa. Vamos a crearnos
un formulario en blanco que llamaremos FInfoCliente. Yo lo crearé con TextBox para simplificar,
pero lógicamente ya sabríamos crear otros controles, como cuadros combinados o cuadros de
lista, para hacerlo más atractivo y eficiente de cara al usuario. Lo que haremos será lo
siguiente:

• Insertar un textBox, que llamaremos txtGama


• Insertar un textBox, que llamaremos txtTerreno
• Insertar un textBox, que llamaremos txtKm
• Insertar un textBox, que llamaremos txtDiasAlq
• Insertar un botón de comando, que llamaremos cmdAbreRAuxInfoCliente

En definitiva, una cosa así:

30
Visítame en http://siliconproject.com.ar/neckkito/
Por cierto, y por si alguien no lo sabe, para establecer el texto de la ayuda contextual debemos
ir a las propiedades del control → Otras → Texto de Ayuda del control

El código que debemos asignar al botón de comando será el


siguiente (ojo: no pondré ningún control para validar la
introducción de los valores para simplificar el código;
vosotros ya deberíais saber qué líneas de código os
servirían para controlar eso):


Private Sub cmdAbreRAuxInfoCliente_Click()
'Declaramos las variables
Dim vGama As String, vTerreno As String
Dim vKm As Long, vDias As Integer
Dim filtroDeseosCliente As String
'Asignamos valor a las variables
vGama = Me.txtGama.Value
vTerreno = Me.txtTerreno.Value
vKm = Me.txtKm.Value
vDias = Me.txtDiasAlq.Value
'Llamamos al procedimiento creoTAux, asignándole los parámetros requeridos
Call creoTAux(vKm, vDias)
'Creo el filtro para abrir el informe
filtroDeseosCliente = "[Gama]='" & vGama & "' AND [Terreno]='" & vTerreno & "'"
'Abro el informe, filtrándolo por las peticiones del cliente
DoCmd.OpenReport "RAuxInfoCliente", acViewPreview, , filtroDeseosCliente
End Sub

Y creo que ya está. Esta vez no diré aquello de... ¿fácil, verdad? 

PARA FINALIZAR ESTE CAPÍTULO


Las clases también nos permiten definir eventos (usaríamos EVENT), llamar a dichos eventos
(tenemos para ello el RAISE EVENT), y algunas cosillas más. Sin embargo, vamos a pararnos
aquí. Creo que ya tenemos bastante material para programar nuestras BD's.

Alguien me podría objetar: “para desarrollar este último ejemplo se podrían haber utilizado
otros métodos, y quizá no tan complicados”, y yo diré: “Totalmente de acuerdo... si sólo nos
centramos en el ejemplo”. Pero la “gracia” de lo anterior es que nuestra futura aplicación no es
sólo darle la información que hemos comentado al cliente, sino que, hipotéticamente, la BD
nos serviría para gestionar nuestro negocio. Y la pregunta es: “Si, por ejemplo, tenemos que
emitir una factura al cliente, ¿deberíamos volver a calcular todo de nuevo mediante código? O,
si por ejemplo, un histórico nos muestra una media de días que se alquila un coche de una
determinada gama, y una media de kilómetros que realiza dicho automóvil, y queremos sacar
una previsión de cara al año siguiente, ¿debemos volver a realizar por tercera vez todos los
cálculos? ¿No es mejor tener los cálculos y datos “clave” recogidos en nuestra clase, y acceder
a ella cuando los necesitemos? Porque así sólo necesitamos programar dichos cálculos una sola
vez, ¿no es cierto?”

Más alla, podemos exportar la estructura básica de la BD (tablas) y módulos de clase y


estándar, e importarlos en otra BD a la que queramos dar otra utilidad. Con ello todo el trabajo
de programación, en lo que a estos elementos se refiere, ya lo tenemos hecho, y si hay que
adaptarlo no debería suponernos el mismo trabajo que empezar de cero.

En fin... que creo que, según cómo sea nuestro proyecto, la utilización de clases es un recurso

31
Visítame en http://siliconproject.com.ar/neckkito/
muy útil... y ahora ya sabemos cómo manejarlo.

Espero que lo que hayáis podido aprender de este capítulo os sea útil.

Un saludo y...

¡suerte!

32
Visítame en http://siliconproject.com.ar/neckkito/
CURSO DE VB

CAPÍTULO 191

Índice de contenido
PERSONALIZAR LA CINTA DE OPCIONES .................................................................................2
INTRODUCCIÓN...........................................................................................................................2
CREANDO LA ESTRUCTURA DE NUESTRA BD.....................................................................3
LA CINTA DE OPCIONES. IDEAS GENERALES.......................................................................4
CÓDIGO XML...........................................................................................................................5
NUESTRA TABLA USysRibbons..............................................................................................5
LOS NOMBRES DE LA CINTA DE OPCIONES.....................................................................6
PLANIFICANDO NUESTRA CINTA DE OPCIONES.................................................................6
EL CÓDIGO XML INICIAL......................................................................................................7
PROGRAMACIÓN DE NUESTRA CINTA COMÚN..............................................................9
¿CÓMO ACTIVAR NUESTRA CINTA COMÚN?.............................................................12
PROGRAMACIÓN DE NUESTRA CINTA DE FORMULARIO..........................................13
¿CÓMO ACTIVAR NUESTRA CINTA DE FORMULARIO?...........................................14
PROGRAMANDO NUESTROS BOTONES CON VBA............................................................15
PREPARATIVOS PREVIOS....................................................................................................15
AHORA SÍ, A POR EL CÓDIGO VBA...................................................................................15
UNAS ÚLTIMAS LÍNEAS SOBRE LA CINTA DE OPCIONES...............................................17
PARA FINALIZAR EL CAPÍTULO.............................................................................................18

1 La BD donde están los ejemplos de este capítulo os la podéis bajar aquí.

1
Visítame en http://neckkito.siliconproject.com.ar
PERSONALIZAR LA CINTA DE OPCIONES

INTRODUCCIÓN
Este capítulo será de aplicación para usuarios de Access
2007 y 2010. Microsoft, a partir de Access 2007, ha
cambiado el sistema de menús que era típico de Access
2003, y lo ha sustituido por la llamada “cinta de opciones”.

Es por ello por lo que si alguno de vosotros aún utilizáis 2003 el


presente capítulo sólo os será de utilidad si pensáis migrar a una
versión superior de Access.

Así como la configuración de menús era extremadamente versátil en Access 2003, en sus
siguientes versiones este tema se ha vuelto un poco más complejo.

Tenemos algunas soluciones accesorias, como la creación de menús a través de macros. Si


estáis interesados en husmear por este tema os recomiendo que analizéis este ejemplo de la
web (aquí).

Más cosas: para poder programar una cinta de opciones personalizada vamos a requerir de
unos mínimos conocimientos de XML (¡no os asustéis: son realmente mínimos! ). Lo que
haremos (entre otras cosas) en general, en este capítulo, será:

1.- Aprenderemos cómo podemos programar nuestra cinta de opciones personalizada.


2.- Aprenderemos cómo “modificar” la cinta de opciones de Access

Dentro del punto 1, veremos:

a.- cómo crear una cinta de opciones para toda la aplicación


b.- cómo crear otra cinta de opciones “especial”, que sólo nos aparecerá cuando abramos un
formulario (ya que la ligaremos a dicho formulario).

Y dentro de los puntos a y b anteriores, veremos cómo:

i.- Aprovechar las funciones de los botones de los que ya dispone Access
ii.- Crearnos nuestros propios botones para que realicen las acciones que nosotros queramos
(y ahí es donde entra en juego nuestra programación en VBA).

Al final del capítulo os indicaré algunos enlaces que he considerado interesantes para que
podáis profundizar en diferentes aspectos del ribbon de Access.

Visto el esquema genérico del capítulo creo que la metodología más efectiva es seguir un
ejemplo y aprovecharlo para ir explicando las características de cada paso.

Por eso, si os parece bien, vamos a crearnos una BD muy simple (para no hacer este capítulo
interminable) desde cero. Esta BD nos servirá para gestionar las entradas/salidas en préstamo
de libros de una biblioteca.

Pongámonos pues manos a la obra:

2
Visítame en http://neckkito.siliconproject.com.ar
CREANDO LA ESTRUCTURA DE NUESTRA BD2
Crearemos una tabla, a la que llamaremos TPrestamos, con
la siguiente estructura:

Con esto nos bastará.

Ahora nos creamos un formulario que nos hará de menú, y que llamaremos FMenu.

A continuación creamos los siguientes objetos:

El formulario FPrestamos, basado en la tabla TPrestamos.


El formulario FHistorial, basado en la tabla TPrestamos, pero en forma de formularios
continuos.

El formulario FPrestamos me ha quedado así:

El código del botón cerrar (cmdCerrar) es


Private Sub cmdCerrar_Click()
DoCmd.Close acForm, Me.Name
DoCmd.OpenForm "FMenu"
End Sub

El formulario FHistorial me ha quedado así

2 La estructura de nuestra BD será mínima y no óptima, pues el objetivo del capítulo no es aprender a estructurar una BD. Por ello,
no toméis esta referencia como lo que debería ser una “BD bien estructurada” (porque no lo es).

3
Visítame en http://neckkito.siliconproject.com.ar
El código del botón cerrar (cmdCerrar) será:


Private Sub cmdCerrar_Click()
DoCmd.Close acForm, Me.Name
End Sub

En el formulario FMenu vamos a insertar:

Un botón (cmdAbreFPrestamos), que nos abrirá FPrestamos preparado para la adición de un


nuevo registro
Un combo (cboDevolucion) que nos abrirá FPrestamos preparado para marcar la devolución del
libro.

El código de cmdAbreFPrestamos será:


Private Sub cmdAbreFPrestamos_Click()
DoCmd.Close acForm, Me.Name
DoCmd.OpenForm "FPrestamos", , , , acFormAdd
End Sub

En el combo, en sus propiedades, nos vamos a la pestaña Datos → Origen de la fila, y


escribimos la siguiente SQL:

SELECT DISTINCT TPrestamos.Titulo FROM TPrestamos;

Y en el evento “Después de actualizar” escribimos el siguiente código:


Private Sub cboDevolucion_AfterUpdate()
DoCmd.OpenForm "FPrestamos", , , "[Titulo]='" & Me.cboDevolucion.Value & "'" _
& " AND [Devolucion]=FALSE"
DoCmd.Close acForm, Me.Name
End Sub

Y, en principio, eso es todo. Veamos cómo podemos construirnos nuestra cinta de opciones
personalizada para nuestra aplicación.

LA CINTA DE OPCIONES. IDEAS GENERALES


Vamos a ver cuáles son los elementos que necesitamos para poder personalizar la cinta de
opciones con nuestros botones.

4
Visítame en http://neckkito.siliconproject.com.ar
CÓDIGO XML
Para programar nuestra cinta de opciones deberemos
hacerlo utilizando código XML. Y para ello vamos a necesitar
un editor de XML. Aunque eso puede hacerse en cualquier
editor de textos (el mismo bloc de notas nos serviría) yo os
recomiento otros editores un poco más “especializados” en
xml. Por ejemplo, el que utilizo yo es el “notepad++”, que
podréis encontrar en la web portableapps (efectivamente,
como habéis intuido, es un programa portable, por lo que
no requiere instalación en nuestro disco duro).

De todas maneras insisto en que hay otros programas “por esos mundos de Dios” que os
pueden servir igualmente bien.

NUESTRA TABLA USysRibbons


El código que escribamos, junto con diferentes identificadores (ya los veremos en su
momento) se guardan en una tabla que crearemos, y que denominaremos USysRibbons.

La tabla USysRibbons es detectada por Access como una tabla de sistema, aunque no de
Access (las tablas de sistema de Access llevan el prefijo MSys), sino de usuario. ¿Cómo
podemos verlas? Pues de la siguiente manera:

• Nos situamos sobre el panel de exploración, en cualquier espacio en blanco, y hacemos


click derecho del ratón. Seleccionamos la opción “Opciones de exploración...” y, en la
ventana que nos aparece, marcamos el check de “Mostrar objetos del sistema”. Si
hecho esto aceptamos veremos cómo nos aparecen todas las tablas de sistema.

¿Y por qué no construirnos nuestra tabla USysRibbons ahora? Pues creamos una tabla nueva, a
la cual, lógicamente, llamaremos USysRibbons, con la siguiente estructura:

5
Visítame en http://neckkito.siliconproject.com.ar
LOS NOMBRES DE LA CINTA DE OPCIONES
Veremos que existe una especie de jerarquía para llamar a
los diferentes elementos de una cinta de opciones.
Básicamente, nos encontraremos con:

La definición de la cinta (ribbon)


La pestaña de la cinta (tab)
El grupo de una pestaña (group)
El nombre de un control personalizado (id) o el nombre de un control estándar (idMso).

Y ahora es cuando aparece la gran pregunta: ¿cómo se llaman los nombres de las cintas, de
los grupos y de los controles estándar? Es decir, a la cinta de opciones que ya existe, ¿qué
nombres les ha puesto Microsoft?

Por ejemplo, la primera pestaña de inicio tiene el curioso nombre de <TabHomeAccess>; el


grupo “Buscar” tiene el nombre <GroupFindAccess>; el control para aplicar filtro se llama
<ApplyFilter>...

Para ver todos estos nombres podéis bajaros estos documentos, con los nombres
exclusivamente para Access3:

– Nombres en Access 2007 (aquí) + Actualización 2007 (aquí)


– Nombres en Access 2010 (aquí).

Si queréis ver los nombres para todas las aplicaciones de Office podéis visitar los siguientes
enlances:

– Nombres Office 2007:


– (http://www.microsoft.com/en-us/download/details.aspx?id=3582)
– Nombres Office 2010:
– (http://www.microsoft.com/en-us/download/details.aspx?id=6627)

PLANIFICANDO NUESTRA CINTA DE OPCIONES


Es un buen ejercicio, antes de empezar a rellenar código XML, plantear cuidadosamente cómo
va a ser nuestra cinta de opciones: qué agrupaciones tendrá, que controles tendrá cada
grupo... Una cinta de opciones pierde parte de su utilidad si el usuario tiene que empezar a
buscar qué botón debe pulsar para realizar una acción si las disposiciones no siguen un poco
de lógica. Sirva esto simplemente como recomendación.

Respecto de nuestra BD de ejemplo vamos a hacer lo siguiente:

• Vamos a crearnos una cinta de opciones común para todos los formularios
• Vamos a crear una cinta específica para el formulario FPrestamos
• Vamos a crear una cinta específica para el formulario FHistorial

3 Los archivos de este ejemplo y que os propongo para su descarga han sido bajados por mí en junio 2012. Es decir, que,
teóricamente (si Microsoft no nos engaña) están actualizados a esa fecha.

6
Visítame en http://neckkito.siliconproject.com.ar
EL CÓDIGO XML INICIAL
Nuestro código va a tener siempre unos elementos
comunes, que van a ser:

La cabecera del código (el namespace): se corresponde con


el CUSTOM UI, con una referencia a Microsoft, y será:

<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui">

A continuación debemos operar con el atributo startFromScratch, que puede adoptar los
valores “true” o “false” (nótense las comillas, pues hay que utilizarlas). Si utilizamos “false”
estaremos indicando que estamos modificando la barra de herramientas estándar; si usamos
“true” es que creamos una desde cero.

Y para ello escribiríamos:

<ribbon startFromScratch="false">

Aprovecho para haceros notar un detalle: cuando escribimos en XML utilizamos la notación
“camello”: es decir, las pablabras se escriben juntas, empezando por minúscula, pero la
primera letra de cada palabra entera (a partir de la segunda) se comienza en mayúscula. Así,
la letra “U” de la palabra “customUI”, por ejemplo, se escribe en mayúscula, dando la
sensación de que es la joroba de un camello. Si son más palabras, como por ejemplo
“startFromScratch”, ya vemos claramente (espero) cómo funciona esta sistemática.

Finalmente, escribimos la estructura jerárquica que comentábamos en el apartado anterior,


siguiendo una sistemática “anidada”. Para indicar que empieza un elemento lo que hacemos es
escribir el nombre del elemento entre <>; para indicar que finaliza dicho elemento lo que
hacemos es escribir de nuevo el nombre del elemento, precedido de una barra inclinada, todo
ello siempre entre <>4.

Más o menos nos debería quedar una cosa así:

<customUI...>
<ribbon startFromScratch="false">
<tabs>
<(elementos del tab y valores)>
<group (elementos del group y valores)>
<button (elementos del button y valores)/>
</group>
</tab>
</tabs>
</ribbon>
</customUI>

Si nos fijamos en el esquema anterior, los interdentados nos muestran el nivel jerárquico. Es
importantísimo cerrar siempre los elementos por orden inverso a su creación. Es decir, que sí
es correcto escribir:

<amo>
4 No puedo aquí explicar cómo funciona el lenguaje XML, lamentablemente. Por ello sólo indicaré las características que necesitemos
para nuestro ribbon.

7
Visítame en http://neckkito.siliconproject.com.ar
<esclavo>
</esclavo>
</amo>

Pero no sería correcto escribir:

<amo>
<esclavo>
</amo>
</esclavo>

Si analizáis la línea del button veréis que parece que no lo


cerramos, pero en realidad sí lo cerramos: podemos utilizar un
cierre en la misma línea de creación. Por ejemplo, podríamos
escribir

<amo/>

Evidentemente, eso lo podemos hacer si no tenemos un nivel inferior de jerarquía, porque si


no, como acabamos de comentar, estaríamos cerrando el “principal” antes que el “secundario”.

Veréis que he puesto (elementos del tab y valores). A continuación os indico en una tabla lo
más común de estos “elementos y valores” para operar con la cinta:

ELEMENTO SUBELEMENTO ¿QUÉ ES? COMENTARIOS


tabs tab id Nombre único de la pestaña
label Nombre que se muestra en la pestaña
visible Muestra u oculta la pestaña “true” o “false”
group id Nombre único del grupo
label Nombre que se muestra en el grupo
control idMso Nombre del control existente de Access
label Nombre que se muestra en el control
enabled Activa o desactiva el control
button id Nombre único del botón “btnNombreBoton”
label Nombre que se muestra en el botón
enabled Activa o desactiva el botón “true” o “false”
imageMso Imagen del botón
size Tamaño del botón “normal” o “large”
onAction Función que se va a ejecutar al hacer “nombreDeLaFuncion”
click sobre el botón
OTROS ELEMENTOS QUE PODEMOS UTILIZAR
dropDown Lista desplegable
comboBox Lista desplegable que admite entradas
manuales
screenTip Texto de ayuda contextual que
aparecerá al pasar el puntero del ratón
sobre el botón

8
Visítame en http://neckkito.siliconproject.com.ar
Vamos a incidir sobre algunos puntos:

• No se pueden ni añadir ni eliminar elementos de la cinta


estándar de Access. Lo máximo que podemos hacer es
llamar a algún elemento de esa cinta predeterminada y
situarlo como no visible.

• El subelemento imageMso hace una llamada a la imagen


predeterminada de los botones de Access que existen. Para
hacer esa llamada necesitamos conocer el nombre exacto
del botón. Podemos recurrir al archivo de nombres cuyo
enlace os indicaba un poco más arriba o podemos recurrir a
otro sistema, que es el siguiente:
◦ Pulsamos el botón de Office (o el menú “Archivo” en 2010)
◦ Opciones de Access
◦ Opción “Personalizar”
◦ En la ventana de la derecha tenemos una lista de controles. Si situamos el puntero
del ratón sobre alguno de ellos y dejamos que nos aparezca la ayuda contextual
veremos que, al final del texto de ayuda y entre paréntesis, nos aparece el nombre
del control: ese es el que debemos utilizar.

• El subelemento imageMso también puede hacer una llamada a una imagen que
hayamos diseñado nosotros mismos para el botón.

PROGRAMACIÓN DE NUESTRA CINTA COMÚN


Si nos fijamos en la parte superior de Access veremos lo siguiente:

9
Visítame en http://neckkito.siliconproject.com.ar
Pronto vamos a ver un elemento más (el nuestro), que llamaremos <Mis opciones>.

Abrimos la tabla USysRibbons y escribimos, en el primer registro, lo siguiente:

Y en el campo [RibbonXML] escribimos el siguiente código:


<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui">
<ribbon startFromScratch="false">
<tabs>
<tab idMso="TabHomeAccess"
visible="false">
</tab>
<tab id="cintaComun"
label="Mis Opciones"
visible="true">
<group id="cintaComunGrupoAccess"
label="Opciones generales Access">
<control idMso="Copy"
label="Copiar datos"
enabled="true"/>
<control idMso="Paste"
label="Pegar datos"
enabled="true"/>
</group>
<group id="cintaComunGrupoMio"
label="Mis opciones personalizadas">
<button id="btnAbreFPrestamos"
imageMso = "FileServerTransferDatabase"
label="Prestar libro"
size="large"
enabled="true"
onAction="subAbreFPrestamos"/>
<button id="btnAbreHistorial"
imageMso = "DataRefreshAll"
label = "Ver historial"
size="large"
enabled = "true"
onAction="subAbreFHistorial"/>
</group>
</tab>
</tabs>
</ribbon>
</customUI>

Analicemos el código:

• La cabecera está compuesta por las líneas comunes que ya conocemos:


<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui">
<ribbon startFromScratch="false">
<tabs>

• La siguiente etiqueta <tab> lo que hace es convertir en no visible la cinta “Inicio”


<tab idMso="TabHomeAccess"

10
Visítame en http://neckkito.siliconproject.com.ar
visible="false">
</tab>

Como vemos, aquí debemos saber que la cinta “Inicio” se


llama “TabHomeAccess” (y para eso podemos recurrir al
fichero para el cual os apuntaba los enlaces más arriba).

Si quisiéramos ocultar no toda la cinta, sino algunos


elementos, deberíamos llegar hasta el nivel que
quisiéramos ocultar. Por ejemplo, si quisiéramos llegar a
ocultar un grupo deberíamos seguir la siguiente estructura:


<tab idMso=”NombreDeLaTab”
visible = “true”>
<group idMso=”NombreDelGrupoAOcultar”
visible = “false”>
</group>
</tab>

Y lo mismo si, más allá del grupo, queremos llegar a un control en concreto.

• La segunda etiqueta tab ya es la definición de nuestra cinta de opciones


<tab id="cintaComun"
label="Mis Opciones"
visible="true">

• A partir de ahí creamos los grupos. Como veis yo he creado dos grupos diferentes, para
que veais cómo manejar los controles de Access y los botones personalizados. Así:

◦ El primer grupo corresponde a los controles de Access


<group id="cintaComunGrupoAccess"
label="Opciones generales Access">
<control idMso="Copy"
label="Copiar datos"
enabled="true"/>
<control idMso="Paste"
label="Pegar datos"
enabled="true"/>
</group>

Como véis, llamo al control a través de <control idMso=”NombreControl”> y manipulo sus


propiedades. Acabado con uno (Copy), empiezo con otro (Paste).

El segundo grupo corresponde a nuestros botones personalizados


<group id="cintaComunGrupoMio"
label="Mis opciones personalizadas">
<button id="btnAbreFPrestamos"
imageMso = "FileServerTransferDatabase"
label="Prestar libro"

11
Visítame en http://neckkito.siliconproject.com.ar
size="large"
enabled="true"
onAction="subAbreFPrestamos"/>
<button id="btnAbreHistorial"
imageMso = "DataRefreshAll"
label = "Ver historial"
size="large"
enabled = "true"
onAction="subAbreFHistorial"/>
</group>

La mecánica es muy parecida al manejo de los controles de Access, sólo que utilizando botones
(control versus button). A los botones les he asignado una imagen (la que me ha gustado
más), recurriendo para ello al nombre que me mostraba el propio Access (tal y como os
explicaba más arriba con el tema buscar los nombres en las opciones de Access). Los
mostraremos como botones grandes (en contraposición a los controles) para que
podáis ver el efecto en la aplicación.

Finalmente, he asignado dos procedimientos (subAbreFPrestamos y subAbreFHistorial). Esos


procedimientos deberemos programarlos, pero esto os lo explicaré un poco más abajo.

• Para acabar completamos el código con todas las etiquetas necesarias de cierre


</tab>
</tabs>
</ribbon>
</customUI>

¿CÓMO ACTIVAR NUESTRA CINTA COMÚN?


La activación de la cinta común es un proceso muy sencillo, lo que algo reiterativo (esas
“cosas” que tiene Access a veces).

El proceso es el siguiente:

1.- Ya tenemos nuestro código en la tabla USysRibbons, pero Access aún no sabe que hemos
creado una cinta personalizada. ¿Qué hacemos?

Salimos completamente de Access y volvemos a entrar.

2.- Una vez reiniciado ahora Access sí ya sabe que existe una cinta de opciones nueva. Vamos
a configurarla:

Hacemos click en el botón de Office (Archivo en Access 2010) → Opciones de Access


→ Base de datos actual, y en el apartado “Opciones de la barra de herramientas y de
la cinta de Opciones” seleccionamos nuestra cinta.

12
Visítame en http://neckkito.siliconproject.com.ar
3.- Ahora Access ya sabe que tiene que mostrar nuestra cinta de opciones, pero aún no ha
“refrescado” esa información. Pues...

Salimos totalmente de Access y volvemos a entrar

4.- Y ahora sí, ya nos aparece nuestra maravillosa cinta personalizada

Nota: si hay algún error de código XML no aparecerá nuestra cinta. Si os pasa eso “no
queda otra” que repasar bien el código de la cinta hasta dar con el error. El “rollo” está en que
para cada modificación tendremos que salir de Access y volver a entrar para ver si lo hemos
corregido correctamente.

PROGRAMACIÓN DE NUESTRA CINTA DE FORMULARIO


El proceso para programar la cinta ligada a un formulario es prácticamente idéntico al que
hemos desarrollado para lo que hemos denominado “cinta común”.

Para la cinta que ligaremos al formulario FPrestamos vamos a realizar lo siguiente:

.- En la tabla USysRibbons escribiremos:

13
Visítame en http://neckkito.siliconproject.com.ar
Siendo el código XML


<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui">
<ribbon startFromScratch="false">
<tabs>
<tab id="cintaFPrestamos"
label="Menú Préstamos"
visible="true">
<group id="toqueteoRegistros"
label="Trabajo con registros">
<control idMso="GoToNewRecord"
label="Nuevo préstamo"
enabled="true"/>
<control idMso="RecordsDeleteRecord"
label="Borrar entrada"
enabled="true"/>
</group>
<group id="operoConFormulario"
label="Opciones de formulario">
<button id="btnCierraFPrestamos"
imageMso = "CloseDocument"
label="Volver a Menú"
size="large"
enabled="true"
onAction="subCierraFPrestamos"/>
<button id="btnAbreHistorial"
imageMso = "DataRefreshAll"
label = "Ver historial"
size="large"
enabled = "true"
onAction="subAbreFHistorialFiltrado"/>
</group>
</tab>
</tabs>
</ribbon>
</customUI>

Os dejo el análisis del código para vosotros, aunque no deberíais tener problemas para
entenderlo. Sólo hay que recordar que debemos programar dos funciones:
subCierraFPrestamos y subAbreFHistorialFiltrado.

¿CÓMO ACTIVAR NUESTRA CINTA DE FORMULARIO?


Como ya habréis intuido, una vez creado el código XML en USysRibbons habrá que salir de
Access para que, al reinicializarlo, cargue la nueva cinta. Una vez hecho esto:

.- Abrimos el formulario FPrestamos en vista Diseño


.- Sacamos sus propiedades → Pestaña Otras → Nombre de banda de opciones, y ahí
seleccionamos “OpcionesFPrestamos”

14
Visítame en http://neckkito.siliconproject.com.ar
Y eso debería ser todo. Si situamos FPrestamos en vista Formulario nos debería aparecer
nuestra cinta. Al cerrar FPrestamos esa cinta debería desaparecer.

Añadir que podemos hacer lo mismo con un informe; es decir, programar una cinta
personalizada para un informe: sólo deberíamos ligarle la cinta correspondiente.

PROGRAMANDO NUESTROS BOTONES CON VBA

PREPARATIVOS PREVIOS
Debemos preparar nuestra BD para que nuestro código VBA pueda interactuar con la cinta de
opciones. Ello implica realizar un paso previos a la programación.

Ese paso consiste en registrar una referencia, dado que nuestro código debe reconocer algunos
códigos que llaman a la cinta de opciones. Para conseguir esto abrimos el VBE (ALT+F11) y
nos vamos a Menú → Herramientas → Referencias..., y buscamos y marcamos el check de la
librería “Microsoft Office 12.0 Object Library” (la versión 14.0 para Access 2010).

Preparativo listo.

AHORA SÍ, A POR EL CÓDIGO VBA


Bueno... nos toca programar un poco con VBA.

Vamos a crear pues una serie de procedimientos, pero debemos tener en cuenta que en todos
los procedimientos que programemos vamos a tener que seguir la siguiente estructura:

Public sub nombreProcedimiento (ByVal control As IRibbonControl)

15
Visítame en http://neckkito.siliconproject.com.ar
De esta manera enlazamos la cinta con el código que escribiremos.

Recapitulemos: de la cinta común tenemos dos


procedimientos:

• subAbreFPrestamos
• subAbreFHistorial

y de la cinta de formulario tenemos dos procedimientos:

• subCierraFPrestamos
• subAbreFHistorialFiltrado

Lo que vamos a hacer es crearnos un módulo estándar, que llamaremos mdlRibbons. Para la
primera función programaremos lo siguiente:


Public Sub subAbreFPrestamos(ByVal control As IRibbonControl)
On Error GoTo sol_err
'Comprobamos que FPrestamos no está ya cargado
If CurrentProject.AllForms("FPrestamos").IsLoaded Then
'Si está cargado avisamos y salimos
MsgBox "El formulario ya está abierto", vbExclamation, "FORMULARIO ABIERTO"
Else
'Si no está cargado cerramos FMenu y lo abrimos para añadir un registro nuevo
DoCmd.Close acForm, "FMenu"
DoCmd.OpenForm "FPrestamos", , , , acFormAdd
End If
Salida:
Exit Sub
sol_err:
MsgBox "Se ha producido el error " & Err.Number _
& " - " & Err.Description
Resume Salida
End Sub

Como veis el código no tiene mayor complicación, salvo por la particularidad que
comprobamos, antes de realizar la acción, si FPrestamos está cargado o no, y actuamos en
consecuencia.

El código para subAbreFHistorial sería:


Public Sub subAbreFHistorial(ByVal control As IRibbonControl)
On Error GoTo sol_err
'Comprobamos que FPrestamos no está ya cargado
If CurrentProject.AllForms("FHistorial").IsLoaded Then
'Si está cargado avisamos y salimos
MsgBox "El formulario ya está abierto", vbExclamation, "FORMULARIO ABIERTO"
Else
'Si no está cargado cerramos FMenu y lo abrimos para añadir un registro nuevo
DoCmd.OpenForm "FHistorial", , , , acFormReadOnly
End If
Salida:

16
Visítame en http://neckkito.siliconproject.com.ar
Exit Sub
sol_err:
MsgBox "Se ha producido el error " & Err.Number _
& " - " & Err.Description
Resume Salida
End Sub

El código de subCierraFPrestamos sería, simplemente:


Public Sub subCierraFPrestamos(ByVal control As IRibbonControl)
DoCmd.Close acForm, "FPrestamos"
DoCmd.OpenForm "FMenu"
End Sub

Y finalmente, el código de subAbreFHistorialFiltrado sería:


Public Sub subAbreFHistorialFiltrado(ByVal control As IRibbonControl)
Dim vLector As String
vLector = Nz(Forms!FPrestamos.Lector.Value, "")
If vLector = "" Then
MsgBox "Debe indicar el nombre de un lector para filtrar su historial"
Else
DoCmd.OpenForm "FHistorial", , , "[Lector]='" & vLector & "'", acFormReadOnly
End If
End Sub

Sólo me queda añadir que, aunque yo no lo he hecho más que en los códigos de la cinta
común (porque los códigos programados son muy simples), es buena costumbre añadir un
control de errores en los códigos (sobre todo si la cinta de opcines es común y no está ligada a
ningún formulario). Si los usuarios “manazas” ya dan problemas con un objeto “normal” de
Access, si le añadimos un menú las consecuencias pueden ser insospechadas... je, je...

UNAS ÚLTIMAS LÍNEAS SOBRE LA CINTA DE OPCIONES


Si hay alguien que se haya entusiasmado con esto de “tener mi propia cinta de opciones”
puede complementar o profundizar sobre la programación de la cinta de opciones en las
siguientes direcciones:

http://office.microsoft.com/es-es/access-help/personalizar-la-cinta-de-opciones-
HA010211415.aspx (cómo crear un ribbon)

http://76areal.wordpress.com/2011/05/10/menus-y-botones-para-access-2007-y-2010/
(ofrece varias opciones adicionales de cómo configurar el menú de las aquí explicadas)

http://www.microsoft.com/en-us/download/details.aspx?id=8640 (ejemplo de Microsoft, donde


podréis ver cómo combinar el ribbon con macros)

http://geeks.ms/blogs/access/archive/2008/04/06/access-2007-cambiar-de-banda-de-
opciones-ribbon-usando-vba.aspx (con cosas curiosas sobre el ribbon que pueden sernos útiles
para nuestra aplicación)

17
Visítame en http://neckkito.siliconproject.com.ar
http://geeks.ms/blogs/access/archive/2009/01/14/el-uso-de-las-cintas-de-opciones-ribbon-y-
xml-lenguaje-de-marcado-extensible-ribbonandxml-01.aspx (para ocultar iconos del menú del
botón de Office)

PARA FINALIZAR EL CAPÍTULO


Si habéis echado un vistazo a los enlaces que os propongo
habréis podido comprobar que el tema de la cinta de
opciones puede “complicarse” bastante, si uno quiere. El
objetivo de este capítulo era simplemente encaminaros y
mostraros el “recibidor” de la casa Ribbon... El resto de
habitaciones deberéis recorrerlas vosotros solos, si queréis
profundizar en el tema.

Espero que lo explicado hasta aquí os sea de utilidad.

Un saludo, y...

¡suerte!

18
Visítame en http://neckkito.siliconproject.com.ar
CURSO DE VB

CAPÍTULO 201

Índice de contenido
UN GRAN EJEMPLO: APRENDEMOS A MANEJAR MÓDULOS Y UNOS CUANTOS
“TRUCOS” MÁS.................................................................................................................................2
INTRODUCCIÓN...........................................................................................................................2
EL EJEMPLO..................................................................................................................................2
PLANIFICANDO LA BD..........................................................................................................2
CONFIGURANDO NUESTRAS RELACIONES.....................................................................3
CREANDO NUESTROS FORMULARIOS..............................................................................4
CERRANDO NUESTROS FORMULARIOS: CREAMOS EL MÓDULO..............................6
FMENU, FPPTO Y NUESTRO MÓDULO...............................................................................6
PARTIDAS DE INGRESO....................................................................................................6
PARTIDAS DE GASTO........................................................................................................9
FMENU, FMOV Y NUESTRO MÓDULO................................................................................9
MOVIMIENTOS DE INGRESO...........................................................................................9
MOVIMIENTO DE GASTO...............................................................................................11
CONFIGURANDO EL INICIO DE LA APLICACIÓN. ¿Y FCHIVATO?..............................12
CONFIGURANDO FCHIVATO..........................................................................................12
CONFIGURANDO EL INICIO DE LA APLICACIÓN.....................................................12
FORMULARIO DE CAMBIO DE AÑO DE TRABAJO........................................................13
¿CUÁNTO HEMOS INGRESADO/GASTADO EN UNA PARTIDA EN PARTICULAR?. .15
CREÁNDONOS UN INFORME FANTASMA.......................................................................18
PARA FINALIZAR ESTE CAPÍTULO........................................................................................22

1 La BD de ejemplo os la podéis bajar aquí.

1
Visítame en http://siliconproject.com.ar/neckkito/
UN GRAN EJEMPLO: APRENDEMOS A
MANEJAR MÓDULOS Y UNOS
CUANTOS “TRUCOS” MÁS

INTRODUCCIÓN
Llegamos al final de este curso. Sé que hay “montones” de
cosas más que explicar, pero si habéis seguido todos los
capítulos (y estudiado un poquito ) deberíais ser
autosuficientes para investigar (entendiéndolo) por vuestra
cuenta.

¿Qué veremos en este capítulo? Vamos a ver un gran ejemplo (“gran” por su tamaño) de cómo
podemos operar a través de módulos y código. Los módulos serán módulos estándar, y el
ejemplo nos permitirá ver cómo podemos “sacarles el jugo”.

Durante el transcurso del ejemplo veremos también un par de “trucos” para conseguir
nuestros objetivos, bajo el lema: si no podemos conseguirlo directamente, demos un rodeo.

Todos los códigos estarán perfectamente comentados, por lo que quizá lo interesante no sea el
código en sí, realmente, sino cómo lo hemos articulado para lograr nuestros fines.
Evidentemente, si hay algo que destacar, os lo indicaré tras el código.

¿Qué cosas vamos a aprender/perfeccionar? Sin que sea una lista exhaustiva, vamos a ver
cómo:

• Reutilizar formularios
• Construir tablas que funcionen sólo cuando las necesitamos, y que desaparezcan
después.
• Utilizar módulos estándar con procedimientos y funciones

¿Y cuál es la idea del ejemplo? La idea del ejemplo es desarrollar una contabilidad
presupuestaria y real doméstica, para controlar nuestros gastos e ingresos. Lo veremos con
detalle en el próximo epígrafe.

EL EJEMPLO
Como os comentaba, vamos a desarrollar una contabilidad doméstica para nuestros gastos e
ingresos. Nos confeccionaremos un presupuesto y después veremos cómo podremos ir
obteniendo una comparativa de los mismos respecto de nuestros gastos reales.

Manos a la obra.

PLANIFICANDO LA BD
Cogemos papel y lápiz y nos dedicamos a pensar un rato. ¿Qué elementos necesito? Pues
parece claro que vamos a necesitar los siguientes elementos:

• Una tabla para los presupuestos


• Una tabla para los movimientos (ingresos o gastos) reales.

2
Visítame en http://siliconproject.com.ar/neckkito/
¿No necesito nada más? Pues así como plantearemos la aplicación no necesito nada más 

Vamos pues a crearnos dichas tablas. La primera, que


llamaremos TPpto, tendrá la siguiente estructura:

Ya tenemos nuestra tabla creada. Ved que le he añadido un campo, [AnoPpto], que nos
permitirá trabajar con diferentes años. Si no añadiéramos este campo deberíamos crear una
BD por cada año.

Como tipo de partida ([TipoPartida]) lo que haremos será establecer una convención: si el
valor es 1 es que hablamos de ingreso; si es 2 es que hablamos de gasto.

¿Y nuestra tabla TMov? Evidentemente TMov será la tabla que nos recogerá los movimientos
reales. Vamos a confeccionarla con la siguiente estructura:

Fijaos que, para esta tabla, hemos indicado que el campo [Partida] sea numérico, porque lo
que nos va a recoger va a ser el [IdPpto], no el nombre de la partida. Veremos más adelante
cómo configurar el formulario para que nosotros “ni nos enteremos” de eso.

En principio no necesitamos nada más.

CONFIGURANDO NUESTRAS RELACIONES


Accedamos a la ventana relaciones, donde añadiremos nuestras dos tablas. Creamos una
relación entre los campos TPpto.IdPpto y TMov.Partida. Cuando nos salga la ventana para
modificar la relación le indicamos que “exigimos integridad referencial”.

La relación debería haberse creado de la siguiente manera:

3
Visítame en http://siliconproject.com.ar/neckkito/
CREANDO NUESTROS FORMULARIOS
Vamos a crearnos una serie de formularios. Empezaremos por nuestro chivato. Es decir,
creamos un formulario en blanco y lo guardamos con el nombre de FChivato. En él incluiremos
un cuadro de texto, que llamaremos txtAnoTrab. Este cuadro de texto nos recogerá el año de
trabajo, que por defecto será el año del sistema.

Añadimos un nuevo cuadro de texto, que llamaremos txtTipoPartida. Este control nos recogerá
la información de si queremos operar con ingresos o gastos.

Nos debería haber quedado algo tan simple como:

Vamos a crearnos ahora otro formulario en blanco, al que llamaremos FMenu. Como habréis
podido imaginar, este será nuestro “panel de control”. Ya iremos añadiendo botones en él con
posterioridad.

Vamos a crearnos un formulario sobre la tabla TPpto, pero en forma de formularios continuos.
Lo llamaremos FPpto. Una vez creado de manera automática lo configuraremos de la siguiente
manera:

• Eliminamos el campo [IdPpto]


• Seleccionamos el campo [TipoPartida] y situamos su propiedad VISIBLE en NO.
Podemos borrar su etiqueta.
• Seleccionamos el campo [AnoPpto] y situamos su propiedad VISIBLE en NO. Podemos
borrar su etiqueta.
• En el encabezado del formulario introducimos una etiqueta y en ella escribimos:
PRESUPUESTO: INGRESOS. A esa etiqueta la llamamos lblTitulo.
• En el encabezado, a la derecha, añadimos un botón para cerrar el formulario. Lo

4
Visítame en http://siliconproject.com.ar/neckkito/
llamaremos cmdCerrar.

Con algo de formato, a mí me ha quedado así:

Vamos a hacer algo similar sobre la tabla TMov, pero en vista de un solo formulario. Una vez
creado nuestro formulario, que guardaremos como FMov, lo configuramos de la siguiente
manera:

• Eliminamos el campo [IdMov]


• Situamos el campo [Partida] con su propiedad VISIBLE en NO
• Para el campo [TipoPart] situamos su propiedad en VISIBLE: NO
• Para el campo [AnoTrab] situamos su propiedad en VISIBLE: NO
• En el encabezado del formulario creamos una etiqueta y en ella escribimos
MOVIMIENTO: INGRESO. A esta etiqueta la llamamos lblTitulo.

Vamos a crearnos un cuadro combinado, que nos servirá para elegir la partida que queremos.
Cuando nos salga el asistente lo cancelamos. Vamos a configurarlo para nuestros propósitos.

Así pues, sacamos las propiedades de ese combo y nos vamos a la pestaña:

• Otras → Nombre, y lo llamamos cboPartida


• Otras → Índice de tabulación, y le indicamos que debe ser el índice 1. De hecho, he
reordenado todos los índices de los campos para que me queden en el siguiente orden:

• Datos → Limitar a la lista: sí


• Datos → Permitir ediciones de lista: no
• Formato → Número de columnas: 2
• Formato → Ancho de columnas: 0cm;3cm

Finalmente, en el encabezado, a la derecha, he añadido un botón de comando que he llamado


cmdCerrar, para salir del formulario.

Con algo de formato a mí me ha quedado así:

5
Visítame en http://siliconproject.com.ar/neckkito/
CERRANDO NUESTROS FORMULARIOS: CREAMOS EL MÓDULO
Como la idea es que los formularios se abran desde FMenu podemos programar un código
común para cerrar los formularios. La idea es la siguiente secuencia:

• Desde FMenu: Cierro FMenu → Abro el formulario correspondiente.


• Desde otro formulario: Cierro el formulario → Abro FMenu

Así pues vamos a crearnos un módulo estándar, que llamaremos mdlCodigos. Si abrimos el
editor de VB (ALT+F11) nos vamos a Menú → Insertar → Módulo

Vamos a escribir el procedimiento de cierre de los formularios. Para ello escribimos este
código:


Public Sub subCierra(nomForm As String)
DoCmd.Close acForm, nomForm
DoCmd.OpenForm "FMenu"
End Sub

Como vemos, le pasamos el nombre del formulario que queremos cerrar como argumento.

FMENU, FPPTO Y NUESTRO MÓDULO

PARTIDAS DE INGRESO
Vamos a situar FMenu en vista diseño y vamos a añadir un botón de comando, que llamaremos
cmdPptoIngreso. Este botón nos servirá para introducir o modificar las partidas de ingreso del
presupuesto del año de trabajo.

¿Cuál es la idea? Que el presupuesto se nos abra pero con la información de las partidas de
ingreso y para el año de trabajo que tenemos estipulado. Pensemos...

6
Visítame en http://siliconproject.com.ar/neckkito/
Lo que vamos a hacer es cambiar el origen del formulario
que, como ya sabemos (¿o no?) se debe cambiar con el
formulario en vista diseño. Lógicamente no podemos, si
estamos trabajando ya como usuario de la BD, modificar
cada vez el origen del formulario (¡eso sería absurdo!).
Vamos a decirle al código VB que haga el “trabajo sucio”
por nosotros.

Pero, ¿cómo vamos a determinar el año de trabajo y el tipo


de gasto? Pues para eso exprimiremos nuestro FChivato. Y
recurriremos a él a través de dos funciones en nuestro
módulo de códigos.

Así pues, en el módulo, insertamos una nueva función que nos determinará el año de trabajo,
y que tendrá este código:


Public Function fncAT() As String
fncAT = Forms!FChivato.txtAnoTrab.Value
End Function

En buena lógica, la función que nos determinará el tipo de partida será la siguiente:


Public Function fncTP() As Long
fncTP = Forms!FChivato.txtTipoPartida.Value
End Function

Aprovecho para recordaros que las funciones devuelven un valor; los procedimientos “hacen
algo” (aunque una función también puede “hacer algo” en medio de su programación).

Sigamos: ¿no nos parece el “ingreso presupuestario” muy similar al “gasto presupuestario”,
exceptuando la diferencia en el tipo de partida? Pues vamos a crearnos un procedimiento en
nuestro módulo para abrir uno u otro en función de un parámetro. ¿Y cuál será? Precisamente
el que marca la diferencia: si es gasto o ingreso.

Pues según lo dicho creemos un nuevo procedimiento en mdlCodigos de la siguiente manera:


Public Sub subAbreFPpto(elTP As Long)
'Declaramos las variables
Dim vAT As String
Dim miSql As String
'Pasamos el valor elTP a FChivato
Forms!FChivato.txtTipoPartida.Value = elTP
'Creamos la SQL que nos dará el origen del formulario
miSql = "SELECT * FROM TPpto" _
& " WHERE TPpto.AnoPpto='" & fncAT() & "'" _
& " AND TPpto.TipoPartida=" & fncTP()
'Abrimos FPpto en vista diseño
DoCmd.OpenForm "FPpto", acDesign
'Cambiamos su origen
Forms!FPpto.RecordSource = miSql
'Lo cerramos guardando los cambios
DoCmd.Close acForm, "FPpto", acSaveYes
'Lo volvemos a abrir en vista normal, pero cambiando el caption
'de lblTitulo en función del tipo de partida
DoCmd.OpenForm "FPpto"

7
Visítame en http://siliconproject.com.ar/neckkito/
If elTP = 2 Then '1=ingreso
Forms!FPpto.lblTitulo.Caption = "PRESUPUESTO: GASTOS"
End If
'Cerramos el formulario de menú
DoCmd.Close acForm, "FMenu"
End Sub

Ahora sí ya tenemos los elementos necesarios para


programar cmdPptoIngreso. Así, el código para el evento
“Al hacer click” de ese botón sería, ya muy simplificado,
sería:


Private Sub cmdPptoIngreso_Click()
Call subAbreFPpto(1)
End Sub

Perfecto. Ya tenemos nuestro FPpto abierto según el tipo de partida. Pero, si queremos añadir
un registro, ¿cómo va a saber qué tipo de partida es y de qué año de trabajo estamos
hablando? Tendremos que hacer algo al respecto.

Así pues, situamos FPpto en vista diseño y en el evento de formulario “Al activar registro”
escribimos el siguiente código.


Private Sub Form_Current()
'Si el registro es un nuevo registro rellenamos el año de trabajo y el tipo de partida
With Me
If .NewRecord Then
.AnoPpto.Value = fncAT()
.TipoPartida.Value = fncTP()
End If
End With
End Sub

Como veis, nuestras funciones empiezan a cobrar mucha utilidad. El código viene a decir que si
lo que hacemos en el formulario es dar de alta un nuevo registro nos rellene automáticamente
los campos [AnoPpto] y [TipoPartida].

Evidentemente, si lo que hacemos es “pasearnos” por los registros el código no actuará y no


se producirá ningún cambio.

El código que nos falta, que es el del botón cmdCerrar, es tan simple como el que sigue:


Private Sub cmdCerrar_Click()
Call subCierra(Me.Name)
End Sub

Pues parece que los ingresos presupuestarios ya los tenemos listos.

8
Visítame en http://siliconproject.com.ar/neckkito/
PARTIDAS DE GASTO
Como nos hemos “pegado el gran currote” en la parte de
ingresos ya tenemos prácticamente hecha la parte de
gasto. Sólo hace falta que añadamos, en FMenu, un botón
de comando, que llamaremos cmdPptoGasto, y en su
evento “Al hacer click” escribiremos el siguiente código:


Private Sub cmdPptoGasto_Click()
Call subAbreFPpto(2)
End Sub

Creo que queda clara la utilidad de utilizar funciones y procedimientos. Pero, por si acaso, lo
practicaremos un poco más en los próximos epígrafes.

 Nota: tened en cuenta que si queréis comprobar el funcionamiento de los botones y de los
códigos es necesario que FChivato esté abierto y con valor para el año de trabajo. Ya veremos
un poco más abajo cómo conseguir que esta apertura se produzca automáticamente.

FMENU, FMOV Y NUESTRO MÓDULO


La operación que vamos a realizar con los movimientos reales va a ser muy parecida a lo que
hemos visto en el epígrafe anterior, pero ahora, además, tenemos el hándicap de que en
nuestro formulario hay un combo que, por ahora, no contiene ningún tipo de información. Al
igual que el origen del formulario, habrá que definir el origen del combo.

MOVIMIENTOS DE INGRESO
Siguiendo con la sistemática aprendida en el epígrafe anterior vamos a programar un
procedimiento en nuestro módulo que nos abra FMov en función de si queremos introducir una
partida de ingreso o de gasto.

Así pues, en nuestro módulo escribiremos el siguiente código (os animo, antes de mirar el
código, a pensar en cómo podría ser dicho código).


Public Sub subAbreFMov(elTP As Long)
'Declaramos las variables
Dim vAT As String
Dim miSql As String
Dim miRce As String
'Pasamos el valor elTP a FChivato
Forms!FChivato.txtTipoPartida.Value = elTP
'Creamos la SQL que nos dará el origen del formulario
miSql = "SELECT * FROM TMov" _
& " WHERE TMov.AnoTrab='" & fncAT() & "'" _
& " AND TMov.TipoPartida=" & fncTP()
'Abrimos FMov en vista diseño
DoCmd.OpenForm "FMov", acDesign
'Cambiamos su origen
Forms!FMov.RecordSource = miSql
'Lo cerramos, guardando los cambios
DoCmd.Close acForm, "FMov", acSaveYes

9
Visítame en http://siliconproject.com.ar/neckkito/
'Lo volvemos a abrir, en vista normal y a punto para introducir
'un nuevo registro
DoCmd.OpenForm "FMov", , , , acFormAdd
'Creamos la SQL para el origen del combo cboPartida
miRce = "SELECT TPpto.IdPpto, TPpto.Partida FROM TPpto" _
& " WHERE TPpto.AnoPpto='" & fncAT() & "'" _
& " AND TPpto.TipoPartida=" & fncTP()
'Cambiamos el RowSource del combo
With Forms!FMov.cboPartida
.RowSource = miRce
'Refrescamos el combo
.Requery
End With
'Si vamos a introducir un gasto cambiamos la caption del título
If elTP = 2 Then
Forms!FMov.lblTitulo.Caption = "MOVIMIENTO: GASTO"
End If
'Cerramos el formulario de menú
DoCmd.Close acForm, "FMenu"
End Sub

¡Listo! Ahora nuestro código en el botón cmdMovIngreso que añadiremos en FMenu se


simplificará así:


Private Sub cmdMovIngreso_Click()
Call subAbreFMov(1)
End Sub

Nos queda, en primer lugar, decirle al formulario que si el registro es un nuevo registro nos
“pase” la información del tipo de partida y el año de trabajo. Para ello sacamos las propiedades
de FMov y en el evento “Al cargar” le generamos el siguiente código:


Private Sub Form_Load()
With Me
If .NewRecord Then
.AnoTrab.Value = fncAT()
.TipoPart.Value = fncTP()
End If
End With
End Sub

En segundo lugar debemos indicar al combo que, una vez se haya seleccionado un valor, lo
pase al campo [Partida]. Para ello, en el evento “Después de actualizar” de cboPartida le
generamos el siguiente código:


Private Sub cboPartida_AfterUpdate()
If Not IsNull(Me.cboPartida.Value) Then
Me.Partida.Value = Me.cboPartida.Value
End If
End Sub

10
Visítame en http://siliconproject.com.ar/neckkito/

Finalmente debemos controlar que todos los datos estén


introducidos correctamente. Si cerráramos sin ese control
se nos crearía un registro en blanco, sólo relleno con los
datos del año de trabajo y el tipo de partida. Así pues, el
código del botón cmdCerrar sería:


Private Sub cmdCerrar_Click()
'Declaramos las variables
Dim hayVacios As Boolean
Dim resp As Integer
'Suponemos que todo está rellenado correctamente
hayVacios = False
'Comprobamos que haya valor en el campo fecha
If IsNull(Me.FechaMov.Value) Then
MsgBox "No ha introducido ninguna fecha", vbInformation, "SIN FECHA"
hayVacios = True
End If
'Comprobamos que haya valor en el combo
If IsNull(Me.cboPartida.Value) Then
MsgBox "No ha introducido ninguna partida", vbInformation, "SIN PARTIDA"
hayVacios = True
End If
'Comprobamos que haya introducido importe
If IsNull(Me.ImpPartida.Value) Then
MsgBox "No ha introducido ningun importe", vbInformation, "SIN IMPORTE"
hayVacios = True
End If
'Si hay campos sin rellenar pedimos al usuario qué hacemos
If hayVacios = True Then
resp = MsgBox("¿Desea salir? Si hay campos vacíos se borrará el registro", _
vbQuestion + vbYesNo, "CONFIRMACIÓN")
'Si sí quiere salir sin guardar los cambios pues deshacemos los cambios
If resp = vbYes Then
Me.Undo
Else
'Si no quiere salir salimos del procedimiento
Exit Sub
End If
End If
'Salimos
Call subCierra(Me.Name)
End Sub

MOVIMIENTO DE GASTO
En FMenu añadimos un nuevo botón de comando, que llamaremos cmdMovGasto, que tendrá
(seguro que lo adivináis) este gran código:


Private Sub cmdMovGasto_Click()
Call subAbreFMov(2)
End Sub

11
Visítame en http://siliconproject.com.ar/neckkito/
CONFIGURANDO EL INICIO DE LA APLICACIÓN.
¿Y FCHIVATO?
Como hemos visto hasta ahora, el funcionamiento correcto
de la aplicación pasa por el hecho de que FChivato esté
cargado para que nuestras funciones tengan de dónde sacar
su valor. Vamos a ver el proceso para conseguir eso de
manera automática.

CONFIGURANDO FCHIVATO
Vamos a situar FChivato en vista diseño y vamos a operar sobre el evento de formulario “Al
cargar”. ¿Qué debe ocurrir? Que txtAnoTrab se rellene automáticamente con un año de
trabajo, que hemos convenido que, por defecto, será el año del sistema.

Pues nada más fácil. El código que debemos asignar al evento “Al cargar” será el siguiente:


Private Sub Form_Load()
Me.txtAnoTrab.Value = Year(Date)
End Sub

Por ahora no debemos hacer nada más con este formulario.

CONFIGURANDO EL INICIO DE LA APLICACIÓN


Como ya sabéis (espero) podemos indicarle a Access qué formulario queremos que se cargue
al abrir la aplicación. Lógicamente, nosotros queremos que se nos cargue FMenu.

Para aquellos que no tengan esto muy claro lo explico muy rápidamente:

Access 2003:
• Menú Herramientas → Inicio...
• Mostrar formulario/página, y ahí seleccionamos FMenu.

Access 2007 y 2010:


• Pulsamos el botón de Office (Menú Archivo en 2010)
• Pulsamos el botón “Opciones de Access”
• Opción “Base de datos actual”
• Opciones de aplicación → Mostrar formulario, y seleccionamos “FMenu”

Y, como imagino que ya habréis adivinado, si necesitamos que se abra también FChivato
únicamente se lo tenemos que decir al formulario que se carga al inicio.

Así pues, en el evento “Al cargar” de FMenu le generamos el siguiente código:


Private Sub Form_Load()
DoCmd.OpenForm "FChivato", , , , , acHidden
End Sub

12
Visítame en http://siliconproject.com.ar/neckkito/
Es decir, que hemos conseguido realizar el siguiente
proceso de manera automática:

1. Abrimos la aplicación
2. La aplicación nos abre FMenu
3. FMenu nos abre FChivato, en modo oculto
4. FChivato se carga con el año de trabajo

¡Listo!

 Truco: para comprobar el correcto funcionamiento de lo anterior lo lógico sería cerrar


Access y volverlo a abrir. Pero en Access 2007 ó 2010, si no queremos ir haciendo “tantos
clicks por ahí”, podemos ir a la opción de “Compactar la BD” y compactarla. Así “mataremos
dos pájaros de un tiro”: eliminaremos archivos temporales y forzaremos el reinicio de la
aplicación.

Hasta aquí parece que nuestra aplicación ya funcionaría a pleno rendimiento, pero
evidentemente, ya que tenemos los datos, vamos a necesitar extraer información de los
mismos.

Veamos pues algunas opciones complementarias y un par de opciones de consulta y emisión


de informes para complementar nuestra aplicación.

FORMULARIO DE CAMBIO DE AÑO DE TRABAJO


¿Qué pasaría si queremos cambiar de año de trabajo? Pues algo tan simple como indicárselo a
nuestra aplicación.

Vamos a crearnos un simple formulario, que llamaremos FCambiaAT, en el cual introduciremos


los siguientes elementos:

• Un textbox para indicar el año de trabajo que queremos


• Un botón de comando, que llamaremos cmdAceptar
• Un botón de comando, que llamaremos cmdCancelar

A mí me ha quedado una cosa así:

Veréis que lo he hecho “pequeñito” porque vamos a configurarlo como emergente y modal. Así
pues, nos vamos a las propiedades del formulario y Pestaña Otras →
→ Emergente: Sí
→ Modal: Sí

13
Visítame en http://siliconproject.com.ar/neckkito/
Al textbox lo vamos a llamar txtAT

El código del botón cmdCancelar va a ser tan simple como:


Private Sub cmdCancelar_Click()
DoCmd.Close acForm, Me.Name
End Sub

El código del botón cmdAceptar va a ser ligeramente más


complejo, puesto que nos va a servir para controlar el valor
introducido por el usuario.

Pensemos... ¿Qué puede hacer el usuario?

• Dejarlo en blanco
• Introducir un valor no numérico
• Introducir un valor numérico, pero no válido como año de trabajo

Nuestro código va a evaluar estas posibilidades y actuará en consecuencia.

Pues tras lo anterior ahí va el código:


Private Sub cmdAceptar_Click()
'Declaramos las variables
Const miMsg As String = "El valor introducido no es correcto"
Dim vAT As Variant
'Cogemos el valor introducido por el usuario
vAT = Nz(Me.txtAT.Value, 0)
'Si lo ha dejado en blanco avisamos y salimos
If vAT = 0 Then
MsgBox "No ha introducido ningún año de trabajo", vbExclamation, "SIN DATOS"
GoTo Salida_error
End If
'Comprobamos que haya introducido un valor numérico
If Not IsNumeric(vAT) Then
MsgBox miMsg, vbExclamation, "INCORRECTO"
GoTo Salida_error
End If
'Comprobamos que el año de trabajo tenga cuatro dígitos
If Len(vAT) <> 4 Then
MsgBox miMsg, vbExclamation, "INCORRECTO"
GoTo Salida_error
End If
'Comprobamos que el año de trabajo empiece por 2
If Left(vAT, 1) <> 2 Then
MsgBox miMsg, vbExclamation, "INCORRECTO"
GoTo Salida_error
End If
'Tras las comprobaciones, pasamos el nuevo año a FChivato
Forms!FChivato.txtAnoTrab.Value = vAT
'Cerramos el formulario
DoCmd.Close acForm, Me.Name
Exit Sub

14
Visítame en http://siliconproject.com.ar/neckkito/
Salida_error:
With Me.txtAT
'Borramos el valor introducido
.Value = Null
'Situamos el enfoque
.SetFocus
End With
End Sub

Ahora sí podemos irnos a nuestro formulario FMenu, introducir un


botón de comando, que llamaremos cmdCambiaAT, y generarle el
siguiente código:


Private Sub cmdCambiaAT_Click()
'Abrimos FCambiaAT
DoCmd.OpenForm "FCambiaAT"
End Sub

¿CUÁNTO HEMOS INGRESADO/GASTADO EN UNA PARTIDA EN


PARTICULAR?
Veamos cómo cambiar el origen de un combo al seleccionar una opción, y cómo podemos
obtener información a través de SQL's parciales, que nos darán la información reflejada en un
textbox (para una información puntual “a lo rápido”).

En nuestro FMenu introduciremos un marco de opciones. El asistente que nos aparece lo


configuraremos de la siguiente manera:

• Nombres de las etiquetas: escribiremos, para la primera (sin comillas), “Ingreso”, y


para la segunda, “Gasto”.
• Dejamos la opción predeterminada en ingreso.
• Dejamos los valores por defecto que nos salen, coincidentes con nuestra convención: 1-
Ingreso; 2-Gasto.
• Le damos el formato que más nos guste.
• Como título escribimos (sin comillas), “Seleccionar:”

A este marco le pondremos de nombre mrcTP.

Debería habernos quedado una cosa así:

Ahora vamos a insertar un cuadro combinado. Para que Access nos haga parte del trabajo de
configuración, al salirnos el asistente, vamos a decirle que queremos que nos coja los valores
de la tabla TPpto, los campos [IdPpto] y [Partida] y ocultaremos la clave principal. Da igual el
título que le pongamos porque borraremos la etiqueta.

A este combo lo llamaremos cboSelPartida.

Con una etiqueta auxiliar, un pequeño rectángulo y algo de formato podemos conseguir algo

15
Visítame en http://siliconproject.com.ar/neckkito/
así:

Vamos primero a por el combo. Vamos a asignarle, en el


evento “Al recibir el enfoque”, el siguiente código, que nos
irá cambiando el origen de la fila según nuestras
necesidades:


Private Sub cboSelPartida_GotFocus()
'Declaramos las variables
Dim vTP As Long
Dim miRce As String
Dim vAT As String
'Cogemos el valor del marco
vTP = Me.mrcTP.Value
'Cogemos el valor del año de trabajo
vAT = fncAT()
'Creamos la SQL para el RowSource, filtrándola por dichos valores
miRce = "SELECT TPpto.IdPpto, TPpto.Partida FROM TPpto" _
& " WHERE TPpto.AnoPpto='" & vAT & "'" _
& " AND TPpto.TipoPartida=" & vTP
'Asignamos nuestro RowSource al combo
With Me.cboSelPartida
.RowSource = miRce
'Refrescamos la información del combo
.Requery
'Borramos el valor que pudiera haber en el combo
.Value = Null
End With
End Sub

OK. Aunque se podría hacer en un sólo código vamos a ver cómo encadenar códigos en
nuestro módulo para que nos dé el resultado deseado, ya que no sólo se nos informará de lo
que llevamos gastado sino también de la desviación presupuestaria.

¿Qué vamos a necesitar? Pues vamos a necesitar:


• Una función que nos dé el valor presupuestado
• Una función que nos dé el valor de todos los movimientos
• Un procedimiento que nos calcule la diferencia y nos muestre toda la información en un
msgbox.

Vamos a por la primera, que es la más sencilla.

En nuestro módulo de códigos escribimos simplemente:


Private Function fncTotalPptado(vIdPartida As Long) As Currency

16
Visítame en http://siliconproject.com.ar/neckkito/
fncTotalPptado = DLookup("[ImpPartida]", "TPpto", "[IdPpto]=" & vIdPartida)
End Function

Fijaos que:

La he declarado privada (Private), lo que significa que no


podrá ser llamada desde otro procedimiento que no sea
alguno de nuestro propio módulo.

Como vamos por identificadores de partida (recordad que el combo


almacenará el ID, no el texto de la partida) no es necesario tener
en cuenta el año de trabajo (es el propio combo el que ya lo tiene
en cuenta).

Para conseguir el sumatorio vamos a crearnos una consulta, pero a través de una SQL, y
después examinaremos su resultado a través de un recordset DAO. El código sería, pues:


Private Function fncTotalMovimientos(vIdPartida As Long) As Currency
'Declaramos las variables
Dim rst As DAO.Recordset
Dim miSql As String
'Creamos la SQL que nos dará la suma de movimientos de la partida seleccionada
miSql = " SELECT Sum(TMov.ImpPartida) AS TotMov FROM TMov" _
& " WHERE TMov.Partida=" & vIdPartida
'Creamos el recordset sobre la SQL
Set rst = CurrentDb.OpenRecordset(miSql)
'Si no hubiera registros la función devolverá 0
If rst.RecordCount = 0 Then
fncTotalMovimientos = 0
Else
'Si no, cogemos el total que nos devuelve la SQL
fncTotalMovimientos = Nz(rst.Fields(0).Value, 0)
End If
'Cerramos conexiones y liberamos memoria
rst.Close
Set rst = Nothing
End Function

Fijaos, en este caso, que también he declarado la función como privada. Debemos contemplar
la posibilidad de que esa partida esté presupuestada, pero que aún no haya movimientos. En
este caso esto lo hemos controlado a través del conteo de registros del recordset, indicando
que, si no hay registros, la función nos retorne 0.

¡Ah! Y recordad que si no os funciona el código debéis comprobar si tenéis registrada la librería
“Microsoft DAO 3.6 Object Library”.

Ahora sí ya tenemos de dónde obtener lo presupuestado y lo ingresado/gastado. Como vamos


a sacar un msgbox crearemos un procedimiento sub, de carácter público (porque lo vamos a
llamar desde el combo), que conjuntará las dos funciones anteriores a fin de darnos el
resultado deseado.

El código para ese procedimiento sub de nuestro módulo sería:

17
Visítame en http://siliconproject.com.ar/neckkito/

Public Sub subMsgInfoPartida(vIdPart As Long)
'Declaramos las variables
Dim vDif As Currency
Dim vTotPpto As Currency
Dim vTotMov As Currency
Dim vTP As Long
'Llamamos a la función que nos da el total
presupuestado
vTotPpto = fncTotalPptado(vIdPart)
'Llamamos a la función que nos da el total de
movimientos
vTotMov = fncTotalMovimientos(vIdPart)
'Calculamos la diferencia
vDif = vTotPpto - vTotMov
'Mostramos el msgbox con la información
MsgBox "Total presupuestado: " & Format(vTotPpto, "#,##0.00") & vbCrLf & vbCrLf _
& "Total movimientos: " & Format(vTotMov, "#,##0.00") & vbCrLf & vbCrLf _
& "Diferencia: " & Format(vDif, "#,##0.00"), vbInformation, "RESUMEN DATOS"
End Sub

Sólo remarcar que he aprovechado el texto del msgbox para dar formato a los valores
obtenidos (separador de miles con punto y separador de decimales con coma, con dos
decimales).

Y ya lo tenemos hecho. Ahora sólo queda decirle al combo que nos llame a nuestro
procedimiento. Para ello, en el evento “Después de actualizar” del combo, escribimos el
siguiente código:


Private Sub cboSelPartida_AfterUpdate()
'Comprobamos que haya valor. Si no lo hay no hacemos nada
If IsNull(Me.cboSelPartida.Value) Then Exit Sub
'Llamamos al procedimiento
Call subMsgInfoPartida(Me.cboSelPartida.Value)
End Sub

Como vemos, le pasamos como argumento del procedimiento sub el valor seleccionado en el
combo.

CREÁNDONOS UN INFORME FANTASMA


Lógicamente vamos a querer un informe con todas las desviaciones. Para este proceso
haremos lo siguiente:

• Nos crearemos una tabla como plantilla, que utilizaremos para crear el informe.
• Una vez creado el informe borraremos la tabla.
• Programaremos la creación de esa tabla a través de código.
• Programaremos el llenado de la tabla a través de SQL y código

Evidentemente se podría hacer a través de consultas-objeto y quizá de manera más simple,


pero lo que interesa del ejemplo es saber que lo que os voy a explicar se puede hacer con
código VA con SQL.

18
Visítame en http://siliconproject.com.ar/neckkito/
Dicho lo anterior vamos a crearnos una tabla plantilla, que llamaremos TReportTotal. Esta tabla
tendrá la siguiente estructura:

Fijaos en que no hay ninguna clave principal. Como la tabla sólo nos va a servir de base para
el informe, y se rellenará con información variable, no necesitamos clave principal.

A continuación nos creamos un informe sobre esta tabla, que llamaremos RReportTotal,
utilizando el asistente. Usaremos el asistente porque vamos a añadir un nivel de agrupamiento
por el campo [TipoPartida] (al seleccionar los campos del informe no necesitamos añadir [Id]).

Es decir, se lo indicamos en este punto:

A ese informe vamos a realizarle un pequeño cambio, para que el usuario lo tenga más claro.
Buscamos el campo [TipoPartida] y situamos su propiedad VISIBLE en no. A continuación
introducimos un cuadro de texto (borramos su etiqueta) y en su interior escribimos la siguiente
expresión:

=Iif([TipoPartida]=1;"INGRESO";"GASTO")

Con esto cambiamos el 1 por INGRESO, y el 2 por GASTO.

En el encabezado del informe podemos añadir un textbox, y en su interior introducimos la


siguiente expresión:

=fncAT()

Así el propio informe nos indicará de qué año estamos hablando.

Vamos a por el procedimiento: el esquema de dicho procedimiento será el siguiente:

• Miraremos si la tabla TReportTotal existe (aunque en teoría no debería existir, lo


hacemos así por precaución y para evitarnos errores posteriores de código).

19
Visítame en http://siliconproject.com.ar/neckkito/
• Si la tabla existe la borraremos.
• Creamos la tabla en tiempo de ejecución
• Combinamos SQL y recordset para ir obteniendo los datos
que necesitamos, y los añadimos a nuestra tabla.
• Abrimos el informe en vista previa.
• Al cerrar el informe se nos borrará la tabla (recurriremos a
un pequeño truco para ello).

Veamos cómo podemos hacer lo anterior.

En nuestro módulo vamos a crearnos un procedimiento como el


que sigue:


Public Sub subCreaTReportTotal()
'Declaramos las variables
Const miTabla As String = "TReportTotal"
Dim miSql As String
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
Dim rstTReport As DAO.Recordset
Dim tbl As Object
'Comprobamos si la tabla existe. Si existe la borramos
For Each tbl In CurrentData.AllTables
If tbl.Name = miTabla Then
DoCmd.SetWarnings False
DoCmd.DeleteObject acTable, miTabla
DoCmd.SetWarnings True
Exit For
End If
Next tbl
'Configuramos la SQL de creación de tabla
miSql = "CREATE TABLE " & miTabla & " (Id INTEGER, Partida STRING, TipoPartida INTEGER," _
& "TotPpto CURRENCY, TotMov CURRENCY, Dif CURRENCY)"
'Ejecutamos la SQL
DoCmd.RunSQL (miSql)
'Creamos la SQL a través de la cual recorreremos todas las partidas presupuestadas
miSql = "SELECT * FROM TPpto WHERE TPpto.AnoPpto='" & fncAT() & "'"
'Creamos el recordset sobre la tabla TReportTotal
Set dbs = CurrentDb
Set rstTReport = dbs.OpenRecordset("TReportTotal", dbOpenTable)
'Creamos el recordset sobre la consulta SQL
Set rst = dbs.OpenRecordset(miSql)
'Si no hay valores saltamos a Salida
If rst.RecordCount = 0 Then GoTo Salida
'Nos movemos al primer registro
rst.MoveFirst
'Iniciamos el proceso de recorrido de registros
Do Until rst.EOF
'Vamos a rellenar la tabla TReportTotal con la información que tengamos, y, si no la tenemos,
'la buscaremos entre nuestros "recursos"
With rstTReport
.AddNew
'El primer campo [Id] nos lo dará el campo [IdPpto] de la consulta
.Fields("Id").Value = rst.Fields("IdPpto").Value
'El segundo campo [Partida] nos lo dará el campo [Partida] de la consulta
.Fields("Partida").Value = rst.Fields("Partida").Value
'El tercer campo [TipoPartida] nos lo dará el campo [TipoPartida] de la consulta
.Fields("TipoPartida").Value = rst.Fields("TipoPartida").Value
'El cuarto campo [TotPpto] nos lo dará el campo [ImpPartida] de la consulta
.Fields("TotPpto").Value = rst.Fields("ImpPartida").Value
'El quinto campo TotMov nos lo dará nuestra función fncTotalMovimientos, y el argumento de
'la función vendrá dado por el campo [IdPpto] de la consulta
.Fields("TotMov").Value = fncTotalMovimientos(rst.Fields("IdPpto").Value)
'El sexto campo [Dif] nos vendrá dado por la resta del total presupuestado menos el valor
'que nos devuelva la función fncTotalMovimientos
.Fields("Dif").Value = rst.Fields("ImpPartida").Value - fncTotalMovimientos(rst.Fields("IdPpto").Value)

20
Visítame en http://siliconproject.com.ar/neckkito/
.Update
End With
'Nos movemos al siguiente registro
rst.MoveNext
Loop
Salida:
'Cerramos conexiones y liberamos memoria
rst.Close
rstTReport.Close
dbs.Close
Set rst = Nothing
Set rstTReport = Nothing
Set dbs = Nothing
End Sub

Ahora sólo nos queda llamar a nuestro procedimiento. Para ello, en FMenu, creamos un botón
de comando para abrir el informe, que llamaremos cmbAbreRReportTotal, y le asignaremos el
siguiente código al evento “Al hacer click”:


Private Sub cmdAbreRReportTotal_Click()
'Llamamos al procedimiento
Call subCreaTReportTotal
'Abrimos el informe en vista preliminar
DoCmd.OpenReport "RReportTotal", acViewPreview
'Cerramos el menú
DoCmd.Close acForm, Me.Name
End Sub

Sigamos. No podemos borrar la tabla al cerrar el formulario porque precisamente la tabla es el


Recordsource del formulario. Vamos a utilizar un “rebote” para conseguirlo evitando ese
“inconveniente” que os comentaba.

En el evento “Al cerrar” del formulario generaremos el siguiente código:


Private Sub Report_Close()
DoCmd.OpenForm "FMenu"
'Situamos el enfoque sobre el botón de comando cmdAbreRReportTotal
Forms!FMenu.cmdAbreRReportTotal.SetFocus
End Sub

Como vemos obligamos a que el enfoque se sitúe sobre cmdAbreRReportTotal en cuanto se


abre FMenu. Y aprovecharemos este “hecho fortuito” (convenientemente planeado por nuestro
“frankensteiniano” cerebro) para asignar un código al evento “Al recibir el enfoque” de dicho
botón, que lo que hará será borrarnos la tabla “para no dejar huellas”.

El código sería:


Private Sub cmdAbreRReportTotal_GotFocus()
'On Error Resume Next
'Le damos dos segundos para que se ejecute el código de borrado. Así conseguimos
'tiempo para que se cierre totalmente el informe
Me.TimerInterval = 2000
End Sub

21
Visítame en http://siliconproject.com.ar/neckkito/

Y ahora programamos el evento del cronómetro.


Seleccionamos pues el evento “Al cronómetro” de FMenu y
le generamos el siguiente código:


Private Sub Form_Timer()
On Error Resume Next
DoCmd.SetWarnings False
DoCmd.DeleteObject acTable, "TReportTotal"
DoCmd.SetWarnings True
'Restablecemos el intervalo de cronómetro a cero para
que deje de actuar
Me.TimerInterval = 0
End Sub

Ved que le he puesto un simple control de errores por si el botón recibiera el enfoque sin
provenir la acción de la apertura del informe. Si no lo hiciéramos así nos daría error de código
porque no encontraría TReportTotal.

Y listos.

¿Y por qué lo de “informe fantasma”? Pues porque si alguien accediera “por detrás” e intentara
ejecutar el informe lógicamente sólo podría un mensaje de error.

PARA FINALIZAR ESTE CAPÍTULO


Este capítulo nos ha servido para ver la utilidad de utilizar los módulos, de combinar SQL con
VBA, y además (espero) nos ha servido para ver unos cuantos “truquillos” para esos
momentos en que “si no funciona algo de frente, pues pruebo por la izquierda, y si no por la
derecha”... je, je...

Espero que todos los capítulos del curso os hayan aportado alguna cosa, por pequeña que sea,
para aprender un “poquito más” cosas de Access y VBA para Access.

Pensad que en la red hay muy buenos foros, hay ejemplos de “monstruos del Access” y hay
otras “cosillas” relacionadas con Access que os pueden ser de utilidad.

Finalmente, también espero que la finalidad marcada en el primer capítulo, que era que al
acabar el curso fuerais capaces de coger cualquier código VBA y, con sus más y sus menos,
fuerais capaces de entenderlo, modificarlo y adaptarlo a vuestras necesidades, se haya
cumplido en gran parte.

Un gran saludo y...

¡suerte!

22
Visítame en http://siliconproject.com.ar/neckkito/

You might also like