Vaya por delante que el tema de la creación de informes (o reports) siempre ha sido una de las partes de la programación que me ha dado más pereza. Pero está claro que pocas aplicaciones se salvan de requerirlos en mayor o menor grado, y la que actualmente tengo entre manos no es, en absoluto, una excepción. Así pues, estos últimos días he tenido que preparar un nuevo report y de paso he aprovechado para investigar un poco y descubrir una nueva manera de realizarlos. Paso a explicarme.
Como ya he comentado en ocasiones, mi entorno de desarrollo es VisualBasic.NET y una base de datos MySQL. Y los informes los hago con el propio Crystal Reports que incorpora el VisualStudio.NET. El tema está en que hasta ahora los hacía a través de una conexión ODBC que tenía que instalar en cada máquina apuntando hacia el servidor MySQL, pero este sistema no me gustaba demasiado. Y no me gustaba porque en realidad de bases de datos tengo distintas con distintos nombres pero con las mismas estructuras de tablas y datos parecidos, ya que una es la productiva (la real, la buena) y las otras son para desarrollo, para que los usuarios hagan pruebas sin que afecte a los datos reales, etc. Controlar la cadena de conexión en la aplicación para que ataque a una u otra base de datos no me supone el más mínimo problema, pero los informes cogen sus datos a través de una conexión ODBC concreta y ésta en cada máquina ataca a una única base de datos. Así que no me convence este sistema.
Y ahora he descubierto cómo puede crear informes a través de un DataTable y de una estructura en un archivo XML, sin necesidad de nada más. De hecho en lugar de un DataTable se puede utilizar perfectamente un DataSet y funciona sin ningún tipo de problemas ni apenas modificaciones (dejo en manos del lector probarlo si le interesa). Voy a pasar a explicar mediante un ejemplo y algunas imágenes cómo realizarlo desde cero.
Partiré de dos tables en mi base de datos MySQL en las que guardaré información de facturas. La primera tabla es de cabeceras de esas facturas y tiene estos datos:
| blh_num | blh_dat | blh_cus |
+---------+------------+-------------------------+
| 1 | 2008-07-30 | CERAMICAS PEPE, S.A. |
| 2 | 2008-07-30 | TALLERES GOMEZ, S.L. |
| 3 | 2008-07-31 | DEPORTES DAMIAN, S.L. |
| 4 | 2008-07-31 | SOFTWARE ALBERTMATA.NET |
+---------+------------+-------------------------+
La segunda tabla son las posiciones de cada factura y tiene estos otros datos:
| blp_num | blp_pos | blp_art | blp_pri | blp_qty |
+---------+---------+------------------------+---------+---------+
| 1 | 1 | RATON LOGITECH | 15.95 | 1 |
| 2 | 1 | MONITOR LG 19 PULGADAS | 210.5 | 1 |
| 3 | 1 | ROUTER DLINK | 56 | 1 |
| 4 | 1 | RATON LOGITECH | 15.95 | 2 |
| 4 | 2 | TECLADO LOGITECH | 12.95 | 1 |
| 4 | 3 | RECEPTOR GPS ZAPPA | 59.95 | 1 |
| 4 | 4 | PAQUETE 500 FOLIOS | 3.7 | 4 |
+---------+---------+------------------------+---------+---------+
Es algo muy sencillo y poco normalizado, pero nos servirá para el ejemplo. Concretamente vamos a crear un informe que será nada más que una impresión de una factura. Como voy a trabajar con un simple DataTable pero mi informe requerirá datos de dos tablas, me crearé primero una vista en MySQL con la siguiente instrucción:
(
SELECT
blh_num AS BILL_NUMBER,
blh_dat AS BILL_DATE,
blh_cus AS BILL_CUSTOMER,
blp_pos AS LINE_NUMBER,
blp_art AS LINE_ARTICLE,
blp_pri AS LINE_UNITPRICE,
blp_qty AS LINE_UNITS,
blp_pri * blp_qty AS LINE_TOTALPRICE
FROM
blh_billheader LEFT JOIN blp_billposits ON blh_num = blp_num
WHERE
blh_num = 4
);
Así, mi informe a partir de ahora se realizará sobre esta vista zbl_bill2print. Vamos pues a empezar con la parte que incumbe a .NET.
Para todo el ejemplo jugaremos con:
1) un formulario frmMain donde tendremos el objeto visualizador de informes.
2) una clase clsReportCreator que crearemos a continuación.
3) un informe rptBill que representará la factura que queremos imprimir.
Comenzamos pues creando la clase clsReportCreator, que constará de un único atributo (el nombre de la tabla o vista), un método constructor, un método para cargar el DataTable correspondiente y un último método para generar el archivo XML. El código completo de esta clase es el siguiente:
' Author: Albert Mata (www.albertmata.net)
' Date: 20080731
' Needs: MySQL.Data reference.
' Description: Class to create a report using just an XML file.
'--------------------------------------------------------------------
Imports MySql.Data.MySqlClient
Public Class clsReportCreator
'----------------------------------------------------------------
' Attributes.
'----------------------------------------------------------------
Private TableOrView As String
'----------------------------------------------------------------
' Constructor method.
'----------------------------------------------------------------
Public Sub New(ByVal TableOrView As String)
Me.TableOrView = TableOrView
End Sub
'----------------------------------------------------------------
' Returns DataTable corresponding to TableOrView.
'----------------------------------------------------------------
Public Function GetDataTable() As DataTable
Dim DA As MySqlDataAdapter
Dim DS As New DataSet
Dim DT As DataTable
Dim ConnectionString As String
Dim SQL As String
'Setting connection string to connect to MySQL database.
ConnectionString = "Database = blog; " _
& "Data Source = localhost; " _
& "User ID = root; " _
& "Password = mypassword"
'Setting SQL string.
SQL = "SELECT * FROM " & Me.TableOrView
'Getting data and filling DataSet and DataTable.
DA = New MySqlDataAdapter(SQL, ConnectionString)
DA.Fill(DS, Me.TableOrView)
DT = DS.Tables(Me.TableOrView)
'Returning DataTable.
Return DT
End Function
'----------------------------------------------------------------
' Creates XML file in desired path.
'----------------------------------------------------------------
Public Sub CreateXMLFile(ByVal FilePath As String)
Dim DT As DataTable
'Creating DataTable.
DT = Me.GetDataTable()
'Writting XML file in desired path.
DT.WriteXmlSchema(FilePath & Me.TableOrView & ".xml")
End Sub
End Class
Y creamos también el formulario frmMain cuyo único código por el momento (después cambiará un poco) será el que sigue:
' Author: Albert Mata (www.albertmata.net)
' Date: 20080731
' Description: Form to show how to create a report using just an XML
' file.
'--------------------------------------------------------------------
Public Class frmMain
'----------------------------------------------------------------
' As a first step, creates XML file.
'----------------------------------------------------------------
Private Sub frmMain_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
'Creating XML file.
Dim RC As New clsReportCreator("zbl_bill2print")
RC.CreateXMLFile("C:\")
End Sub
End Class
Con esto tenemos una primera aplicación que al ejecutarla nos creará el archivo C:\zbl_bill2print.xml con la estructura de la vista zbl_bill2print. Lo probamos pues y obtenemos un archivo como éste.
Añadimos al proyecto un informe al que llamaremos rptBill.rpt y que crearemos seleccionando la alternativa Como informe en blanco y por tanto desestimando plantillas.
A continuación en el menú Explorador de campos seleccionamos la primera opción Campos de base de datos y en su menú contextual hacemos click en la opción Asistente de base de datos.

En el menú que se despliega (Orígenes de datos disponibles) seleccionamos Crear nueva conexión y a continuación la opción ADO.NET.

Con esto se nos abrira un nuevo formulario en el que nos solicitará la Ruta del archivo. Debemos aquí ir a buscar el archivo XML que hemos creado anteriormente (en mi caso el C:\zbl_bill2print.xml) y acto seguido pulsar en Finalizar. Con ello, en el menú anterior (Orígenes de datos disponibles) se nos mostrará ya la opción NewDataSet incluyendo el zbl_bill2print que acabamos de añadir.

Lo marcamos y le damos al botón para trasladarlo al menú de Tablas seleccionadas y pulsamos en Aceptar.

Con esto hemos conseguido que en el menú Explorador de campos nos aparezca ya la estructura de zbl_bill2print con sus campos, tal como se muestra a continuación:

No tiene ningún misterio. Se trata de añadir los campos desde el menú Explorador de campos donde corresponda, insertarle los objetos de texto que nos parezcan adecuados, los totales cuando sea preciso, dar formato a los textos, incorporar imágenes y demás florituras a nuestro antojo…
Yo me inclino por un diseño sobrio como éste:

Por último vamos ya a crear la factura. Para ello en el formulario frmMain añadiremos un objeto de tipo CrystalReportViewer al que llamaremos por ejemplo crvBill. Y modificamos el código de frmMain para dejarlo como sigue:
' Author: Albert Mata (www.albertmata.net)
' Date: 20080731
' Description: Form to show how to create a report using just an XML
' file.
'--------------------------------------------------------------------
Imports CrystalDecisions.CrystalReports.Engine
Public Class frmMain
'----------------------------------------------------------------
' Creates XML file (just once) and creates and loads a report.
'----------------------------------------------------------------
Private Sub frmMain_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
'Creating XML file.
Dim RC As New clsReportCreator("zbl_bill2print")
'RC.CreateXMLFile("C:\")
'Creating report.
Dim RD As ReportDocument = New rptBill()
'Setting data source for report.
Dim DT As DataTable = RC.GetDataTable()
RD.SetDataSource(DT)
'Setting data source for possible subreports.
For Each SR As ReportDocument In RD.Subreports
If SR.Database.Tables.Count > 0 Then
SR.SetDataSource(DT)
End If
Next
'Setting recently created report must be shown in viewer.
Me.crvBill.ReportSource = RD
End Sub
End Class
Nótese que ahora ya he comentado la línea en que creamos el archivo XML, puesto que sólo necesitamos crearlo una única vez para luego poder generar el origen de datos, pero a partir de aquí no necesitaremos andar creándolo cada vez.
En este código lo que estamos haciendo fundamentalmente es crear un objeto del tipo informe que hemos diseñado en el paso 3, obtener un DataTable con los datos que queremos mostrar (en este caso y tal como tenemos definida la vista de MySQL, queremos mostrar la factura número 4), establecer que el origen de datos del informe será este DataTable y finalmente solicitarle al CrystalReportViewer que nos muestre este informe.
Ejecutamos nuevamente la aplicación y obtenemos la factura que queríamos:

Por supuesto en una factura auténtica faltarían datos fiscales, logotipos, impuestos, condiciones de pago y demás, pero lo que aquí pretendía era únicamente mostrar cómo llevar a cabo el informe en sí mismo.
Con esto queda visto cómo simplemente utilizando un archivo XML podemos crear un informe en VisualBasic.NET. Por supuesto habría que mejorar muchas cosas, por ejemplo optimizar cómo se realiza la conexión con la base de datos (particularmente tengo un clase para llevar a cabo esa serie de cuestiones), también evitar poner la condición WHERE directamente en la vista de MySQL y sí por ejemplo cuando recuperamos el DataTable… en fin, unas cuantas cosas. Pero lo que buscaba con este ejemplo era hacer algo muy minimalista para que quedara claro el funcionamiento.
A raíz de uno de los comentarios he añadido una pequeña segunda parte a este post, en la que se explica cómo pasar parámetros desde el formulario hasta el informe.
Estos días estoy mirando opciones para comprarme un receptor de señal GPS que pueda conectar a mi nuevo 




Últimamente he estado programando un servicio de Windows para que el sistema realice de manera periódica y en segundo plano unas determinadas tareas. En mi caso concreto se trata de replicar unas determinadas tablas desde una base de datos Oracle hacia otra MySQL con una determinada periodicidad en función de cada tabla (unos datos son más críticos y necesitan actualizarse cada hora, otros menos variables los actualizamos una vez al día durante las horas valle de actividad). El caso es que con VisualBasic.NET me ha resultado bastante sencillo crearlo y después instalarlo, así que paso a explicar un ejemplo de cómo hacer un sencillo servicio de Windows.
Ordenando estos días viejos archivos que tenía en discos duros varios, he topado con algunas cosas que había desarrollado hace ya tiempo y que en algunos casos ni recordaba. Una de las que me ha parecido más interesante es este calendario para página web creado con JavaScript que presento hoy. Recuerdo que lo creé para recoger una fecha con la que acotar una consulta. Quería evitar problemas con los formatos de entrada de fechas (día/mes/año, mes/día/año, etc) y además me apetecía crear algo más estético que un simple cuadro de texto. Por eso surgió este calendario. De modo que todo el código es de hace unos cuantos años, cuando era un programador 100% amateur, así que no se aceptan críticas despiadadas sobre su corrección…
Cuando programamos una aplicación con POO es habitual que para una determinada entidad nos encontremos trabajando los mismos datos en tres niveles: el objeto en sí mismo, una tabla de la base de datos y un formulario. El objeto tendrá una serie de propiedades o atributos. En la tabla de la base de datos guardaremos estas propiedades para poder recuperarlas en otro momento. En el formulario le mostraremos la información del objeto al usuario o bien recogeremos la información que el usuario introduzca para crear el objeto.