Gestionando Imágenes con ADOEl Código ADO para Gestionar Imágenes en una Base de Datos con Visual BasicPor 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:
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:
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 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:
Los Controles con DataField definido tienen: DataSource = datenvPhotos y DataMember = PhotosQuery Use el siguiente código en el módulo del formulario:
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:
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 DatosUsaremos 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:
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:
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. Reemplazar: '//Database command connection Por: '//Database command connection FIX: ADO: Unable To Update Memo Field > 64K In Access Database 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. |