Gestionando Imágenes con ADO


El Código ADO para Gestionar Imágenes en una Base de Datos con Visual Basic

Por Harvey Triana, Octubre 2001

Existen ciertos detalles para implementar una interfaz Visual Basic que permita gestionar imágenes en una Base de datos que no están documentados explícitamente. Este articulo describe la forma de almacenar y recuperar imágenes en una base de datos usando la tecnología de acceso a datos ADO. Ciertamente las técnicas usadas con DAO son aplicables a la tecnología ADO, salvo algunos pequeños cambios. No obstante las clases de reconocimiento de datos de Visual Basic 6.0 suministran un medio más eficaz para recuperar las imágenes desde código plano, lo que con versiones anteriores de Visual Basic era algo disfrazado e injustificado (necesariamente usamos un control Data en el mejor de los casos).

Tipo de Dato de la Imagen

Necesariamente las imágenes se almacenan en campos binarios en una Base de Datos, sea FoxPro, Oracle, Access, etc. La naturaleza de los formatos de imágenes debe ser interpretada por las capacidades gestoras de herramienta que manipula los datos. Dentro de una base de datos las imágenes son simples stream binarios. El objeto stdPicture de Visual Basic recupera los formatos más comunes de imagen, sea GIF, JPG, BMP, sin quejarse, no obstante, Visual Basic no suministra un método para archivar una imagen en un formato especifico, solo se limita a mapas de bits de Windows (salvo software de terceras partes). Aun sigo esperando la mejora de la instrucción SavePicture con un parámetro que permita especificar el formato de la imagen con el que se archivará.

Normalmente será deseable archivar las imágenes en las bases de datos en formatos GIF o JPG. La naturaleza comprimida de estos formatos los hace supremamente atractivos para preservar el espacio ocupado por los datos, mientras que un BMP con la misma calidad (digamos 24 bits de profundidad en color), ocupara muchas veces más espacio, dilatando aun más el problema cuando se trata con imágenes de gran tamaño. Asi pues, en este articulo suministrare el código suficiente para archivar y recuperar en el formato deseado.

Este articulo cubre dos aspectos básicos, (1) Guardar Imágenes un una Base de Datos, y (2) Recuperar Imágenes desde una Bases de Datos.

Guardar Imágenes un una Base de Datos

Partimos en que la fuente de imágenes serán archivos, sin embargo también es viable guardar imágenes generadas a partir de métodos gráficos. Inicialmente definimos el mecanismo por el cual daremos la oportunidad al usuario de enviar un archivo de imagen a la base de datos. Yo prefiero usar la capacidad de arrastrar y soltar de Windows, ya que es supremamente fácil y cómodo para el usuario. Aunque si lo prefiere, puede usar un control CommonDialog para que el usuario examine los archivos de una manera más estándar.

Guardar una imagen en una base de datos, básicamente requiere del procedimiento: PutImageInField para imágenes relativamente pequeñas (menos de 100k como regla aproximada) o PutLargeImageInField para imágenes muy grandes. Si almacena GIF o JPG probablemente nunca va a necesitar de PutLargeImageInField. La diferencia básica de estos procedimientos en que PutLargeImageInField lee el archivo de imagen por lotes, esto con el objetivo de optimizar el uso de memoria.

Ambos procedimientos los he colocado en la clase cls_ADOServices que publico a continuación:

' CLASS       : cls_ADODBServices
' DESCRIPTION : Some key functions
' AUTHOR      : Harvey T.
' LAST REVIEW : 08/08/99

Option Explicit

'//For retrive or store large pictures
Private Const nBUFFER As Long = 1024

'//NORMAL IMAGES
Public Sub PutImageInField( _
    f As ADODB.Field, _
    File As String _
    )
    Dim b() As Byte
    Dim ff  As Long
    Dim n   As Long

    On Error GoTo ErrHandler
    ff = FreeFile
    Open File For Binary Access Read As ff
    n = LOF(ff)
    If n Then
       ReDim b(1 To n) As Byte
       Get ff, , b()
    End If
    Close ff
    f.Value = b()
    Exit Sub

ErrHandler:
    MsgBox "ERROR: " & Err.Description
End Sub

Public Function GetImageFromField( _
    f As ADODB.Field _
    ) As StdPicture

    Dim b()  As Byte
    Dim ff   As Long
    Dim File As String

    On Error GoTo ErrHandler
    Call GetRandomFileName(File)
    ff = FreeFile
    Open File For Binary Access Write As ff
    b() = f.Value
    Put ff, , b()
    Close ff
    Erase b
    Set GetImageFromField = LoadPicture(File)
    Kill File
    Exit Function

ErrHandler:
    MsgBox "ERROR: " & Err.Description
End Function

'//LARGE IMAGES
Public Sub PutLargeImageInField( _
    f As ADODB.Field, _
    File As String _
    )
    Dim b()      As Byte
    Dim ff       As Long
    Dim i        As Long
    Dim FileLen  As Long
    Dim Blocks   As Long
    Dim LeftOver As Long

    On Error GoTo ErrHandler
    ff = FreeFile
    Open File For Binary Access Read As ff

    FileLen = LOF(ff)
    Blocks = Int(FileLen / nBUFFER)
    LeftOver = FileLen Mod nBUFFER

    ReDim b(LeftOver)
    Get ff, , b()
    f.AppendChunk b()

    ReDim b(nBUFFER)
    For i = 1 To Blocks
        Get ff, , b()
        f.AppendChunk b()
    Next
    Close ff
    Exit Sub

ErrHandler:
    MsgBox "ERROR: " & Err.Description
End Sub

Public Function GetLargeImageFromField( _
    f As ADODB.Field _
    ) As StdPicture

    Dim b()      As Byte
    Dim ff       As Long
    Dim File     As String
    Dim i        As Long
    Dim FileLen  As Long
    Dim Blocks   As Long
    Dim LeftOver As Long

    On Error GoTo ErrHandler
    Call GetRandomFileName(File)
    ff = FreeFile
    Open File For Binary Access Write As ff
    Blocks = Int(f.ActualSize / nBUFFER)
    LeftOver = f.ActualSize Mod nBUFFER
    b() = f.GetChunk(LeftOver)
    Put ff, , b()
    For i = 1 To Blocks
        b() = f.GetChunk(nBUFFER)
        Put ff, , b()
    Next
    Close ff
    Erase b
    Set GetLargeImageFromField = LoadPicture(File)
    Kill File
    Exit Function

ErrHandler:
    MsgBox "ERROR: " & Err.Description
End Function

Public Function RecordLocation( _
    adoRs As ADODB.Recordset, _
    Optional Title As String = vbNullString _
    )
    On Error GoTo ErrHandler
    With adoRs
        If Not (.EOF Or .BOF) Then
           RecordLocation = Title & .AbsolutePosition & _
                            " de " & .RecordCount
        Else
           RecordLocation = vbNullString
        End If
    End With
    Exit Function

ErrHandler:
    RecordLocation = "VOID"
End Function

Public Sub PutPictureInField( _
       f As ADODB.Field, _
       pic As StdPicture _
    )
    Dim File As String
    On Error GoTo ErrHandler
    Call GetRandomFileName(File)
    Call SavePicture(pic, File)
    Call PutImageInField(f, File)
    Exit Sub

ErrHandler:
    MsgBox "ERROR: " & Err.Description
End Sub

Private Sub GetRandomFileName(ByRef File As String)
    Randomize Timer
    File = App.Path & IIf(Right$(App.Path, 1) = "\", "", "\") & _
           Format(Rnd() * 1000000, "00000000") & ".tmp"
End Sub

Esta clase incluye los procedimientos GetImageFromField, GetLargeImageFromField y RecordLocation. La ultima es solo un detalle de presentación, mientras que las dos anteriores se usan para recuperar datos de imagen en casos especiales.

El procedimiento PutPictureInField se usa cuando se desea archivar en la base de datos una imagen generada por métodos gráficos, o simplemente el valor Picture de un control. Para el primer caso sería: Call adoSrv.PutPictureInField(rs.Fields("My Photo"), picX.Image).

Ejemplo para Almacenar Imágenes en una Base de Datos

El siguiente ejemplo muestra la forma de usar la clase cls_ADOServices y un modo bien tratado de manejar el Recordset de ADO.

Todos los ejemplos a continuación debe tener estas tres referencias: (1) Microsoft ActiveX Data Object 2.1 Library (aplica también a la versión 2.0), (2) Microsoft Data Binding Collection, y (3) Microsoft Data Formatting Object Library.

La siguiente es una imagen del formulario que sigue el ejemplo:

A modo de laboratorio, creé una base de datos nueva en Access con la siguiente estructura:

Campo

Tipo

Descripción

Id Photo Entero Largo Establece la clave principal.
Photo Objeto OLE Contiene imágenes
Note Texto (254) Alguna descripción

Le di el nombre PhotosSample.mdb a la base de datos, y la archivé en el mismo directorio de la aplicación. Use un DataEnvironment (Entorno de Datos) para conectar la base de datos, el cual tiene la siguiente configuración:

No explico como crear el Entorno de Datos. Si desea información, busque «Interactuar con datos de una base de datos Microsoft Jet o Microsoft Access» en la documentación MSDN, el cual le guiará paso a paso el modo de crear y usar un DataEnvironment.

El nombre del objeto DataEnvironment es datenvPhotos, el cual tiene una única conexión: cnnPhotosSample, y un solo objeto Command: PhotosQuery, el cual usa la siguiente consulta:

SELECT Photos.[Id Photo], Photos.Photo, Photos.Note
FROM Photos
ORDER BY Photos.[Id Photo];

Finalmente, he habilitado la conexión a la base de datos para que no sea de solo lectura (cuando se crean de forma predeterminada las conexiones son de solo lectura).

No necesariamente tiene que crear un DataEnvironment para usar los procedimientos de este articulo, puede recrear el ejemplo usando otro mecanismo de conexión como un ADO.Recordset simple o un Control ADODC (realmente prefiero evitar el control ADODC, que por demás es incompatible con ADO 2.1).

Los controles y disposición de este ejemplo se muestran el la imagen de formulario, y siguen esta configuración:

Control

Nombre

Otras propiedades

DataField

PictureBox

picPhoto

OLEDropMode=1

Photo

Label

lblImageFieldSize

BorderStyle=1

Id Photo

Label

lblIdPhoto

BorderStyle=1

 

TextBox

txtNote

 

Note

CommandButton (array de 0 a 3)

cmdMoveRecord

Captions: Fisrt, Previous, Next, Last

 

CommandButton

cmdAdd

Caption: Add

 

CommandButton

cmdDelete

Caption: Delete

 

CommandButton

cmdClose

Caption: Close

 

Los Controles con DataField definido tienen: DataSource = datenvPhotos y DataMember = PhotosQuery

Use el siguiente código en el módulo del formulario:

' FORM        : frmPhotosSample
' DESCRIPTION : Muestra de almacenar/recuperar formatos gráficos
'               en una base de datos
' AUTHOR      : Harvey T.
' LAST REVIEW : 08/08/99

Option Explicit

Private WithEvents rs As ADODB.Recordset '//Referencia al Recordset del                                          '//DataEnvironment
Private adoSrv As cls_ADODBServices      '//Servicios de funciones

Private Sub cmdAdd_Click()
    On Error GoTo ErrHandler

    '//Crea un registro y lo actualiza automáticamente
    rs.AddNew
    rs("Id Photo") = Int(100000 * Rnd) '//Clave falsa
    rs("Note") = "Void Note"
    '//imagen predeterminada
    Call adoSrv.PutImageInField(rs.Fields("Photo"), _
                App.Path + "\i_doc.gif")
    rs.Update
    ActiveControls True
    rs.MoveLast
    Exit Sub

ErrHandler:
    MsgBox "ERROR: " & Err.Description
End Sub

Private Sub cmdClose_Click()
    Unload Me
End Sub

Private Sub cmdDelete_Click()
    rs.Delete adAffectCurrent

    If rs.RecordCount Then
       rs.MoveLast
    Else
       ActiveControls False, "cmdAdd"
    End If
End Sub

Private Sub cmdMoveRecord_Click(Index As Integer)
    picPhoto.SetFocus
    With rs
        Select Case Index
        Case 0 '//First
        If .AbsolutePosition > 1 Then
           .MoveFirst
        End If
        Case 1 '//Previous
        If .AbsolutePosition > 1 Then
           .MovePrevious
        End If
        Case 2 '//Next
        If .AbsolutePosition < .RecordCount Then
           .MoveNext
        End If
        Case 3 '//Last
        If .AbsolutePosition < .RecordCount Then
           .MoveLast
        End If
        End Select
    End With
End Sub

Private Sub Form_Load()
    Set adoSrv = New cls_ADODBServices
    Set rs = datenvPhotos.rsPhotosQuery

    If rs.RecordCount = 0 Then
       ActiveControls False, "cmdAdd"
    Else
       Call MoveComplete
    End If
End Sub

Private Sub Form_Unload(Cancel As Integer)
    Set rs = Nothing
    Set adoSrv = Nothing
End Sub

Private Sub ActiveControls( _
    Action As Boolean, _
    ParamArray Exeptions() As Variant _
    )
    Dim v       As Variant
    Dim ctlName As String
    Dim Use     As Boolean
    Dim ctl     As Control

    On Error Resume Next
    For Each ctl In Controls
        ctlName = ctl.Name
        Use = True
        For Each v In Exeptions
            If ctlName = v Then
               Use = False
               Exit For
            End If
        Next
        If Use Then
           If Not ctl.Enabled = Action Then
              ctl.Enabled = Action
           End If
        End If
    Next
End Sub

Private Sub picPhoto_OLEDragDrop( _
    Data As DataObject, _
    Effect As Long, _
    Button As Integer, _
    Shift As Integer, _
    x As Single, Y As Single _
    )
    '//Filtra
    If InStr(".bmp.gif.jpg", LCase$(Right$(Data.Files(1), 4))) Then
       '//Guarda en la base de datos
       Call adoSrv.PutImageInField(rs.Fields("Photo"), Data.Files(1))
       rs.Update
    End If

    '//NOTA. Para imagenes grandes usar:
    'Call adoSrv.PutLargeImageInField(rs.Fields("Photo"), Data.Files(1))
    'rs.Update
    'rs.Move 0 '//Muestra la imagen
End Sub

Private Sub rs_MoveComplete( _
    ByVal adReason As ADODB.EventReasonEnum, _
    ByVal pError As ADODB.Error, _
    adStatus As ADODB.EventStatusEnum, _
    ByVal pRecordset As ADODB.Recordset _
    )
    Call MoveComplete
End Sub

Private Sub MoveComplete()
    If Not (rs.EOF Or rs.BOF) Then
       Caption = adoSrv.RecordLocation(rs, "Photo: ")
       lblImageFieldSize = "Bytes stored = " & _
                           rs.Fields("Photo").ActualSize
    End If
End Sub

Estimado lector, si es un programador relativamente nuevo en ADO, el análisis del código del modulo anterior, frmPhotosSample, le mostrará muchos detalles. Este código es sólido y muestra el modo correcto de manejar un objeto Recordset de ADO con código.

Note que el procedimiento de adicionar un registro, en el evento clic del Control cmdAdd, crea el registro físicamente, es decir, no deja en modo de cache el nuevo registro. Esto da seguridad y estabilidad a la base de datos. Note también, que se dan valores predeterminados a los Campos de la base de datos, en particular, se envía una imagen pequeña a la base de datos (por favor indique un archivo de imágen cualquiera en su disco), esto con el propósito de asegurar que eventualmente no queden el Campo de la imagen vacío (lo que posiblemente traería problemas subsecuentes al usar la bases de datos). Se busca estabilidad.

Recuperar Imágenes desde una Bases de Datos

Si es usuario de alguna versión anterior a Visual Basic 6.0 y desea leer las imágenes desde código, tiene la alternativa de emplear los procedimientos GetImageFromField y GetLargeImageFromField de la clase cls_ADOServices. Estas funciones devuelven un objeto stdPicture; por ejemplo para cargar una imagen en un control PictureBox usaría: Set pic.Picture = GetImageFromField(rsX.Fields("Mi Imagen")). Necesariamente estas funciones no tienen gran rendimiento dado que leen el valor del campo de la base de datos, crean una archivo y vierten el contenido de bytes, para finalmente recuperarlo con LoadPicture. Esta estrategia no es atractiva si debe a leer muchas imágenes al tiempo. Una alternativa es usar una conexión Recordset en un formulario no visible con un control Image enlazado al campo fuente de las imágenes. Luego empaquetamos este formulario en una clase y le damos la capacidad de leer y suministrar las imágenes a través de procedimientos en una interfaz de propiedades. Realmente esta técnica no es código elegante tal y como le espera un programador de clases. Francamente esta es la mejor alternativa con Visual Basic 5.0.

Visual Basic 6.0 resuelve este problema eficazmente dadas las capacidades del objeto Recordset de ADO junto a un objeto BindingsCollection y, para usar sin interfaz de usuario, una clase de reconocimiento de datos. Francamente, los procedimientos GetImageFromField y GetLargeImageFromField pasan a ser pieza histórica de colección, pues ya no se necesitaran más. Por demás, las clases de reconocimiento de datos suministran un contexto más avanzado para un diseño Cliente-Servidor con una arquitectura multicapa.

Inicialmente daré un bosquejo de como luce un cliente que lee imágenes con un simple Recordset y un ObjetoBindingCollection. Luego sugiero como escribir una clase de receptora de datos y la forma de usarla en el cliente

Leyendo Imágenes con un Simple Recordset

Mostrar las imágenes y otros datos en un Formulario básicamente requiere de lo siguiente: Crear un objeto Recordset, un objeto BindingCollection, y enlazar los controles. Una alternativa más simple es usar un DataEnvironmet, tal y como se presento en la primera parte de este articulo.

Para los siguientes ejemplos cree un formulario con dos controles: Un Image con nombre imgPhoto, y un Label con nombre lblNote. El siguiente módulo es para el formulario, muestra la arquitectura más simple de usar un Recordset de ADO, y vincular algunos Controles a Campos del mismo:

' FORM        : frmTestRecordset
' DESCRIPTION : La forma de accesar imagenes desde un Recordset
'               de ADO y vincular los Campos a Controles
' AUTHOR      : Harvey T.
' LAST MODIFY : -

Option Explicit

Private WithEvents rs As ADODB.Recordset
Private bcl As BindingCollection

Private Sub Form_Load()
    Call InitRecordset
    Call DoBindings
End Sub

Private Function InitRecordset()
    Dim cnn As Connection
    Dim cmd As Command

    Set cnn = New Connection
    Set cmd = New Command
    Set rs = New Recordset

    '//Database command connection
    cnn.Open "Provider=Microsoft.Jet.OLEDB.3.51;" & _
             "Data Source=PhotosSample.mdb;"
    With cmd
        Set .ActiveConnection = cnn
        .CommandType = adCmdText
        .CommandText = "SELECT [Id Photo], Photo, Note FROM Photos;"
    End With

    With rs
        .CursorLocation = adUseClient
        .Open cmd, , adOpenForwardOnly, adLockReadOnly
        Set cmd.ActiveConnection = Nothing
        Set cmd = Nothing
        Set .ActiveConnection = Nothing
    End With
    cnn.Close
    Set cnn = Nothing
End Function

Private Sub Form_Unload(Cancel As Integer)
    If Not rs Is Nothing Then
       rs.Close
    End If
End Sub

Public Sub DoBindings()
    Dim df As StdDataFormat

    Set bcl = New BindingCollection
    Set df = New StdDataFormat

    Set bcl.DataSource = rs

    bcl.Add lblNote, "Caption", "Note"
    df.Type = fmtPicture
    bcl.Add imgData, "Picture", "Photo", df

    Set df = Nothing
End Sub

Private Sub lblNote_Click()
    '//only as sample
    If rs.AbsolutePosition < rs.RecordCount Then
       rs.MoveNext
    End If
End Sub

Cuando se da clic en el Label, el registro se mueve al siguiente. Esto es solo para muestra.

La parte más relevante del código anterior, en lo que concierne a este articulo, es la forma en que se debe enlazar la propiedad Picture del control al campo de la base de datos. Note que requerimos de una instancia del objeto stdDataFormat en el método Add del objeto BindingCollection.

Este diseño simple muestra que para leer la imagen necesita un enlace a un control que suministre una propiedad del tipo stdPicture, tal como PictureBox o Image (si el Recordset es de solo lectura, es recomendado usar un control Image, ya que este es más liviano y por tanto entrega mejor rendimiento que PictureBox). Bien, ¿qué tal si desea leer las imágenes para usar en otro contexto que no sea mostrar la foto?. La respuesta es usar una clase de reconocimiento de datos que reciba los datos a través de una propiedad del tipo stdPicture.

Usando una Clase Receptora de Datos

Usaremos una clase receptora de datos cuando no necesitamos desplegar las imágenes en un control, es decir sin interfaz de usuario. Aunque como muestra el siguiente ejemplo, leo las imágenes con una clase receptora de datos y las muestro en un control Image, con el propósito de demostrar que se están leyendo correctamente los datos.

La clase simple Receptora de Datos se construye empezando por fijar la propiedad (en tiempo de diseño) DataBindingBehavior = 1 (vbSimpleBound). El código de la clase luce de la siguiente manera:

' CLASS       : cls_PictureConsumer
' DESCRIPTION : Una clase receptora de datos, de los
'               cuales una columna son imagenes
' AUTHOR      : Harvey T.
' LAST MODIFY : -
' NOTE        : DataBindingBehavior = 1 (vbSimpleBound)

Option Explicit

'//Data fields
Private m_Picture As StdPicture
Private m_Note    As String

Public Property Get Picture() As StdPicture
    Set Picture = m_Picture
End Property

Public Property Set Picture(v As StdPicture)
    Set m_Picture = v
End Property

Public Property Get Note() As Variant
    Note = m_Note
End Property

Public Property Let Note(v As Variant)
    m_Note = v
End Property

Note que se crean propiedades para suministrara los datos del Recordset. Estas propiedades también suelen servir de enlaces a Controles con esta capacidad (DataBound), los que es común en soluciones para Web.

El cliente de la clase receptora puede seguir este modelo, según el ejemplo:

' FORM        : frmTestClass
' DESCRIPTION : La forma de accesar imagenes desde un Recordset
'               de ADO y vincular a una clase lectora de datos
' AUTHOR      : Harvey T.
' LAST MODIFY : -

Option Explicit

Private rs  As ADODB.Recordset '//Source
Private bcl As BindingCollection '//Data Binding
Private dc  As cls_PictureConsumer '//Consumer

Private Sub Form_Load()
    Call InitRecordset
    Call DoBindings
    Call ShowData
End Sub

Private Sub ShowData()
    '//Procedimiento solo para mostrar los datos
    Set imgData.Picture = dc.Picture
    lblNote.Caption = dc.Note
End Sub

Private Function InitRecordset()
    Dim cnn As Connection
    Dim cmd As Command

    Set cnn = New Connection
    Set cmd = New Command
    Set rs = New Recordset

    '//Database command connection
    cnn.Open "Provider=Microsoft.Jet.OLEDB.3.51;" & _
             "Data Source=PhotosSample.mdb;"
    With cmd
        Set .ActiveConnection = cnn
        .CommandType = adCmdText
        .CommandText = "SELECT [Id Photo], Photo, Note FROM Photos;"
    End With

    With rs
        .CursorLocation = adUseClient
        .Open cmd, , adOpenForwardOnly, adLockReadOnly
        Set cmd.ActiveConnection = Nothing
        Set cmd = Nothing
        Set .ActiveConnection = Nothing
    End With
    cnn.Close
    Set cnn = Nothing
End Function

Private Sub Form_Unload(Cancel As Integer)
    Set dc = Nothing
    If Not rs Is Nothing Then
       rs.Close
    End If
End Sub

Public Sub DoBindings()
    Dim df As StdDataFormat

    Set dc = New cls_PictureConsumer
    Set bcl = New BindingCollection
    Set df = New StdDataFormat

    Set bcl.DataSource = rs

    bcl.Add dc, "Note", "Note"
    df.Type = fmtPicture
    bcl.Add dc, "Picture", "Photo", df

    Set df = Nothing
End Sub

Private Sub lblNote_Click()
    '//Procedimiento solo para mostrar los datos
    If rs.AbsolutePosition < rs.RecordCount Then
       rs.MoveNext
       ShowData
    End If
End Sub

Note que el código es similar al caso de lectura desde un simple Recordset, salvo que fue cambiada la recepción de datos, en este caso ya no son los controles enlazados imgData y lblNote, es la clase consumidora de datos que suministra dos propiedades: Picture y Note.

En este caso el Cliente (el formulario) suministra la fuente de datos a través de un Recordset de ADO. En un diseño Cliente-Servidor normalmente esta fuente de datos será otra clase de reconocimiento de datos, más exactamente una Clase Origen de Datos (Data Source). Realmente extendería bastante este escrito al colocar el código completo de un ejemplo de estas características, pero las bases están dadas en este escrito.

El problema de las Imágenes Capturadas con Controles OLE

La tecnología OLE es un mecanismo para usar fuentes binarias heterogéneas como origen de datos. Por ejemplo, las imágenes almacenadas en aplicaciones de Microsoft Access guardan los datos en un formato que solo entiende el Control OLE intrínseco de Visual Basic. No sé porque razón este control aun no es compatible con ADO (es increíble dado que la tecnología de fondo es OLE DB). Consultar el articulo ID: Q191103 (BUG: ADO Bound OLE Control Does Not Display Bitmap) de MSDN para detalles.

Realmente este es un problema incomodo. Un programador Visual Basic para leer las imágenes guardadas por aplicaciones de Access debe valerse de artificios para resolver el problema. El articulo Q191103 menciona que una solución es usar los métodos AppendChunk y GetChunk para recuperar y almacenar en forma binaria. Es decir, en teoría la solución que dan los procedimientos GetImageFromField y GetLargeImageFromField de la clase cls_ADOServices, no obstante lo intente y se producen errores de imagen no válida. Aun espero una respuesta de a este extraño comportamiento. Necesariamente tendremos que usar DAO y el control OLE, pero esta no es una buena solución para aplicaciones de carga exigente (por ejemplo una solución para el Web). Asi pues, esperemos un Control OLE compatible con ADO.

Por esto y otros aspectos, no recomiendo gestionar imágenes con controles OLE, el rendimiento (y en algunos casos la calidad) es mucho menor comparado con las técnicas descritas en la primera parte de este articulo.


NOTA.
1. Los ejemplos expuestos anteriormente, no trabajan cuando la imagen tiene más de 64kb. Esto es debido a un BUG con el proveedor OLE DB Jet 3.51. El problema se soluciona usando el proveedor Jet 4.0 (Access2000) o usando una conexión OBDC como se muestra a continuación:

Reemplazar:

'//Database command connection
cnn.Open "Provider=Microsoft.Jet.OLEDB.3.51;Data Source=PhotosSample.mdb;"

Por:

'//Database command connection
gsMainConn = "DRIVER=Microsoft Access Driver (*.mdb);DBQ=PhotosSample.mdb;"

Más detalles en:

    FIX: ADO: Unable To Update Memo Field > 64K In Access Database
    ( http://support.microsoft.com/support/kb/articles/q198/5/32.asp )

2. El procedimiento PutLargeImageInField no es  necesario, debe usar PutImageInField en todos los casos. Esto debido a que se emplean array de Bytes en vez de Strings para cargar los paquetes binarios.


DESCARGA: PhotosSample.zip (92 kb), contiene la bases de datos PhotosSample.mdb, y el primer ejemplo de este articulo.

NOTA. Desde la versión ADO 2.5 se incorpora el objeto Stream, el cual hace más efectivo el manejo de la fuente binaria de la imagen en memoria, sin tener que crear un fichero pala la transferencia de datos.