Crear un informe Crystal Report a partir de un DataGridView

Problema: Tengo mi aplicación lista y funcionando, ahora solo me falta imprimir los informes o reportes necesarios, pero uno de ellos debe mostrar lo que tengo en un grid.
Solución: Una mezcla de DataSet, DataTable y paciencia.

Explicaré paso a paso lo que descubrí e implementé.

No solía utilizar los datasets "tipados" como suele llamarlos, pues las tablas las creaba directamente por código con DataTables.

¿Qué es un dataset tipado? Pues un archivo DataSet.xsd que se añade al proyecto.

¿Y para qué lo necesito? Para añadir los campos al reporte que crearé.

¿Es necesario crear un DataSet para crear mi reporte? Desgraciadamente, sí. No he encontrado otra forma de hacerlo. Bueno, existen muchas maneras, pero esta es la más sencilla para utilizar los datos de mi DataGridView.

¿Para qué necesito un DataSet si mis datos los tengo en un DataTable (que obviamente muestro en un grid)? Para poner los campos en el reporte, no se puede anexar directamente los campos de un DataTable creado desde código.

¿Cómo lo hago? Click derecho en el Explorador de Soluciones en el nombre del proyecto, se escoge Agregar, luego "Nuevo elemento" y se abre la ventana de "Agregar nuevo elemento" o también en el menú Proyecto - Agregar nuevo elemento...

En la lista de "Categorías" escogemos la sección "Datos" o navegamos hasta encontrar una cosita llamada "Conjunto de Datos", en cuya explicación indica que es un DataSet, le doy Agregar y se abre una ventana en gris que indica que se pueden añadir tablas.

¿Cómo lleno el DataSet con mis datos del DataTable? Pues, directamente desde el diseñador, no se puede, si es que el DataTable se llena por código, como en mi caso, que el DataTable lo lleno con los datos que muestro en un grid, lo que podemos hacer es anexar después, como lo explico más adelante.

Necesito, de ley, una DataTable dentro del DataSet, para poder añadir los campos en el reporte, así que, con paciencia, agrego un dataTable con los campos que necesito.

¿Cómo creo un dataTable dentro del dataset? Una vez abierto el diseñador del dataset (una pantalla en gris), se da clic derecho en cualquier sitio, en el menú despegable escoger Agregar - DataTable.

Esto creará un cuadradito azul con gris de nombre "DataTable1", al dar clic allí se podrá cambiar el nombre de la tabla (sí, ese es el nombre del dataTable), luego, para añadir los campos, se da clic derecho en el área gris del dataTable (justo debajo del nombre), en el menú que aparece escoger Agregar - Columna, y aparecerá un rectángulo blanco con el nombre "DataColumn1", al dar clic allí se podrá cambiar el nombre de la columna.  Para añadir más columnas repetir los pasos anteriores y listo.

OJO: De ser posible, poner el mismo nombre del DataTable con que se llena el grid y los mismos campos. O sea, si en mi grid lleno los datos con un DataTable llamado dtTabla con los campos c1, c2 y c3, esto mismo lo pongo en el DataSet.

¿Cómo lleno el DataSet con la información que tengo en mi grid? Como recordaremos, ya tengo un DataSet el cual contiene los datos que se muestran en mi grid, y por lo tanto, tengo un procedimiento, Sub, Function o lugar donde están siendo llenados, lo único necesario de hacer es llenar también el DataSet tipado en el mismo lugar.

Un ejemplo, tengo mi proceso que lleno el DataTable el cual está enlazado a mi grid, y le añadí el DataSet.

Private Sub Llenar_Grid_y_Reporte(ByVal cotizacion_codigo As Integer)
        'Obtengo los datos
        Using connection As SqlConnection = New SqlConnection(args) 'Previamente tendremos nuestra variable de conexión
                command = New SqlCommand("sp_llena_grid", connection)
                command.CommandType = CommandType.StoredProcedure
                adapter = New SqlDataAdapter(command)
                With command.Parameters
                        .Add(New SqlParameter("@var1", SqlDbType.Int)).Value = var1
                End With
                Try
                        adapter.Fill(dtTablaGrid) 'Este DataTable es que utilizo para llenar el grid
                        adapter.Fill(dataSet1, "dtTablaReporte") 'Este es el DataSet tipado que añadí
                Catch expSQL As SqlException
                        MsgBox(expSQL.ToString, MsgBoxStyle.OkOnly, "SQL Exception")
                        Exit Sub
                End Try
        End Using
        
        'Lleno el grid
        DataGridView1.AutoGenerateColumns = False
        DataGridView1.DataSource = dtTablaGrid
        With DataGridView1
                .Columns("col1").DataPropertyName = "col1"
                .Columns("col2").DataPropertyName = "col2"
        End With
End Sub

Bien, tengo mi DataSet con mi DataTable, ahora tengo que crear el reporte.

Agrego un nuevo elemento al proyecto, pero esta vez busco la categoría Reporte y escojo el que dice Crystal Reports.

Luego aparecerá una ventana para crear un nuevo documento de Crystal Report, el cual está seleccionado Usar asistente para informes, de momento escojo ese por ser el más fácil. Abajo escojo "Estandar" y doy click en Aceptar. Mi reporte se llamará CrystalReport1.rpt.

Expandir las carpetas "Datos del proyecto" y la "ADO.NET DataSets", allí debe encontrarse el DataSet que creé anteriormente junto con la DataTable también creada, la añado y continúo con el asistente.

El asistente indicará los campos que deseo añadir al reporte, el modo de agrupamiento, si deseo un subconjunto de datos e infinidad de cosas por el estilo, pero como yo deseo ver mi reporte ya, solo le doy click en Finalizar. No me preocupo, porque luego se puede añadir todo lo que deseo o se me haya olvidado.

Una vez abierto el informe en modo diseño, puedo revisar como se visualiza desde la lengüeta "Vista previa del Informe", pero me mostrará datos ficticios o en el peor de los casos, ningún dato.

Tengo mi archivo del reporte, ahora necesito un "visor" de dicho informe, esto es, un formulario que muestre el informe que he creado. Para ello, agrego otro Windows Forms, el cual llamo Form1, y que le doy el tamaño y la personalización que quiero, al final, desde el "Cuadro de herramientas", en la sección Informe, escojo un "CrystalReportViewer" que lo llamo CrystalReportViewer1 y lo añado al formulario. El viewer o visor se puede personalizar a gusto. No es necesario anexarle un reporte, pues esto lo hago más adelante.

Bien, tengo mi formulario que contiene el visor, el reporte Crystal Report, mi DataSet con una DataTable, ahora queda unirlo todo. Desde el formulario en que tengo mi grid, tengo además, un botón que dice Imprimir, el cual mostrará el reporte o informe con los datos de mi grid. Para ello creé un proceso que junta todo y que se llama desde el botón Imprimir.

Private Sub Imprimir_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Imprimir.Click
        'Invoco al procedimiento que llena los datos del reporte
        Cargar_Crystal()
        'Abro el formulario que contiene el reporte
        Form1.ShowDialog()
End Sub
Private Sub Cargar_Crystal()
        'El CrystalReport1 es el nombre de mi archivo reporte CrystalReport1.rpt
        Dim rdInforme As New CrystalReport1
        'El dtTabla es mi DataTable que contiene los datos del DataGridView llenado anteriormente
        rdInforme.SetDataSource(dtTabla)
        'El Form1 es el formulario que contiene el visor llamado CrystalReportViewer1
        Form1.CrystalReportViewer1.ReportSource = rdInforme
End Sub

Si todo sale bien (y espero que así sea) se mostrarán los datos de los campos añadidos en el reporte. Recuerda que los nombres son importantes, los DataTables del grid y del reporte (DataSet) deben llamarse iguales, con todo y mayúsculas, así mismo con los campos.

Nota: Esto está creado en Microsoft Visual Basic 2008 y el Crystal Reports Basic para Visual Studio 2008.