Programando el DataReport de VB6.docx

16
Programando el DataReport de VB6 Algo de Código para Gestionar el Objeto DataReport en Tiempo de Ejecución 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 debería 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 múltiples carencias actuales, DataReport es sumamente atractivo, algo para programadores Visual Basic, y estoy seguro que pronto será robusto. Entre las cosas más 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 código, y no usar los asistentes (partiendo delsde el DataEnvironment)?. Sencillamente la respuesta es la creación de reportes reutilizables (dinámicos). 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 revés. Primeros Pasos con DataReport Como siempre, mis artículos no son estrictamente didácticos, y van más halla de la documentación estándar (de otra manera no tendría sentido). Para empezar con DataReport, recomiendo los siguientes títulos de la MSDN (siga los árboles subsecuentes). Es importante que domines aquellos conceptos para seguir con esta lectura. Acerca del Diseñador de entorno de datos Escribir informes con el Diseñador de informe de datos de Microsoft Tener acceso a datos mediante Visual Basic El Objeto DataReport Se trata de unas librerías ActiveX escritas para Visual Basic, soportadas en tecnología ADO. Un DataReport se asimila mucho a un

Transcript of Programando el DataReport de VB6.docx

Page 1: Programando el DataReport de VB6.docx

Programando el DataReport de VB6

Algo de Código para Gestionar el Objeto DataReport en Tiempo de Ejecución

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 debería 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 múltiples carencias actuales, DataReport es sumamente atractivo, algo para programadores Visual Basic, y estoy seguro que pronto será robusto. Entre las cosas más 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 código, y no usar los asistentes (partiendo delsde el DataEnvironment)?. Sencillamente la respuesta es la creación de reportes reutilizables (dinámicos). 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 revés.

Primeros Pasos con DataReport

Como siempre, mis artículos no son estrictamente didácticos, y van más halla de la documentación estándar (de otra manera no tendría sentido). Para empezar con DataReport, recomiendo los siguientes títulos de la MSDN (siga los árboles subsecuentes). Es importante que domines aquellos conceptos para seguir con esta lectura.

Acerca del Diseñador de entorno de datos Escribir informes con el Diseñador de informe de datos de Microsoft Tener acceso a datos mediante Visual Basic

El Objeto DataReport

Se trata de unas librerías ActiveX escritas para Visual Basic, soportadas en tecnología ADO. Un DataReport se asimila mucho a un formulario, con su diseñador y todo. A grandes rasgos, he encontrado las siguientes características:

Carencias

1. Los Controles para el diseñador son pocos y algo limitados.2. No permite la adición de Controles en tiempo de ejecusión.3. Los controles enlazables a datos deben obligatoriamente estar enlazados a un DataField.4. Carece de una interfaz para exportar a documentos a formatos de Office.5. El diseñador tiene limitaciones (por ejemplo no permite copiar y pegar controles).6. El problema de la orientación del papel ha hecho carrera en los News (ver MSDN: Articulo

197915 - Report Width is Larger than the Paper Width). Aun no encuentro solución para impresoras en Red.

7. Debería compartir la interfaz del objeto Printer.8. 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).

Beneficios

Page 2: Programando el DataReport de VB6.docx

1. Es manipulable desde código (tiene un modulo de código).2. Es tecnología ADO (acepta cualquier origen de datos).3. Acepta el conjunto de datos en tiempo de ejecución (siempre que sea lógico con la estructura del

reporte)4. Esta bien organizado en términos de objetos5. El acceso a los controles es a través de cadenas de texto (los controles en un DataReport son

diferentes a los controles ActiveX normales)6. Crea informes con buen rendimiento

Existirán 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 gestión de datos: ¿alguna vez ha deseado imprimir el contenido de un conjunto de registros de forma simple (títulos 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 situación similar. Se presentarán problemillas, que se podrán 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 (relaciónes maestro-detalle). No obstante es posible escribir reportes complejos (varios niveles de relación) y reutilizables basándose la tecnología de comandos SHAPE.

Bien, daré una solución aproximada al problema expuesto.

Ejercicio

Crear un Proyecto EXE Estándar.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 LibraryAgregar un Data Report (menú Proyecto)Diseñe el DataReport como se ve en la siguiente figura:

Page 3: Programando el DataReport de VB6.docx

Más detalles de los controles para reporte y se encuentra en la siguiente tabla:

Seccion Tipo Nombre

stEncabezadoDeInforme RptLabel lblEncabezadoDeInforme_H

stEncabezadoDeInforme RptLine lnEncabezadoDeInforme_H

stEncabezadoDePagina RptLabel lblTituloDeCelda1

stEncabezadoDePagina RptLabel lblTituloDeCelda2

stDetalle RptTextBox txtCelda1

stDetalle RptTextBox txtCelda2

stPieDePagina RptLabel lblPieDePagina_H

stPieDeInforme RptLabel lblPieDeInforme_H

stPieDeInforme RptLine lnPieDeInforme_H

El propósito de los caracteres _H al final de algunos nombres de los Controles es poder, mediante código, extender el ancho del control todo el ancho del informe, lo que es conveniente para líneas y títulos (esto nos permite ignorar el ancho del papel sin dañar el la presentación 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á código al modulo del DataReport.

Agregue el siguiente bloque de código 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

Page 4: Programando el DataReport de VB6.docx

'// SOURCE      : -'// ------------------------------------------------------------Option Explicit

'//MEMBERSPrivate m_DetailMember As StringPrivate m_Report       As rptGerneral1

'//COLLECTIONSPrivate DetailCells As Collection

'//CONTANTSPrivate 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 cell      As cls_CeldaDetalle    Dim txt       As RptTextBox    Dim lbl       As RptLabel    Dim LineRight As 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

Page 5: Programando el DataReport de VB6.docx

    '//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 = NothingEnd 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.CountEnd 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 gResetPageOrientEnd Sub

Public Property Get MaxCells() As Integer    MaxCells = nMAXCELLSEnd Property

Public Property Let PieDePagina(ByVal v As String)    gLetCaption GetLabel("stPieDePagina", "lblPieDePagina_H"), vEnd Property

Public Property Let PieDeInforme(ByVal v As String)    gLetCaption GetLabel("stPieDeInforme", "lblPieDeInforme_H"), vEnd Property

Public Property Let EncabezadoDeInforme(ByVal v As String)    gLetCaption GetLabel("stEncabezadoDeInforme", _ "lblEncabezadoDeInforme_H"), v    m_Report.Caption = vEnd Property

Page 6: Programando el DataReport de VB6.docx

Private Function GetCaption( _    SectionName As String, _    LabelName As String _    ) As String    GetCaption = _    m_Report.Sections(SectionName).Controls(LabelName).CaptionEnd Function

Public Property Set DataSource(v As ADODB.Recordset)    Set m_Report.DataSource = vEnd Property

Public Property Set DataEnviron(v As Object)    Set m_Report.DataSource = vEnd Property

Public Property Let DataMember(v As String)    m_Report.DataMember = vEnd Property

Public Property Let DetailMember(v As String)    m_DetailMember = vEnd 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 IfEnd 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 colección de la clase cls_Informe1, y servirá para tener referencia a cada columna agregada al DataReport. El código 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      : -

Page 7: Programando el DataReport de VB6.docx

'// ------------------------------------------------------------Option Explicit

Public Key As Integer

Private m_txtCell As RptTextBox

Friend Property Set txtCell(v As RptTextBox)    Set m_txtCell = vEnd Property

Friend Property Get txtCell() As RptTextBox    Set txtCell = m_txtCellEnd Property

Por ultimo, agrega un modulo estándar a la DLL, con Name = modCommon y el siguiente código. Es modulo modCommon hace parte de una biblioteca de código más 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 '//twipsPublic Const nGRIDLINESCOLOR    As Long = &H808080

Public gRptWidthLandscape As Long '//twipsPublic gRptWidthPortrait  As Long '//twipsPublic gRptCurOrientation As LongPublic gRptNewOrientation As Long

'//As global multiusePrivate 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 = rptJustifyRightEnd Sub

Public Sub gCellMargin(txt As RptTextBox)    Const nCELLMARGIN As Long = 60 '//twips    With txt        .Width = .Width - 2 * nCELLMARGIN        .Left = .Left + nCELLMARGIN    End WithEnd Sub

Public Sub gCorrectPRB8456( _    objRpt As Object, _    SectionName As String, _    CellPrefix As String, _    MemberName As String _

Page 8: Programando el DataReport de VB6.docx

    )    '//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    NextEnd 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 = TrueEnd Sub

Public Sub gLetCaption( _    lbl As RptLabel, _    Caption As String _    )    lbl.Caption = Caption    If Not lbl.Visible Then lbl.Visible = TrueEnd 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

Page 9: Programando el DataReport de VB6.docx

           '//Swap           tmp = gRptWidthPortrait           gRptWidthPortrait = gRptWidthLandscape           gRptWidthLandscape = tmp           objRpt.ReportWidth = gRptWidthLandscape        End If    End With    Set ptr = NothingEnd Sub

Public Sub gChangesOrientation(ro As Enum_ReportOriention)    gRptNewOrientation = ro    groo.SetPrinterOrientation roEnd 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    NextEnd Sub

Public Sub gResetPageOrient()    If Not gRptNewOrientation = gRptCurOrientation Then       Call gChangesOrientation(gRptCurOrientation)    End IfEnd 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

Page 10: Programando el DataReport de VB6.docx

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    NextEnd Sub

Agregue una nueva clase a la DLL. Esta clase contiene la API para manipular la orientación del papel. Observe los creditos al autor. El código de esta clase lo consigue en este Link: ReportOrientation.zip (3k)

Finalmente, al modulo del formulario del proyecto estándar, agrega un Hierarchacal FlexGrid, Name = flexMuestra, un CommanButton, Name = cmdInforme. El formulario llevara el siguiente código de ejemplo:

Los nombres y estructura de los proyectos se muestra a continuación:

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 código 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

Page 11: Programando el DataReport de VB6.docx

    DoEvents    GenerarReporteEnd 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 "Compañía", "NombreCompañía", , 6000        .AddDetailCell "Contacto", "NombreContacto", , 3000        .ShowReport True    End WithEnd Sub

Private Sub Form_Load()    Call InicieConjuntoDeRegistros

    '//Cofigurar Grilla    flexMuestra.ColWidth(0) = 300    flexMuestra.ColWidth(1) = 2000    flexMuestra.ColWidth(2) = 2000    Set flexMuestra.DataSource = rsEnd 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 NombreCompañía, NombreContacto " & _        "FROM Clientes " & _        "ORDER BY NombreCompañía;"    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 = NothingEnd Function

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

Page 12: Programando el DataReport de VB6.docx

    End IfEnd 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 IfEnd Sub

La ejecución del proyecto muestra la siguiente interfaz de usuario:

La ejecución del informe a través del botón Informe, mostrara el siguiente Informe:

Mostrado en un Zoom = 50 %.

Discusión y Ampliación 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 código del cliente, a saber el procedimiento: InicieConjuntoDeRegistros. Para ampliar la capacidad a más columnas, deberá agregar controles (debido a la limitación 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 limitación numero 5). Por ultimo deberá modificar la constante nMAXCELLS del la clase cls_Informe1 (esta contante evita el error por desbordamiento del número de columnas enviadas a DataReport).

Se puede dar una grilla a la presentación 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.

Page 13: Programando el DataReport de VB6.docx

El componente InformeGeneral1 intenta solucionar el problema de la orientación del papel de la siguiente manera: Si el numero de columnas no cabe en posición Portrait, el reporte pasa (automaticmente) a orientación LandScape, hasta que acepte un numero de columnas que cubran el área del reporte, más halla no se mostraran más columnas (sin generar error). Si estudia el código, la clase ReportOrientation contiene la API necesaria para cambiar la orientación del papel. Desdichadamente el código trabaja solo para impresoras locales.

Debido a la carencia número 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.

También puede agregar más RptLabel, Imágenes, numeración de páginas, etc. para mejorar la apariencia del Informe. Un informe de ejemplo llevado sobre la base de código se muestra a continuación: