Autor: Doug Hennig (http://www.stonefield.com)
Traducido por: Ana María Bisbé York
(Sesión Extending the VFP 9 Reporting System, Part II: Run-Time presentada por el autor en la Conferencia DevEssentials Kansas, 2004)
ReportListener
La nueva clase base ReportListener es una de las claves para informes más flexibles y con soporte de nuevos tipos de salida, además de pantalla e impresora. Al ejecutar un informe, VFP expone eventos de informe a los ReportListeners tal y como ocurren. La clase base ReportListener tiene un comportamiento nativo; pero la emoción llega cuando se crean y utilizan subclases propias.
Veamos las propiedades, eventos y métodos de ReportListener para entender su potencialidad
Propiedades
Propiedad | Tipo | Descripción |
---|---|---|
AllowModalMessages | L | Si .T., permite mensajes modales mostrando el progreso del informe (.F. es predeterminado) |
CommandClauses | O | Objeto Empty con las propiedades que indican qué cláusulas del comando REPORT se utilizaron |
CurrentDataSession | N | ID de sesión de datos actual |
CurrentPass | N | Indica el paso actual por el informe. Un informe con _Pagetotal requerirá dos pases, los otros sólo requieren un pase, entonces CurrentPass siempre será igual 1 |
DynamicLineHeight | L | .T. (predeterminado) para utilizar espaciado de líneas, que varían de acuerdo a las características de la fuente, o .F. para utilizar el viejo estilo de espaciado. |
FRXDataSession | N | ID de la sesión de datos para el cursor FRX (una copia del archivo de informe que el motor de informe está ejecutando, abierto para un uso de ReportListener) |
GDIPlusGraphics | N | El objeto controlador de GDI+ utilizado para el dibujo. De solo lectura. |
ListenerType | N | Tipo de salida que produce el listener. El predeterminado es -1, que especifica no salida, de tal forma que tiene que cambiar este valor a uno más razonable. Vea la descripción del método OutputPage para una lista de valores. |
OutputPageCount | N | Número de páginas configuradas |
OutputType | N | Tipo de salida que especifica en la cláusula OBJECT TYPE del comando REPORT |
PageNo | N | Número actual de la página que se está dibujando |
PageTotal | N | Número total de páginas en el informe |
PreviewContainer | O | Una referencia a la superficie de muestra, en la cual el informe será dibujado para ser pre-visualizado |
PrintJobName | C | Nombre del trabajo de impresión como aparece en la ventana Cola de trabajos de impresión. |
QuietMode | L | .T. para suprimir la información del progreso. Predeterminado es .F. |
SendGDIPlusImage | N | 1 para enviar un controlador para una imagen para el campo General en el método Render (0 es predeterminado) |
TwoPassProcess | L | Indica si se utilizarán dos pases para el informe |
La propiedad CommandClauses contiene una referencia a un objeto Empty con propiedades que representan las cláusulas del comando REPORT más algunas otras golosinas.
Propiedad | Tipo | Descripción |
---|---|---|
ASCII | L | .T. si la palabra clave ASCII fue especificada al indicar salida a un archivo. |
DE_Name | C | Nombre del objeto DataEnvironment para el informe. O por el nombre especificado en la cláusula NAME o el nombre del informe si no se ha especificado. |
Environment | L | .T. si se especificó la palabra clave ENVIRONMENT |
File | C | Nombre del informe a ejecutar |
Heading | C | Encabezado especificado con la palabra clave HEADING |
IsDesignerLoaded | L | Indica si la persona que diseña el informe ha estado bebiendo. Ahora en serio, .T. si el informe se está ejecutando desde dentro del diseñador de informes |
InScreen | L | .T. si se especificó la palabra clave INSCREEN |
InWindow | C | Nombre de la ventana especificada con la palabra clave IN WINDOW |
IsReport | L | .T. si es un informe o .F. si es una etiqueta |
NoConsole | L | .T. se especificó la palabra clave NOCONSOLE |
NoDialog | L | .T. se especificó la palabra clave NODIALOG |
NoEject | L | .T. se especificó la palabra clave NOEJECT |
NoPageEject | L | .T. se especificó la palabra clave NOPAGEEJECT |
NoReset | L | .T. se especificó la palabra clave NORESET |
NoWait | L | .T. se especificó la palabra clave NOWAIT con la palabra clave PREVIEW |
Off | L | .T. se especificó la palabra clave OFF |
OutputTo | N | Tipo de salida especificada en la cláusula TO: 0 = no se especificó TO, 1 - printer, 2 - file |
PDSetup | L | .T. se especificó la palabra clave PDSETUP con el comando LABEL |
Plain | L | .T. se especificó la palabra clave PLAIN |
Preview | L | .T. se especificó la palabra clave PREVIEW |
Prompt | L | .T. se especificó la palabra clave PROMPT |
RangeFrom | N | Página de inicio especificada en la cláusula RANGE, o 1 si no se especifica |
RangeTo | N | Página final especificada en la cláusula RANGE, o 1 si no se especifica |
RecordTotal | N | Número total de registros en el cursor principal del informe |
Sample | L | .T. se especificó la palabra clave SAMPLE con el comando LABEL |
Summary | L | .T. se especificó la palabra clave SUMMARY |
ToFile | C | Nombre del archivo especificado en la cláusula TO FILE |
ToFileAdditive | L | .T. se especificó la palabra clave ADDITIVE en la salida a un archivo. |
Window | C | Nombre de la ventana especificada en la cláusula TO FILE |
Un comentario especial sobre el control de la sesión de datos. En realidad hay 3 sesiones de datos involucradas cuando se ejecuta un informe. La primera sesión de datos es en la que se instancia el ReportListener. Esta será la predeterminada.
La segunda es la sesión de datos, en la que se abre el cursor FRX. La propiedad FRXDataSession contiene el ID de la sesión de datos para este cursor, entonces, utilice SET DATASESSION TO This.FRXDataSession si necesita acceder al FRX.
La tercera es la sesión de datos, de los datos del informe. Si el informe tiene sesión privada de datos, tendrá una sesión de datos única, en caso contrario, será la sesión de datos predeterminada. La propiedad CurrentDataSession dice cuál sesión de datos utilizar, entonces, si el ReportListener necesita acceder a los datos del informe, necesita SET DATASESSION TO This.CurrentDataSession. Recuerde guardar la sesión de datos del ReportListener y regresar a esta sesión después de seleccionar o la sesión de datos del FRX o la sesión de datos del informe.
Eventos
Hay varios tipos de eventos, así que los vamos a ver de forma separada.
Eventos de informe
Los eventos de informe son aquellos que se disparan cuando algo afecta al informe como tal.
Evento | Parámetro | Descripción |
---|---|---|
LoadReport | Ninguno | Análogo al evento Load de un formulario en que es el primer evento que se dispara antes de que se carga el FRX y se abra la bandeja de la impresora, es un lugar donde puede cambiar el contenido del FRX en el disco o cambiar el entorno de impresora antes de que se ejecute el informe. |
UnloadReport | Ninguno | Como el evento UnLoad de un formulario, se dispara después que el informe se ha ejecutado. Se utiliza típicamente para las tareas de limpieza. |
BeforeReport | Ninguno | Se dispara después que se carga el FRX; pero antes de que se ejecute el informe |
AfterReport | Ninguno | Se dispara después que se ejecuta un informe |
OnPreviewClose | tlPrint | Se dispara cuando el usuario cierra la ventana preliminar o imprime un informe desde la presentación preliminar. |
Eventos de banda
Se disparan cuando se procesa una banda
Evento | Parámetro | Descripción |
---|---|---|
BeforeBand | tnObjCode,tnFRXRecno | Se dispara antes de que se procese una banda. Este primer parámetro representa el valor con el campo OBJCODE en el FRX para la banda especificada, y el segundo es el número de registro en el cursor FRX para el registro de banda. |
AfterBand | tnObjCode,tnFRXRecno | Se dispara después que se procesa una banda. Los mismos parámetros que BeforeBand |
Eventos de objetos
Estos eventos se activan cuando se procesa un objeto
EvaluateContents(tnFRXRecno, toObjProperties): Este evento se activa al inicio del procesamiento de la banda para cada objeto campo (pero no etiqueta), y da la oportunidad de cambiar la apariencia del campo. El primer parámetro es el número de registro FRX para el objeto campo que se está procesando y el segundo es un objeto que contiene propiedades sobre el objeto campo. Las propiedades que contiene este objeto se muestran en la siguiente tabla. Puede cambiar alguna de estas propiedades para cambiar la apariencia del campo en el informe. Si hace esto, establezca la propiedad Reload del objeto = .T. para notificar al motor de informe que desea cambiar una o más propiedades adicionales. Además, devuelva .T. si otros listeners pueden hacer otros cambios al campo. Veremos algunos ejemplos prácticos más adelante.
Propiedad | Tipo | Descripción |
---|---|---|
FillAlpha | N | Alfa, o transparente o color de relleno. Permite un control más fino que simplemente opaco o transparente. Los rangos de valores son desde 0 para transparente a 255 para opaco. |
FillBlue | N | Valor azul en un color RGB() para el color de relleno. |
FillGreen | N | Valor verde en un color RGB() para el color de relleno. |
FillRed | N | Valor rojo en un color RGB() para el color de relleno. |
FontName | C | Nombre de la fuente |
FontSize | N | Tamaño de la fuente |
FontStyle | N | Un valor que representa el estilo de la fuente. Son valores acumulativos 1- negrita, 2 – itálica, 4 - subrayada, 128 - tachada |
PenAlpha | N | El valor alfa del color del pincel |
PenBlue | N | Valor azul en un color RGB() para el color del pincel. |
PenGreen | N | Valor verde en un color RGB() para el color del pincel. |
PenRed | N | Valor rojo en un color RGB() para el color del pincel. |
Reload | L | Establezca a .T. para notificar al motor de informe que ha cambiado una o más propiedades |
Text | C | El texto de salida del objeto campo |
AdjustObjectSize(tnFRXRecno, toObjProperties): este evento se activa al inicio del procesamiento de la banda para cada objeto shape. Le brinda la posibilidad de cambiar un objeto, y es utilizado usualmente cuando desea sustituir una forma con un objeto custom y necesita dimensionar el objeto dinámicamente. El primer parámetro es el número de registro del RFX para el objeto shape que está siendo procesado y el segundo es un objeto que contiene las propiedades del objeto shape. Las propiedades que este objeto contiene se muestran en la siguiente tabla. Si cambia Height o Width, entonces establezca la propiedad Reload en .T. para notificar al motor de informes que ha cambiado esas propiedades.
Propiedad | Tipo | Descripción |
---|---|---|
Height | N | Altura del objeto, de 0 a 64000. Cambiar estos valores a un valor mayor (un valor menor se ignora) provoca que otros objetos flotantes en la banda sean movidos hacia abajo y la banda se expanda. |
Left | N | Posición a la izquierda del objeto. Solo-lectura, se proporciona solo para referencia |
Top | N | Posición superior del objeto. Solo-lectura, se proporciona solo para referencia |
Width | N | Altura del objeto, de 0 a 64000. Cambiar estos valores no altera el comportamiento del motor de informe; pero el nuevo valor se pasa al método Render para que el listener pueda hacer algo con el. |
Reload | L | Establezca a .T. para notificar al motor de informe que ha cambiado una o más propiedades |
Render(tnFRXRecno, tnLeft, tnTop, tnWidth, tnHeight, tnObjectContinuationType, tcContentsToBeRendered, tnGDIPlusImage): este método es muy grande, es llamado al menos una vez por cada objeto que se está procesando (puede ser llamado más de una vez por objetos que están en las bandas o páginas). Como el resto de eventos de objeto, su primer parámetro es el número del registro FRX para el objeto que se procesa. Los cuatro parámetros siguientes representan la posición y el tamaño del objeto. tnObjectContinuationType indica si un campo, forma o línea del objeto se expande en una banda o página, contiene 4 valores posibles:
Valor | Descripción |
---|---|
0 | Este objeto está completo, no continúa en la siguiente banda o página |
1 | El objeto ha comenzado; pero no finaliza en la página actual. |
2 | El objeto está en la mitad de su generación, ni comienza ni termina en la página actual. |
3 | El objeto ha terminado en la página actual. |
tcContentsToBeRendered contiene el texto o el campo o el nombre de archivo de la imagen. Para campos, el contenido se proporciona en Unicote, adecuadamente traducido a la configuración local utilizando la información FontCharSet del registro FRX.
Utilice STRCONV() para convertir la cadena si desea hacer algo con ella, por ejemplo almacenarla en una tabla. Se utiliza tnGDIPlusImage si una imagen viene de un campo General y la propiedad SendGDIPlusImage es .T., contiene el controlador gráfico de la imagen.
Puede colocar código en este método si desea re-generar un objeto de forma diferente a como sería hecho. Nota, sin embargo, antes de nada, necesitará llamar a funciones API GDI+, ya que esto es el alma de todo. Para mayor información sobre GLI+, ver http://msdn.microsoft.com/library/en-us/gdicpp/GDIPlus/GDIPlusReference/FlatGraphics.asp. Más adelante veremos un ejemplo de sobrescribir esta generación.
Métodos
Evento | Parámetro | Descripción |
---|---|---|
CancelReport | Ninguno | Permite al código Xbase terminar el informe más pronto. Es requerido para que el ReportListener pueda realizar la limpieza necesaria para prevenir roturas de configuración, cerrar la bandeja de impresión, etc. |
OutputPage | tnPageNo,teDevice,tnDeviceType,tnLeft,tnTop tnWidth,tnHeight,tnClipLeft,tnClipTop,tnClipWidth,tnClipHeight | Saca la página que se está generando por el dispositivo indicado. Los parámetros opcionales permiten al Listener especificar exactamente qué área del dispositivo debe ser utilizada para la generación cuando el tipo de dispositivo es un contenedor. Se detalla con más detenimiento más adelante. |
IncludePageInOutput | tnPage | No Indica si la página especificada se incluye en la salida o no. |
SupportsListenerType | tnType | Indica si el listener soporta el tipo de salida especificada |
GetPageWidth | Ninguno | Devuelve el ancho de página durante la ejecución de un informe |
GetPageHeight | Ninguno | Devuelve la altura de página durante la ejecución de un informe |
DoStatus | tcMessage | Proporciona un retorno no modal durante la ejecución de un informe |
UpdateStatus | Ninguno | Actualiza el retorno de interfaz de usuario |
ClearStatus | Ninguno | Elimina el retorno no modal de interfaz de usuario |
DoMessage | tcMessage,tiParams,tcTitle | Proporciona un retorno modal durante la ejecución de un informe si es AllowModalMessages .T. En caso contrario, llama a DoStatus |
El método OutputPage justifica mayor discusión. El parámetro tnDeviceType determina el tipo de salida que este método debe realizar, determina además qué tipo de parámetros se esperan para tcDevice. La siguiente tabla lista los tipos de salida soportados en la clase base ReportListener. Las subclases pueden soportar otros tipos de salida.
tnDeviceType | Descripción | teDevice |
---|---|---|
0 | Impresosa | Controlador de impresora |
1 | Dispositivo gráfico | Controlador gráfico GDI+ |
2 | Ventana preliminarXbase | Referencia a control de salida VFP |
101 | Archivo EMF | Nombre de archivo |
102 | Archivo TIFF | Nombre de archivo |
103 | Archivo JPEG | Nombre de archivo |
104 | Archivo GIF | Nombre de archivo |
105 | Archivo BMP | Nombre de archivo |
201 | Archivo TIFF Multi-página | Nombre de archivo (el archivo ya debe existir) |
Existen cuatro valores para la propiedad y cada uno afecta de forma diferente en cómo es llamado:
ListenerType | Descripción |
---|---|
0 | OutputPage es llamado por el motor de informes después que es generada cada página para salir a la impresora. Pasa 0 (impresora) a tnDeviceType y el controlador GDI+ para la impresora para teDevice. |
1 | OutputPage es llamado por la presentación preliminar (previewer) para mostrar la página especificada después que se completa toda la generación. |
2 | OutputPage es llamado por el motor de informe después que cada página es generada; pero no es enviada a la salida por impresora, pasa -1 para tnDeviceType y 0 para teDevice |
3 | OutputPage debe ser llamado manualmente para la página especificada después que se completa la generación. |
A propósito, debido a que los report listeners utilizan código Xbase, ahora es posible hacer traza del código durante la ejecución de informes, algo que no era posible antes y que causó mucha frustración entre los que utilizaban funciones definidas por el usuario (UDF) en los informes.
Registrar Listeners
Ahora que sabemos la apariencia que tienen los ReportListener, vamos a crear diferentes subclases y darles el comportamiento que necesitamos. Antes de hacer esto, veamos cómo hablarles de ellos a ReportOutput.APP.
Debido a que ReportBuilder.APP (para ver más detalles sobre ReportBuilder.APP, vea mi artículo Extender el sistema de informes en tiempo de diseño), ReportOutput.APP utiliza una tabla de registro para definir sobre qué listeners tiene conocimiento. Aunque esta tabla se genera dentro de ReportOutput.APP puede crear una copia de ella llamada OutputConfig.DBF utilizando DO (_ReportOutput) WITH -100 (este mecanismo pudiera cambiar en futuras versiones). Si ReportOutput.APP encuentra una tabla con este nombre en el directorio actual o la ruta VFP, será utilizado como una fuente para los listeners que busca cuando ejecuta un informe. He aquí la estructura de la tabla:
Nombre de campo | Tipo | Valores | Descripción |
---|---|---|---|
OBJTYPE | I | 100 para un registro listenes | Otro tipo de registro se utiliza también, vea la documentación de VFP para los detalles. |
OBJCODE | I | Cualquier valor válido de tipo de listener | Tipo de listener (por ejemplo 1 para presentación preliminar) |
OBJNAME | V(60) | Clase a instanciar | |
OBJVALUE | V(60) | Biblioteca de clase en la que se encuentra | |
OBJINFO | M | Aplicación que contiene la biblioteca de clases. | |
- | - | - | |
- | - | - |
Observe que ReportOutput.APP sólo busca el primer registro con OBJTYPE = 100 y OBJCODE establece el tipo de listener deseado. Entonces, va a necesitar eliminar o desregistrar (establecer OBJCODE a otro valor, algo así como sumarle 100) otros registros listeners del mismo tipo. InstallListener.PRG puede controlar esto por usted. Pase la clase listener, la biblioteca y el tipo y el se encarga de los detalles.
lparameters tcClass, ; tcLibrary, ; tnObjCode local lcClass, ; lcLibrary * Abre la tabla report listener (la crea si es necesario). if not used('OutputConfig') if not file('OutputConfig.DBF') do (_ReportOutput) with -100 endif not file('OutputConfig.DBF') use OutputConfig endif not used('OutputConfig') * Si ya está el listener especificado, se asegura de habilitarlo * dando a ObjCode un valor correcto. En otro caso, lo agrega. lcClass = upper(tcClass) lcLibrary = upper(tcLibrary) locate for ObjType = 100 and upper(ObjName) == lcClass and ; upper(ObjValue) == lcLibrary if found() replace ObjCode with tnObjCode else insert into OutputConfig ; (ObjType, ; ObjCode, ; ObjName, ; ObjValue) ; values ; (100, ; tnObjCode, ; tcClass, ; tcLibrary) endif found() * Deshabilita cualquier otro listener con el mismo ObjCode * dando un valor inusual a ObjCode replace ObjCode with ObjCode + 100 for ObjType = 100 and ; ObjCode = tnObjCode and not upper(ObjName) == lcClass * Limpia y cierra. use
Vea que no necesita registrar un listener para utilizarlo, simplemente instáncielo manualmente y pase la referencia a la cláusula OBJECTO del comando REPORT. Este mecanismo es un poco más trabajoso; pero le da el control al desarrollador, no requiere una copia externa de la tabla de registro ReportOutput.APP, y permite hacer cadenas de report listeners juntos, como veremos en breve.
SFReportListener
Debido a que nunca debemos utilizar las clases base de VFP, he creado una subclase de la clase ReportListener llamado SFReportListener, definido en SFReportListener.VCX. Proporciona unos pocos métodos utilitarios que requieren la mayoría de los listeners. Permite además encadenar listeners ya que posee la propiedad Successor que puede contener una referencia de objeto a otro listener y teniendo todas las llamadas a eventos el mismo método en el objeto sucesor, si este existe, usando un código similar a:
if vartype(This.Successor) = 'O' This.Successor.ThisMethodName() endif vartype(This.Successor) = 'O'
SelectFRX activa las sesiones de datos en la que está un cursor FRX, guarda el puntero de registro en una propiedad de usuario y opcionalmente lo posiciona el FRX si el número de registro fue especificado.
lparameters tnFRXRecno This.nDataSession = set('DATASESSION') set datasession to This.FRXDataSession This.nFRXRecno = recno() if pcount() = 1 go tnFRXRecno endif pcount() = 1
UnselectFRX restablece el puntero de registro FRX y restablece la sesión de datos anterior.
go This.nFRXRecno set datasession to This.nDataSession
GetReportObject utiliza estos dos métodos para devolver un objeto del registro especificado en el FRX. Esto facilita examinar la información sobre el objeto FRX.
lparameters tnFRXRecno local loObject This.SelectFRX(tnFRXRecno) scatter memo name loObject This.UnSelectFRX() return loObject
Debido a que eventos tales como Render y EvaluateContents se ejecutan solo una vez por cada registro del FRX; pero para cada objeto que será generado (esto es, se dispara tantas veces como registros hay en el FRX y la cantidad de registros en el dato que ha de ser procesado), puede querer minimizar la cantidad de trabajo hecho por estos métodos. Por ejemplo, si almacena una directiva en el campo memo User que dice al listener cómo procesar un objeto de informe, cualquier código que analiza el memo User será llamado muchas veces, incluso si realmente necesita sólo una vez. Entonces, he agregado una matriz propiedad de usuario llamada aRecords que puede contener cualquier información sobre los registros FRX que necesite. Por ejemplo, la primera vez que el objeto es generado, puede verificar si hay una entrada en aRecords para el objeto. Si no hay, hace lo que sea necesario (como parking User) y guardar alguna información en aRecords. De otro modo, simplemente recupera la información de aRecords.
Para soportar este concepto, el evento BeforeReport dimensiona aRecords a tantos registros como hay en el FRX, así no habrá que redimensionarlo mientras se ejecuta el informe.
with This * Dimensiona aRecords tantos registros como hay en FRX para no tener que * redimensionarlo cuando se ejecuta el informe. .SelectFRX() if alen(.aRecords, 2) > 0 dimension .aRecords[reccount(), alen(.aRecords, 2)] else dimension .aRecords[reccount()] endif alen(.aRecords, 2) > 0 .UnSelectFRX() * Controla la cadena de sucesión. if vartype(.Successor) = 'O' .Successor.CurrentDataSession =.CurrentDataSession .Successor.BeforeReport() endif vartype(.Successor) = 'O' endwith
Debido a que OutputPage es llamado con un número de página determinado, y un sucesor no tiene necesariamente conocer qué página es, este método guarda el número de página pasado a una propiedad de usuario nOutputPageNo, que otros listeners podrán utilizar. La clase SFReportListenerProgressMeter, por ejemplo, lo utiliza para mostrar qué página está siendo procesada en la barra de salida.
SFReportListenerDirective
SFReportListenerDirective es una subclase de SFReportListener. Su objetivo es soportar las directivas en el memo User que dicen al listener cómo procesar el objeto de informe. Un ejemplo de una directiva puede ser *:LISTENER ROTATE = -45, que dice al listener que rote este objeto 45 grados en contra de las manecillas del reloj. Debido a que User puede ser utilizado para varios propósitos, las directivas soportadas por SFReportListenerDirective deben comenzar con *:LISTENER (aquellos que utilizaron GENSCRNX en los días de FoxPro 2.x reconocerán este tipo de directiva).
Son controladas diferentes directivas por diferentes objetos. Si sólo cambian propiedades del objeto que se está generando, no tienen necesariamente que ser subclases de ReportListener (algunos de los ejemplos que veremos más tarde se basan en Custom). Debido a que puede utilizar múltiples directivas para un mismo objeto, SFReportListenerDirective mantiene una colección de controles de directivas y llama a la apropiada cuando es necesario.
El método INIT crea la colección de controladores de directivas y lo llena con los controladores más comúnmente utilizados. Controladores adicionales pueden ser agregados en una subclase o después de que esta clase sea instanciada agregando a la colección (observe que la palabra clave utilizada por la colección debe estar en mayúsculas).
with This .oDirectiveHandlers = createobject('Collection') loHandler = newobject('SFDynamicForeColorDirective', 'SFReportListener.vcx') .oDirectiveHandlers.Add(loHandler, 'FORECOLOR') loHandler = newobject('SFDynamicBackColorDirective', 'SFReportListener.vcx') .oDirectiveHandlers.Add(loHandler, 'BACKCOLOR') loHandler = newobject('SFDynamicStyleDirective', 'SFReportListener.vcx') .oDirectiveHandlers.Add(loHandler, 'STYLE') loHandler = newobject('SFDynamicAlphaDirective', 'SFReportListener.vcx') .oDirectiveHandlers.Add(loHandler, 'ALPHA') * Se redimensiona aRecords ala cantidad de columnas necesarias dimension .aRecords[1, 2] endwith
El método EvaluateContents verifica si hemos creado ya el objeto actual del informe para directivas, en ese caso, la segunda columna de la fila correspondiente en aRecords será .T. Si no, llamamos a UpdateListenerDirectives para ver si hay alguna directiva para el objeto de informe y actualizar aRecords adecuadamente. Entonces, se verifica la primera columna en aRecords que contiene una colección de directivas para este objeto de informe (porque podemos tener más de una directiva para un objeto dado). Si es así, cada elemento de la colección contiene el nombre de un controlador de directiva en la colección oDirectiveHandlers y el argumento de directiva (por ejemplo, si la directiva es *:LISTENER ROTATE = -45, este argumento será "-45"). Llamamos al método HandleDirective de cada controlador que se supone que hagamos, pasando sus propiedades en el objeto pasado y el argumento de directiva.
lparameters tnFRXRecno, ; toObjProperties local loDirective, ; loHandler with This * Por razones de rendimiento, queremos minimizar la cantidad de trabajo que hacemos en * este método, entonces chequeamos la colección de registros para ver si ya lo hemos * procesado. En caso contrario, llamamos a UpdateListenerDirectives para que * actualice la colección if not .aRecords[tnFRXRecno, 2] .UpdateListenerDirectives(tnFRXRecno) endif not .aRecords[tnFRXRecno, 2] * Si tenemos cualquier directiva de listener para este registro, llamamos a cada una. if vartype(.aRecords[tnFRXRecno, 1]) = 'O' for each loDirective in .aRecords[tnFRXRecno, 1] loHandler = .oDirectiveHandlers.Item(loDirective.DirectiveHandler) loHandler.HandleDirective(This, loDirective.Expression, ; toObjProperties) next loDirective endif vartype(.aRecords[tnFRXRecno, 1]) = 'O' endwith
UpdateListenerDirectives llama al método GetReportObject (definido en la clase padre) para encontrar el objeto de informe en el FRX y devuelve un objeto SCATTER NAME que contiene propiedades para cada campo en el registro FRX. Luego analiza el memo User del objeto informe, buscando directivas *:LISTENER
Se verifica la validez de cualquier directiva que se haya encontrado mirando si existe un controlador para ella en la colección oDirectiveHandlers y si es así, la directiva se agrega al objeto colección almacenado en la primera columna de la fila aRecords para el objeto de informe. La segunda columna de la fila aRecords es igual a .T. si alguna directiva ha sido encontrada o no para indicar que este objeto de informe ha sido procesado y no necesita ser procesado nuevamente.
lparameters tnFRXRecno local loFRX, ; laLines[1], ; lnLines, ; lnI, ; lcLine, ; lnPos, ; lcClause, ; lcExpr loHandler, ; loDirective with This * Establecemos un Flag indicando que hemos procesado este registro. .aRecords[tnFRXRecno, 2] = .T. * Toma el registro especificado de FRX y procesa cualquier línea en el memo User. loFRX = .GetReportObject(tnFRXRecno) lnLines = alines(laLines, loFRX.User) for lnI = 1 to lnLines lcLine = alltrim(laLines[lnI]) * Si encontramos una directiva de listener y es una directiva que soportamos, * la agrega, a la colección, junto a la expresión especificada * (crea la colección la primera vez que es necesario) if upper(left(lcLine, 10)) = '*:LISTENER' lcLine = substr(lcLine, 12) lnPos = at('=', lcLine) lcClause = alltrim(left(lcLine, lnPos - 1)) lcExpr = alltrim(substr(lcLine, lnPos + 1)) lnPos = ascan(.aDirectiveHandlers, lcClause, -1, -1, 1, 15) try loHandler = .oDirectiveHandlers.Item(upper(lcClause)) loDirective = createobject('Empty') addproperty(loDirective, 'DirectiveHandler', lcClause) addproperty(loDirective, 'Expression', lcExpr) * Crea una colección de todas las directivas que tiene este registro. if vartype(.aRecords[tnFRXRecno, 1]) <> 'O' .aRecords[tnFRXRecno, 1] = createobject('Collection') endif vartype(.aRecords[tnFRXRecno, 1]) <> 'O' .aRecords[tnFRXRecno, 1].Add(loDirective) catch endtry endif upper(left(lcLine, 10)) = '*:LISTENER' next lnI endwith
No hay comentarios. :
Publicar un comentario
Los comentarios son moderados, por lo que pueden demorar varias horas para su publicación.