Friday, January 19, 2007

Programando Crystal Reports I, una mirada práctica

Desde hace ya unos meses atrás vengo trabajando con la herramienta Crystal Reports para mi aplicación ASP.NET, y si algo es muy cierto, la versión actual de Crystal, la XI, es definitivamente una herramienta muy evolucionada para la creación de reportes que requieren un alto grado de complejidad, y que además de poder organizar los elementos para presentar la información con buena presentación gráfica, podamos automatizar y programar tareas que hacen de esos reportes elementos realmente útiles para el usuario final. Y es finalmente el usuario quien nos pide cosas que a veces parecen casi que imposibles de resolver, pero que al conocer un poco más las herramientas, estas resultan proveyendo toda una gama de características para hacer que esas tareas se vuelvan una realidad en código.

Quiero en esta oportunidad compartir con ustedes ciertos tips de código y diseño, que me han gastado varias horas de investigación y desarrollo, para poder plasmar el problema de negocio del cliente en una serie de reportes que resultan ser al final muy satisfactorios.

Subreportes:
Este es quizás uno de los temas, por los cuales la gente tiene muchos problemas cuando está construyendo reportes de nivel profesional, los cuales requieren diferentes elementos y jerarquías de cierta complejidad, las cuales pueden ser resueltas con esta característica provista por Cristal de una manera sencilla de utilizar. En esta parte entonces veamos cómo puedo desde mi aplicación ASP.NET y utilizando lenguaje C#, acceder a estos elementos, y adicionalmente poder enviar parámetros, tanto a su reporte contenedor, como a estos subreportes.

Las dos primeras instancias van a ser de tipo SubreportObject y ReportDocument. La primera nos permite definir el representante en código de nuestro subreporte que ya hemos adicionado en diseño al reporte contenedor. La segunda nos permite trabajar con nuestro subreporte, tal como si estuviéramos accediendo un reporte común y corriente, pudiendo acceder a propiedades para fijar por ejemplo la fuente de datos y parámetros si son necesarios.

SubreportObject subreportObject;
ReportDocument subReporte;
Reporte miReporte = new Reporte();
DataSet miDs = new DataSet ()
miDs = Consultar();
subReporte = new ReportDocument();
subreportObject = miReporte.ReportDefinition.ReportObjects["subReporte"] as SubreportObject;
subReporte = miReporte.OpenSubreport(subreportObject.SubreportName);
subReporte.SetDataSource(miDs.Tables[0]);

Adicionalmente a las dos instancias mencionadas, se tiene entonces un objeto de tipo DataSet, el cual servirá como contenedor para la data devuelta por el método que consulte la información desde algún repositorio, por ejemplo una base de datos. Es importante observar la colección ReportObjects, en la cual le indicamos al objeto subreportObject que será el contenedor de un subreporte llamado “subReporte”, nombre asignado en la propiedad Name del subreporte. Si no se tiene cuidado con el nombre asociado al subreporte, al ejecutar la aplicación, el Sistema lanza una excepción que indica que el índice está fuera del rango. Se utiliza un el operador “as”, el cual ofrece seguridad y eficiencia en este proceso, y adicionalmente no tenemos que hacer ni tratamiento de NULL ni manipular excepciones, pero esto será tema de otra entrada.
Entonces es claro que el objeto subreportObject, se ha vuelto el representante en código del subreporte, y por ejmplo lo pasamos como parámetro al método OpenSubreport() con el cual abrimos el subreporte y lo asignamos al objeto subReporte, con el cual finalmente asignamos la fuente de datos al subreporte. Es claro que cuando tenemos un reporte principal, con diferentes subreportes, podemos tener diferentes fuentes de datos para alimentar cada elemento del reporte principal, incluyendo la fuente de este último.

Finalmente podemos entonces consultar la información para el reporte principal.

miDs = new DataSet();
miDs = ConsultarPrincipal();
miReporte.SetDataSource(miDs.Tables[0]);
this.crvVisorReporte.ReportSource = miReporte;
this.crvVisorReporte.DataBind();

El objeto crvVisorReporte es una instanacia de la clase CrystalReportVoewer, que es el visor o control que encontramos en el ToolBox de Visual Studio .NET, el cual nos permite visualizar nuestros reportes en las páginas .aspx o Windows forms.
Quiero anotar algo, y es que en el método SetDataSource(), le pasamos el DataSet y se le indica el objeto Table de la colección que se va a utilizar. Si no se hace esto y si le pasamos solamente el objeto DataSet, inmediatamente el objeto crvVisorReporte, despliega uso campos para ejecutar logon contra la base de datos.

Pasar parámetros al Reporte y Subreportes

Primero que todo es importante conocer las posibles razones por las que ocurre el error más típico cuando se utilizan parámetros en Crystal reports que se envian desde el código de nuestra aplicación. Básicamente el error es Missing Parameter Value y Referencia a objeto no establecida. Esto es lo único que Crystal nos muestra en el visor. Ocurre cuando se han declarado parámetros en el reporte y subreportes, y no se les está enviando ningún valor. También puede ser que desde código estamos asignando un valor a un parámetro que no se ha definido aún en los reportes. Entonces se debe tener un poco de cuidado y así evitar este error que es difícil de depurar algunas veces.

miReporte.SetParameterValue("anio", 2004);
miReporte.SetParameterValue("mes", 1);

El código anterior está asignando los valores a 2 parámetros que deberán estar definidos en nuestro reporte principal y sus subreportes, bueno si es que los subreportes los necesitan, es decir si se requiere utilizar los parámetros en el subreporte deben definirse en el mismo. La manera de pasar los valores enviados desde código entre el reporte principal y sus subreportes es fijando en la opción Change subreports links, que se puede acceder en tiempo de diseño, parándose sobre el subreporte y haciendo clic derecho. Se abre entonces una ventana donde podemos seleccionar uno o más campos sobre los cuales se enlazan los parámetros que hayamos fijado en el reporte principal y sus subreportes. Esto es porque no podemos asignar valores a parámetros de subreportes directamente desde código.

En conclusión, una tarea que puede volverse dispendiosa cuando se hace por primera ves, realmente con el modelo de objetos de Crystal reports desde C#, realmente es sencilla y muy poderosa. Por ejemplo podemos asignar desde código valores a los parámetros, de acuerdo a determinada opción, y ya en el reporte, el valor de ese parámetro determinara el comportamiento del reporte, por ejemplo mostrar u ocultar una de sus secciones.

17 comments:

Anonymous said...

Muchismimas gracias por la información, justo lo que estaba buscando.

Que bueno que haya gente como tú publicando este tipo de información

Anonymous said...

buena la informacion gracias por compartir tu conocimientos
Funciona perfectamente

Anonymous said...

Saludos,

De donde estas tomando esta informacion.

Que representa la palabra Reporte??

Reporte miReporte = new Reporte();

Andrés Ortíz said...

Hola

La clase Reporte es la que se crea para el reporte principal, es decir el que contiene a los subreportes. Si observas el código, la instancia miReporte es la que se asigna al CrystalReportViewer.

La información es solo tomada de mi trabajo diario, y traté de poner nombres genéricos en el ejemplo.

Cualquier otra duda con gusto.

Chico said...

Excelente, lo que me faltaba era indicar la tabla.
Muchísimas Gracias...

Anonymous said...

Muchas gracias, esta información ha sido de mucha ayuda, ya empezaba a estresarme con tantos errores, jeje.

Muchas gracias

Unknown said...

Hola!

tengo problemas para refrescar el origen de datos que esta ligados al subreporte(server,BD,User,Pwd)

Antecedentes:
- este subreporte tiene echos links con el reporte principal a nivel de diseño
- el subreporte contiene un procedimiento almacenado

agradeceria mucho si me pudieras dar una mano con este problema

atte.
César.

MARIA_Elisa said...

El metodo SetParameterValue solo me toma el valor del segundo textBox,Como hacerle para que el parametro estatus me hacepte dos valores?
objRpt.SetParameterValue("estatus", textBox1.Text);
objRpt.SetParameterValue("estatus", textBox2.Text);
crystalReportViewer1.ReportSource = objRpt;

Yobb said...

Está muy bueno el post, pero tengo una duda.
Estoy usando crystal report XI y cuando envío los parametros antes visualizarse en la aplicación, me salta un cuadro de dialogo en dónde me pide introducir los parámetros. Tengo entendido que en crystal X era necesario poner a false cierta propiedad del ReportViewer para que dicho cuadro no saltara y los parámetros fueran pasados directamente, lo único es que era necesario controlar bien que no existieran errores de tipo, de lo contrario se imprimiría un error.
El problema es que no encuentro ninguna propiedad dentro del ReportViewer del crystal XI en dónde deshabilitar dicho comportamiento. Sabes cómo hacerlo?

Anonymous said...

Muchas Gracias, ahora tengo una duda, si necesito devolver un valor o un parametro de un subreporte al contenedor, cual es el procedimiento.

Anonymous said...

Hola buen día
Tengo una pagina en asp.net y desde esta le mando parametros a mi reporte en crystal solo que me manda el error de faltan valores de parametro y Referencia a objeto no establecida "lo que dices que hay que tener cuidado" ya verifique los datos que se envian como parametros y estan bien pero no se que mas pudiera causar ese error espero me puedas orientar. De antemano gracias :D

Anonymous said...

Muchas gracias Andrés, el ejemplo que compartiste me permitió resolver un misterio con mi subreporte. Durante días intenté sin éxito pasar un DataTable al subreporte, me daba el error "Faltan valores de parámetro" al enviar el documento a la impresora. El problema era que yo pasaba primero los datos al reporte principal, luego al subreporte. Al hacerlo al contrario todo se solucionó -inrecible! MIL GRACIAS ANDRES, SOS UN GENIO!

Unknown said...

Hola, me parece interesante tu aporte, muy bueno en realidad... Pero tengo un problema que no he podido solucionar, y quiza tu puedas ayudarme, paso a explicarte..
Tengo un Reporte con un subreporte, ambos usan proc almacenados en SQL server 2005, del reporte principal linkeo (se podria decir) los parametros que se usan en el subreporte.. Tengo una aplicación en visual basic 9.0 desde la cula llamo al reporte.. todo bien mientras no cambie la BD original con la que se diseño el reporte, pues si desde codigo actualizó la conexión, al momento de ejecutar me sale un ventana pidiendome tambien los parametros del subreporte, es decir, no respeta el linkeo que hice en el archivo originalmente.. espero me hayas entendido y puedas ayudarme... muchas gracias de antemano

Unknown said...

hola, tango un problema algo tedioso (Es muy urgente) y no se como hallar el inconveniente.

Resulta que realice el reporte de un formulario que tiene como 500, parametros pero al correrlo vaya sorpresa missing parameter value, al hacer debug se muere cuando voy a exportar el reporte
repDoc.ExportToDisk(ExportFormatType.PortableDocFormat, name);

el problema es que haciendo debug no me muestra exactamente cuales son los parametros que quedan sin valor y realizar la verificacion de cada campo es algo engorroso.

Te agradeeceria si sabes como obtener esta lista de parametros sin valor.

Muchas Gracias
Leidy

Anonymous said...

Buenas tardes, mi nombre es Carlos y estoy realizando un reporte con un subreporte y a ese subreporte le puse dos subreportes más, es decir 1 reporte que contiene 3 subreportes y uno de los subreporte contiene 2 subreportes, esto funciona, porque a mi no me muestra los subreportes del subreporte, desde ya agradezco su colaboración.

Unknown said...

Gracias por el aporte pero tengo un problema y no le encuentro solucion es el caso de pasar parametros de tipo bool. me da error.
se puede pasar valores de tipo bool?

Octavio Linares said...

Saludos. Este artículo me sirvió para modificar los atributos de los objetos (TextObject, BoxObject, LineObject) de un subreporte incrustado en un reporte principal. Gracias!