QrCreateList lo hace por nosotros

¿Ha pensado en la posibilidad de teclear una consulta SQL (sobre tablas locales, por ejemplo), y enviar el resultado directamente a la impresora? Con QuickReport y unas pocas líneas de código podemos conseguirlo.

Tavo Ibaceta

(Publicado originalmente en la revista Mundo Delphi en noviembre de 2000)

¿Es que no nos avergüenza buscar siempre la forma de trabajar menos?…. Ah ¿No?

En ese caso, tendríamos que analizar primero el problema: si hablamos de teclear una consulta SQL tendremos que suponer componentes como TQuery (en el caso de BDE), TSQLQuery, TIBQuery, etc. Es decir, juegos de datos de los cuales podemos no conocer sus campos sino hasta que sean abiertos. Habrá que crear entonces un procedimiento que pueda listar los campos de un juego de datos (un TDataSet) sin saber su estructura en tiempo de diseño. Se trata de hacer un procedimiento genérico, que pueda ser llamado desde cualquier “lugar” de nuestro código.

Afortunadamente esto es posible ya que QuickReport permite construir dinámicamente casi cualquier reporte. Recuerde que todo lo que puede hacerse en tiempo de diseño, puede hacerse dinámicamente.

Repasemos cómo lo haríamos desde código :

Sólo habría que crear un TQuickRep y agregarle tres bandas: título, cabecera y detalle. Luego tendríamos que recorrer los campos de la tabla creando, para cada uno, un control como TQrDbtext o TQrExpr (descendientes de TQRPrintable), para luego dimensionarlos, darles un lugar dentro de la banda detalle mediante la instrucción AddQrPrintable(). No nos olvidemos de la banda rbTitle, también aquí habría que crear etiquetas para los nombres de los campos y colocarlas justo sobre las columnas.

Los pasos, en líneas generales serían:

  1. Creación del TQuickRep
  2. Creación de las tres bandas rbTitle, rbHeader y rbDetail.
  3. Creación de controles para cada campo en la banda rbDetail.
  4. Creación de controles para cada campo en la banda rbHeader.

Para nuestro propósito (trabajar menos), la buena noticia es que estos 4 pasos están ya encapsulados en métodos de la clase TQrListBuilder y nuestra función estrella -QrCreateList- los sabrá utilizar perfectamente con sólo llamarla y pasarle cinco parámetros. Es por ello que para éste, nuestro primer ejemplo, un intérprete SQL que liste el resultado, sólo hará falta obtener el resultado de la consulta, agregar las ocho líneas de código que podemos ver en el listado 1 y correr a la impresora a ver cómo sale.

procedure ReportSQL(Data:TDataSet) ;
var
  aReport : TCustomQuickRep ;
begin
  areport := nil;
  QRCreateList(aReport, nil, Data, '', nil );
  Try
    areport.preview ;  // ó Print
  Finally
    aReport.Free;
  End ;
end;
Listado 1. El código de llamada a QrCreateList

El primer parámetro es una variable de tipo TCustomQuickRep que será el reporte propiamente dicho, el segundo es el “Owner” de éste, que puede ser nil, el tercer parámetro, de tipo TDataSet, es el conjunto de datos que vamos a listar, el cuarto es el título que va a llevar y el quinto será un nil para que nuestro reporte sea sobre todos los campos del Query.

Este código nos muestra cómo construir de manera totalmente dinámica un reporte sobre un conjunto de datos determinado. Ya sabemos que para agregar un previsualizador personalizado es necesario crear un procedimiento que debe ser asignado al evento OnPreview del reporte, cosa relativamente fácil cuando vemos el reporte en tiempo de diseño. Pero como el nuestro es construido dinámicamente una de las formas de hacerlo es crear una clase que herede de TCustomQuickRep y agregarle un método (que asignaremos al evento OnPreview) donde será creada la ventana previsualizadora. En realidad podríamos utilizar un método de otro objeto para ese propósito pero no sería muy claro. El listado 2 nos muestra la declaración de la clase y su método CreaPreview. Sólo habrá que asignar ese procedimiento al evento OnPreview antes de llamar a Print o Preview. Puede remitirse al ejemplo que acompaña este artículo para entenderlo mejor.

type
  TDinamicQRep = class(TCustomQuickRep)
    Procedure CreaPreview(Sender: TObject) ;
  end;
procedure TDinamicQRep.CreaPreview(Sender: TObject) ;
begin
  With TfrmPreview.Create(Application) do begin
    Caption := 'Generando el reporte. Aguarde' ;
    pQuickReport := TQuickRep(Sender) ;
    QRPreview.QRPrinter := TQRPrinter(Sender) ;
    Show ;
  end;   
end ;
Listado 2. La nueva clase con previsualizador a medida.

Y…¿Para qué me puede servir ?

Un intérprete de SQL que imprima no es, en líneas generales, la necesidad del usuario final típico. ¿Verdad ? Pero nos puede dar ideas sobre una de las formas posibles de crear reportes rápidamente si tenemos ya en “nuestras manos” el conjunto de datos que será “listado”. Veamos posibles usos.

Todo programador Delphi que se precie debe conocer la famosa herencia visual que otros lenguajes no poseen. Por mi parte es la única herencia que podré disfrutar así que, ¡a usarla! Acostumbro usar un formulario “Grilla general” y otros que heredan de él para mostrar tanto los contenidos de las tablas como los resultados de las consultas, de esta forma todo el código escrito en la “Grilla general” es heredado y reutilizado en las “Grillas hijas”. No resulta muy difícil entonces agregarle a estas grillas la prestación “salida por impresora”, sólo habrá que escribir dicha funcionalidad en una sola grilla.

Podríamos encontrar otro uso posible, y más interesante, si quisiéramos desarrollar una de esas aplicaciones con “motores” de reportes que basan su potencia en la complejidad de los controles de selección, para luego con ello lograr una instrucción SQL final y pasarle la responsabilidad (y el trabajo) al motor SQL. Este modelo resulta muy versátil pero multiplica la cantidad de títulos, columnas y grupos posibles. Si adherimos a dicho “modelo” deberemos crear dinámicamente nuestras columnas y entonces la función QrCreateList nos será de utilidad.

Psé… Pero, mis títulos siempre van en rojo…

Sabemos como programadores/as que somos, que podemos poner los títulos en rojo, en negrita o como se nos ocurra. Podríamos entonces crear una función con parámetros como color, fuente, título, la orientación…

Aquí aparece el dilema de siempre: ¿Hasta qué punto es conveniente dar “configurabilidad” a un procedimiento que se supone fue creado para ser genérico? ¿Quiero usar todas las prestaciones posibles? ¿Tiene sentido una función con dieciocho parámetros? Todo desarrollador encontrará su punto justo.

El ejemplo que desarrollaremos a continuación tampoco es para un uso real (por lo menos en su totalidad). Se trata de conocer la forma de acceder a los controles creados para que, si nuestros títulos siempre van en rojo, podamos ponerlos en rojo mediante código.

El primer inconveniente es que esta manera de crear el reporte hace dificultosa la creación de bandas de tipo TQrGroup, muy útiles ellas. En el caso del intérprete SQL, podrían servirnos para agregar un separador de grupos habiendo detectado la instrucción order by en la sentencia SQL. A esto podemos sumarle el deseo de colocar en el tope de la página el logotipo de cuarenta y dos colores de la empresa que nos contrata. ¿Habrá que crear todo dinámicamente? ¿No era que íbamos a trabajar menos?

Recordemos que uno de nuestros propósitos, además de trabajar menos, es crear un reporte para un conjunto de datos del cual no conocemos sus campos. Por lo tanto lo único que no conocemos son los campos. ¿Por qué no dejarle sólo esa tarea a QrCreateList ?

Esto significa que podemos disfrutar de las bondades de ver en tiempo de diseño, la apariencia final (o casi) de nuestra página, el logotipo de muchos colores, el pie de página con los nº de teléfono y todas las aberraciones estéticas que se nos de la gana. Utilicemos entonces el IDE de Delphi para diseñar un reporte (o su esqueleto, ya que no conocemos los campos), luego podremos llamar a QrCreateList para que nos cree –ya en tiempo de ejecución- sólo las columnas.

Habrá que pasar el reporte creado como primer parámetro, QrCreateList se encargará de verificar si las bandas rbDetail y rbColumnHeader ya existen y crear en ellas los controles necesarios. Si no existen, las creará. Esto nos permitiría por ejemplo, agregar “bitmaps” u controles de tipo TQrShape a la banda rbColumnHeader.

Lo primero que deberemos cambiar con respecto al ejemplo anterior si queremos acceder al reporte es la declaración de la variable de tipo TCustomQuickRep, ya que ahora necesitamos acceder a ella desde varios procedimientos por lo tanto su alcance debe ser por lo menos, toda la unit. Recuerde que en nuestro ejemplo anterior QrCreateList creaba por sí mismo todo el reporte, incluso el objeto TQuickRep. Ahora dicho objeto será una instancia de una clase creada y diseñada por nosotros.

Anteriormente habíamos inicializado como nil la variable de tipo TCustomQuickRep antes de pasarla como primer parámetro a QrCreateList para que haga el trabajo de crear todo el reporte. En nuestro siguiente ejemplo QrCreateList recibirá un reporte ya creado como primer parámetro, asumirá que ya hemos hecho todo el trabajo menos la creación de las columnas. Por lo tanto será lo único que haga, ignorando todo lo que no tenga que ver con ella, incluso el cuarto parámetro, el título.

Esta vez el quinto parámetro será un tipo TStringList conteniendo los nombres de los campos que queremos ver impresos. Aquí no hace mucha falta explicar el código, se trata simplemente de cargar en un control TCheckListBox los nombres de los campos para que el usuario final pueda seleccionarlos. Recordemos que hay tipos de campos para los cuales QrCreateList no crea controles, como ftBlob, ftMemo, ftGraphic.

Uno de los problemas que se nos pueden presentar es que alguna de las columnas refleje un valor perteneciente en realidad a un campo con el mismo nombre pero de otra tabla. Esto es porque QrCreateList todavía no es tan inteligente como debería y crea controles TQrExpression que hacen referencia sólo al campo, sin especificar el conjunto de datos. Si se tratara de controles TQrDbText podríamos asignarle un valor a su propiedad DataSet pero como se trata de TQrExpression tendremos que corregir el valor de la propiedad Expression para especificar el conjunto de datos al que debe hacer referencia.

Para ello recorreremos los controles de la banda detalle. ¿Y cómo hacemos esto? Así haya sido creada dinámicamente, siempre podremos hacer referencia a la banda detalle a través de la propiedad Bands.DetailBand del objeto TCustomQuickRep o TQuickRep. En el listado 3 podemos ver el código de corrección, iteración que podría ser aprovechada también para cambiar el espaciado o el ancho de las columnas, dando nuevos valores a las propiedades Left y Top.

for n := 0 to aReport.Bands.DetailBand.ControlCount -1 do 
  if aReport.Bands.DetailBand.Controls[n] is TQRExpr then
    with TQREXpr(aReport.Bands.DetailBand.Controls[n]) do
      Expression := '['+Data.Name+'.'+Copy(Expression,2,99);
Listado 4.El código de corrección a QrCreateList

La forma de acceder a las “etiquetas” de la banda rbColumnHeader es similar: serán los controles incluidos en Bands.ColumnHeaderBand. En la aplicación de ejemplo se incluye el código para acceder a ellas.

Uno de los conocidos problemas del control TQrExpression es la imposibilidad de ser asignado felizmente a un campo que tenga un nombre con espacios. Porque aunque no sea una idea recomendable, Paradox por ejemplo, permite nombres con espacios. Los desarrolladores de QuickReport aconsejaron entonces en sus FAQ’s encerrar entre corchetes la expresión, por ejemplo [Table1.Fecha Ingreso] para que el control, ya creado, funcione correctamente. Con esto seguramente habrán pensado tener todo solucionado. El problema es que QrCreateList no crea controles para campos con estas características, así que deberemos olvidarnos de ellos si nuestras columnas son creadas de esta manera.

Finalmente el ancho de cada columna es inicializado según el valor de la propiedad DisplayWidth de cada campo. Por lo tanto debemos saber que si estuvimos viendo los datos en una grilla y modificamos los anchos de las columnas del control TDbGrid, hemos modificado esos valores y eso se verá en los anchos de las columnas del listado.

Para la aplicación de ejemplo hemos utilizado BDE, pero como hemos dicho al principio puede utilizar QRCreateList (y QuickReport en su totalidad) con cualquier descendiente de TDataSet.

Conclusión…

No es ninguna novedad que lo peor de QuickReport es la ayuda y la documentación. Si existe la sensación de tener que ceñirse siempre a la utilización de reportes estáticos o complicadísimas rutinas de creación, valgan estas líneas para ayudar a la reflexión. Probablemente la idea de la necesidad de atarnos a la creación estática de reportes esté ligada a la complejidad de la creación de columnas, cosa en la que, QrCreateList puede darnos una mano.