18 de septiembre de 2015

El Generador de informes de VFP 9.0 en acción - Parte 3

Autor: Cathy Pountney
Original en inglés: Visual FoxPro 9.0 Report Writer In Action
http://msdn.microsoft.com/library/en-us/dnfoxgen9/html/VFP9Reports2.asp
Traducido por: Ana María Bisbé York
Se aplica a: Visual FoxPro 9.0


Listener personalizado: Paginación

VFP ofrece la opción de comenzar cada grupo en una nueva página. Sin embargo, no ofrece la opción de comenzar condicionalmente un grupo de datos en una página nueva. VFP no permite tener una banda de detalles entera en la misma página cuando hay un campo alargable estirable (stretchable) involucrado. Siguiendo la misma línea, VFP no brinda la posibilidad de mantener una banda entera en la misma página cuando están implicados los campos.

Estos dos problemas pueden ser solucionados con VFP 9.0 y alguna manipulación ingeniosa. El truco para hacer este trabajo es formar objetos shape objects. La clase ReportListener tiene un método llamado AdjustObjectSize(), el cual se llama desde todas las formas. En este método se puede alterar la altura del objeto. Sabiendo eso, una forma puede ser colocada estratégicamente en el informe justo antes del conjunto de información que necesita permanecer en la misma página. Esta forma puede extenderse por programación para llenar el resto de la página cuando el conjunto de información no quepa. Al hacer que la forma llene el resto de la página, la información que necesita estar junta automáticamente queda en la siguiente página.

Otra clave para hacer que trabaje este concepto es que el informe necesita ser preprocesado para calcular cuanto espacio hace falta para cada conjunto de información que necesite colocar junta. Una vez que se ha completado el preproceso, se ejecuta el informe para el real, utilizando la información acopiadas durante el pase del preproceso.

Definición del informe

Comience por diseñar un informe como necesite, incluir todas las bandas, objetos, y variables de informe. Una vez que el informe esté complete, agregue los grupos de datos, formas, y directivas necesarias para la paginación.

Grupos de datos

El primer elemento especial para la paginación es tener grupos de datos. Envuelva el conjunto de información que necesita colocar junta en dos grupos de datos. La Figura 24 muestra un ejemplo de cómo envolver la banda de detalle en dos grupos de datos.

Figura 24. Para el control de paginación se utilizan formas y grupos.

El nivel superior de grupos de datos se basa en una expresión que se evalúa en tiempo de ejecución. Esta expresión evalúa cualquier valor que identifique un conjunto de información que tiene que estar junta, RECNO() en esta situación, o se evalúa como nada. Durante el pase pre-proceso de ejecución del informe, esta expresión evalúa a RECNO() de tal forma que cada banda se imprime en una nueva página. Esto es necesario para contar la altura de toda la banda. Durante el proceso real de este informe, el valor de esta expresión, es blanco, por tanto, esto no afecta la paginación del informe.

El Segundo grupo de datos agregados al informe se basa en el conjunto de información que necesita estar junta. Al mantener toda la banda de detalle junta en una página, utilice RECNO() como la expresión para este grupo de dato. La banda encabezado del grupo y pie de grupo creada por este segundo grupo se utiliza para sujetar (hold) los objetos forma utilizados por la clase ReportListener.

Para mantener un grupo de datos entero en una página, el significado del encabezado, detalle y pie de grupo sigue los mismos pasos que se han descrito. Agregue dos nuevos grupos sobre el grupo de datos regular, que significa un total de tres grupos de datos existentes en el informe, como se muestra en la Figura 25.

Figura 25. Utilice tres grupos de datos para mantener un conjunto de grupo de datos entero en una página.

Objetos Forma (Shape Objects)

Una vez que se han definido los grupos de datos, el siguiente paso es crear tres objetos forma (shape) utilizados como desencadenantes de la clase ReportListener. El primer objeto shape es colocado en la banda encabezado del segundo grupo de datos. El tercer objeto shape se coloca al inicio de la banda pie de página. Cree esta forma con una altura de .02 pulgadas y color (Forecolor) igual a rojo.

Durante el pase pre-proceso del informe, la posición del objeto forma en el encabezado de grupo y el objeto shape en el pie del grupo de dato se almacena en un cursor para cada conjunto de dato. El valor del objeto en el pie del grupo de datos solo necesita guardarse una vez, ya que se guarda en una propiedad.

Durante el Segundo pase del informe, el valor guardado en el cursor y la propiedad se referencian para determinar si existe suficiente espacio disponible en la página para imprimir cada nuevo conjunto de datos. Si no hay suficiente espacio, el objeto shape del encabezado de grupo es manipulado para que se apropie del resto de la página. Entonces, cuando el dato se comienza a imprimir, comienza automáticamente en la página nueva.

Ahora que se han definido los grupos de datos y que se ha agregado el objeto shape, el paso final es agregar las directivas para varios objetos de tal forma que la clase ReportListener sepa qué tiene que hacer.

Directivas

El paso final en la creación del informe es agregar las directivas al campo USER de cada objeto shape.

  • Objeto Shape en el encabezado de grupo de datos:
*:LISTENER||PAGINATION||START||1 
  • Objeto Shape en el pie de grupo de datos:
*:LISTENER||PAGINATION||END||1 
  • Objeto Shape en el pie de página:
*:LISTENER||PAGINATION||PAGEFOOTER 

Observe que los dos primeros shapes utilizan dos parámetros después del nombre de la directiva. El primer parámetro identifica qué tipo de registro de paginación es. El segundo parámetro identifica a qué conjunto (set) pertenecen estos registros para asignar este mismo valor a los registros START y END. Teóricamente, esto significa más de un set de directiva de paginación puede ser utilizada en un informe. La directiva PAGEFOOTER no necesita un segundo parámetro porque se aplica a todo el registro.

Clase ReportListener_Pagination

Creamos una nueva clase llamada MyReportListener_Pagination, y la basamos en la clase MyReportListener_Directives. Luego, agregamos algunas propiedades y algún código para varios métodos.

Propiedades

A esta clase se añaden dos propiedades nuevas:

  • lPreProcess (predeterminado .f.): Esta propiedad se utiliza como bandera para determinar si el informe se está ejecutando en el pase de pre-proceso o en el segundo pase real.
  • nPageFooterPos (predeterminado 0): Esta propiedad se utiliza para guardar la posición del pie de página, utilizado en los cálculos para determinar si existe suficiente área imprimible en la página.

Solamente son necesarias estas dos propiedades. Ahora se agregan algunos métodos.

Método BeforeReport()

Además de todo el código heredado desde la clase MyReportListener_Directive, esta clase necesita código adicional en más de un método, BeforeReport(). Es necesario para crear el cursor utilizado para guardar la posición de los objetos shape.

*-- MyReportListener_Pagination::BeforeReport()
DODEFAULT() 
IF NOT This.lPreProcess
  RETURN
ENDIF 
LOCAL lnSession, lcAlias 
*-- Se asegura de que esté la sesión de datos correcta
lcAlias = ALIAS()
lnSession = SET('DATASESSION')
SET DATASESSION TO This.CurrentDataSession 
*-- Generar el cursor temporal
CREATE CURSOR tmpPagination ;
  (nFRXRecNo I, nCounter I, cType C(1), nSet I, nPage I, nPos I)
INDEX ON BINTOC(nFRXRecNo) + BINTOC(nCounter) TAG FRXCounter
INDEX ON cType + BINTOC(nSet) + BINTOC(nCounter) TAG TypeSetCtr ADDITIVE 
*-- Restablece la sesión de datos
SET DATASESSION TO lnSession
SELECT (lcAlias) 

Clase directiva Pagination

Creamos una nueva clase llamada MyDirectives_Pagination, y la basamos en la clase MyDirectives. Luego, agregamos algunas propiedades y algún código para varios métodos.

Propiedades

A esta clase se añaden tres propiedades nuevas:

  • cPaginationType (predeterminado ""): Esta propiedad se utiliza para identificar qué tipo de registro de asignación es.
  • lAddHeight (predeterminado.f.): Esta propiedad determina si la altura del objeto shape se añade a la posición de inicio (starting position) cuando se guarda en el cursor.
  • nCounter (predeterminado 0): Esta propiedad es un contador que identifica únicamente cada conjunto (set) de datos que es impreso. Por ejemplo, cuando la banda de detalle se estira con las formas especiales Start y End shapes, esto cuenta cada banda de detalle. Cuando un grupo de datos será estirado, esto representa una cantidad de grupos de datos procesados.

Método AdditionalInit()

El método AdditionalInit() necesita código adicional para establecer algunas propiedades, pasadas en parámetros utilizados en la instrucción de la directiva (directive statement).

*-- MyDirectives_Pagination::AdditionalInit() 
*-- Establecer algunas propiedades
DO CASE
  CASE This.aParameters[1] == 'START'
    This.cPaginationType = 'S'
    THis.lAddHeight = .f.
  CASE This.aParameters[1] == 'END'
    This.cPaginationType = 'E'
    This.lAddHeight = .t. 
  CASE This.aParameters[1] == 'PAGEFOOTER'
    This.cPaginationType = 'F'
    This.lAddHeight = .t.
ENDCASE 
Método DoAdjustObjectSize()

El método DoAdjustObjectSize() necesita código para alterar el tamaño del objeto shape durante el pase real. Primero, el registro START lo busca por el cursor. Siguiente, el registro END lo busca por el cursor. Una vez que los ha encontrado, se el código calcula la cantidad de espacio necesaria establecer y compararlo con la cantidad de espacio restante en la página. Si no cabe, la altura de este objeto shape se ajusta para ser igual a la cantidad de espacio libre en la página.

*-- MyDirectives_Pagination::DoAdjustObjectSize()
LPARAMETERS tnFRXRecno, toObjProperties, toFRX 
LOCAL loObj, lnStartPage, lnStartPos, ;
  lnSet, lnEndPage, lnEndPos, lcCounter 
*-- Si no existe registro START, sale
*-- Si no es el pase previo, sale
*-- Si no está guardada la posición PageFooter, sale
IF NOT This.cPaginationType == 'S' OR ;
  This.oListener.lPreProcess OR ;
  This.oListener.nPageFooterPos = 0
  RETURN 
ENDIF 
*-- Establece el contador 
*-- NOTA: Hay que agregar 1 porque el método render es el que 
*-- lo establece y no ha sido llamado todavía.
lcCounter = BINTOC(This.nCounter + 1) 
*-- Busca la posición inicial
IF SEEK(BINTOC(tnFRXRecNo) + lcCounter, ;
  'tmpPagination', 'FRXCounter')
  lnStartPage = tmpPagination.nPage
  lnStartPos = tmpPagination.nPos
  lnSet = tmpPagination.nSet
ELSE
  *-- Sale si no la puede encontrar
  RETURN
ENDIF 
*-- Busca la posición final
IF SEEK('E' + BINTOC(lnSet) + lcCounter, ;
  'tmpPagination', 'TypeSetCtr')
  lnEndPage = tmpPagination.nPage
  lnEndPos = tmpPagination.nPos
ELSE
  *-- Sale si no la puede encontrar
  RETURN
ENDIF 
*-- Si el conjunto se expande por más de una página, 
*-- no hay que preocuparse más
IF NOT lnStartPage = lnEndPage
  RETURN
ENDIF 
*-- Si no cabe, se aumenta la altura 
*-- del objeto shape para llenar el resto de la página
IF toObjProperties.Top + lnEndPos ;
  - lnStartPos > ;
  This.oListener.nPageFooterPos
  toObjProperties.Height = ;
    (This.oListener.nPageFooterPos-;
    toObjProperties.Top)
  toObjProperties.Reload = .t.
ENDIF 
Método DoBeforeRender()

El método DoBeforeRender() necesita código para incrementar el contador, y luego guardar la posición del shape en el cursor temporal o la propiedad aplicable. El contador se incrementa en el pase previo y en el pase real. El cursor se modifica solamente durante el pase previo. El paso final en este método es establecer la propiedad lRender en .f. entonces, el objeto render no está generado realmente.

*-- MyDirectives_Pagination::DoBeforeRender()
LPARAMETERS tnFRXRecno, tnLeft, tnTop, ;
tnWidth, tnHeight, ;
tnObjectContinuationType, ;
tcContentsToBeRendered, ;
tGDIPlusImage 
IF This.cPaginationType == 'F'
  *-- PAGEFOOTER 
  *-- Si es el pase previo y la primera página, 
  *-- guarda la posición de este registro
  IF This.oListener.lPreProcess AND This.oListener.PageNo = 1
    This.oListener.nPageFooterPos = ;
      tnTop + IIF(This.lAddHeight, tnHeight, 0)
  ENDIF 
ELSE
  *-- START y END 
  *-- Incrementa el contador para este registro
  *-- o marca la posición como guardada
  This.nCounter = This.nCounter + 1 
  *-- Si es el pase previo, 
  *-- guarda la posición de este registro
  IF This.oListener.lPreProcess 
    INSERT INTO tmpPagination VALUES ( ;
      tnFRXRecNo, ;
      This.nCounter, ;
      This.cPaginationType, ;
      VAL(This.aParameters[2]), ;
      This.oListener.PageNo, ;
      tnTop + ;
      IIF(This.lAddHeight, tnHeight, 0)) 
  ENDIF 
ENDIF 
*-- No se moleste generando el objeto shape 
*-- porque es justamente un place holder
This.oListener.lRender = .f. 
Ejecutar el informe

Utilice el código siguiente para instanciar la clase ReportListener y visualizar el informe. Primero, instancia la clase ReportListener. Luego, establece algunas propiedades en el objeto ReportListener y ejecuta el pase previo del informe. Luego, cambia algunas de las propiedades y corre el pase real del informe.

SET REPORTBEHAVIOR 90 
SET CLASSLIB TO MyReportListeners
LOCAL ox AS ReportListener
ox = CREATEOBJECT('MyReportListener_Pagination') 
*-- Hace el pase previo para calcular las Alturas de grupo
ox.lPreProcess = .t.
ox.ListenerType = -1 
ox.QuietMode = .t.
ox.AddProperty('cGroupPageBreak', 'customer_id')
WAIT WINDOW 'Preprocessing report...' NOWAIT
REPORT FORM Orders_KeepGroupTogether OBJECT ox 
*-- Hace el pase real
ox.lPreProcess = .f.
ox.ListenerType = 1 && Preview
ox.QuietMode = .f.
ox.cGroupPageBreak = '""'
REPORT FORM Orders_KeepGroupTogether OBJECT ox 

Hasta ahora, ha hecho falta mucho código; pero ahora, que las clases están listas, es bastante sencillo reutilizarlas en informes futuros.

Listener personalizado: Salida TIFF

Crear un archivo TIFF es bastante sencillo utilizando un ReportListener. Esta sección muestra cómo crear un ReportListener que pueda sacar un informe en archivo TIFF de múltiples páginas, o archivos individuales para cada página.

Nota: El rendimiento de archivos TIFF de múltiples páginas puede ser peor en reportes largos, de 20 páginas o más. Puede desear experimentar con la salida de varios archivos TIFF y luego combinarlos con llamadas GDI+.

Clase ReportListener_TIFF

Cree una nueva clase llamada MyReportListener_TIFF, basada en la clase MyReportListener. Luego agregamos algunas propiedades y código en algunos métodos.

Propiedades

Es necesario dar valor a una propiedad y agregar otras dos.

  • ListenerType: Establecer su valor igual a 2.
  • cFileName: Crear esta propiedad para guardar el nombre del archivo de salida. Es opcional un valor predeterminado.
  • lCombinePages (predeterminado igual a .t.): Esta propiedad determina si un archivo TIFF de múltiples páginas se crea para todas las páginas o si se crea un archivo TIFF individual para cada página.

El siguiente paso es crear dos métodos.

Método OutputPage()

El método OutputPage() necesita ser sobre-escrito para sacar la página a un archivo TIFF. El código necesita decidir si saca un archivo individual TIFF para cada página. Además, en necesario eliminar el comportamiento predeterminado.

*-- MyReportListener_TIFF::OutputPage()
LPARAMETERS tnPageNo, teDevice, tnDeviceType, ;
  tnLeft, tnTop, tnWidth, tnHeight, ;
  tnClipLeft, tnClipTop, tnClipWidth, tnClipHeight 
#DEFINE OutputNothing -1
#DEFINE OutputTIFF 101
#DEFINE OutputTIFFAdditive (OutputTIFF+100) 
LOCAL lcFileName 
*-- Si es posible, genera una salida TIFF
IF tnDeviceType == OutputNothing 
  DO CASE
    CASE This.lCombinePages AND tnPageNo = 1
      *-- First page, combined file
      tnDeviceType = OutputTIFF
      lcFileName = This.cFileName 
    CASE This.lCombinePages
      *-- Subsequent pages, combined file
      tnDeviceType = OutputTIFFAdditive
      lcFileName = This.cFileName
    OTHERWISE
      *-- Archivos individuales
      tnDeviceType = OutputTIFF
      lcFileName = This.cFileName + TRANSFORM(tnPageNo, @L 9999')
  ENDCASE 
  *-- Salida de la página
  THIS.OutputPage(tnPageNo, lcFileName, tnDeviceType) 
  *-- Suprime el comportamiento preliminar
  NODEFAULT
ENDIF 
Método ShowFile()

Un método Nuevo, llamado ShowFile(), puede ser creado para mostrar el (los) archivo (s) TIFF generado(s). Puede ser llamado por el programa que genera el informe para mostrarlo al usuario. Este paso no requiere generar archivos TIFF y se incluye sólo para que se vea fácilmente los archivos TIFF creados por este ejemplo.

*-- MyReportListener_TIFF::ShowFile()
LPARAMETERS tnPageNo 
DECLARE INTEGER ShellExecute ;
  IN SHELL32.DLL ;
  INTEGER nWinHandle,;
  STRING cOperation,;
  STRING cFileName,;
  STRING cParameters,;
  STRING cDirectory,;
  INTEGER nShowWindow 
LOCAL lcFileName, lnPage
IF VARTYPE(tnPageNo) = 'N' AND tnPageNo > 0
  *-- Páginas individuales
  lcFileName = This.cFileName + TRANSFORM(tnPageNo, @L 9999') + '.TIF'
ELSE
  *-- Un archivo combinado 
  lcFileName = This.cFileName + '.TIF'
ENDIF 
ShellExecute(0,"Open", lcFileName, "", "", 1) 
CLEAR DLLS ShellExecute 
Ejecutar el informe

Una vez que ha sido creada esta clase sencilla, ejecutamos el informe con el siguiente código. Crea el objeto ReportListener , establece el nombre de los archivos, ejecuta el informe, y luego muestra la salida al usuario. El código siguiente muestra un ejemplo de la creación de un archivo TIFF con múltiples páginas y otro ejemplo de creación de archivos individuales para cada página.

*-- Crea la clase ReportListener 
SET CLASSLIB TO MyReportListeners
ox = CREATEOBJECT('MyReportListener_TIFF') 
*-- Configura propiedades
ox.cFileName = 'Sample' 
*-- Ejecuta el informe (páginas combinadas)
REPORT FORM accounts OBJECT ox RANGE 1,5 && limit to 5 pages 
*-- Ejecuta el informe (páginas individuales)
ox.lCombinePages = .f.
REPORT FORM accounts OBJECT ox RANGE 1,5 && limit to 5 pages 
*-- Muestra un fichero TIFF con múltiples páginas 
ox.ShowFile()
 *-- Muestra ficheros individuales
FOR ln = 1 TO 5
  ox.ShowFile(ln)
ENDFOR 

Listener personalizado: Formas X-Up

Tratar de imprimir formas 2-up, 3-up, o cualquier forma utilizando el generador de informes (Report Writer) era muy difícil antes de VFP 9.0. Sin embargo, utilizando formas, directivas, grupos de datos, y una clase ReportListener, puede hacerse con VFP 9.0.

Definición de Informe

Comenzamos por la creación de un informe Nuevo. La definición de informe mostrado en la Figura 26 es un ejemplo de forma orden x-up. Observe que la altura de las bandas encabezado y pie de página es cero. Esto es muy importante para que trabaje este ejemplo Además, el informe está definido como Página completa (Whole Page) en el cuadro de diálogo Configurar página (Page Setup).

Figura 26. Utilice shapes, directivas y grupos de datos para crear formas x-up.

Grupos de datos

El primer paso necesario para las formas x-up son tres grupos de datos. Para el primer grupo de datos, utilice la expresión aplicable, como Order_ID. El segundo grupo de datos se utiliza para forzar al informe que se mueva a la siguiente forma (form). La expresión para este dato debe ser una combinación de la propiedad especial fudge, más la expresión utilizada el el primer grupo de datos, como ox.cFudgeBreak + Order_ID. Finalmente, el tercer grupo de datos utiliza la expresión RECNO() para envolver la banda de detalle con un grupo de datos.

Objetos Shape

Una vez definidos los grupos de datos, el siguiente paso es crear tres objetos shape que son utilizados como desencadenantes en una clase ReportListener. El primer objeto se coloca en la parte superior de la banda encabezado del segundo grupo de datos y se define con una altura de .02 pulgadas. El segundo objeto forma se coloca en la parte superior de la banda encabezado del tercer grupo de datos y también tiene una altura de 0.2 pulgadas. El tercer objeto forma se coloca en la banda pie del segundo grupo de datos y su altura es igual a la de la banda. Cambie la propiedad Forecolor para cada objeto a rojo.

Directivas

El siguiente paso en la creación del informe es agregar las directivas al campo USER de cada objeto shape y al campo USER de algunas bandas.

  • Objeto Shape en la banda encabezado del segundo grupo de datos:
*:LISTENER||XUP||TOP 
  • Objeto Shape en la banda encabezado del tercer grupo de datos:
*:LISTENER||XUP||BEFOREDETAIL 
  • Objeto Shape en la banda pie del segundo grupo de datos:
*:LISTENER||XUP||BOTTOM 
  • Banda de detalles:
*:LISTENER||XUP||DETAILBAND 
  • Banda pie del Segundo grupo de datos:
*:LISTENER||XUP||GROUPFOOTERBAND 
Pie de grupo

El elemento impreso en la banda pie del Segundo grupo de datos está marcada como (Fixed relative to bottom of band). Esto permite entonces, mover la página según la altura de las objetos forma está ajustado para fudge la altura de la forma.

Otro punto que hay que hacer aquí es qué ocurre cuando una orden desborde otro form. Usualmente cuando una orden desborda una segunda o tercera forma, los totales solo necesitan ser impresos en la última página. Para trabajar con esta situación la clase ReportListener que será explicada establece una propiedad que indica cuando ocurre una situación continuada. Esta propiedad puede ser verificada con la expresión Imprimir cuando (Print When) de cualquier elemento de la banda pie de grupo que necesita imprimir condicionalmente. Por ejemplo, NOT ox.lContinued como expresión para Imprimir cuando (Print When) en los campos totalizados. Utilice lógica inversa, ox.lContinued, para objetos etiqueta que diga, continúa en la siguiente página… (continued on next page...). De esta forma, el final de la forma imprimirá continuará o imprimirá el total.

Clase ReportListener_Xup

Creamos una clase nueva llamada MyReportListener_Xup, basada en la clase MyReportListener_Directive. Esta clase necesita algunas propiedades; pero no necesita agregar ningún código a los métodos porque todo el trabajo se hace con clases directivas aplicadas.

Propiedades

Hay que agregar seis nuevas propiedades a la clase.

  • cFudgeBreak (predeterminado "=0"): Se utiliza para forzar una ruptura de forma (form break), siempre que una orden desborde una forma. Note el signo igual en el valor predeterminado para esta propiedad. Se requiere para forzar la propiedad a un carácter en lugar de un valor numérico.
  • lContinued (predeterminado.f.): Esta propiedad indica si esta orden ha sido desbordada a una nueva forma.
  • nCounter (predeterminado 0): Se utiliza para contra la cantidad de formas forms que están siendo procesadas.
  • nDetailBandHeight (predeterminado 0): Esta propiedad establece por programa la altura de la banda de detalle y se utiliza para determinar cuando una orden necesita desbordarse a una forma nueva.
  • nGroupFooterBandHeight (predeterminado 0): Establece por programa la altura de la banda pie del segundo grupo, y se utiliza para determinar cuando una orden necesita desbordar a una forma nueva.
  • nXup (predeterminado 2): Establece el número de forms en una página.

Clase directiva Xup

Creamos una nueva clase, llamada MyDirectives_XUp, basada en la clase directiva abstracta. No se necesitan propiedades nuevas para añadir o cambiar; pero se necesita cambiar el código en tres métodos.

Método AdditionalInit()

El método AdditionalInit() necesita tener código que tome y guarde la altura de los registros DETAILBAND y GROUPFOOTERBAND.

*-- MyDirectives_xup::AdditionalInit()
IF INLIST(This.aParameters[1], 'DETAILBAND', 'GROUPFOOTERBAND') 
  *-- Establecer la sesión de datos
  LOCAL lnSession
  lnSession = SET("Datasession")
  SET DATASESSION TO This.oListener.FRXDataSession 
  *-- Guardar la altura
  IF This.aParameters[1] == 'DETAILBAND'
    This.oListener.nDetailBandHeight = HEIGHT * 960 / 10000
  ELSE
    This.oListener.nGroupFooterBandHeight = HEIGHT * 960 / 10000
  ENDIF
  *-- Restaurar la sesión de datos
  SET DATASESSION TO lnSession 
ENDIF 
Método DoAdjustObjectSize()

The code in this class handles the three different shape objects added to the report. When the TOP record is processed, the form counter is incremented and the lContinued property is set to .f..

Cuando es procesado el registro BEFOREDETAIL, el código verifica se existe suficiente espacio para imprimir otra banda de detalle y la banda pie de grupo. Si no existe espacio suficiente, se cambia la propiedad cFudgeBreak y se establece en .t. la propiedad lContinued.

Cuando es procesado el registro BOTTOM, el código verifica cuánto espacio queda hasta el final de la forma. Entonces, cambia la altura del objeto forma para forzar el pie de grupo para que aparezca al final de la forma.

*-- MyDirectives_xup::DoAdjustObjectSize()
LPARAMETERS tnFRXRecno, toObjProperties, toFRX 
LOCAL lnNeed, lnFormHeight 
DO CASE
  CASE This.aParameters[1] == 'TOP'
    *-- Incrementar el contador
    This.oListener.nCounter = This.oListener.nCounter + 1 
    This.oListener.lContinued = .f.
  CASE This.aParameters[1] == 'BEFOREDETAIL'
    *-- Si no queda espacio para el siguiente detalle 
    *-- y el pie, fuerza un "form break"
    lnNeed = This.oListener.nDetailBandHeight + ;
      This.oListener.nGroupFooterBandHeight 
    lnFormHeight = ;
      (MOD(This.oListener.nCounter-1, ;
      This.oListener.nXup)+1) * ;
      (This.oListener.GetPageHeight() / ;
      This.oListener.nXup)
    IF toObjProperties.Top + lnNeed > lnFormHeight 
      *-- Fuerza un break para reimprimir el encabezado de grupo
      This.oListener.cFudgeBreak = ;
        IIF(This.oListener.cFudgeBreak = '0', '1', '0')
        This.oListener.lContinued = .t.
    ENDIF 
  CASE This.aParameters[1] == 'BOTTOM'
    *-- Completar el resto de la forma
    lnFormHeight = ;
      (MOD(This.oListener.nCounter-1, ;
      This.oListener.nXup)+1) * ;
      (This.oListener.GetPageHeight() / ;
      This.oListener.nXup)
    lnShape = toObjProperties.Top + ;
      toObjProperties.Height
    IF lnShape < lnFormHeight
      toObjProperties.Height = ;
        lnFormHeight-;
        toObjProperties.Top
      toObjProperties.Reload = .t.
    ENDIF 
ENDCASE 
Método DoBeforeRender()

El código en esta clase establece la propiedad lRender en .f. de tal forma que los objetos forma no se generen en realidad.

*-- MyDirectives_Xup::DoBeforeRender()
LPARAMETERS tnFRXRecno, tnLeft, tnTop, ;
  tnWidth, tnHeight, ;
  tnObjectContinuationType, ;
  tcContentsToBeRendered, ;
  tGDIPlusImage 
*-- No generar los objetos shape porque
*-- son solamente contenedores de espacio (place holders)
IF INLIST(This.aParameters[1], ;
  'TOP', 'BEFOREDETAIL', 'BOTTOM')
  This.oListener.lRender = .f.
ENDIF 
Ejecutar el informe

Utilice el siguiente código para ejecutar un informe. Primero, instancia la clase ReportListener. Luego, establece algunas propiedades del objeto ReportListener, incluyendo el número de formas por página. Finalmente, ejecuta el informe.

SET CLASSLIB TO MyReportListeners
ox = CREATEOBJECT('MyReportListener_Xup')
ox.ListenerType = 1
ox.nXUp = 3 
REPORT FORM Orders_2Up OBJECT ox RANGE 1, 10 

La clase Xup ReportListener que acabamos de crear es suficientemente genérica para controlar cualquier número de formas por página. Ahora Se pueden crear formas 2-up W2s, 3-up Order Forms, 4-up Receipts, y cualquier otra cosa que se le ocurra. La siguiente sección describe cómo crear gráficas de barras.

Listener personalizado: Gráfica de barras

Las gráficas de barra, como la mostrada en la Figura 27, pueden ser creadas utilizando una combinación de formas y directivas.

Figura 27. Utilice una combinación de formas y directivas para crear una gráfica de barras en VFP 9.0.

A diferencia de los ejemplos mostrados hace tiempo, mucho del trabajo de este ejemplo es hecho por el informe como tal. Y solo una pequeña cantidad del trabajo es realizada por el ReportListener y sus directivas.

Definición del informe

Comenzamos por la creación de un Nuevo informe y le agregamos una banda Resumen. Debido a que el informe es un Resumen no hace falta hacer nada en la banda detalle. Cambie la altura de la banda detalle a cero y así no ocupa ningún espacio en el informe, como se muestra en la Figura 28.

Figura 28. La definición final para una gráfica de barras tiene este aspecto.

Variables del informe

El siguiente paso es crear Variables de informe (Report Variables) para calcular los valores a imprimir por cada banda. Para este ejemplo, creamos doce variables de informe definidas de la siguiente forma:

  • rnMonth1: El Valor a guardar (Value to store) es IIF(MONTH(order_date)=1, 1, 0), el Cálculo (Calculation type) es Suma (SUM), y Reiniciar valor basado en (Reset value based on) es Fin del informe End of Report.
  • rnMonth2: El Valor a guardar (Value to store) es IIF(MONTH(order_date)=2, 1, 0), el Cálculo (Calculation type) es Suma (SUM), y Reiniciar valor basado en (Reset value based on) es Fin del informe End of Report.
  • rnMonth3: El Valor a guardar (Value to store) es IIF(MONTH(order_date)=3, 1, 0 el Cálculo (Calculation type) es Suma (SUM), y Reiniciar valor basado en (Reset value based on) es Fin del informe End of Report.
  • Así hasta rnMonth12.

Luego, se crea otra variable de informe, que calcula el total del valor el total máximo de todas las barras.

  • rnMaxValue: El Valor a guardar (Value to store) es como sigue. No son necesarios cálculos para poner en esta variable. Sin embargo, asegúrese que esa variable aparece al final de la lista de variables. Depende de las otras variables que deben ser procesadas antes de esta variable.
    (INT(MAX(rnMonth1, rnMonth2, rnMonth3, rnMonth4, rnMonth5, rnMonth6, rnMonth7, rnMonth8, rnMonth9, rnMonth10, rnMonth11, rnMonth12)+9)/10)*10
Objetos Shape

Comienza con la creación de doce rectángulos verticales para la barra. Deben tener el mismo tamaño, y cambiar el color según su necesidad. Agregue la directiva siguiente para cada barra, cambiando el nombre de la variable desde rnMonth1 al mes correspondiente.

*:LISTENER||VBAR||BAR||rnMonth1||rnMaxValue

Luego, agregue un rectángulo grande que rodee el gráfico, y seleccione Enviar al fondo (Send to Back) para garantizar que quede detrás de las barras.

Finalmente, agregue tres líneas horizontales pequeñas como tick marcas a la izquierda del gráfico. Agregue uno al inicio y al final del todo, y uno en el medio.

Objetos Field y Label

Agregar las etiquetas para cada mes al final del gráfico. Luego, agregue un objeto campo (field) junto a cada ticks a la derecha del gráfico. La expresión para cada una de estas es la siguiente:

  • Superior: Establecer la expresión a rnMaxValue.
  • Media: Establecer la expresión a rnMaxValue / 2.
  • Inferior: Establecer la expresión a 0.

Agregue un objeto etiqueta con una expresión Total Orders al lado izquierdo del gráfico. Observe que la posición de este en el ejemplo de la Figura 28. Aquí se ve vieja, pero siguiendo las directivas siguientes agregadas al campo USER, rota verticalmente cuando se imprime el informe.

*:LISTENER||ROTATETEXT||-90 

El último conjunto de objetos field son para imprimir los números al inicio de cada barra. Para hacer este trabajo correctamente, cada campo objeto debe tener la misma altura que su barra correspondiente, aunque puede tener ancho diferente, si es necesario. Coloque el objeto campo sobre el tope de la barra. Las expresiones para los objetos campo sonrnMonth1, rnMonth2, rnMonth3, etc. Puede agregarse formatear si es necesario. Finalmente, agregue la siguiente directiva al campo USER de cada objeto campo, cambiando la variable rnMonth1 por cada una.

*:LISTENER||VBAR|TEXT||rnMonth1||rnMaxValue 

No hace falta una clase ReportListener especial en este ejemplo, y utiliza la clase MyReportListener_Directives que ya se ha creado. Sin embargo, es necesaria una clase directiva.

Clase directiva VBar

Crea una nueva directiva de clase, llamada MyDirectives_VBar, basada en la directiva de clase abstracta. No se necesita modificar ni agregar propiedades; pero necesita agregar código en dos métodos.

Método DoBeforeRender()

El código de este método compara el valor de esta barra contra el valor máximo para determinar cuán alta debe ser la barra.

Las propiedades nAdjustTop y nAdjustHeight se ajustarán, en dependencia de si el elemento será procesado en la barra o en el texto. Para las barras, se ajustan la altura y el tope. Para los objetos, se ajustan la posición superior.

*-- MyDirectives_VBarText::DoBeforeRender()
LPARAMETERS tnFRXRecNo, tnLeft, tnTop, tnWidth, tnHeight, ;
  tnObjectContinuationType, tcContentsToBeRendered, ;
  tGDIPlusImage 
LOCAL lnPercent, lnAdjust 
*-- Calcular los ajustes
lnPercent = EVALUATE(This.aParameters[2]) / EVALUATE(This.aParameters[3])
lnAdjust = tnHeight * (1-lnPercent) 
*-- Aplicar los ajustes
IF This.aParameters[1] == 'BAR'
  *-- Barra
  This.oListener.nAdjustTop = lnAdjust
  This.oListener.nAdjustHeight = (lnAdjust * -1)
ELSE
  *-- Texto
  This.oListener.nAdjustTop = lnAdjust-150
ENDIF 
Método DoAfterRender()

Este método necesita codificar el reinicio de las propiedades nAdjustTop y nAdjustHeight de tal forma que todo lo demás se genere adecuadamente.

*-- MyDirectives_VBar::DoAfterRender()
LPARAMETERS tnFRXRecNo, tnLeft, tnTop, tnWidth, tnHeight, ;
  tnObjectContinuationType, tcContentsToBeRendered, ;
  tGDIPlusImage 
*-- Restaurar los ajustes
This.oListener.nAdjustTop = 0
This.oListener.nAdjustHeight = 0 
Ejecutar el informe

Utilice el siguiente código para ejecutar el informe. Simplemente instancia la clase ReportListener_BarGraph, establece el modo presentación preliminar y ejecuta el informe.

*-- Crear la clase ReportListener 
SET CLASSLIB TO MyReportListeners
ox = CREATEOBJECT('MyReportListener_Directives')
ox.ListenerType = 1 
*-- Ejecutar el informe
REPORT FORM TestBarChart OBJECT ox && TO PRINTER PROMPT 

Utilizar Contenedor preliminar

El contenedor preliminar (Preview Container) es la superficie utilizada para visualizar los informes en pantalla. Antes de VFP 9.0, no había control sobre esto. Sin embargo, en VFP 9.0, ha quedado expuesto a los desarrolladores como un objeto que puede ser manipulado.

ReportPreview.app

El Nuevo contenedor preliminar que sale con VFP 9.0, llamado ReportPreview.app, está bastante mejorado con relación al viejo contenedor. Además de permitir cambiarlo por programa, una de sus mayores ventajas es la opción de mostrar páginas múltiples. Es posible ver 1, 2, ó 4 páginas a la vez, lo cual es muy bueno cuando se realizan búsquedas a lo largo del informe. Para ver cómo trabaja, utilice el siguiente comando con un informe ya existente.

REPORT FORM MyReport OBJECT TYPE 1 

Otra mejora importante del contenedor preliminar es el hecho de que respeta la cláusula RANGE. En versiones anteriores de VFP, la cláusula RANGE era respetada sólo en las salidas por impresora, no es la presentación preliminar. Ahora, que estamos viendo el Nuevo contenedor preliminar, la siguiente sección trata de cómo cambiar la salida y cómo utilizar mejor las cadenas de informes para crear algunos informes.

Cambiar predeterminados

Al imprimir con la nueva salida asistida por objetos (object-assisted output), VFP mira la variable _REPORTPREVIEW para determinar qué contenedor preliminar cargar automáticamente. Se puede, sin embargo, cargar uno propio de la siguiente forma.

DO (_REPORTPREVIEW) WITH loPreviewContainer
loReportListener = CREATEOBJECT('ReportListener')
loReportListener.ListenerType = 1 && Preview
loReportListener.PreviewContainer = loPreviewContainer

Este proceder da la posibilidad de cambiar algunas de las propiedades predeterminadas. El ejemplo siguiente muestra como cambiar el encabezado, cambiar el nivel de zoom, anclar la barra de herramientas a la parte superior y formar la presentación preliminar para que comience maximizada.

LOCAL loPreviewContainer, loReportListener
*-- Crear el contenedor preliminar
DO (_REPORTPREVIEW) WITH loPreviewContainer 
*-- Cambiar algunos valores predeterminados
loPreviewContainer.Caption = 'My Special Report'
loPreviewContainer.ZoomLevel = 5 && 100%
loPreviewContainer.ToolbarIsVisible = .t. 
*-- Crear el Report Listener
loReportListener = CREATEOBJECT('ReportListener')
loReportListener.ListenerType = 1 && Preview 
*-- Asignar el contenedor preliminar y al listener
loReportListener.PreviewContainer = loPreviewContainer 
*-- Ejecutar el informe (con NOWAIT)
REPORT FORM MySpecialReport OBJECT loReportListener NOWAIT 
*-- Cambiar algunas propiedades del contenedor preliminar
loPreviewContainer.oForm.Toolbar.Dock(0) && Dock toolbar at the top
loPreviewContainer.oForm.WindowState = 2 && Maximize preview 

Encadenar informes

VFP 8.0 introdujo el concepto de encadenar informes con las cláusulas NOPAGEEJECT y NORESET. Desafortunadamente, esto solo trabajaba con la impresión y no con la presentación preliminar. En VFP 9.0, los informes pueden ser encadenados en el modo preliminar como muestra el siguiente código.

#define ListenerPreview 1
REPORT FORM Report1 OBJECT TYPE ListenerPreview NOPAGEEJECT 
REPORT FORM Report2 OBJECT TYPE ListenerPreview NORESET 

Ahora es posible encadenar informes que trabajen mucho mejor, puede ser muy creativo utilizando estas nuevas posibilidades. Por ejemplo, puede crear una Tabla de contenidos o un índice para ayudar a leer mejor el informe

Tabla de contenidos - Final

Crear una Tabla de contenidos al vuelo al escribir la información a un cursor en la medida que se va procesando el informe principal. Cuando se hace el informe principal, se encadena junto con otro informe que lee el cursor nuevo e imprime la Tabla de contenidos. La Figura 29 muestra un ejemplo de una Tabla de contenidos. Por supuesto, esto significa que la tabla de contenidos se imprime al final del informe y tiene que moverlo físicamente para que se establezcan las páginas al inicio del informe.

Figura 29. Utilice cadena de informes para crear una Tabla de contenidos.

Para crear una Tabla de contenidos al final de un informe siga los siguientes pasos:

  1. Crear un informe principal
    1. Diseñar el informe según las necesidades.
    2. Crear un grupo de datos basado en la información que debe aparecer en la Tabla de contenidos.
    3. Crear un cursor que guarde el número de página para la Tabla de contenidos. Al utilizar el Entorno de datos para abrir las tablas, y el código siguiente al método Init() del Entorno de datos. Al abrir la tabla en el código, crea un cursor en el código anterior para ejecutar el informe, como sigue a continuación.
    4. CREATE CURSOR tmpTOC (category_id C(6), cat_name C(25), pagenum I)INDEX ON CATEGORY_ID TAG CATEGORY
    5. En la expresión On Entry en el encabezado del grupo de datos, y un código para llamar a la UDF o método para insertar un registro en el cursor.
    6. Insert_TOC() 
  2. Crear la Tabla de contenidos del informe.
    1. Agregar código, descripción, y el número de página a la banda detalle.
    2. Agregar la guía de puntos al crear un objeto con una expresión de REPLICATE('. ', 50). El 50 debe ser ajustado para que quepa de ancho y fuente del informe. Asegúrese que el objeto guía de puntos está Enviado al fondo (Sent to Back), entonces, se imprime detrás del código y la descripción.
  3. Crear el programa para ejecutar los dos informes.
4. *-- Asegúrese de que el motor del Nuevo informe está activo
5. SET REPORTBEHAVIOR 90
6. 
7. *-- Preparación
8. USE IN SELECT('tmpTOC')
9. 
10. *-- Crear un ReportListener
11. ox = CREATEOBJECT('ReportListener')
12. ox.DynamicLineHeight = .f.
13. ox.ListenerType = 1 && Preview
14. 
15. *-- Imprimir el informe principal
16. REPORT FORM Category OBJECT ox NOPAGEEJECT 
17. 
18. *-- Preparar e imprimir la Tabla de contenidos
19. IF USED('tmpTOC')
20.   SELECT tmpTOC
21.   SET ORDER TO
22.   GOTO TOP
23.   REPORT FORM Category_TOC OBJECT ox 
24. ENDIF
25. 
26. *-- Cleanup
27. USE IN SELECT('tmpTOC')
28. 
29. ********************
30. PROCEDURE INSERT_TOC
31. ********************
32. *-- Agregar el registro TOC (Table of Contents)
33. IF NOT SEEK(products.category_id, 'tmpTOC', 'CATEGORY')
34.   INSERT INTO tmpTOC ;
35.     values (products.category_id, category.category_name, _PAGENO)
36. ENDIF
37. RETURN 

Hay algunos aspectos a tener en cuenta. Primero, el código asegura cerrar el cursor antes de ejecutar el informe. Esto garantiza que el cursor no está colgando de otro informe o programas previamente ejecutado. Entonces, verifica para asegurarse que el cursor existe antes de ejecutar la tabla de contenidos del informe. Esto asegura que la Tabla de contenidos no se ejecuta si el informe principal no corre adecuadamente.

Segundo, el procedimiento utilizado para agregar un registro Tabla de contenidos verifica si existe código particular. La razón para esto es evitar registros duplicados en la situación cuando el encabezado del grupo de datos se repite en páginas adicionales.

Tercero, observe que una clase ReportListener se utiliza para ejecutar estos informes. Esto no es necesario para encadenar estos informes. Sin embargo, debido a un cambio en la generación de objetos de VFP 90, en necesario hacer una guía de untos correctamente en la Tabla de contenidos. Haga la propiedad DynamicLineHeight igual a .f., de lo contrario, la línea de puntos no aparecerá hasta después del final del ancho definido de la descripción, sin importar el hecho de que la descripción sea recortada al imprimir.

Tabla de contenidos — Inicio

Si la ejecución de la Tabla de contenidos al final del informe principal no es aceptable, es posible utilizar un método doble pase para forzar la Tabla de contenidos al inicio. Aquí hay una pequeña trampa; pero se puede hacer. Primero, preprocesa el informe principal, luego, imprime la Tabla de contenidos, y finalmente imprime el informe principal.

Primero, preguntar al usuario por el tipo de impresora. La razón para esto es que reprocesando el informe utiliza la información de la impresora predeterminada como controlador de impresión. Si el usuario final cambia de impresora cuando se vaya a imprimir el informe es posible que los márgenes de impresión no sean iguales. Esto significa que el corte de páginas del informe preprocesado pueda ser diferente a las páginas del informe real, por tanto, el número de páginas estará mal en la Tabla de contenidos.

Para crear una tabla de contenidos al inicio del informe, se siguen los siguientes pasos:

1. Crea los informes: principal y tabla de contenidos como se 
   ha mostrado previamente
2. Crea el programa que ejecuta los dos informes nuevos, como sigue: 
3. *-- Se asegura de que el Nuevo motor de informe está en su lugar
4. SET REPORTBEHAVIOR 90
5. 
6. *-- Preparación
7. USE IN SELECT('tmpTOC')
8. PRIVATE plAddToTOC
9. 
10. *-- Pregunta por la impresora
11. IF SYS(1037) = '1'
12.   
13.   *-- Crea un ReportListener
14.   ox = CREATEOBJECT('ReportListener')
15.   ox.DynamicLineHeight = .f.
16.   ox.ListenerType = 1 && Preview
17.   
18.   *-- Imprimir el informe principal nowhere
19.   ox.ListenerType = -1
20.   plAddToTOC = .t.
21.   REPORT FORM Category OBJECT ox
22.   
23.   IF USED('tmpTOC')
24.     ox.ListenerType = 1
25.     
26.     *-- Preparar e imprimir el TOC
27.     SELECT tmpTOC
28.     SET ORDER TO
29.     GOTO TOP
30.     REPORT FORM Category_TOC OBJECT ox NOPAGEEJECT
31.     
32.     *-- Imprimir el informe principal
33.     plAddToTOC = .f.
34.     REPORT FORM Category OBJECT ox
35.     
36.   ENDIF
37.   
38.   *-- Cleanup
39.   USE IN SELECT('tmpTOC')
40.   
41. ENDIF
42. 
43. ********************
44. PROCEDURE INSERT_TOC
45. ********************
46. *-- Insertar el registro TOC 
47. IF plAddToTOC AND NOT SEEK(products.category_id, 'tmpTOC', 'CATEGORY')
48.   INSERT INTO tmpTOC ;
49.     values (products.category_id, category.category_name, _PAGENO)
50. ENDIF
51. RETURN 

Indice

  • Al crear un índice al final del informe es muy similar a crear una tabla de contenidos al final del informe. Una diferencia es que la llamada a Insert_TOC() necesita ser hecha con más frecuencia que en el encabezado de grupo. Por ejemplo, hay que llamarla una vez por cada registro detalle, además de una vez por cada encabezado de grupo.
  • Otra diferencia es que el cursor debe ser indexado en base a la descripción. Utilice este índice al imprimir índices de informes de tal forma quE los elementos aparezcan en orden alfabético, en lugar de en orden numérico.

Contenedor preliminar personalizado

VFP 9.0 incluye un contenedor preliminar (Preview Container), llamado ReportPreview.app. Sin embargo, si no se desea, no hay que utilizar el Nuevo contenedor. Es posible crear un contenedor propio y configurar la variable de sistema _REPORTPREVIEW con el nombre de la aplicación creada.

Escribir un contenedor personalizado no es objetivo en este artículo; sin embargo, el código fuente de ReportPreview.app ise incluye en el archivo xsource.zip file, que es localizado en la carpeta HOME() + 'tools\xsource' y puede ser utilizado como un ejemplo mientras escribe su propio Contenedor personalizado.

Conclusión

Este artículo cubre en realidad mucho material. La sección de informes con múltiples bandas de detalle muestra algunos ejemplos prácticos, como son porcentajes de impresión con datos. La sección del Diseñador de informe mostró todos los cuadros de diálogo nuevos y mostró el nuevo sentido del Diseñador de informes. La sección de Listeners estuvo llena de ejemplos, tales como: generar salidas XML, HTML y TIFF. Esta sección muestra como cambiando propiedades se pueden manipular informes, forzar paginación, y muchas otras ideas. La sección Contenedor preliminar introduce un poco la nueva área de presentación preliminar y explica cómo aprovecharse de las nuevas ventajas.

Viene de: El Generador de informes de VFP 9.0 en acción - Parte 1

3 comentarios :

  1. Buenas tardes, estaria necesitando un ejemplo de paginación, ya que segui los pasos tal cual hacen referencia al articulo , creando las clases y subclases, como asi el diseño del reporte y el proceso lo realiza correctamente en cuanto a que puedo observar los mensajes de preproceso y final como esta descripto pero cuando llego al informe no logro que la banda de detalle pase a la prxima hoja si no se puede contener en la hoja anterior. Si alguien me puede dar una mano se los voy a agradecer,
    Slds!

    ResponderBorrar
  2. Necesito alguien que me ayude con esto de mantener un grupo de detos en la misma hoja, caso contrario si es mayor , debe pasar a la siguiente hoja, desde ya muchas gracias

    ResponderBorrar

Los comentarios son moderados, por lo que pueden demorar varias horas para su publicación.