Aplicación que carga datos desde una base de datos de Microsoft Access, los muestra en unos controles TextBox que se enlazan a los datos por medio de DataBinding y genera un informe PDF usando NReco.PDFGenerator a partir de un archivo HTML intermedio obtenido al aplicar transformación XSL a un archivo XML que reproduce el estado actual de selección de registros, en C#
DataBinding: enlace de datos a controles
En este ejercicio se enlazan directamente controles TextBox del formulario con campos del origen de datos. Para enlazar controles de un formulario con un origen de datos se usa la propiedad DataBinding que sirve para obtener acceso a la ControlBindingsCollection que representa a la colección de enlaces de datos de un control. Al objeto Binding le pasamos la propiedad del control que se debe enlazar, el origen de datos (DataSet) y la tabla o columna. El objeto Binding se añade a la colección de enlaces de datos del control elegido utilizando el método Add().
//conexión de tipo OleDb private OleDbConnection conn = new OleDbConnection(); //almacena la ruta a la base de datos private string ruta; //DataAdapter: adaptador de datos private OleDbDataAdapter adaptador; private DataSet datos; //DataSet: conjunto de datos desconectados //constructor de comandos para el DataAdapter private OleDbCommandBuilder comando; private bool abierto = false; //para saber si ya está abierta la base de datos //administrador de los objetos de enlace a datos private BindingManagerBase enlaceBase; //objeto que enlaza control y datos private Binding enlace; //ruta a la base de datos en la carpeta del ensamblado ruta = Application.StartupPath + "\\500empresas.mdb"; // para usar BD de Access con extensión accdb (también sirve para .mdb) hay que recurrir a Provider=Microsoft.ACE.OLEDB.12.0 conn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.16.0;Data Source=" + ruta + ";User Id=admin;Password=;"; // para usar BD de Access con extensión mdb más antigua hay que recurrir a Provider=Microsoft.Jet.OLEDB.4.0 //con.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & ruta & ";User Id=admin;Password=;" //DataAdapter: objeto que hace de puente entre la base de datos y el DataSet o conjunto de datos adaptador = new OleDbDataAdapter("SELECT * FROM AgendaTb ORDER BY Nombre", conn); //DataAdapter puede contener sentencias SQL y objetos Command comando = new OleDbCommandBuilder(adaptador); //DataSet: contiene una copia de la base de datos, en esquema XML, independiente del proveedor, con sus elementos tablas y relaciones. Es el verdadero almacén de datos desconectados: actuamos sobre el DataSet desconectado y el DataAdapter se conecta para volcar los datos entre base de datos y DataSet, en ambos sentidos datos = new DataSet(); //Abrir la conexión, el DataAdapter llena el DataSet, cerrar la conexión. DataAdapter se encarga de abrir y cerrar la conexión //conn.Open(); adaptador.Fill(datos, "AgendaTb"); //conn.Close(); //Para enlazar controles del formulario con el DataSet usamos DataBinding; al objeto Binding le pasamos la propiedad del control que se debe enlazar, el DataSet y la tabla o columna. El objeto Binding se añade a la colección de enlaces de datos (ControlBindingsCollection) del control elegido, con el método Add() enlace = new Binding("Text", datos, "AgendaTb.Nombre"); txNombre.DataBindings.Add(enlace); enlace = null; enlace = new Binding("Text", datos, "AgendaTb.Teléfono1"); txTel1.DataBindings.Add(enlace); enlace = null; enlace = new Binding("Text", datos, "AgendaTb.Teléfono2"); txTel2.DataBindings.Add(enlace); enlace = null; //Al objeto BindingManagerBase le pasamos el DataSet y la tabla desde el objeto BindingContext del formulario this.enlaceBase = this.BindingContext[datos, "AgendaTb"];
En una etiqueta se informa del nº de orden del registro mostrado y del total del nº de registros de la vista actual.
lbRegistro.Text = "Registro " + (enlaceBase.Position + 1).ToString() + " de " + (this.enlaceBase.Count).ToString();
Gracias al enlace automático entre controles y datos (DataBinding) es muy sencillo navegar entre registros y se necesita poco código. El objeto enlaceBase (BindingManagerBase) se encarga de actualizar de manera automática el control con los datos de la fila actual.
//avanzar un registro this.enlaceBase.Position++; //retroceder un registro this.enlaceBase.Position--; //ir al primer registro this.enlaceBase.Position = 0; //ir al último registro this.enlaceBase.Position = this.enlaceBase.Count - 1;
Espacios de nombres
Hay que importar espacios de nombres para que la aplicación funcione.
//conectar con bases de datos de Microsoft Access using System.Data.OleDb; //transformación XSLT de archivos XML using System.Xml.Xsl; //lectura y escritura de archivos en el disco using System.IO; //procesos del sistema local using System.Diagnostics;
Buscar texto coincidente
Hay un cuadro de texto en el que se puede escribir y 2 botones de búsqueda, uno busca el texto introducido al principio del campo Nombre y otro lo busca en cualquier lugar del campo Nombre. Después se vacía el DataSet de los datos que ya contiene y se vuelve a rellenar desde OleDbDataAdapter con el comando SQL.
//crear nuevos ojetos OleDbDataAdapter y OleDbCommandBuilder con sentencia SQL para buscar al principio del nombre adaptador = new OleDbDataAdapter("SELECT * FROM AgendaTb WHERE Nombre LIKE '" + this.txBuscar.Text + "%'", conn); //crear nuevos ojetos OleDbDataAdapter y OleDbCommandBuilder con sentencia SQL para buscar en cualquier lugar del nombre adaptador = new OleDbDataAdapter("SELECT * FROM AgendaTb WHERE Nombre LIKE '%" + this.txBuscar.Text + "%'", conn); comando = new OleDbCommandBuilder(adaptador); //vaciar el DataSet y rellenarlo de nuevo datos.Clear(); adaptador.Fill(datos, "AgendaTb");
XML desde el DataSet transformado en HTML como origen del informe PDF
Si el informe generado como PDF carga los datos directamente desde la base de datos de Access, se muestran para imprimir todos los registros de la tabla independientemente de los criterios de búsqueda o selección que estén aplicados. Un truco para solucionar esto y que el informe muestre los registros que en ese momento están seleccionados es, cada vez que se pulsa el botón que muestra el informe, volcar el DataSet actual en un archivo XML en la carpeta del ensamblado y que este archivo XML sea el origen de los datos del archivo HTML y del informe PDF
void XmlUpdate() { //Actualizar DataAdapter, se puede pasar la tabla como parámetro de 2 maneras: datos as DataSet, "tabla" as string o datos.tables("tabla") as DataSet this.adaptador.Update(this.datos.Tables["AgendaTb"]); //Vaciar el DataSet: conserva los datos de tablas y registros y si no se limpia acumula las operaciones sucesivas de modificación de filas this.datos.Clear(); //Volver a llenar el DataSet desde el DataAdapter ya modificado this.adaptador.Fill(datos, "AgendaTb"); //actualizar el archivo XML con la selección actual de registros this.datos.WriteXml("500empresas.xml"); }
El archivo XML es transformado en HTML de acuerdo con una hoja de estilos XSL creada para ello y guardada en la carpeta del ejecutable. El archivo XSL configura el diseño HTML del archivo XML para mejorar su presentación. El archivo HTML actúa como fuente del informe PDF gracias a NReco.PDFGenerator, conversor de archivos HTML en PDF para Visual Studio .NET con licencia libre. La hoja de estilos XSL tiene este contenido:
<?xml version="1.0" encoding="utf-8" ?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/"> <head> <title>500 empresas</title> </head> <html> <body style="font-family: Arial; font-size: 10pt;"> <div align="center"> <h3> <u>Agenda con 500 empresas</u> </h3> <br></br> <table width="500" border="0"> <tr style="font-family: Arial; font-size: 10pt;"> <td width="300" align="left" style="background-color:#f0f2f3;"> <b> <font color="Black">Nombre</font> </b> </td> <td width="100" align="left" style="background-color:#f0f2f3;"> <b> <font color="Black">Teléfono 1</font> </b> </td> <td width="100" align="left" style="background-color:#f0f2f3;"> <b> <font color="Black">Teléfono 2</font> </b> </td> </tr> <tr> <td align="center" colspan="4"> <!--<HR></HR>--> <br></br> </td> </tr> <xsl:for-each select="NewDataSet/AgendaTb"> <xsl:sort select="Nombre" order="ascending"></xsl:sort> <tr bgcolor="white" style="font-family: Arial; font-size: 10pt;"> <td align="left"> <font color="black"> <xsl:value-of select="Nombre"></xsl:value-of> </font> </td> <td align="left"> <font color="black"> <xsl:value-of select="Teléfono1"></xsl:value-of> </font> </td> <td align="left"> <font color="black"> <xsl:value-of select="Teléfono2"></xsl:value-of> </font> </td> </tr> <tr> <td align="left" colspan="4"> <HR width="100%"></HR> </td> </tr> </xsl:for-each> </table> </div> </body> </html> </xsl:template> </xsl:stylesheet>
Lo que hace es generar una tabla cuya línea primera es la cabecera de las columnas con los nombres de los campos y el resto de filas se rellena con bucles xsl:for-each para mostrar los registros actuales del DataSet leídos desde el archivo XML. Esto produce un archivo HTML con el formato diseñado. NReco.PDFGenerator coge el archivo HTML y lo convierte en un archivo PDF apto para impresión. La conversión es buena pero el aspecto del PDF no es totalmente idéntico al HTML.
El código que efectúa la transformación XSLT del archivo XML recure a la clase XslCompiledTransform que transforma datos XML utilizando una hoja de estilos XSLT. Su método Load carga la hoja de estilos y su método Transform convierte el archivo XML en HTML aplicando la hoja de estilos.
string ruta1 = "500empresas.xsl"; string ruta2 = "500empresas.xml"; string ruta3 = "500empresas.html"; XslCompiledTransform xslt = new XslCompiledTransform(); xslt.Load(ruta1); xslt.Transform(ruta2, ruta3);
Generar el informe PDF con NReco.PdfGenerator
Para utilizar NReco hay que instalar el paquete NuGet NReco.PdfGenerator, generar una instancia de la clase HtmlToPdfConverter que convierte un archivo HTML en un archivo PDF y llamar al método GeneratePdfFromFile al que se le pasan 3 parámetros: string archivo HTML de entrada obligatorio, página de portada opcional y string archivo PDF de salida obligatorio.
//generar una instancia de la clase HtmlToPdfConverter var html2Pdf = new NReco.PdfGenerator.HtmlToPdfConverter(); //opciones de personalización del archivo PDF html2Pdf.Zoom = 1.2F; //html2Pdf.PageWidth = 210; //html2Pdf.PageHeight = 297; //método GeneratePdfFromFile que genera el PDF desde el HTML html2Pdf.GeneratePdfFromFile(Application.StartupPath + "\\500empresas.html", null, Application.StartupPath + "\\500empresas.pdf");
Mostrar los archivos HTML y PDF con su programa asociado en Windows
Se utiliza la clase ProcessStartInfo de System.Diagnostics que especifica valores para iniciar procesos en Windows: la propiedad FileName para el nombre de la aplicación o el documento y la propiedad Verb para el modo de apertura (abrir, editar, imprimir, etc.). La clase Process de System.Diagnostics permite iniciar y detener procesos del sistema local y su método Start inicia el proceso pasando el nombre del archivo. Con este código se abren ambos archivos, HTML y PDF, y se pueden comparar viendo sus diferencias.
ProcessStartInfo abrirPdf = new ProcessStartInfo(); abrirPdf.FileName = Application.StartupPath + "\\500empresas.pdf"; //modo de apertura de archivo (también puede ser edit, print...) abrirPdf.Verb = "open"; //abrir en ventana normal abrirPdf.WindowStyle = ProcessWindowStyle.Normal; //abrir el archivo mediante su programa asociado en Windows Process.Start(abrirPdf);
Puedes descargar un archivo ZIP con la aplicación C#.