16 de septiembre de 2015

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

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


Clase ReportListener abstracta

Creamos una clase nueva llamada basada MyReportListener en la clase base de VFP 90 ReportListener. Esta clase va a contener algunos métodos que son necesarios en algunas clases ReportListener que se desarrollaran en el futuro. Por consiguiente, es mejor crear una clase abstracta tal que todas las clases ReportListener restantes sean subclases de ella. Será necesario agregar código en los métodos Init(), BeforeBand(), y crear un nuevo método GetFRXRecord().

Método Init()

En el método Init() de la clase abstracta ReportListener, aprovechamos dos clases que encontramos en la carpeta HOME() + 'FFC'. Estas clases se utilizarán posteriormente y ahorrarán mucho tiempo y código.

*-- MyReportListener::Init()
DODEFAULT()
*-- Crear un objeto gráfico para usar después
This.AddProperty('ogpGraphics', ;
NEWOBJECT('gpGraphics', HOME() + 'FFC\_GDIPlus')) 
*-- Crear un objeto FRXCursor para usar después
This.AddProperty('oFRXCursor', ;
NEWOBJECT('frxCursor', HOME() + 'FFC\_FRXCursor'))

En el método BeforeBand(), agregar código para establecer el controlador gráfico utilizado en la llamada al GDI+.

*-- MyReportListener::BeforeBand()
LPARAMETERS nBandObjCode, nFRXRecno 
DODEFAULT(nBandObjCode, nFRXRecNo) 
* Debido a que el controlador GDI+ cambia en cada página,
* es necesario configurar el controlador para nuestro objeto GPGraphics.
This.ogpGraphics.SetHandle(This.GDIPlusGraphics) 

Luego, crear un método nuevo.

Método GetFRXRecord()

Existen varias ocasiones donde es necesario referenciar el registro FRX del objeto a imprimir. Creamos un método nuevo, llamado GetFRXRecord(), que pase a la sesión de datos apropiada, ponga los datos del registro FRX en un objeto, restablezca la sesión de datos, y luego, devuelva el objeto que hace la llamada (the caller)

*-- MyReportListener::GetFRXRecord()
LPARAMETERS pnFRXRecNo
LOCAL lnSession, loFRX 
*-- Establecer la nueva sesión de datos - el FRX
lnSession = SET("Datasession")
SET DATASESSION TO This.FRXDataSession 
*-- Ir al registro
GOTO pnFRXRecNo 
*-- Tomar los datos
SCATTER MEMO NAME loFRX 
*-- Restablecer la sesión de datos
SET DATASESSION TO lnSession 
*-- Devolver el dato
RETURN loFRX 

Clase ReportListener_Directives

Creamos una clase nueva, llamada MyReportListener_Directives, basada en la clase abstracta MyReportListener. Luego, agregamos algunas propiedades nuevas y código a algunos de sus métodos.

Propiedades

Agregamos siete propiedades nuevas a la clase:

  • aFRXRecords[1]: Este arreglo unidimensional puede ser eventualmente redimensionado con la misma cantidad de números de filas que coinciden con el total de números de registro en la tabla FRX. Para cada registro con directivas, se crea una colección (collection) en el registro que guarda la referencia a cada clase directiva que necesita.
  • cDelimiter (predeterminado ||): Esta propiedad es utilizada para identificar el carácter delimitador utilizado en la directiva del campo de un objeto de informe. El delimitador puede cambiarse con facilidad, al agregar este dato a una propiedad en lugar de escribir código duro.
    Precaución: No utilice el delimitador que sea común a los comandos y expresiones de VFP. Por ejemplo, no utilice la coma.
  • lRender (predeterminado .t.): Esta propiedad puede ser utilizada, en caso de ser necesario, para suprimir la generación actual del objeto.
  • nAdjustHeight (predeterminado 0): Esta propiedad puede ser utilizada para manipular la altura de un objeto durante el método Render().
  • nAdjustLeft (predeterminado 0): Esta propiedad puede ser utilizada para manipular el extremo izquierdo de un objeto durante el método Render().
  • nAdjustTop (predeterminado 0): Esta propiedad puede ser utilizada para manipular el extremo superior durante el método Render().
  • nAdjustWidth (predeterminado 0): Esta propiedad puede ser utilizada para manipular el ancho de un objeto durante el método Render().

Ahora que están definidas las propiedades, creamos algunos métodos nuevos y agregamos código a algunos métodos existentes.


Método GetFRXDirectives()

Creamos un nuevo método, llamado GetFRXDirectives(). Este método recupera todas las directivas para un registro dado del FRX, analiza la información, crea un objeto collection, y luego instancia la clase directiva correspondiente para cada directiva. El nombre de la clase directiva de usuario para instanciar se deriva de la directiva en el FRX. A la colección se le agrega una referencia de objeto a la clase directiva, junto con cualquier parámetro adicional que siga al nombre de la directiva.

*-- MyReportListener_Directives::GetFRXDirectives() 
*-- Toma las directivas del objeto en el FRX 
LPARAMETERS tnFRXRecNo 
LOCAL loFRX, ;
  lnLines, ;
  laLines, ;
  ln, ;
  lcText, ;
  loObj, ;
  llSuccess, ;
  lnSession, ;
  lnWordCount, ;
  lnWord 
*-- Toma el registro FRX
loFRX = This.GetFRXRecord(tnFRXRecNo) 
*-- Establece la sesión de datos para que la directiva de objetos 
*-- sea creada en la misma sesión de datos que el informe
lnSession = SET("Datasession")
SET DATASESSION TO This.CurrentDataSession 
*-- Procesa el campo USER buscando las directivas
DIMENSION laLines[1]
lnLines = ALINES(laLines, loFRX.USER)
IF lnLines > 0
  FOR ln = 1 TO lnLines 
    lnWordCount = GETWORDCOUNT(laLines[ln], This.cDelimiter)
    IF lnWordCount >= 2 AND ;
      GETWORDNUM(laLines[ln], 1, This.cDelimiter) == '*:LISTENER' 
      *-- Agrega una colección (collection) al arreglo
      *-- en caso de que no esté
      IF VARTYPE(This.aFRXRecords[tnFRXRecNo]) <> 'O'
        This.aFRXRecords[tnFRXRecNo] = CREATEOBJECT('Collection')
      ENDIF
      *-- Procesa la directiva y trata de agregar el objeto
      lcDirective = GETWORDNUM(laLines[ln], 2, This.cDelimiter)
      llSuccess = .t.
      TRY
        This.aFRXRecords[tnFRXRecNo].Add( ;
          CREATEOBJECT('MyDirectives_' + lcDirective, This))
      CATCH
        llSuccess = .f.
      ENDTRY
      IF NOT llSuccess
        LOOP
      ENDIF 
      *-- Crea el objeto
      loObj = This.aFRXRecords[tnFRXRecNo] 
      *-- Analiza los parámetros
      IF lnWordCount >= 3 
        DIMENSION loObj[loObj.Count].aParameters[lnWordCount-2]
        FOR lnWord = 3 TO lnWordCount
          loObj[loObj.Count].aParameters[lnWord-2] = ;
            GETWORDNUM(laLines[ln], lnWord, This.cDelimiter)
        ENDFOR
      ENDIF 
      *-- Procesa cualquier código adicional en el INIT de la directiva
      loObj[loObj.Count].AdditionalInit() 
    ENDIF
  ENDFOR 
  *-- Si existe una colección (collection) se deshace de ella
  IF VARTYPE(This.aFRXRecords[tnFRXRecNo]) = 'O' AND ;
    This.aFRXRecords[tnFRXRecNo].Count = 0
    This.aFRXRecords[tnFRXRecNo] = .f.
  ENDIF 
ENDIF 
*-- Restablece la sesión de datos
SET DATASESSION TO lnSession 
Método BeforeReport()

Después de definir el método GetFRXDirectives(), agregamos el método BeforeReport() para calcular cuántos registros hay en el FRX, la dimensión de la nueva matriz de propiedades, y llamar al método GetFRXDirectives() para cada registro FRX.

*-- MyReportListener_Directives::BeforeReport() 
LOCAL lnSession
 *-- Conectar con el FRX
lnSession = SET('DATASESSION')
SET DATASESSION TO This.FRXDataSession 
*-- Crear una matriz
DIMENSION This.aFRXRecords[RECCOUNT()]
STORE .f. TO This.aFRXRecords 
*-- Busca las directivas
GOTO TOP
SCAN
  This.GetFRXDirectives(RECNO())
ENDSCAN 
*-- Restablece la sesión de datos
SET DATASESSION TO lnSession 
Método AdjustObjectSize()

El siguiente método que necesita código es el método AdjustObjectSize(). Varias directivas necesitan realizar acciones especiales en este método, por tanto, este método necesita un método correspondiente en la clase directiva de usuario para realizar el procesamiento especial.

*-- MyReportListener_Directives::AdjustObjectSize()
LPARAMETERS tnFRXRecno, toObjProperties 
LOCAL loObj, lcExec, loFRX 
*-- Si es aplicable, procesa la directiva
IF VARTYPE(This.aFRXRecords[tnFRXRecNo]) <> 'L' 
  *-- Toma el dato FRX
  loFRX = This.GetFRXRecord(tnFRXRecNo) 
  FOR EACH loObj IN This.aFRXRecords[tnFRXRecNo]
    loObj.DoAdjustObjectSize(tnFRXRecNo, toObjProperties, loFRX)
  ENDFOR
ENDIF 
Método EvaluateContents()

El siguiente método que necesita código es el método EvaluateContents().Varias directivas necesitan realizar acciones especiales en este método, por tanto, este método necesita un método correspondiente en la clase directiva de usuario para realizar el procesamiento especial.

*-- MyReportListener_Directives::EvaluateContents()
LPARAMETERS tnFRXRecno, toObjProperties 
LOCAL loObj, lcExec, loFRX 
*-- Si es aplicable, procesa la directiva
IF VARTYPE(This.aFRXRecords[tnFRXRecNo]) <> 'L' 
  *-- Toma el dato FRX
  loFRX = This.GetFRXRecord(tnFRXRecNo) 
  FOR EACH loObj IN This.aFRXRecords[tnFRXRecNo]
    loObj.DoEvaluateContents(tnFRXRecNo, toObjProperties, loFRX)
  ENDFOR
ENDIF 
Método Render()

Varias directivas necesitan realizar acciones especiales en el método Render(). Sin embargo no es tan fácil como en el método EvaluateContents(). La generación no puede hacerse por la clase directiva de usuario. Debe hacerse por este método. Por tanto, este método llama al método DoBeforeRender() en la clase directiva de usuario, luego genera el objeto si es necesario, entonces llama al método DoAfterRender() de la clase directiva de usuario. Es muy importante observar que en este método se utiliza NODEFAULT para que el objeto no se genere dos veces.

*-- MyReportListener_Directives::Render()
LPARAMETERS tnFRXRecno, tnLeft, tnTop, ;
  tnWidth, tnHeight, ;
  tnObjectContinuationType, ;
  tcContentsToBeRendered, tGDIPlusImage 
LOCAL loObj 
*-- Si es aplicable, procesa la directiva
IF VARTYPE(This.aFRXRecords[tnFRXRecNo])<>'L' 
  *-- Ejecuta el código de BeforeRender 
  FOR EACH loObj IN This.aFRXRecords[tnFRXRecNo]
    loObj.DoBeforeRender( ;
      tnFRXRecno, tnLeft, tnTop, ;
      tnWidth, tnHeight, ;
      tnObjectContinuationType, ;
      tcContentsToBeRendered, ;
      tGDIPlusImage)
  ENDFOR
  *-- Ahora genera el objeto
  IF This.lRender
    ReportListener::Render(tnFRXRecno, ;
      tnLeft + This.nAdjustLeft, ;
      tnTop + This.nAdjustTop, ;
      tnWidth + This.nAdjustWidth, ;
      tnHeight + This.nAdjustHeight, ;
      tnObjectContinuationType, ;
      tcContentsToBeRendered, ;
      tGDIPlusImage)
  ENDIF
  *-- Ejecuta el código de AfterRender
  FOR EACH loObj IN This.aFRXRecords[tnFRXRecNo]
    loObj.DoAfterRender( ;
      tnFRXRecno, tnLeft, tnTop, ;
      tnWidth, tnHeight, ;
      tnObjectContinuationType, ;
      tcContentsToBeRendered, ;
      tGDIPlusImage)
  ENDFOR 
  *-- Suprime el comportamiento normal
  NODEFAULT
ENDIF 

Clase directiva abstracta

Después de crear la clase MyReportListener_Directives, el siguiente paso es crear la clase directiva de usuario. Es entonces donde se ejecutan todos los procesos especiales. Creamos una clase nueva llamada MyDirectives, y la basamos en la clase base Custom de VFP. Luego, agregamos algunas propiedades y agregamos código en algunos métodos.

Propiedades

Es necesario añadir tres propiedades a la clase abstracta.

  • aParameters[1]: Esta matriz guarda los parámetros que hay que aplicar en la directiva. Por ejemplo, si el campo USER de un informe contiene *:LISTENER||ROTATETEXT||-90, esta propiedad contiene una fila con un valor de -90. Esta matriz podría contener filas y valores adicionales, si se hubiesen indicado otros parámetros.
  • nSaveGraphicsHandle (predeterminado 0): Contiene con conjunto de valores para llamadas especiales al GDI+. Toma el conjunto en el método DoBeforeRender()y lo utiliza en el método DoAfterRender() to reset things
  • oListener:Contiene una referencia de objetos hacia atrás al objeto ReportListener.
Método Init()

El método Init() obtiene una referencia pasada al objeto ReportListener. Este método necesita guardar esta referencia en la propiedad oListener para utilizarla posteriormente en otro método.

*-- MyDirectives::Init()
LPARAMETERS toListener 
*-- Rcordar el objeto Listener 
This.oListener = toListener 
Método ConvertFontStyleToCodes()

El método EvaluateContents() permite cambiar las propiedades de fuente. Sin embargo, referencia estilos de fuente con valores numéricos en lugar de una cadena de código de estilos de fuente. Para controlar esta conversión, creamos un método nuevo, llamado ConvertFontStyleToCodes().

*-- MyDirectives::ConvertFontStyleToCodes() 
*-- Converir FontStyle a partir de valores numéricos
*-- a código carácter
LPARAMETERS tnFontStyle 
LOCAL lcStyle
lcStyle = ''
IF BITTEST(tnFontStyle, 0)
  lcStyle = lcStyle + 'B'
ENDIF
IF BITTEST(tnFontStyle, 1)
  lcStyle = lcStyle + 'I'
ENDIF
IF BITTEST(tnFontStyle, 2)
  lcStyle = lcStyle + 'U'
ENDIF
IF BITTEST(tnFontStyle, 7)
  lcStyle = lcStyle + 'S'
ENDIF
IF EMPTY(lcStyle)
  lcStyle = 'N'
ENDIF 
RETURN lcStyle 
Método ConvertRGBToGDI()

Cuando trabajamos con GDI+, referencia colores de forma diferente a como lo hace VFP. Por tanto, para hacer la conversión, es necesario crear un método llamado ConvertRGBToGDI(), en esta clase abstracta.

*-- MyDirectives::ConvertRGBToGDI()
LPARAMETERS tnAlpha, tnRed, tnGreen, tnBlue 
RETURN (0x1000000 * tnAlpha) + ;
  (0x10000 * tnRed) + ;
  (0x100 * tnGreen) + ;
  tnBlue 
Método Empty

En la clase abstracta es necesario crear algunos métodos que serán llamados por la clase ReportListener. En el nivel abstracto no es necesario ningún código, solamente una instrucción para los parámetros en cuatro de ellos. Utilice la siguiente definición de parámetros para cada uno de ellos.

  • DoAdjustObjectSize()
LPARAMETERS tnFRXRecno, toObjProperties, toFRX 
  • DoAfterRender()
LPARAMETERS tnFRXRecNo, tnLeft, tnTop, ;
  tnWidth, tnHeight, ;
  tnObjectContinuationType, ;
  tcContentsToBeRendered, ;
  tGDIPlusImage 
  • DoBeforeRender()
LPARAMETERS tnFRXRecNo, tnLeft, tnTop, ;
  tnWidth, tnHeight, ;
  tnObjectContinuationType, ;
  tcContentsToBeRendered, ;
  tGDIPlusImage 
  • DoEvaluateContents()
LPARAMETERS tnFRXRecNo, toObjProperties, toFRX 
  • AdditionalInit()
*-- No son necesarios parámetros – este método permanecerá vacío

No todas las directivas de todos estos métodos; pero es necesario tenerlas definidas en el nivel abstracto, para que el ReportListener pueda hacer las llamadas, aunque no hagan nada. La clase directiva abstracta ya está creada y se pueden crear ahora las clases directivas de forma tal que sean subclases de la clase abstracta. Estas son las clases que realmente procesan una directiva dada.

Clase Directiva RedNegative

Para hacer que los números negativos aparezcan en rojo, se crea una clase nueva, llamada MyDirectives_RedNegative. Esta clase se basa en la clase directiva abstracta y se le agrega el código al método DoEvaluateContents().

DoEvaluateContents()

Este método comienza evaluando la expresión a imprimir. Entonces, cambia el color de la pluma a rojo en caso que el valor sea negativo. Entonces cambia la propiedad ReLoad para decirle a VFP que algo ha cambiado y VFP necesita recargar las propiedades antes de generar.

*-- MyDirectives_RedNegative::DoEvaluateContents() 
*-- Utiliza rojo si el valor es negativo,
*-- en caso contrario, utiliza negro
LPARAMETERS tnFRXRecNo, toObjProperties, toFRX 
LOCAL llNegative 
DODEFAULT(tnFRXRecNo, toObjProperties, toFRX) 
*-- ¿Es negativo?
TRY
  llNegative = (VAL(toObjProperties.Text) < 0)
CATCH
 llNegative = .f.
ENDTRY 
*-- Asignar el color
IF llNegative
  toObjProperties.PenRed = 255
  toObjProperties.PenGreen = 0
  toObjProperties.PenBlue = 0 
  toObjProperties.Reload = .T.
ENDIF 

Para utilizar esta directiva, escriba:

*:LISTENER||REDNEGATIVE 

en el campo USER de cualquier objeto al que se le pueda aplicar.

Clase Directiva SqueezeText

Para reducir el tamaño de un texto para hacer que quepa en el área definida, creamos una nueva clase directiva, llamada MyDirectives_SqueezeText. Esta clase se basa en la clase directiva abstracta y se le agrega el código al método DoEvaluateContents().

DoEvaluateContents()

Este método verifica si el texto a imprimir cabe en el área definida. Si no cabe, el tamaño de la fuente se reduce en uno y se verifica nuevamente. La verificación se mantiene hasta que encuentre una fuente que permita que quepa todo el texto en el área definida. Se respeta un mínimo de fuente de 4 para evitar que el texto impreso sea demasiado pequeño; pero puede ser sobrescrito con un parámetro en esta directiva.

*-- MyDirectives_SqueezeText::DoEvaluateContents() 
*-- Si no cabe el texto, asignamos un tamaño más pequeño 
LPARAMETERS tnFRXRecNo, toObjProperties, toFRX 
DODEFAULT(tnFRXRecNo, toObjProperties, toFRX) 
LOCAL lnSmallest, lnFontSize, lnWidth, ;
  lnMaxWidth, lcText, lnDecimals, ;
  lcStyle 
*-- ¿Cuál es la fuente más pequeña para la comprobación?
*-- (predeterminado 4)
lnSmallest = IIF(EMPTY(This.aParameters[1]), ;
  4, VAL(This.aParameters[1])) 
*-- Preparamos condiciones para el lazo
lcStyle = This.ConvertFontStyleToCodes( ;
toObjProperties.FontStyle)
lnMaxWidth = toFRX.Width && In FRUs
lnFontSize = toObjProperties.FontSize
lnDecimals = SET("Decimals")
SET DECIMALS TO 3 
*-- Agrega un carácter extra para asegurarse de que cabe.
*-- En caso contrario, pudiera quedar demasiado justo
*-- y no caber correctamente 
lcText = toObjProperties.Text + 'X' 
*-- Si es necesario, cambia la fuente
DO WHILE .t. 
  *-- Si es la fuente más pequeña, sale del lazo
  IF lnFontSize <= lnSmallest
    EXIT
  ENDIF 
  *-- Al utilizar lnFontSize 
  *-- ¿Cuán ancho será el texto(en FRUs)?
  lnWidth = This.oListener.oFRXCursor.GetFRUTextWidth(lcText, ;
    toObjProperties.FontName, lnFontSize, lcStyle ) 
  *-- Si el texto cabe con esta letra,
  *-- sale del lazo
  IF lnWidth <= lnMaxWidth
    EXIT
  ENDIF 
  *-- Reduce el tamaño de fuente
  lnFontSize = lnFontSize-1 
ENDDO 
*-- Si es necesario, cambia la fuente
IF toObjProperties.FontSize <> lnFontSize
  toObjProperties.FontSize = lnFontSize
  toObjProperties.Reload = .T.
ENDIF 
*-- Restablece los decimales
SET DECIMALS TO &lnDecimals

Para utilizar esta directiva, escriba

*:LISTENER||SQUEEZETEXT

en el campo USER de cualquier objeto al que se le pueda aplicar. El tamaño mínimo de la fuente ya está generado en la clase directiva. Para forzar un tamaño mínimo diferente, se pasa como parámetro a continuación de la directiva. Por ejemplo:

*:LISTENER||SQUEEZETEXT||6

fuerza un tamaño mínimo de fuente igual a 6.

Clase Directiva RotateText

Para rotar un texto, creamos una directiva nueva, llamada MyDirectives_RotateText. Esta clase se basa en la clase directiva abstracta y se le agrega el código a los métodos DoBeforeRender() y DoAfterRender().

Método DoBeforeRender()

El método DoBeforeRender() toma el valor de rotación desde un parámetro de la directiva. Entonces, realiza varias llamadas a las clases GDI+ para establecer los valores necesarios en el método Render() en la clase ReportListener.

*-- MyDirectives_RotateText::DoBeforeRender() 
*-- Rotar texto
LPARAMETERS tnFRXRecNo, tnLeft, tnTop, ;
  tnWidth, tnHeight, ;
  tnObjectContinuationType, ;
  tcContentsToBeRendered, ;
  tGDIPlusImage 
DODEFAULT(tnFRXRecNo, tnLeft, tnTop, ;
  tnWidth, tnHeight, ;
  tnObjectContinuationType, ;
  tcContentsToBeRendered, ;
  tGDIPlusImage) 
LOCAL lnX, ;
  lnY, ;
  lnRotate, ;
  lnHandle
*-- ¿Qué es la rotación?
lnRotate = VAL(This.aParameters[1]) 
*-- Rotar si es necesario
IF lnRotate <> 0 
  * Tomar la version adecuada de coordenadas
  lnX = tnLeft
  lnY = tnTop 
 * guarder el estado actual del controlador gráfico
  lnHandle = 0
  This.oListener.ogpGraphics.Save(@lnHandle)
  This.nSaveGraphicsHandle = lnHandle
  * Ahora, movemos el punto 0,0 hasta donde queremos de tal forma
  * que al rotar, lo hacemos alrededor del punto adecuado
  This.oListener.ogpGraphics.TranslateTransform(lnX, lnY, 0)
  * Ahora cambiamos el ángulo en e que ocurre el dibujo
  This.oListener.ogpGraphics.RotateTransform(lnRotate, 0) 
  * Restauramos el punto 0,0
  This.oListener.ogpGraphics.TranslateTransform(-lnX, -lnY, 0) 
ENDIF 
Método DoAfterRender()

Después de que el objeto es generado, hay que ejecutar el método DoAfterRender(). En este método se restablece la configuración GDI+ para que el objeto siguiente se genere correctamente. En caso contrario, todo el resto de objetos del informe quedan rotados de igual forma.

*-- MyDirectives_RotateText::DoAfterRender() 
*-- Rotar texto
LPARAMETERS tnFRXRecNo, tnLeft, tnTop, ;
  tnWidth, tnHeight, ;
  tnObjectContinuationType, ;
  tcContentsToBeRendered, ;
  tGDIPlusImage 
DODEFAULT(tnFRXRecNo, tnLeft, tnTop, ;
  tnWidth, tnHeight, ;
  tnObjectContinuationType, ;
  tcContentsToBeRendered, ;
  tGDIPlusImage) 
* Devolver el estado del controlador gráfico 
This.oListener.ogpGraphics.Restore(This.nSaveGraphicsHandle) 

Para utilizar esta directiva, escriba:

*:LISTENER||ROTATETEXT||nnn 

en el campo USER de cualquier objeto al que se le pueda aplicar, donde nnn representa los grados de rotación. Un número positivo rota en sentido de las manecillas del reloj, un número negativo rota en contra de ellas. Por ejemplo:

*:LISTENER||ROTATETEXT||-90 

rota el texto 90 grados, contrario a las manecillas del reloj.

Crear un informe

Para utilizar estas tres directivas, cree un informe Nuevo o modifique un informe existente.

Directiva RedNegative

Añada la siguiente directiva al campo USER de uno de los campos numéricos del informe:

*:LISTENER||REDNEGATIVE 
Directiva SqueezeText

Añada la siguiente directiva al campo USER, de un campo texto que tiene mucha información, como es un campo descripción. Asegúrese de que el campo es más corto que algunas de las descripciones más largas:

*:LISTENER||SQUEEZETEXT 
Directiva RotateText

Añada la siguiente directiva al campo USER de uno de los objetos que será rotado:

*:LISTENER||ROTATETEXT||-90 

Ejecutar el informe

Está definida La clase ReportListener, todas las clases directivas están definidas, así que lo único que resta por hacer es ejecutar el informe. Utilice el siguiente código para instanciar la clase ReportListener y mostrar previamente el informe.

SET CLASSLIB TO MyReportListeners
ox=NEWOBJECT('MyReportListener_Directives', 'MyReportListeners')
ox.ListenerType = 1 && Preview
REPORT FORM accounts OBJECT ox 

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

No hay comentarios. :

Publicar un comentario