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 para imprimir usando Crystal Reports a partir de un archivo XML intermedio que reproduce el estado actual de selección de registros en un DataSet tipado, 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;
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 como origen del informe Crystal Reports
Si el informe Crystal Reports carga los datos 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 informe Crystal Reports.
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"); }
Imprimir con Crystal Reports
Es necesario instalar Crystal Reports (CR) para utilizar con Visual Studio (VS) (SAP Crystal Reports for Visual Studio). La versión de CR ha de ser adecuada a la versión de VS. Por ejemplo en el momento de escribir este artículo para VS 2019 se descarga la versión 25 de CR. Al terminar la instalación un diálogo pregunta si se debe instalar también el entorno de ejecución de 64 bits, elige esta opción si deseas probar aplicaciones de VS con CR compiladas para 64 bits. Hay que agregar al proyecto 4 referencias:
- CrystalDecissions.CrystalReports.Engine
- CrystalDecissions.ReportSource
- CrystalDecissions.Shared
- CrystalDecissions.Windows.Forms.
Se encuentran en la carpeta:
C:\Program Files (x86)\SAP BusinessObjects\Crystal Reports for .NET Framework 4.0\Common\SAP BusinessObjects Enterprise XI 4.0\win32_x86\dotnet
Después hay que añadir al proyecto el componente CR que se encuentra en el grupo Reporting, se configura su origen de datos que es el archivo XML y se formatea a nuestro gusto para obtener una impresión correcta. Este componente CR está incluido en un segundo formulario que se abre desde el formulario principal.
//rellenar el archivo XML con el Dataset actual XmlUpdate(); //abrir el segundo formulario que contiene el visor de informes Crystal Reports Form2 informe = new Form2(); informe.Show();
DataSet tipado como origen de datos del informe CR
Una manera muy efectiva de configurar el origen de datos del informe CR es creando un Dataset tipado (que posee un esquema de datos que es un documento XML con extensión XSD generado por Visual Studio de forma automática, a diferencia del DataSet no tipado que no necesita de ese esquema) desde Proyecto > Agregar componente > Datos > Conjunto de datos y añadir desde el cuadro de herramientas un objeto DataTable al DataSet con idéntico nombre que la tabla del origen de datos y agregar al DataTable tantas columnas como la tabla origen con los mismos nombres de los campos de la tabla.
En este ejercicio se crea el DataSet datosXML.xsd, se añade DataTable AgendaTb y se añaden 4 columnas: Id, Nombre, Teléfono1 y Teléfono2 para coincidir con la tabla de la base de datos. Cada columna del DataTable ha de tener la propiedad DataType como el campo equivalente de la base de datos. En este ejemplo todas son System.String. Estas columnas por ahora están vacías pero en el código se rellenarán con los valores actuales del archivo XML.
Al cargar el segundo formulario que contiene el visor de informes CR se crea una instancia nueva del DataSet tipado y otra instancia del objeto Dataset genérico que actúa como almacén temporal. El DataSet temporal lee el archivo XML y combina esos datos con el DataSet tipado. Se genera una instancia nueva del informe CR cuya propiedad DataSource es la tabla del DataSet tipado. Y por fin se configura la propiedad ReportSource del visor de informes en la instancia del informe CR.
//espacio de nombres para importar en el segundo formulario using CrystalDecisions.CrystalReports.Engine; //al cargar Form2 se rellena el DataSet desde el archivo XML que está actualizado con los criterios de búsqueda empleados en Form1 private void Form2_Load(object sender, EventArgs e) { //instanciar un objeto del tipo DataSet datosXml añadido al proyecto DataSet datos1 = new datosXml(); //crear otro DataSet temporal para leer el archivo XML DataSet datos1Temp = new DataSet(); //leer el archivo XML usando el método ReadXml datos1Temp.ReadXml(Application.StartupPath + "\\500empresas.xml"); //copiar los datos XML desde el DataSet temporal al DataSet generado en el proyecto datos1.Tables[0].Merge(datos1Temp.Tables[0]); //preparar el informe para su presentación instanciando un objeto CrystalReport1 CrystalReport1 informeXml = new CrystalReport1(); //propiedad DataSource del informe informeXml.SetDataSource(datos1.Tables[0]); //propiedad ReportSource del visor de informes crystalReportViewer1.ReportSource = informeXml; }
Puedes descargar un archivo ZIP con la aplicación C#.