Documente Academic
Documente Profesional
Documente Cultură
Sin embargo, el objetivo de este ejemplo es intentar explicaros cómo podemos manejar los
ficheros adjuntos a través de código VBA. No es complicado (?), pero su manipulación requiere
tener en cuenta ciertas características “especiales” que vamos a ir conociendo a lo largo del
ejemplo (o, al menos, voy a intentar presentároslas de la manera más sencilla de que sea
capaz). ;-)
Para hacerlo lo más operativo posible, es decir, de cara a que podáis aplicarlo a cualquier BD,
el grueso del trabajo lo vamos a hacer a través de módulos. De ahí que sea importante, si no
estáis muy duchos en el tema, que analicéis bien este pdf que tenéis ahora entre las manos
para entender cómo se hacen las llamadas a los diferentes procedimientos de los módulos,
antes de lanzaros a intentar hacer directamente un copy-paste de la BD de ejemplo. Además
de eso, el formulario donde vayáis a trabajar ha de tener unos “requerimientos mínimos” que
debéis aplicar para que el invento nos funcione correctamente.
También son interesantes frente a los OLE porque son más eficientes, en el sentido que
mejoran el rendimiento porque comprimen los archivos que almacenan (obviamente versus
OLE).
¿Tienen algo negativo? Pues que no son manipulables a través de una SQL de actualización de
datos. Tampoco pueden ser utilizados en consultas de unión. Finalmente, existen ciertas
limitaciones para trabajar con ellos a través de macros de datos L
Para finalizar, y esto lo pongo porque lo he leído (no recuerdo dónde, así que no puedo poneros
la fuente) pero, al no utilizarlo, pues no hablo con un 100% de seguridad por no haberlo
podido probar, los datos adjuntos no son compatibles con los tipos de datos de SQL Server, y
1
Visítame en http://neckkito.siliconproject.com.ar
necesitan un proceso de conversión a OLE antes de poder migrar los datos.
NUESTRA TABLA...
Vamos a partir de una tabla muy simple, que llamaremos
TExpedientes. Su estructura será la siguiente:
… Y NUESTRO FORMULARIO
Sobre la tabla anterior vamos a crearnos un formulario que, curiosamente, llamaremos
FExpedientes.
Lo primero que vamos a hacer en ese formulario es eliminar el campo de los ficheros adjuntos.
Si no, ¿qué gracia tendría el ejemplo?
Vamos a crearnos una consulta, que llamaremos CAdjuntosSimple, que se basará en la tabla
TExpedientes. Al ir construyéndola, ¿nos llama algo la atención?
Pues la respuesta debería ser un rotundo “sí”, dado que la tabla que nos muestra la consulta
nos muestra, además, tres subcampos del campo que contiene los adjuntos.
2
Visítame en http://neckkito.siliconproject.com.ar
Es decir, tenemos “FileData”, “FileName” y “FileType”.
Dicho en otro idioma, si nos construimos la consulta de esta manera estaríamos emulando lo
que veríamos en la tabla en vista Hoja de Datos.
Vamos a por la segunda manera de construir la consulta. Vamos a crearnos una consulta que
llamaremos CAdjuntos, con la siguiente estructura:
3
Visítame en http://neckkito.siliconproject.com.ar
Y que al situarla en vista Hoja de Datos nos devuelve la siguiente información:
Gracias a lo anterior ya sabemos que el adjunto tiene tres subcampos, ya sabemos cómo se
llaman y ya sabemos qué tipo de información muestran. Y eso es importante recordarlo en el
momento en que vayamos a utilizar VBA.
Ahora sí, volvamos a nuestro formulario. Para poder ver los adjuntos vamos a insertar un
cuadro de lista, que llamaremos lstAdjuntos2, y a través del asistente estableceremos la
siguiente configuración:
Ahora el cuadro de lista nos sacará todos los adjuntos que haya, que es lo que nos saca la
consulta de origen, pero sin filtrar por el registro en el que estamos. Para solucionar este
“inconveniente” sacamos las propiedades del cuadro de lista y nos vamos a Pestaña Datos >
Origen de la Fila, y sacamos la consulta subyacente haciendo clic sobre el pequeño botón de
puntos suspensivos.
Una vez tengamos esa consulta subyacente a la vista le añadimos un filtro a través del
identificador del registro, de manera que nos quede de la siguiente manera:
2 Para asignar un nombre a un control lo que debemos hacer es sacar las propiedades de ese control e irnos a la Pestaña Otras >
Nombre. Ahí escribimos el nombre que queramos.
4
Visítame en http://neckkito.siliconproject.com.ar
Y para forzar a que la lista “relea” los valores cada vez que
nos cambia el [Id] (porque naveguemos por los registros,
por ejemplo) y, al mismo tiempo, forzar que, en cada
navegación de registro, el cuadro de lista inicialmente no
devuelva valor (importante para evitar el “efecto
memoria”), lo único que debemos hacer es sacar las
propiedades del formulario (ojo, del formulario) y Pestaña
Eventos > Al Activar Registro, y generamos el siguiente
código3:
…
Private Sub Form_Current()
Me.lstAdjuntos.Requery
Me.lstAdjuntos.Value = Null
End Sub
…
5
Visítame en http://neckkito.siliconproject.com.ar
…
Public Function buscaArchivo() As String
'Requiere referencia a Microsoft Office x.y Object Library, donde x.y es la versión que tengamos en nuestro PC.
Dim fDialog As Office.FileDialog
'Instanciamos el objeto fDialog
Set fDialog = Application.FileDialog(msoFileDialogFilePicker)
With fDialog
.AllowMultiSelect = False
.ButtonName = "Seleccionar"
.Title = "Seleccionar el archivo a adjuntar"
.InitialFileName = "C:\"
.InitialView = msoFileDialogViewDetails
.Filters.Clear
.Filters.Add "All Files", "*.*"
If .Show = True Then
buscaArchivo = .SelectedItems(1)
Else
'No hago nada, o puedo avisar si descomento la línea siguiente
'MsgBox "Ha pulsado el botón <Cancelar>."
End If
End With
End Function
…
Tened en cuenta que, para poderlo utilizar, es necesario registrar la referencia a la librería
“Microsoft Office x.y Object Library”, donde x.y es la versión de Office que tengamos instalada
en nuestro PC5.
Bueno… pues sigamos. Vamos a hacer una parada en nuestro formulario para añadir un botón
de comando, que llamaremos cmdAnadeAdjunto, y en su evento “Al hacer clic” le generaremos
el siguiente código:
…
Private Sub cmdAnadeAdjunto_Click()
Dim laRutaArchivo As String
Dim unId As Long
5 Para registrar una referencia debemos irnos, en el editor de VB, a Menú > Herramientas > Referencias... Se nos abrirá una
ventana mostrándonos todas las referencias disponibles. Buscamos la que nos interese, marcamos su check y aceptamos.
6
Visítame en http://neckkito.siliconproject.com.ar
Me.lstAdjuntos.Requery
End Sub
…
2.- Llamamos a la función (es decir, al fileDialog), para poder seleccionar el archivo que
queremos adjuntar. De nuevo, si cancelamos la función buscaArchivo() devuelve una cadena
vacía, con lo cual saldríamos del procedimiento.
4.- Realizado el proceso de inserción efectuamos un refresco de nuestro listbox para que nos
muestre el nuevo adjunto insertado.
Con todo lo indicado en el punto 3 vemos que podemos aplicar la llamada a cualquier
formulario, a cualquier tabla e independientemente de cómo hayamos llamado a nuestros
campos.
Y como estamos metidos de lleno en la inserción de adjuntos continuaremos con lo que sería el
código del módulo que nos permitirá trabajar con adjuntos. Así que, ni cortos ni perezosos,
vamos a crearnos un nuevo módulo estándar que llamaremos mdlManejoAdjuntos.
Como vamos a realizar varias operaciones lo que haremos será, bajo la/s línea/s Option,
declarar las variables privadas comunes a todo el módulo, así;
…
'-----------------------------------------------------------------------------------------------------------------------
' Declaramos las variables comunes de todo el módulo
'-----------------------------------------------------------------------------------------------------------------------
Dim dbs As Database
Dim rst As Recordset2
Dim rstAdj As Recordset2
Dim fldCampoAdj As Field2
Dim miSql As String
…
Sigamos. A continuación insertaremos el procedimiento que nos permite añadir los adjuntos en
función de los parámetros que habíamos comentado en el punto 3 unas líneas más arriba:
7
Visítame en http://neckkito.siliconproject.com.ar
…
'-----------------------------------------------------------------------------------------------------------------------
' Sub subAnadeAdjunto(parametros): procedimiento que permite añadir adjuntos a un campo de una tabla
'-----------------------------------------------------------------------------------------------------------------------
Public Sub subAnadeAdjunto( _
elArchivo As String, _
laTabla As String, _
elCampoAdj As String, _
elCampoId As String, _
elId As Long)
On Error GoTo sol_err
Salida:
'Cerramos conexiones y liberamos memoria
rst.Close
dbs.Close
sol_err:
If Err.Number = 3820 Then 'El adjunto ya existe
MsgBox "Ya existe un adjunto con el nombre y extensión que intenta adjuntar", _
vbExclamation, "DUPLICADO"
Else
MsgBox "Se ha producido el error " & Err.Number & " - " & Err.Description, _
vbCritical, "ERROR"
End If
Resume Salida
End Sub '-----------------------------------------------------------------------FIN subAnadeAdjunto
…
8
Visítame en http://neckkito.siliconproject.com.ar
Fijaos que declaro los recordsets y el campo como
Recordset2 y Field2. Ello es así porque este tipo de objetos
está especialmente preparado para poder trabajar con
campos multivalor y, por supuesto, con campos de datos
adjuntos.
Fijaos que creo un recordset sobre el contenido del campo adjunto ( Set rstAdj =
fldCampoAdj.Value).
Es decir, y hablando en abstracto, es como si creara un recordset sobre un
recordset. Y lo hago sobre la propiedad value.
Quizá estos conceptos se vean un poco más claros en las siguientes acciones que
programaremos, donde nos veremos obligados a recorrer la colección de adjuntos. Así que, ¡no
desesperéis! ;-)
Finalmente, fijaos que para cargar el adjunto hacemos referencia al subcampo “FileData”
(que ya conocemos al haber preparado la consulta que os explicaba más arriba), y que
utilizamos el método LoadFromFile (y ya veremos que para extraer adjuntos podremos utilizar
el método SaveToFile) para cargar el adjunto que queremos.
9
Visítame en http://neckkito.siliconproject.com.ar
ELIMINAR ADJUNTOS DE NUESTRO
EXPEDIENTE
Siguiendo con la tónica del ejemplo, veamos cómo podemos
borrar adjuntos de nuestro campo adjuntos empezando por
nuestro formulario.
…
Private Sub cmdBorraAdjunto_Click()
Dim nombreAdjunto As String
Dim resp As Integer
Dim unId As Long
De nuevo vemos que, tras solicitar confirmación de la eliminación del adjunto seleccionado en
nuestro cuadro de lista, hacemos una llamada al procedimiento subBorraAdjunto(), al cual
pasamos como parámetros el nombre del adjunto seleccionado, la tabla de trabajo, el nombre
del campo que contiene los adjuntos, el nombre del campo que hayamos definido como
identificador y el identificador del registro en el que estamos situados.
Incidir que, tras borrar el archivo, refrescamos la información de nuestro listbox a través de el
forzado de una nueva lectura del origen de datos (Me.lstAdjuntos.Requery) y, además, y para evitar
el efecto memoria, establecemos el valor que devuelve la lista en NULL ( Me.lstAdjuntos.Value =
Null).
Alguien podría decirme: “¿Y qué es esto del “efecto memoria”? Pues bien: todas las
operaciones las estamos realizando “por detrás” a través de código. Supongamos que tenemos
un adjunto llamado “XXX.yyy”. Si realizamos la eliminación del mismo el archivo,
efectivamente, se elimina, y nuestro cuadro de lista ya no lo muestra. Si volvemos a pulsar el
botón de eliminar, ¿qué mensaje de confirmación nos saldrá? Pues nos pedirá, otra vez, si
queremos eliminar “XXX.yyy”, que ya no existe, y si lo intentamos borrar nos saltará un error
de código porque no se encuentra el adjunto que se quiere eliminar. Es decir, es “como” si el
listbox siguiera devolviéndonos el valor del último archivo seleccionado; esto es, “se acuerda”
(hablando en términos no muy técnicos… je, je…). Para evitar esto (y el consiguiente error de
código) es por lo que forzamos a nuestro listbox a devolver un NULL, y ya hemos visto en el
código que si no hay valor el código se interrumpe y no se ejecuta
( 'Cogemos el adjunto seleccionado en la lista. Si no hay valor seleccionado salimos
nombreAdjunto = Nz(Me.lstAdjuntos.Value, "")
If nombreAdjunto = "" Then Exit Sub)
10
Visítame en http://neckkito.siliconproject.com.ar
¿Y cómo realizamos la eliminación “efectiva”? Pues volvemos a nuestro módulo
mdlManejoAdjuntos y escribimos el siguiente procedimiento:
…
'-----------------------------------------------------------------------------------------------------------------------
' Sub subBorraAdjunto(parametros): procedimiento que permite borrar adjuntos de un campo de una tabla
'-----------------------------------------------------------------------------------------------------------------------
Public Sub subBorraAdjunto( _
elAdjunto As String, _
laTabla As String, _
elCampoAdj As String, _
elCampoId As String, _
elId As Long)
On Error GoTo sol_err
sol_err:
MsgBox "Se ha producido el error " & Err.Number & " - " & Err.Description _
& vbcrlf & "en el procedimiento mdlManejoAdjuntos > subBorraAdjunto()", _
vbCritical, "ERROR"
Resume Salida
End Sub '-----------------------------------------------------------------------FIN subBorraAdjunto
…
La mayoría de comentarios que realizaba en la adición del adjunto nos sirven para este código.
11
Visítame en http://neckkito.siliconproject.com.ar
Solamente destacar que, en este caso, sí realizo un
recorrido por la colección de adjuntos ( Do Until .EOF) y voy
mirando si coincide el nombre del adjunto examinado (a
través del valor devuelto por el subcampo que ya
conocemos “FileName”) con el del adjunto seleccionado
(If .FileName = elAdjunto Then). Y, cuando coinciden… ¡zas! Me lo
cargo al estilo Terminator (Sayonara, baby). :-)
…
Private Sub cmdExtraeAdjunto_Click()
Dim nombreAdjunto As String
Dim unaRuta As String
Dim resp As Integer
Dim unId As Long
'Llamamos a la función buscaCarpeta para que el usuario pueda seleccionar una ruta de extracción
'Si se cancela la función devuelve una cadena vacía. Si es así salimos
unaRuta = buscaCarpeta()
If unaRuta = "" Then Exit Sub
Pues, en primer lugar, vemos que permitimos al usuario elegir la carpeta de destino a través
de la llamada a la función buscaCarpeta()
En segundo lugar, observemos que hemos tenido que añadir un parámetro nuevo a nuestro
procedimiento subExtraeAdjunto(), que es la ruta donde queremos guardar el archivo
(representada por la variable unaRuta).
Pues, ¿qué mejor que ir por orden? Nos situamos en nuestro módulo
mdlSeleccionCarpetaArchivo y añadimos una nueva función pública a través del siguiente
código:
12
Visítame en http://neckkito.siliconproject.com.ar
…
Public Function buscaCarpeta() As String
'Requiere referencia a Microsoft Office x.y Object Library, donde x.y es la versión que tengamos en nuestro PC.
Dim fDialog As Office.FileDialog
'Instanciamos el objeto fDialog
Set fDialog = Application.FileDialog(msoFileDialogFolderPicker)
With fDialog
.AllowMultiSelect = False
.ButtonName = "Seleccionar"
.Title = "Seleccionar la carpeta de destino"
.InitialFileName = "C:\"
.InitialView = msoFileDialogViewDetails
If .Show = True Then
buscaCarpeta = .SelectedItems(1) & "\"
Else
'No hago nada, o puedo avisar si descomento la línea siguiente
'MsgBox "Ha pulsado el botón <Cancelar>."
End If
End With
End Function
…
Fijaos, en relación a nuestra función para seleccionar un archivo, que ahora nuestro FileDialog
utiliza la constante adecuada para seleccionar una carpeta ( msoFileDialogFolderPicker), con lo que
ya no necesitamos filtros de ninguna clase (hemos eliminado las líneas que contenían las
órdenes relativas a “Filters”).
…
'-----------------------------------------------------------------------------------------------------------------------
' Sub subExtraeAdjunto(parametros): procedimiento que permite extraer adjuntos de un campo de una tabla
'-----------------------------------------------------------------------------------------------------------------------
Public Sub subExtraeAdjunto( _
laRuta As String, _
elAdjunto As String, _
laTabla As String, _
elCampoAdj As String, _
elCampoId As String, _
elId As Long)
On Error GoTo sol_err
13
Visítame en http://neckkito.siliconproject.com.ar
'Recorremos los adjuntos hasta encontrar el que queremos extraer, y lo
extraemos en la ruta seleccionada
With rstAdj
.MoveFirst
Do Until .EOF
If .FileName = elAdjunto Then
.FileData.SaveToFile laRuta
MsgBox "Archivo extraído correctamente", vbInformation, "CORRECTO"
Exit Do
End If
.MoveNext
Loop
End With
Salida:
sol_err:
MsgBox "Se ha producido el error " & Err.Number & " - " & Err.Description _
& vbCrLf & "en el procedimiento mdlManejoAdjuntos > subExtraeAdjunto()", _
vbCritical, "ERROR"
Resume Salida
End Sub '-----------------------------------------------------------------------FIN subExtraeAdjunto
…
Creo que, después de todo lo aprendido, no hace falta comentar este último código. Sólo
remarcar como elemento novedoso que, para guardar el archivo extraído utilizamos el método
“SaveToFile” (.FileData.SaveToFile laRuta) indicándole la ruta donde queremos salvarlo, pero, ojo,
sin indicar en esa ruta el nombre y extensión de archivo.
…
Private Sub cmdExtraeTodosAdjuntos_Click()
Dim unaRuta As String
Dim unId As Long
'Llamamos a la función buscaCarpeta para que el usuario pueda seleccionar una ruta de extracción
'Si se cancela la función devuelve una cadena vacía. Si es así salimos
unaRuta = buscaCarpeta()
If unaRuta = "" Then Exit Sub
14
Visítame en http://neckkito.siliconproject.com.ar
Y en nuestro módulo mdlManejoAdjuntos nos creamos un procedimiento como el que sigue:
…
'-----------------------------------------------------------------------------------------------------------------------
' Sub subExtraeTodosAdjuntos(parametros): procedimiento que permite extraer todos los adjuntos de un campo de
una tabla
'-----------------------------------------------------------------------------------------------------------------------
Public Sub subExtraeTodosAdjuntos( _
laRuta As String, _
laTabla As String, _
elCampoAdj As String, _
elCampoId As String, _
elId As Long)
On Error GoTo sol_err
Salida:
'Cerramos conexiones y liberamos memoria
rst.Close
dbs.Close
sol_err:
MsgBox "Se ha producido el error " & Err.Number & " - " & Err.Description _
& vbCrLf & "en el procedimiento mdlManejoAdjuntos > subExtraeTodosAdjuntos()", _
vbCritical, "ERROR"
Resume Salida
End Sub '-----------------------------------------------------------------------FIN subExtraeTodosAdjuntos
15
Visítame en http://neckkito.siliconproject.com.ar
…
UN ÚLTIMO APUNTE
Soy consciente de que hay partes de código que pueden
mejorarse o, mejor dicho, optimizarse. Igualmente soy
consciente de que se podría mejorar la parte de control de
errores. Lo sé, lo sé… no hace falta que me lo
recordéis… ;-)
Sin embargo, me he decidido a dejar las cosas así como están porque, en realidad, mi objetivo
es que entendáis una a una las acciones que hemos explicado en relación al tratamiento de
adjuntos. En aras de ese objetivo he querido sacrificar cierta “pulcritud” en el código. Supongo
que me perdonaréis esta pequeña licencia, ¿verdad? :D
Ahora mismo puedo pensar en operaciones no explicadas y tal vez no tan comunes pero que,
quizá, en algún momento, pudiéramos requerir. Por ejemplo, ¿cómo copiaríamos un registro
completo a otra tabla?
Pues en ese caso se me ocurren dos posibilidades: o bien utilizar única y exclusivamente
recordsets, o bien realizar dos operaciones: la primera a través de una SQL de inserción de
datos, dejando de lado los adjuntos, y a continuación, y a través de un identificador, utilizar un
conjunto de recordsets para ir escribiendo, de uno a otro, la información de los adjuntos
(recordemos, sus tres subcampos: “FileData”, “FileName” y “FileType”). Eso es algo que no he
probado y que, ahora mismo, no podría definiros más claramente (¿funcionaría? Gran
pregunta… ).
¡suerte!
16
Visítame en http://neckkito.siliconproject.com.ar