Anda di halaman 1dari 13

Programando el DataReport de VB6

Algo de Cdigo para Gestionar el Objeto DataReport en Tiempo de Ejecucin


El componente DataReport es una de aquellas ideas excelentes para Visual Basic, pero como siempre, parece que siempre nacen prematuras y suelen dar problemas donde aparentemente no los debera haber (siempre tenemos que encontrarnos con alguna carencia o BUG). No obstante estudiando a fondo DataReport, le he encontrado su esencia y capacidad para gestionar reportes de datos. En fin, a pesar de las mltiples carencias actuales, DataReport es sumamente atractivo, algo para programadores Visual Basic, y estoy seguro que pronto ser robusto. Entre las cosas ms interesantes de DataReport encuentro que se puede enlazar no solo a DataEnvironments, sino a clases de reconocimiento de datos, y a simples objetos de ADO. Este articulo muestra un ejemplo. Porque que gestionar DataReport con cdigo, y no usar los asistentes (partiendo delsde el DataEnvironment)?. Sencillamente la respuesta es la creacin de reportes reutilizables (dinmicos). Por ejemplo, hace poco tenia que crear cerca de cien reportes de una base de datos petrolera. Solucione el problema con solo tres DataReport y clases que los manipulan al derecho y al revs.

Primeros Pasos con DataReport


Como siempre, mis artculos no son estrictamente didcticos, y van ms halla de la documentacin estndar (de otra manera no tendra sentido). Para empezar con DataReport, recomiendo los siguientes ttulos de la MSDN (siga los rboles subsecuentes). Es importante que domines aquellos conceptos para seguir con esta lectura. Acerca del Diseador de entorno de datos Escribir informes con el Diseador de informe de datos de Microsoft Tener acceso a datos mediante Visual Basic

El Objeto DataReport
Se trata de unas libreras ActiveX escritas para Visual Basic, soportadas en tecnologa ADO. Un DataReport se asimila mucho a un formulario, con su diseador y todo. A grandes rasgos, he encontrado las siguientes caractersticas:

Carencias
1. 2. 3. 4. 5. 6. Los Controles para el diseador son pocos y algo limitados. No permite la adicin de Controles en tiempo de ejecusin. Los controles enlazables a datos deben obligatoriamente estar enlazados a un DataField. Carece de una interfaz para exportar a documentos a formatos de Office. El diseador tiene limitaciones (por ejemplo no permite copiar y pegar controles). El problema de la orientacin del papel ha hecho carrera en los News (ver MSDN: Articulo 197915 - Report Width is Larger than the Paper Width). Aun no encuentro solucin para impresoras en Red. Debera compartir la interfaz del objeto Printer. La variable de tipo DataReport no acepta todas las propiedades definidas en un objeto DataReport especifico (ver MSDN: Articulo 190584- Some Properties or Methods Not Displayed in DataReport).

7. 8.

Beneficios

1. 2. 3. 4. 5. 6.

Es manipulable desde cdigo (tiene un modulo de cdigo). Es tecnologa ADO (acepta cualquier origen de datos). Acepta el conjunto de datos en tiempo de ejecucin (siempre que sea lgico con la estructura del reporte) Esta bien organizado en trminos de objetos El acceso a los controles es a travs de cadenas de texto (los controles en un DataReport son diferentes a los controles ActiveX normales) Crea informes con buen rendimiento

Existirn mas carencias y beneficios, pero por el momento estos enunciados son suficientes. Para Los programadores Visual Basic, el primer beneficio enunciado es suficiente para tener muy en cuanta a DataReport, ya que permitir explorar todas su posibilidades. De eso trata este articulo.

Reportes Reutilzables
Como programador de gestin de datos: alguna vez ha deseado imprimir el contenido de un conjunto de registros de forma simple (ttulos y los datos en una plantilla)?, de la misma forma que abrimos una tabla o consulta en MS Access o MS FoxPro y usamos el comando Print. O, imprimir el contenido de un DataGrid tal cual, sin mucho complique. Bien, podemos intentar escribir un componente ActiveX usando un DataReport y solucionar el problema casi para cualquier situacin similar. Se presentarn problemillas, que se podrn solucionar en el componente y este evolucionara de manera conveniente para nosotros. El problema expuesto anteriormente es, desde el punto de vista de acceso a datos, sencillo, es decir no existen conjuntos de datos subyacentes (relacines maestro-detalle). No obstante es posible escribir reportes complejos (varios niveles de relacin) y reutilizables basndose la tecnologa de comandos SHAPE. Bien, dar una solucin aproximada al problema expuesto.

Ejercicio
Crear un Proyecto EXE Estndar. Agregar referencia a MS ActiveX Data Objects 2.1 Library. Agregar Proyecto DLL. Agregar referencia a MS ActiveX Data Objects 2.1 Library. Agregar referencia a MS Data Formatting Object LibraryReferencias: MS ActiveX Data Objects 2.1 Library Agregar un Data Report (men Proyecto) Disee el DataReport como se ve en la siguiente figura:

Ms detalles de los controles para reporte y se encuentra en la siguiente tabla: Seccion stEncabezadoDeInforme stEncabezadoDeInforme stEncabezadoDePagina stEncabezadoDePagina stDetalle stDetalle stPieDePagina stPieDeInforme stPieDeInforme Tipo RptLabel RptLine RptLabel RptLabel RptTextBox RptTextBox RptLabel RptLabel RptLine Nombre lblEncabezadoDeInforme_H lnEncabezadoDeInforme_H lblTituloDeCelda1 lblTituloDeCelda2 txtCelda1 txtCelda2 lblPieDePagina_H lblPieDeInforme_H lnPieDeInforme_H

El propsito de los caracteres _H al final de algunos nombres de los Controles es poder, mediante cdigo, extender el ancho del control todo el ancho del informe, lo que es conveniente para lneas y ttulos (esto nos permite ignorar el ancho del papel sin daar el la presentacin del informe). Otras propiedades del DataReport son Name = rptGerneral1, ReportWidth = 9360 twips (para un papel de 8.5 pulgadas y se calcula mediante ReportWidth = 8.5*1440 - 1440 (LeftMargin) - 1440 (RightMargin), donde 1440 son twips por pulgada) Por el momento no dar cdigo al modulo del DataReport. Agregue el siguiente bloque de cdigo a la clase creada por defecto por la DLL, luego el nombre debe ser Name = cls_Informe1: '// '// '// '// '// -----------------------------------------------------------CLASS : Report1Level DESCRIPTION : Code Template for Report 2 Leves AUTHOR : Harvey T. LAST UPDATE : 17/11/99

'// SOURCE : '// -----------------------------------------------------------Option Explicit '//MEMBERS Private m_DetailMember As String Private m_Report As rptGerneral1 '//COLLECTIONS Private DetailCells As Collection '//CONTANTS Private Const nMAXCELLS As Integer = 10 Public Function AddDetailCell( _ ByVal Title As String, _ ByVal FieldName As String, _ Optional ByVal FormatString As String = vbNullString, _ Optional ByVal ColumnWidth As Long = nDEFAULCOLUMNWIDTH _ ) As cls_CeldaDetalle Static Key As Integer Static NextLeft As Long Dim Dim Dim Dim cell txt lbl LineRight As As As As cls_CeldaDetalle RptTextBox RptLabel Long

Key = Key + 1 '//Filter maximun cells If Key > nMAXCELLS Then Exit Function '//Filter ReportWidth If ColumnWidth <= 0 Then ColumnWidth = nDEFAULCOLUMNWIDTH If NextLeft + ColumnWidth > m_Report.ReportWidth Then '//Try Landscape If NextLeft + ColumnWidth > gRptWidthLandscape Then Exit Function '//No chances of add new cell Else '//changes orientation to Landscape Call gChangesOrientation(vbPRORLandscape) m_Report.ReportWidth = gRptWidthLandscape End If End If '//Cell Set cell = New cls_CeldaDetalle Set txt = m_Report.Sections("stDetalle").Controls("txtCelda" & Key) With txt .DataField = FieldName .DataMember = m_DetailMember .Visible = True .Width = ColumnWidth .Left = NextLeft LineRight = .Left + .Width NextLeft = NextLeft + .Width End With If Len(FormatString) Then gGiveFormat txt, FormatString

'//Cell title Set lbl = GetLabel("stEncabezadoDePagina", "lblTituloDeCelda" & Key) With lbl .Left = txt.Left .Width = txt.Width .Caption = gAdjustNameToWidth(lbl, Title) .Visible = True End With gCellMargin txt cell.Key = Key Set cell.txtCell = txt DetailCells.Add cell, CStr(Key) Set AddDetailCell = cell Set cell = Nothing End Function Public Property Get Item(vntIndexKey As Variant) As cls_CeldaDetalle Set Item = DetailCells(vntIndexKey) End Property Public Property Get Count() As Long Count = DetailCells.Count End Property Public Property Get NewEnum() As IUnknown Set NewEnum = DetailCells.[_NewEnum] End Property Private Sub Class_Initialize() Set DetailCells = New Collection Set m_Report = New rptGerneral1 Call gGetPageSize(m_Report) End Sub Private Sub Class_Terminate() Set DetailCells = Nothing Set m_Report = Nothing Call gResetPageOrient End Sub Public Property Get MaxCells() As Integer MaxCells = nMAXCELLS End Property Public Property Let PieDePagina(ByVal v As String) gLetCaption GetLabel("stPieDePagina", "lblPieDePagina_H"), v End Property Public Property Let PieDeInforme(ByVal v As String) gLetCaption GetLabel("stPieDeInforme", "lblPieDeInforme_H"), v End Property Public Property Let EncabezadoDeInforme(ByVal v As String) gLetCaption GetLabel("stEncabezadoDeInforme", _ "lblEncabezadoDeInforme_H"), v m_Report.Caption = v End Property

Private Function GetCaption( _ SectionName As String, _ LabelName As String _ ) As String GetCaption = _ m_Report.Sections(SectionName).Controls(LabelName).Caption End Function Public Property Set DataSource(v As ADODB.Recordset) Set m_Report.DataSource = v End Property Public Property Set DataEnviron(v As Object) Set m_Report.DataSource = v End Property Public Property Let DataMember(v As String) m_Report.DataMember = v End Property Public Property Let DetailMember(v As String) m_DetailMember = v End Property Public Sub ShowReport(Optional Modal As Boolean = True) If Not m_Report.Visible Then gCorrectPRB8456 m_Report, "stDetalle", "txtCelda", m_DetailMember gElongedToWidth m_Report '//Show m_Report.Show IIf(Modal, vbModal, vbModeless) Else m_Report.SetFocus End If End Sub Private Function GetLine( _ SectionName As String, _ LineName As String _ ) As RptLine Set GetLine = m_Report.Sections(SectionName).Controls(LineName) End Function Private Function GetLabel( _ SectionName As String, _ LabelName As String _ ) As RptLabel Set GetLabel = m_Report.Sections(SectionName).Controls(LabelName) End Function Luego agrega una clase, con propiedad Instancing = 2-PublicNotCreatable, Name = cls_CeldaDetalle. Esta clase ser un objeto de coleccin de la clase cls_Informe1, y servir para tener referencia a cada columna agregada al DataReport. El cdigo de la clase cls_CeldaDetalle es: '// '// '// '// '// '// '// -----------------------------------------------------------CLASS : DetailCell DESCRIPTION : A cell in custum report. Member rpttextbox of some collection AUTHOR : Harvey T. LAST UPDATE : 17/11/99 SOURCE : -

'// -----------------------------------------------------------Option Explicit Public Key As Integer Private m_txtCell As RptTextBox Friend Property Set txtCell(v As RptTextBox) Set m_txtCell = v End Property Friend Property Get txtCell() As RptTextBox Set txtCell = m_txtCell End Property Por ultimo, agrega un modulo estndar a la DLL, con Name = modCommon y el siguiente cdigo. Es modulo modCommon hace parte de una biblioteca de cdigo ms general escrita por m para manipular DataReport. '// -----------------------------------------------------------'// MODULE : Common '// DESCRIPTION : Shared any '// AUTHOR : Harvey T. '// LAST UPDATE : 29/11/99 '// -----------------------------------------------------------Option Explicit Public Const nDEFAULCOLUMNWIDTH As Long = 1800 '//twips Public Const nGRIDLINESCOLOR As Long = &H808080 Public Public Public Public gRptWidthLandscape gRptWidthPortrait gRptCurOrientation gRptNewOrientation As As As As Long '//twips Long '//twips Long Long

'//As global multiuse Private groo As New ReportOrientation Public Sub gGiveFormat(txt As RptTextBox, FormatString As String) Dim f As New StdDataFormat f.Format = FormatString Set txt.DataFormat = f txt.Alignment = rptJustifyRight End Sub Public Sub gCellMargin(txt As RptTextBox) Const nCELLMARGIN As Long = 60 '//twips With txt .Width = .Width - 2 * nCELLMARGIN .Left = .Left + nCELLMARGIN End With End Sub Public Sub gCorrectPRB8456( _ objRpt As Object, _ SectionName As String, _ CellPrefix As String, _ MemberName As String _

) '//rptErrInvalidDataField '// No se encuentra el campo de datos '//Solution: Give the first DataField in hide Cells Dim txt As RptTextBox Dim ctl As Variant Dim s As String '//Fisrt DataField s = objRpt.Sections(SectionName).Controls(CellPrefix & "1").DataField For Each ctl In objRpt.Sections(SectionName).Controls If InStr(ctl.Name, CellPrefix) Then Set txt = ctl If txt.DataField = vbNullString Then txt.DataMember = MemberName txt.DataField = s txt.Width = 0 End If End If Next End Sub Public Sub gMoveLine( _ ln As RptLine, _ Optional LineLeft, _ Optional LineTop, _ Optional LineWidth, _ Optional LineHeight _ ) If Not IsMissing(LineLeft) Then ln.Left = LineLeft If Not IsMissing(LineTop) Then ln.Top = LineTop If Not IsMissing(LineWidth) Then ln.Width = LineWidth If Not IsMissing(LineHeight) Then ln.Height = LineHeight If Not ln.Visible Then ln.Visible = True End Sub Public Sub gLetCaption( _ lbl As RptLabel, _ Caption As String _ ) lbl.Caption = Caption If Not lbl.Visible Then lbl.Visible = True End Sub Public Sub gGetPageSize(objRpt As Object) Dim ptr As Printer Dim tmp As Long Set ptr = Printer With ptr gRptCurOrientation = groo.GetPrinterOrientation( _ .DeviceName, .hDC) gRptNewOrientation = gRptCurOrientation .ScaleMode = vbTwips gRptWidthPortrait = .Width - objRpt.LeftMargin - _ objRpt.RightMargin gRptWidthLandscape = .Height - objRpt.LeftMargin - _ objRpt.RightMargin If gRptCurOrientation = vbPRORLandscape Then

'//Swap tmp = gRptWidthPortrait gRptWidthPortrait = gRptWidthLandscape gRptWidthLandscape = tmp objRpt.ReportWidth = gRptWidthLandscape End If End With Set ptr = Nothing End Sub Public Sub gChangesOrientation(ro As Enum_ReportOriention) gRptNewOrientation = ro groo.SetPrinterOrientation ro End Sub Public Sub gElongedToWidth(objRpt As Object) Const sFLAG As String = "_H" Dim sect As Section Dim ctl As Variant Dim n As Long n = objRpt.ReportWidth For Each sect In objRpt.Sections For Each ctl In sect.Controls If Right(ctl.Name, 2) = sFLAG Then ctl.Left = 0 ctl.Width = n End If Next Next End Sub Public Sub gResetPageOrient() If Not gRptNewOrientation = gRptCurOrientation Then Call gChangesOrientation(gRptCurOrientation) End If End Sub Public Function gAdjustNameToWidth( _ lbl As RptLabel, _ Caption As String _ ) As String Dim rtn As String Dim s As String With Printer Set .Font = lbl.Font If .TextWidth(Caption) > lbl.Width Then s = Caption + Space(2) Do s = Left(s, Len(s) - 1) rtn = s + "..." Loop Until .TextWidth(rtn) < lbl.Width Or Len(s) = 0 gAdjustNameToWidth = rtn Else gAdjustNameToWidth = Caption End If End With

End Function Public Sub gGetControlsList(objRpt As Object) Const CO As String = " " Dim sect As Section Dim ctl As Variant Debug.Print "Section"; CO; "Type"; CO; "Name" For Each sect In objRpt.Sections For Each ctl In sect.Controls Debug.Print sect.Name; CO; TypeName(ctl); CO; ctl.Name Next Next End Sub Agregue una nueva clase a la DLL. Esta clase contiene la API para manipular la orientacin del papel. Observe los creditos al autor. El cdigo de esta clase lo consigue en este Link: ReportOrientation.zip (3k) Finalmente, al modulo del formulario del proyecto estndar, agrega un Hierarchacal FlexGrid, Name = flexMuestra, un CommanButton, Name = cmdInforme. El formulario llevara el siguiente cdigo de ejemplo: Los nombres y estructura de los proyectos se muestra a continuacin:

El grupo de proyectos se llamar: grpReporteDeMuestra.vbg. Este grupo de proyectos es til para depurar el componente InformeGeneral1, que posteriormente se puede dar compatibilidad binaria para colocarlo al servicio de futuros proyectos. El cdigo del cliente (frmMuestra) es el siguiente: '// -----------------------------------------------------------'// FORM : frmMuestra '// DESCRIPTION : Ejemplo de DataReport general '// AUTHOR : Harvey T. '// LAST MODIFY : '// -----------------------------------------------------------Option Explicit Private rs As ADODB.Recordset Private Sub cmdInforme_Click() flexMuestra.SetFocus

DoEvents GenerarReporte End Sub Private Sub GenerarReporte() Dim rpt As cls_Informe1 Set rpt = New cls_Informe1 With rpt Set .DataSource = rs .EncabezadoDeInforme = "Base de Datos NWIND (Clientes)" .PieDeInforme = "Fin de Informe" .PieDePagina = "Clientes con su Contacto" .AddDetailCell "Compaa", "NombreCompaa", , 6000 .AddDetailCell "Contacto", "NombreContacto", , 3000 .ShowReport True End With End Sub Private Sub Form_Load() Call InicieConjuntoDeRegistros '//Cofigurar Grilla flexMuestra.ColWidth(0) = 300 flexMuestra.ColWidth(1) = 2000 flexMuestra.ColWidth(2) = 2000 Set flexMuestra.DataSource = rs End Sub Private Function InicieConjuntoDeRegistros() 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=D:\Archivos de programa\VB98\Nwind.mdb;" With cmd Set .ActiveConnection = cnn .CommandType = adCmdText .CommandText = "SELECT NombreCompaa, NombreContacto " & _ "FROM Clientes " & _ "ORDER BY NombreCompaa;" 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 Private Sub Form_Resize() If Not Me.WindowState = vbMinimized Then flexMuestra.Move 0, 0, Me.ScaleWidth, Me.ScaleHeight - 330 cmdInforme.Move 0, Me.ScaleHeight - cmdInforme.Height End If End Sub La ejecucin del proyecto muestra la siguiente interfaz de usuario:

La ejecucin del informe a travs del botn Informe, mostrara el siguiente Informe:

Mostrado en un Zoom = 50 %.

Discusin y Ampliacin del Informe Reutilizable


Tal cual el componente InformeGeneral1, servir para mostrar cualquier tabla o vista de datos con dos columnas, solo habr que modificar el cdigo del cliente, a saber el procedimiento: InicieConjuntoDeRegistros. Para ampliar la capacidad a ms columnas, deber agregar controles (debido a la limitacin numero 2) RptLabel de nombre lblTituloDeCeldaX, y controles txtCeldaX a sus respectivas secciones (X es el nuevo numero del control agregado, por ejemplo si agrega una tercera columna, X = 3). Aun no termina el trabajo tedioso, tendr que dar las propiedades pertinentes a cada nuevo control (debido a la limitacin numero 5). Por ultimo deber modificar la constante nMAXCELLS del la clase cls_Informe1 (esta contante evita el error por desbordamiento del nmero de columnas enviadas a DataReport). Se puede dar una grilla a la presentacin de la tabla en el informe, pero es un trabajo algo tedioso, deber agregar controles RptLine a los lados de las celdas y sus titulo. Sin bien vale la pena y le queda de tarea.

El componente InformeGeneral1 intenta solucionar el problema de la orientacin del papel de la siguiente manera: Si el numero de columnas no cabe en posicin Portrait, el reporte pasa (automaticmente) a orientacin LandScape, hasta que acepte un numero de columnas que cubran el rea del reporte, ms halla no se mostraran ms columnas (sin generar error). Si estudia el cdigo, la clase ReportOrientation contiene la API necesaria para cambiar la orientacin del papel. Desdichadamente el cdigo trabaja solo para impresoras locales. Debido a la carencia nmero 3: Los controles enlazables a datos deben obligatoriamente estar enlazados a un DataField , es necesario ejecutar el procedimiento gCorrectPRB8456 del modulo modCommon antes de mostrar el Informe. Este procedimiento da un DataField repetido y oculto a las columnas que no se utilizan. Tambin puede agregar ms RptLabel, Imgenes, numeracin de pginas, etc. para mejorar la apariencia del Informe. Un informe de ejemplo llevado sobre la base de cdigo se muestra a continuacin: