Академический Документы
Профессиональный Документы
Культура Документы
: : : :
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 Dim Dim Dim Dim Dim Dim b() ff File i FileLen Blocks LeftOver As As As As As As As Byte Long String Long Long Long 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 presentacin, 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 mtodos grficos, o simplemente el valor Picture de un control. Para el primer caso sera: Call adoSrv.PutPictureInField(rs.Fields("My Photo"), picX.Image).
A modo de laboratorio, cre una base de datos nueva en Access con la siguiente estructura: Campo Id Photo Photo Note Tipo Entero Largo Objeto OLE Texto (254) Descripcin Establece la clave principal. Contiene imgenes Alguna descripcin
Le di el nombre PhotosSample.mdb a la base de datos, y la archiv en el mismo directorio de la aplicacin. Use un DataEnvironment (Entorno de Datos) para conectar la base de datos, el cual tiene la siguiente configuracin:
No explico como crear el Entorno de Datos. Si desea informacin, busque Interactuar con datos de una base de datos Microsoft Jet o Microsoft Access en la documentacin 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 conexin: 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 conexin 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 conexin como un ADO.Recordset simple o un Control ADODC (realmente prefiero evitar el control ADODC, que por dems es incompatible con ADO 2.1).
http://vexpert.mvps.org/articles/adoImages.htm (5 de 14)05/12/2011 18:59:43
Los controles y disposicin de este ejemplo se muestran el la imagen de formulario, y siguen esta configuracin: Control PictureBox Label Label TextBox CommandButton (array de 0 a 3) CommandButton CommandButton CommandButton Nombre picPhoto lblImageFieldSize lblIdPhoto txtNote cmdMoveRecord cmdAdd cmdDelete cmdClose Captions: Fisrt, Previous, Next, Last Caption: Add Caption: Delete Caption: Close Otras propiedades OLEDropMode=1 BorderStyle=1 BorderStyle=1 Note DataField Photo Id Photo
Los Controles con DataField definido tienen: DataSource = datenvPhotos y DataMember = PhotosQuery Use el siguiente cdigo en el mdulo del formulario: ' ' ' ' ' FORM : frmPhotosSample DESCRIPTION : Muestra de almacenar/recuperar formatos grficos 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 automticamente 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 anlisis del cdigo del modulo anterior, frmPhotosSample, le mostrar muchos detalles. Este cdigo es slido y muestra el modo correcto de manejar un objeto Recordset de ADO con cdigo. Note que el procedimiento de adicionar un registro, en el evento clic del Control cmdAdd, crea el registro fsicamente, es decir, no deja en modo de cache el nuevo registro. Esto da seguridad y estabilidad a la base de datos. Note tambin, que se dan valores predeterminados a los Campos de la base de datos, en particular, se enva una imagen pequea a la base de datos (por favor indique un archivo de imgen cualquiera en su disco), esto con el propsito de asegurar que eventualmente no queden el Campo de la imagen vaco (lo que posiblemente traera problemas subsecuentes al usar la bases de datos). Se busca estabilidad.
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 ms relevante del cdigo 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 mtodo Add del objeto BindingCollection. Este diseo 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 ms liviano y por tanto entrega mejor rendimiento que PictureBox). Bien, qu tal si desea leer las imgenes 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 travs de una propiedad del tipo stdPicture. Usando una Clase Receptora de Datos Usaremos una clase receptora de datos cuando no necesitamos desplegar las imgenes en un control, es decir sin interfaz de usuario. Aunque como muestra el siguiente ejemplo, leo las imgenes con una clase receptora de datos y las muestro en un control
http://vexpert.mvps.org/articles/adoImages.htm (10 de 14)05/12/2011 18:59:43
Image, con el propsito de demostrar que se estn leyendo correctamente los datos. La clase simple Receptora de Datos se construye empezando por fijar la propiedad (en tiempo de diseo) DataBindingBehavior = 1 (vbSimpleBound). El cdigo 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 tambin suelen servir de enlaces a Controles con esta capacidad (DataBound), los que es comn en soluciones para Web. El cliente de la clase receptora puede seguir este modelo, segn 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 cdigo es similar al caso de lectura desde un simple Recordset, salvo que fue cambiada la recepcin 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 travs de un Recordset de ADO. En un diseo ClienteServidor normalmente esta fuente de datos ser otra clase de reconocimiento de datos, ms exactamente una Clase Origen de Datos (Data Source). Realmente extendera bastante este escrito al colocar el cdigo completo de un ejemplo de estas caractersticas, pero las bases estn dadas en este escrito.
NOTA. 1. Los ejemplos expuestos anteriormente, no trabajan cuando la imagen tiene ms 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 conexin OBDC como se muestra a continuacin: 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;" Ms 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 versin ADO 2.5 se incorpora el objeto Stream, el cual hace ms efectivo el manejo de la fuente binaria de la imagen en memoria, sin tener que crear un fichero pala la transferencia de datos.