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:
- Crear un informe principal
- Diseñar el informe según las necesidades.
- Crear un grupo de datos basado en la información que debe aparecer en la Tabla de contenidos.
- 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.
-
CREATE CURSOR tmpTOC (category_id C(6), cat_name C(25), pagenum I)INDEX ON CATEGORY_ID TAG CATEGORY
- 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.
-
Insert_TOC()
- Crear la Tabla de contenidos del informe.
- Agregar código, descripción, y el número de página a la banda detalle.
- 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.
- 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
gracias
ResponderBorrarBuenas 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,
ResponderBorrarSlds!
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