Professional Documents
Culture Documents
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
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
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
4
Visítame en http://siliconproject.com.ar/neckkito/
Y ACABAMOS EL TEMA DE MATRICES.....................................................................................21
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
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
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
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
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
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:
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.
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/
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 por ahora esos seis elementos serán suficientes para “ir tirando”.
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:
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” ;)
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
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
…
En definitiva, que tenemos tres opciones de comparación para nuestra base de datos:
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
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.
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.
…
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
5
1
20
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:
◊ 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.
◊ 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
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
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
niIdea = “Ahora ya sé lo que es” esta variable Variant adquirirá las características de una
variable tipo String.
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”:
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.
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
…
…
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?
…
Private Sub miError()
Dim miInt As Integer
miInt = "Aquí hay un error"
Debug.Print miInt
End Sub
…
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
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:
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.
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
…
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.
…
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()
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.
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.
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
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.
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.
…
Private Sub...
'Definimos la variable
Dim miFecha As Date
'Le asignamos la fecha actual
miFecha = Date
'Llamamos al procedimiento
Call fechaManana(miFecha)
End Sub
…
…
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
…
5
Visítame en http://siliconproject.com.ar/neckkito/
EL ADORNO FINAL: BYVAL, BYREF y PARAMARRAY
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.
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í:
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 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.
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.
…
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”:
…
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
…
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.
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.
8
Visítame en http://siliconproject.com.ar/neckkito/
Dim miValor As Int...
Public Sub unSub() y pulsamos Enter automáticamente VBA nos escribirá End Sub
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
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:
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).
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
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”
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.
¡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.
Nuestro espacio de trabajo para código será, pues, entre estas dos líneas.
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 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.
6
Visítame en http://siliconproject.com.ar/neckkito/
LA FUNCIÓN MSGBOX
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.
7
Visítame en http://siliconproject.com.ar/neckkito/
SALTOS DE LÍNEA EN MENSAJE
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!
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 ;)
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).
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.
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.
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.
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” ;) )
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.
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
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
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
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
Visítame en http://siliconproject.com.ar/neckkito/
BLOQUES DE DECISIÓN
BLOQUE IF...THEN...ELSE...END IF
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.
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
'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
…
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
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
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.
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:
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”.
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:
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
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
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”
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:
+;-;*
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
…
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.
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?
La estructura pues nos quedaría de la siguiente manera (añadiremos el CASE ELSE ahora que
ya lo “controlamos”:
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.
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.
FOR valor=1 TO 3
'Acciones
NEXT valor
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).
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
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
…
¿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í:
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:
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:
…
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
Insisto que el proceso en sí no tiene mucho sentido; debéis centrar vuestra atención en la
mecánica del mismo.
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.
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:
…
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 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.
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.
'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
El bucle DO...LOOP tiene dos variantes. Le podemos decir al código, de manera genérica,
o tambié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.
14
Visítame en http://siliconproject.com.ar/neckkito/
MsgBox "Se encontró el valor objetivo", vbInformation, "OK"
Me.txtResults.Value = vValor
End Sub
…
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
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
¡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
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.
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”.
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.
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
Visítame en http://siliconproject.com.ar/neckkito/
¡Y LA ESTRELLA INVITADA... DOCMD!
¿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.
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.
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)
DoCmd.OpenXXX “nombreObjeto”
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
…
Private Sub cmdAbreFDatos_Click()
DoCmd.OpenForm "FDatos"
3
Visítame en http://siliconproject.com.ar/neckkito/
End Sub
…
…
Private Sub cmdAbreTDatos_Click()
DoCmd.OpenTable "TDatos"
End Sub
…
…
Private Sub cmdAbreCDatos_Click()
DoCmd.OpenQuery "CDatos"
End Sub
…
…
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.
La sintaxis es:
Vamos a añadir tres botones de comando en nuestro formulario FMenu y los vamos a llamar:
– cmdCierraFDatos
– cmdCierraTDatos
– cmdCierraCDatos
…
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.
…
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.
…
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
…
¿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.
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
…
…
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?
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
…
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”.
…
Private Sub cmdAbreFDatosNuevoRegistroStma1_Click()
DoCmd.OpenForm "FDatos"
DoCmd.GoToRecord acDataForm, "FDatos", acNewRec
End Sub
…
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).
Docmd.RunCommand …
…
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
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
8
Visítame en http://siliconproject.com.ar/neckkito/
…
Private Sub cmdPrimero_Click()
DoCmd.RunCommand acCmdRecordsGoToFirst
End Sub
…
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”.
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:
Pues con todo lo anterior ya podemos decirle al código qué tipo de vista deseamos cuando
abramos el formulario.
…
Private Sub cmdAbreFDatosHojaDatos_Click()
DoCmd.OpenForm "FDatos", acFormDS
End Sub
9
Visítame en http://siliconproject.com.ar/neckkito/
…
…
Private Sub cmdAbreFDatosGrafDin_Click()
DoCmd.OpenForm "FDatos", acFormPivotChart
End Sub
…
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.
…
Private Sub cmdAbreFDatosFiltroSencillo_Click()
DoCmd.OpenForm "FDatos", , , "[Edad]=" & 55
End Sub
…
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...)
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.
…
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
…
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.
Significa que:
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í:
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”):
¿Lo vemos?
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.
…
Private Sub cmdAbreFDatosConsulta_Click()
DoCmd.OpenForm "FDatos", acFormDS
Forms!FDatos.AllowAdditions = False
Forms!FDatos.AllowEdits = False
Forms!FDatos.AllowDeletions = False
End Sub
…
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
…
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.
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
…
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
…
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:
…
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
Visítame en http://siliconproject.com.ar/neckkito/
SIGAMOS UN POCO MÁS CON DOCMD Y FORMULARIOS
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:
2.- Creamos otra tabla, que llamaremos TConfidencial, que contendrá los siguientes campos:
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.
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.
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.
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).
En resumen:
– O bien: Forms!nombreFormulario.nombreControl.<método>
3
Visítame en http://siliconproject.com.ar/neckkito/
– O bien: Forms!nombreFormulario.nombreControl.<propiedad>
…
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
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:
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:
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!
…
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).
…
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
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.
…
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
6
Visítame en http://siliconproject.com.ar/neckkito/
que ahora ya no nos aparece ningún cónyuge.
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”.
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:
Vamos pues:
.- La contraseña será siempre la misma, por lo que el tipo de variable que utilizaremos será
una constante.
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
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
…
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
…
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.
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.
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.
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
…
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.
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.
…
Private Sub cmdOcultaConfidencial_Click()
With Me
.cmdMuestraConfidencial.Enabled = True
.cmdMuestraConfidencial.SetFocus
.cmdOcultaConfidencial.Enabled = False
.subFrmConfidencial.Visible = False
End With
End Sub
…
¿Sencillo, no?
2.- En las propiedades del formulario nos vamos a la pestaña Formato → Titulo, y ahí
escribimos ALTA CLIENTES
…
Private Sub cmdCierra_Click()
DoCmd.Close acForm, Me.Name
DoCmd.OpenForm "FMenu"
End Sub
…
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?
…
Private Sub cmdAgregaClientes_Click()
DoCmd.OpenForm "FClientes", , , , acFormAdd
DoCmd.Close acForm, Me.Name
End Sub
…
Y listo.
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.
…
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 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”.
…
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í.
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.
…
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
…
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
Visítame en http://siliconproject.com.ar/neckkito/
APLICANDO FILTROS
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
2.- Creamos un nuevo formulario, que llamaremos FClientes2, basado sobre la tabla
TClientes2.
Algunos de los ejemplos los podremos aplicar tanto a formularios como informes.
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”
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
Nz (Valor, valor_si_nulo)
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:
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.
Sin embargo, nosotros vamos a utilizar otra opción, que es reutilizar nuestro combo sin tocar
sus propiedades. Para ello:
…
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
…
– 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:
– 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:
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
…
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).
…
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”.
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”.
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:
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)
…
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
…
– 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:
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:
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).
…
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”.
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).
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.
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...
'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 = ""
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
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.
A TRAVÉS DE UN “FILTERON”
Ahora vamos a ver cómo utilizar un filtro en el propio formulario. Vamos a preparar el terreno:
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.
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
…
¿Fácil, verdad?
…
Private Sub cmdQuitaFiltro_Click()
Me.txtFiltro.Value = Null
Me.FilterOn = False
End Sub
…
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:
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
1.- Vamos a copiar nuestro formulario FClientes2 y lo vamos a pegar como FClientes3
…
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?
…
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
…
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
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:
.- 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).
…
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.
…
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Í:
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.
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:
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...).
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.
…
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.
1.- Copiamos nuestra tabla TVentas (estructura y datos) y la pegamos con el nombre de
TVentasHistorico.
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 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.
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?).
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
…
¡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
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”...)
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).
…
Private Sub cmdFechaStma_Click()
Me.txtFResultado.Value = Date
End Sub
…
…
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
…
…
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:
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
…
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
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:
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/
…
…
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
…
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).
…
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).
…
Private Sub txtCadena2_AfterUpdate()
Me.txtCadena2.InputMask = ">????????????????????"
End Sub
…
…
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:
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:
La aplicación de esta función, si seguimos nuestro ejemplo, sobre la variable vText sería:
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:
Como habréis intuido, lo que hace vbProperCase es poner en mayúsculas la primera letra de
cada palabra “individual”.
…
Private Sub txtCadena4_AfterUpdate()
Dim vText As String
vText = Me.txtCadena4.Value
vText = StrConv(vText, vbProperCase)
Me.txtCadena4.Value = vText
End Sub
…
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:
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”.
9
Visítame en http://siliconproject.com.ar/neckkito/
a través de un InputBox.
¿Cómo lo hacemos?
…
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
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()
…
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
…
Su estructura es
Len(texto)
Su estructura es:
InStr(posición de inicio, expresión que contiene la cadena a buscar, cadena a buscar, método
de comparación)
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?
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.
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.
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!!!
Podríamos haber utilizado otra función para conseguir el mismo efecto, y que vemos en el
supuesto siguiente:
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...
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).
…
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
…
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:
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
…
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
…
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.
…
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()
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.
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.
…
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:
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
…
…
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
– 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!
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.
19
Visítame en http://siliconproject.com.ar/neckkito/
Análisis del supuesto y casos posibles:
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.
→ ¿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.
…
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
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.
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...).
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.
¿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...
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:
…
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
.- 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)
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)
Dim DiasSem(-3 To 4)
Fácil, ¿verdad?
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”.
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).
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)
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
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!
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().
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
…
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.
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:
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”.
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:
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
…
miMatriz(elementosPrimeraDimension, elementosSegundaDimension)
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:
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ó.
…
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
…
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.
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.
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.
– 1 = Sujeto 1
– 2 = Elemento 2 = Adrenalina
– 3 = Semana = Semana 3
…
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
…
MATRICES DINÁMICAS
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.
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.
…
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
…
¿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.
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:
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.
...
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.
– 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 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:
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) & """"
DoCmd.RunSQL sqlAnexa
– La SQL para borrar los registros de una tabla tiene una estructura muy simple:
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”.
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?
16
Visítame en http://siliconproject.com.ar/neckkito/
Redim matriz(2)
matriz(1) = “Neckkito”
matriz(2) = “Access”
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”
¿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.
…
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
…
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)
18
Visítame en http://siliconproject.com.ar/neckkito/
Tercera iteracción → i=3
mtrInvit(2+3) → mtrInvit(5)
End If
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!
UBound nos proporciona el índice máximo; LBound nos proporciona el índice mínimo.
…
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 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:
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
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.
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:
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.
<<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.
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
….
…
Private Sub...
MsgBox “Mi versión del motor Microsoft Jet es “ &
DBEngine.Version
End Sub
…
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.
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
o también
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
…
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.
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).
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
…
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:
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:
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
…
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
…
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.
…
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...
…
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
…
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.
…
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
…
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.
…
Private Sub cmdNumQueriesSinForm_Click()
Dim dbs As DAO.Database
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
…
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
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
…
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:
11
Visítame en http://siliconproject.com.ar/neckkito/
Permitir longitud cero AllowZeroLenght
Regla de validación ValidationRule
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.
– Parameter
– Index
– Relation
– Container
– Document
– User
– Group
– Property
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”.
– Creo la tabla
– Importo el Excel
– Trabajo con la tabla
– Borro la tabla
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:
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
…
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.
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...
…
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
...
18
Visítame en http://siliconproject.com.ar/neckkito/
¿Qué pasa?
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.
…
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.
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.
…
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
…
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.
…
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
…
22
Visítame en http://siliconproject.com.ar/neckkito/
muy fáciles), porque ahora interesa centrarnos en la creación de consultas.
…
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
…
– 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.
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
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.
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
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”.
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.
Y después de este ambiente chillout en que nos hemos sumergido vamos a ver un par de cosas
sobre los recordset.
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.
¿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.
….
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
…
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
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.
…
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
…
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.
¿Qué hacemos si no tenemos claro el número del campo? Podemos indicárselo al código
directamente por su nombre.
nomTrab = rst.Fields(“Nombre”).Value
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.
…
rst.MoveFirst
Do Until rst.EOF
'Acciones a desarrollar
rst.MoveNext
Loop
…
…
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
…
MODIFICANDO UN REGISTRO
Vamos a abrir directamente la tabla y vamos a modificar el nombre del último trabajador.
Vamos a cambiar “Laura” por “Laurita”.
– 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.
…
With rst
.Edit
.Fields(x).Value = <NuevoValor>
.Update
End With
…
…
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
…
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).
…
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.
.Fields(1).Value = UCase(.Fields(1).Value)
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?
…
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:
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”.
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.
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
…
– 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.
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.
SELECT
nombreTabla.nombreCampo
14
Visítame en http://siliconproject.com.ar/neckkito/
FROM
nombreTabla
WHERE
condición
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.
Así, si reescribimos nuestro código añadiendo unas líneas de código que nos controlen lo
anterior podremos evitar dicho error.
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
…
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
…
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.
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.
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.
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.
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:
20
Visítame en http://siliconproject.com.ar/neckkito/
Bueno, bueno... ya tenemos la “carne en el asador”.
Sigamos.
– 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
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:
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.
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”.
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
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.
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.
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):
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
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):
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.
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:
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:
– 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.
...
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.
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).
…
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>;”
…
…
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;”
…
…
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;”
...
Fijaos en que es muy importante no olvidarse de los punto y coma (;) en la declaración de
apertura de la conexión.
Aunque ya lo hemos visto, para mayor claridad vamos a ver las estructuras:
7
Visítame en http://siliconproject.com.ar/neckkito/
– La variable cnn es una conexión ADO
– Establecemos cnn como una nueva conexión
…
Dim cnn As ADODB.Connection
Set cnn = NEW ADODB.Connection
…
2ª manera: declaración directa de que la conexión es ADO y que además es una nueva
conexión:
…
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 ;)
…
rs.Close
cnn.Close
Set rs = Nothing
Set cnn = nothing
…
8
Visítame en http://siliconproject.com.ar/neckkito/
– CommitTrans → Guarda la transacción
– RollbackTrans → Cancela la transacción
…
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
…
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>
LOS RECORDSET
Para abrir un recordset de una tabla o consulta, entendida la consulta como objeto de Access,
debemos, en el código:
¿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:
…
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
…
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).
…
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/
…
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
…
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”.
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.
15
Visítame en http://siliconproject.com.ar/neckkito/
[Destino]?
…
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!
…
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
…
Por ejemplo, vamos a echarle un vistazo a las “entrañas” de nuestra tabla TVuelos sin abrirla.
…
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:
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.
¡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
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).
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.
…
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! ;)
Para indicar que es la etiqueta escribimos la palabra elegida seguida de dos puntos (:)
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
…
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
…
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
…
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
5
Visítame en http://siliconproject.com.ar/neckkito/
y liberamos memoria.
¿Pillamos la mecánica?
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”.
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.
…
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).
…
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?
– NUMBER
– DESCRIPTION
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
…
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.
…
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
…
…
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
…
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
…
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”.
…
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.
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:
…
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?
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
…
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í:
…
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
…
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).
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...
¡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/
…
Claro, ¿verdad?
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
…
…
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.
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:
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:
16
Visítame en http://siliconproject.com.ar/neckkito/
Y mi error se completaría con: Err.Raise 700, “CursoVB”
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.
…
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.
…
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
…
TIPOS DE ERRORES
Podemos encontrarnos diversos tipos de error, y cada uno tiene un tipo diferente de
tratamiento.
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
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.
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.
…
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.
…
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
…
¿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.
…
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.
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”.
Para ello:
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.
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
…
…
Private Sub...
'Código
sql = “SELECT * FROM Tabla WHERE Tabla.var1=” & vVar1 & “ AND Tabla.var2=” & vVar2
Msgbox sql
Exit sub
'Código
End Sub
…
Para quienes les guste utilizar el VBE el MsgBox sería una especie de “sucedáneo” de
Debug.Print
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
Visítame en http://siliconproject.com.ar/neckkito/
AUTOMATIZACIÓN
INTRODUCCIÓN
Y la pregunta del millón es... ¿qué significa
“automatización”?
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.
– 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.
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:
Finalmente vamos a crearnos una consulta, que llamaremos CDatosAct, que tendrá la siguiente
estructura:
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).
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.
4
Visítame en http://siliconproject.com.ar/neckkito/
Ambos argumentos son requeridos. Y
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
…
– <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:
– Cerramos Excel
– Volvemos a hacer click sobre el botón
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.
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:
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”).
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:
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
…
Para poder probar este método tendremos que hacer algunas pequeñas modificaciones en
nuestras aplicaciones.
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
…
Su estructura es la siguiente:
…
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
…
9
Visítame en http://siliconproject.com.ar/neckkito/
DDETerminate <canal>
DDETerminateAll
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.
– 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”)
Sé que esto puede parecer un poco confuso al principio, pero con el ejemplo del epígrafe
ACCESS – WORD creo que lo entenderemos perfectamente.
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.
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 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”
…
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
12
Visítame en http://siliconproject.com.ar/neckkito/
wdFormatHTML 8 Formato HTML estándar.
wdFormatRTF 6 Formato RTF.
Wrd.ActiveDocument.SaveAs FileName:=miArchivo
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.
…
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
Vamos a:
Recordad que debemos registrar la referencia “Microsoft Excel x.y Object Library”
…
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
…
.- 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).
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.).
…
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
…
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”.
Vamos a crearnos un PowerPoint con una diapositiva que nos dará la información del factor de
actualización.
…
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. ;)
La idea principal para utilizar esas dos funciones radica en dos pasos:
…
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
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
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.
¿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”.
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.
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>
…
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:
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).
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).
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:
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...
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.
…
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
…
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:
¿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?
¿Curioso, verdad?
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.
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.
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).
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
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).
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”.
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:
10
Visítame en http://siliconproject.com.ar/neckkito/
<DatosOrigen> → archivo xml donde están los datos.
…
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
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.
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:
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
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.
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:
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
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.
…
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
…
- Es necesario abrir el informe para poder imprimir las páginas seleccionadas, de ahí que esté
escrito este bloque:
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”.
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.
…
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
…
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.
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
…
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.
…
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
…
…
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.
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?
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?
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
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.
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.
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).
…
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:
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
…
14
Visítame en http://siliconproject.com.ar/neckkito/
TRABAJANDO CON DIRECTORIOS
MkDir ruta
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.
…
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
…
RmDir ruta
Vamos a crear, manualmente, una carpeta en el directorio donde tenemos la BD. Por ejemplo,
creamos la carpeta “Borrar”.
…
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
…
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
…
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
…
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?”
LA FUNCIÓN Dir()
La estructura de la función Dir() es la siguiente:
Dir(nombreRuta, atributos)
¿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.
…
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
…
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).
…
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
…
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
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.
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.
…
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:
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.
…
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
…
¿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?
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()
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).
…
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().
…
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
…
…
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()).
CHDRIVE()
Su sintaxis es:
ChDrive “unidad”
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.
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...
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
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.
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é).
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.
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.
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):
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).
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.
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
…
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
…
Public Function selectArchivo() As String
'Creamos un control de errores
On Error GoTo sol_err
Salida:
Exit Function
sol_err:
MsgBox "Se ha producido el error: " & Err.Number & " - " & Err.Description
Resume Salida
End Function
…
5
Visítame en http://siliconproject.com.ar/neckkito/
Private Sub cmdFileDialogArchivo_Click()
Me.txtArchivo.Value = selectArchivo()
End Sub
…
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
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
…
Y ya está
¿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.
7
Visítame en http://siliconproject.com.ar/neckkito/
Pues la mecánica sería la misma, pero operando con tablas.
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.
3.- El usuario tendría un formulario con un FileDialog para seleccionar una carpeta como nueva
ruta de trabajo
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).
Es decir, que tendríamos una carpeta “madre” (v.gr. “C:\MisArchivos”) y diferentes subcarpetas
(“C:\MisArchivos\Imagenes”, “C:\MisArchivos\PDFs”, etc.).
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.
…
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 .
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).
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.
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.
…
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
…
Vamos a crear, en el mismo directorio donde tenemos la base de datos, un archivo de texto. Lo
llamaremos Edades.txt.
donde <numero> es, precisamente, el número que queramos, y sirve para distinguir el archivo
txt que hemos abierto.
4.- Para añadir líneas en blanco utilizamos la primera parte de la estructura del punto 3, es
decir:
PRINT #1,
…
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
A modo de introducción sobre esta biblioteca vamos a ver cómo podemos utilizarla para
manipular archivos de texto.
¿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”)
…
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á.
• 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
…
Private Sub cmdRecuperaContenidoTxt_Click()
'Borramos el contenido del cuadro de texto
Me.txtContenidoTxt.Value = Null
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
…
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
…
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.
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:
…
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...
…
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
…
…
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
…
…
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
…
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
…
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.
…
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
…
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
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
…
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.
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.
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.
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
…
…
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
…
Sé que este ejemplo puede resultar un poco “tonto”, pero quizá su mecánica de
funcionamiento os pueda ser útil en otro contexto.
…
Private Sub cmdBackup_Click()
25
Visítame en http://siliconproject.com.ar/neckkito/
Call creaBackup
End Sub
…
…
Public Sub creaBackup()
'-----CREADO POR NECKKITO EL 07/05/12-----
'----------Requiere registrar la librería "Microsoft Scripting
Runtime"-----
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.
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
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>>.
• “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?
2
Visítame en http://siliconproject.com.ar/neckkito/
Pues los elementos deben ser definidos dentro de un procedimiento.
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:
…
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”).
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:
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
…
…
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
…
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
…
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.
…
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
…
…
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”.
<<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:
Con todo lo anterior ya vemos que podemos crearnos nuestras propias colecciones.
y definirla así:
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:
La estructura sería:
nomColección.Add “Elemento”
o bien
Por ejemplo, si yo quiero añadir mi coche “Porshe Carrera” a la colección escribiría lo siguiente:
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:
nomColeccion.Count
Es decir:
misCoches.Count
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”)
misCoches.Remove (6)
o así
misCoches.Remove (“PorCar”)
…
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)
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.
• 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)?
…
'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.
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
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).
…
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
…
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.
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...”
…
Private Sub cmdListaVehiculos_Click()
Call misVehiculos
End Sub
…
…
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
…
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.
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!).
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.
…
Public Property Let …
'Si pasa esto haz esto
'Si pasa aquello haz esto otro
'Si pasa eso haz eso otro
End Property
…
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.
…
Public Property Set nombre(variableX As Objeto)
Set variableY = variableX
End Property
…
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.
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
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
• 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).
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
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.
…
Option Compare Database
Option Explicit
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
…
…
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
…
…
.capacDep = -80.5
…
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).
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).
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
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.
…
'Rellenamos el siguiente vehículo
Set unVehiculo = New clsVehic
With unVehiculo
…
Imaginemos que yo tengo una clase, clsBebe, y defino un ejemplar de clase así:
With miBebe
.Caracter = “Dulce”
.Ojos = “Llorones”
End with
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 A → NO hemos obtenido otro bebé, sino que lo que hemos hecho es mostrar mi bebé en
su adolescencia
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.
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:
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.
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).
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.
25
Visítame en http://siliconproject.com.ar/neckkito/
Espero que, hasta aquí, no nos hayamos perdido... je, je...
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í.
…
Option Compare Database
Option Explicit
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:
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.
…
Option Compare Database
Option Explicit
'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
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.
...
'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:
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:
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
…
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?
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?”
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
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”.
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.
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á:
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.
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:
Ahora nos creamos un formulario que nos hará de menú, y que llamaremos FMenu.
…
Private Sub cmdCerrar_Click()
DoCmd.Close acForm, Me.Name
DoCmd.OpenForm "FMenu"
End Sub
…
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
…
…
Private Sub cmdAbreFPrestamos_Click()
DoCmd.Close acForm, Me.Name
DoCmd.OpenForm "FPrestamos", , , , acFormAdd
End Sub
…
…
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.
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.
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:
¿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:
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?
Para ver todos estos nombres podéis bajaros estos documentos, con los nombres
exclusivamente para Access3:
Si queréis ver los nombres para todas las aplicaciones de Office podéis visitar los siguientes
enlances:
• 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:
<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.
<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.
<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>
<amo>
<esclavo>
</amo>
</esclavo>
<amo/>
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:
8
Visítame en http://neckkito.siliconproject.com.ar
Vamos a incidir sobre algunos puntos:
• El subelemento imageMso también puede hacer una llamada a una imagen que
hayamos diseñado nosotros mismos para el botón.
9
Visítame en http://neckkito.siliconproject.com.ar
Pronto vamos a ver un elemento más (el nuestro), que llamaremos <Mis opciones>.
…
<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:
…
<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui">
<ribbon startFromScratch="false">
<tabs>
…
…
<tab idMso="TabHomeAccess"
10
Visítame en http://neckkito.siliconproject.com.ar
visible="false">
</tab>
…
…
<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.
…
<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í:
…
<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"
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.
• Para acabar completamos el código con todas las etiquetas necesarias de cierre
…
</tab>
</tabs>
</ribbon>
</customUI>
…
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?
2.- Una vez reiniciado ahora Access sí ya sabe que existe una cinta de opciones nueva. Vamos
a configurarla:
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...
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.
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.
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.
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.
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:
15
Visítame en http://neckkito.siliconproject.com.ar
De esta manera enlazamos la cinta con el código que escribiremos.
• subAbreFPrestamos
• subAbreFHistorial
• 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.
…
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
…
…
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...
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://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)
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
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:
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
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.
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.
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:
4
Visítame en http://siliconproject.com.ar/neckkito/
llamaremos cmdCerrar.
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:
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:
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:
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.
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.
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.
…
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
…
…
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].
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
…
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.
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
…
…
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/
…
…
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
…
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.
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.
…
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!
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.
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
…
Private Sub cmdCancelar_Click()
DoCmd.Close acForm, Me.Name
End Sub
…
• Dejarlo en blanco
• Introducir un valor no numérico
• Introducir un valor numérico, pero no válido como año de trabajo
…
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
…
…
Private Sub cmdCambiaAT_Click()
'Abrimos FCambiaAT
DoCmd.OpenForm "FCambiaAT"
End Sub
…
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.
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í:
…
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.
…
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:
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”.
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.
• 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
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]).
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")
=fncAT()
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).
…
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
…
…
Private Sub Report_Close()
DoCmd.OpenForm "FMenu"
'Situamos el enfoque sobre el botón de comando cmdAbreRReportTotal
Forms!FMenu.cmdAbreRReportTotal.SetFocus
End Sub
…
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/
…
…
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.
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.
¡suerte!
22
Visítame en http://siliconproject.com.ar/neckkito/