Access con DataBinding en C#: informe Crystal Reports desde DataSet tipado

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#.