16 enero 2009

Creación de informes con QuickReport (III)

Hoy vamos a ver como modificar la impresión de un informe en tiempo real para poder cambiar su aspecto. También veremos otro ejemplo de un informe con detalle y subdetalle.

MODIFICANDO LA IMPRESIÓN EN TIEMPO REAL


Supongamos que en el listado de clientes que vimos en ejemplos anteriores hemos añadido la columna SALDO INICIAL y nos interesa que salgan todos los importes en negro menos aquellos con saldo negativo que aparecerán en rojo:

Cuando se insertan campos en un informe, Delphi suele darles el nombre QRDBText1, QRDBText2, etc. Debemos acostumbrarnos a poner en la propiedad Name de estos componentes el mismo nombre que tiene el campo en la tabla, en nuestro caso SALDOINICIAL. Sólo para los campos de la base de datos, no es necesario para etiquetas y figuras gráficas.

También sería recomendable ponerle nombre a la banda donde se imprimen los campos (Detalle).

Luego programamos el evento BeforePrint de la banda Detalle del siguiente modo:

procedure TFListadoClientes.DetalleBeforePrint(Sender: TQRCustomBand;
var PrintBand: Boolean);
begin
if Form1.Clientes['SALDOINICIAL'] >= 0 then
SALDOINICIAL.Font.Color := clBlack
else
SALDOINICIAL.Font.Color := clRed;
end;

El evento BeforePrint (antes de imprimir) se va a ejecutar antes de dibujar cada registro en la banda. De este modo podemos cambiar en tiempo real la visualización de cada campo antes de que se plasme en el folio.

Otro caso especial que nos puede solucionar este método es el de modificar los campos booleanos para que no imprima True o False. Por ejemplo, si quiero imprimir el campo activo pasaría esto:


Para solucionar esto en vez de imprimir directamente el campo ACTIVO utilizando un objeto TQRBDText lo que vamos a hacer es meter una etiqueta llamada EActivo (TQRLabel):


Ahora modificamos el método BeforePrint de la banda Detalle:

procedure TFListadoClientes.DetalleBeforePrint(Sender: TQRCustomBand;
var PrintBand: Boolean);
begin
if Form1.Clientes['SALDOINICIAL'] >= 0 then
SALDOINICIAL.Font.Color := clBlack
else
SALDOINICIAL.Font.Color := clRed;

if Form1.Clientes['ACTIVO'] then
EActivo.Caption := 'SI'
else
EActivo.Caption := 'NO';
end;

Si el campo ACTIVO está a True imprimimos SI, NO en canso contrario. Estos truquillos nos ahorran mucho tiempo y evitan tener que modificar los datos de la tabla original.

Hasta modemos meter en la banda figuras gráficas o iconos según los datos del registro actual. Pongamos por ejemplo que a los clientes cuyo saldo inicial sea superor a 800 euros le ponemos el icono de un lápiz a lado como advertencia para su edición:

Lo que he hecho es insertar el objeto imagen TQRImage con el icono del lápiz (a través de su propiedad Picture) y luego vuelvo a ampliar el evento BeforePrint de este modo:

procedure TFListadoClientes.DetalleBeforePrint(Sender: TQRCustomBand;
var PrintBand: Boolean);
begin
if Form1.Clientes['SALDOINICIAL'] >= 0 then
SALDOINICIAL.Font.Color := clBlack
else
SALDOINICIAL.Font.Color := clRed;

if Form1.Clientes['ACTIVO'] then
EActivo.Caption := 'SI'
else
EActivo.Caption := 'NO';

if Form1.Clientes['SALDOINICIAL'] >= 800 then
Icono.Enabled := True
else
Icono.Enabled := False;
end;

Como puede verse en el código he utilizado la propiedad Enabled en vez de la propiedad Visible para ocultar el icono. La propiedad Visible no tiene ningún efecto en QuickReport (por lo menos a mi no me ha funcionado en ningún caso).

Con estos pequeños retoques nuestros informes parecerán mucho más profesionales (y a nuestros clientes les sacamos más pasta diciendo que nos ha costado mucho hacerlo :)

CREANDO UN INFORME CON DETALLE Y SUBDETALLE

Ahora vamos a complicar un poco más el asunto y vamos hacer un listado desglosado de facturas con dos bandas de tipo detalle. Más bien con un detalle y un subdetalle:


Este listado muestra las cabeceras de las facturas (los totales) y sus líneas de detalle. Este sería su diseño en QuickReport:


El documento se compone de tres bandas:

Cabecera: esta banda es un objeto TRQBand y va a ser de tipo rbTitle. En esta banda sólo vamos a meter el título del listado:


Detalle: también va a ser un objeto TRQBand y su tipo será rbDetail. En esta banda necesitamos los títulos de la factura así como los campos de la misma:


El único campo que no hay que vincular con la tabla Factura es el NOMBRE que va vinculado a la tabla CLIENTES.

SubDetalle: aquí introducimos una banda de tipo TQRSubDetail. No es lo mismo una banda TRQBand de tipo rbSubDetail que una banda TQRSubDetail. La primera sólo suele utilizarse para totalizar datos (sin vincularlo a tablas) pero esta última tiene la propiedad DataSet que permite vincular sus campos a otra tabla:


Ahora viene la vinculación con las tablas. Primero hay que seleccionar el objeto TQuickRep y vincular a su propiedad DataSet la tabla Facturas (la cabecera). En la banda de Detalle vinculamos todos los campos a la tabla Facturas.

En la banda SubDetail tenemos que vincular los campos a la tabla Detalle (el contenido de la factura).

Si ejecutáramos el listado como está, nos pasaría esto:


Han ocurrido dos cosas:

1º Al no filtrar la tabla de Clientes por cada factura resulta que aparece el mismo cliente en ambas facturas. Debería ser: CLIENTES.ID = FACTURAS.IDCLIENTE.

2º Al no filtrar la tabla Detalle por cada factura nos aparece el detalle de ambas facturas debajo de cada cabecera. Debería ser así: DETALLE.IDFACTURA = FACTURAS.ID.

Para solucionar esto tenemos que escribir este código en el evento BeforePrint (antes de imprimir) de la banda Detalle:

procedure TFListadoFacturas.DetalleBeforePrint(Sender: TQRCustomBand;
var PrintBand: Boolean);
begin
with Form1 do
begin
Clientes.Filter := 'ID=' + IntToStr(Facturas['IDCLIENTE']);
Clientes.Filtered := True;

Detalle.Filter := 'IDFACTURA=' + IntToStr(Facturas['ID']);
Detalle.Filtered := True;
end;
end;

Lo que hacemos es filtrar el cliente y el detalle de la factura respecto a la factura que estamos imprimiendo en cada momento. De este modo no hay que recurrir a sentencias SQL para filtrar la tablas (aunque también se podría hacer así).

En la siguiente parte de este artículo veremos más ejemplos de informes así como la forma de exportar a PDF y otros formatos.

Pruebas realizadas en RAD Studio 2007.

11 comentarios:

Mariano Rocha dijo...

Hola estoy siguiendo sus tutoriales con gran emocion, aunque no encuentro como es posible colocar en un informe una grafica, las graficas las aprendi a realizar con el techart, me gustaria saber si se puede colocar una en un quick report, y la otra pregunta es si todo lo que aca se demuestra es valido en delphi 7. Muchas gracias por todo.

Administrador dijo...

En principio en un objeto TQuickRep sólo pueden colocarse componente de la paleta QuickReport.

Eso que tu quieres hacer se puede conseguir capturando la imagen del canvas del objeto gráfico y luego lo llevas al informe como una imagen.

El único inconveniente que tiene esto es que sale pixelado, por lo que hay que hacer el gráfico bien grande para que a la hora de llevarlo al folio se vea normal.

En principio todo lo que estoy escribiendo en estos artículos tiene que funcionar sin problemas en Delphi 7, siempre y cuando los componentes QuickReport estén mas o menos actualizados a la última versión.

Saludos.

Jackoman dijo...

Con la versión que tengais de Delphi/QR/TeeChart no lo se, pero yo recuerdo haber tenido un componente TQRChart en la paleta de componentes de QuickReport que aportaba la misma funcionalidad que el componente chart normal. Míra un poco que seguro que encuentras cómo activarlo.

Por cierto muy bueno el blog. Sigue así. Te tengo en mi RSS desde hace tiempo ;)

nelson mateo de la cruz dijo...

hola saludos
estoy realizando los ejercicios que usted plantea en esta pagina pero cuando vay a imprimir me precenta un error que dice no convet variant null a int64 y solo me muestra el encabesado y con la difencia que yo no filtro por codcliente sino por el nombre me gustaria saber de su pareser y saber cual seria el error y como puedo solucionarlo uso como base de datos sql server 2000 y delphi 2010 mediante odbc y quick report 2010
grasias de antemano y grasias por tan grandiosos aporte a la cominidad de programadores ispanos grasias una vez mas

Administrador dijo...

Pues ahí si que me pillas porque nunca he utilizado el motor de bases de datos Microsoft SQL Server con Delphi y menos a través de ODBC.

Hace poco estuve mirando unos vídeos de embarcadero y recomiendan encarecidamente que se utilice siempre los DBExpress para conexión de bases de datos, ya que los BDE, ODBC, ADO y demás hierbas los dan por descatalogados.

¿Has probado a utilizar otros componentes de conexión a bases de datos?

nelson mateo de la cruz dijo...

no hace tiempo estaba probando con zeos y no di piez con bola por que casi no existen manuales para estos componentes y preferi anbandonarlos me gustaria aprender a manejar firebir o interbase pero los manuales que consigo son para personas que conoscan y poco del tema y de ese tema nose nada.
me gustaria que personas como usted en su educatibo blog diera un taller sobre el uso y manejo de interbase o firebir para neofitos en el tema.
de antemano gracias una y otra vez

Administrador dijo...

Los componentes Zeos los he probado y son bastante decentes, aunque a la hora de generar las SQL en un componentes TZUpdateSQL no funciona muy bien y hay que hacer correcciones a mano.

Puede que más adelante cree un tutorial sobre como hacer un programa de gestión con Interbase/Firebird para principiantes.

Eso si tengo tiempo, porque últimamente lo tengo algo crudo.

Saludos.

Oscar Guzmán dijo...

Administrador estoy viendo su curso con mucho entusiasmo porque es poca la información que hay en internet sobre quickreport y apenas lo estoy conociendo y me gustaría saber si tiene algún link en este blog que trate un poco más en profundidad como trabajar con las funciones de quickreport, tengo un reporte al que tengo que totalizarle una columna con montos negativos y positivos, pero para la sumatoria de esta columna no se pueden tomar en cuenta los montos negativos. Muchas gracias.

Administrador dijo...

Para no pelearte con las funciones de Quickreport, puedes hacerlo más rápido creando un campo calculado en la misma tabla que sea una réplica del original pero sin tomar valores nevativos:

procedure TBases.TFacturasCalcFields(DataSet: TDataSet);
begin
if TFacturasTOTAL.AsFloat > 0
then
TFacturasTOTALCALC.AsFloat := TFacturasTOTAL.AsFloat
else
TFacturasTOTALCALC.AsFloat := 0;
end;

Luego realizas un SUM de TOTALCALC y ya está. Yo estoy tan quemado con todos los editores de informes que prefiero preprocesar los datos antes de imprimir.

cdzz74 dijo...

Muy buen aporte, yo tenia que tapar un registro dependiendo de un valor para que no se viera, ya que son etiquetas y este tutorial me ayudo.
Gracias

angelito dijo...

necesito que le traiga el nombre pero yo solo guardo el id_cliente. tabla 1 (@id_personaje+id_cliente+ .......) tabla 2 (@id_cliente+nombre.....) . en dataSet tabla1, en datafield ¿? id_cliente . en el procedure va algo?

Publicidad