28 de noviembre de 2017

Algunos consejos para el uso del depurador

Artículo original: Debugging Tip
http://rickschummer.com/blog/2006/05/debugging-tip.html
Autor: Rick Schummer
Traducido por: Ana María Bisbé York


En la Conferencia GLGDW hubo una sesión en panel sobre las mejores prácticas y consejos para depurar. Yo fui uno de los que debió participar; pero Doug Hennig necesitó mi PC para su presentación sobre desarrollo de aplicaciones verticales. Entonces, he pensado mostrar mis mejores prácticas por aquí, aunque creo que Dan Freeman las mencionó brevemente durante la sesión.

Un desarrollador Visual FoxPro puede realizar un gran trabajo de configuración del depurador al configurar la ventana Examinar, colocando exactamente los puntos de interrupción que necesita para una aplicación o módulo y seleccionando ciertos eventos para que sean corridos con traza. La configuración puede cambiar en dependencia de la aplicación o un módulo específico en una aplicación. Podemos eliminar expresiones desde la ventana Examinar y colocar en ella nuevas ya que estamos verificando varios módulos, podemos activar y desactivar puntos de ruptura, y podemos mover los eventos que estamos verificando hacia y desde la lista. Otra vía en la que podemos guardar la configuración exacta del módulo y llamarlo después la configuración sin necesidad de reconfigurar las expresiones o puntos de ruptura.

Esto se puede lograr sólo desde el marco de depuración (Debug frame) Utilizando el menú, puede seleccionar Archivo - Guardar Configuración ... para crear un archivo. Este archivo guarda un diálogo de forma predeterminada en la carpeta raíz de VFP. Para recuperar una configuración anterior Menú - Archivo - Cargar Configuración ... El contenido del archivo se guarda en texto ASCII. He aquí un ejemplo:

DBGCFGVERSION=4
WATCH=_screen
WATCH=set("deleted")
WATCH=set("path")
WATCH=thisform
WATCH=curdir()
WATCH=recno()
WATCH=eof()
WATCH=_vfp.ActiveProject
BPMESSAGE=OFF
BREAKPOINT BEGIN
TYPE=2
CLASS= 
LINE=0
EXPR=EOF("curReport")
DISABLED=0
EXACT=0
BREAKPOINT END

BREAKPOINT BEGIN
TYPE=3
CLASS= 
LINE=0
EXPR="MAIN"$PROGRAM()
DISABLED=1
EXACT=0
BREAKPOINT END

EVENTWINDOW=ON
EVENTFILE=
EVENTLIST BEGIN
Activate, Deactivate
EVENTLIST END

Puede manipular el contenido de forma segura con un editor de texto y recargar la configuración. Haga copias de este archivo si le preocupa perder esta configuración.

Lo que es realmente bueno es que estos archivos se pueden crear por programación. Vea que las expresiones de la ventana Examinar, son sencillamente líneas y pueden estar en cualquier lugar del archivo. Por tanto se pueden agregar sencillamente con esta sencilla línea de código:

STRTOFILE("WATCH=ALIAS()", "MyDebugSettings.DBG", 1)

Puede además organizar las expresiones en el archivo. Algo que me molesta de la ventana Examinar es que cada nueva expresión se agrega al final de la lista. Puede agregarlas al inicio utilizando el siguiente código:

lcFileContents = FILETOSTR("MyDebugSettings.DBG")
STRTOFILE("WATCH=ALIAS()" + CHR(13) + lcFileContents, "MyDebugSettings.DBG", 1)

Una vez que la agregue, tiene que cargar nuevamente el archivo de configuración. Una desventaja es que pierde las posibilidades que ofrece IntelliSense para la ventana Examinar.

Cada vez que comento a alguien sobre esta posibilidad, siempre hay quien dice que nunca escuchó de que existía.


23 de noviembre de 2017

Importar desde Excel a un Cursor actualizable

La rutina abre un libro de Excel y lo pasa a un cursor tomando la primera fila como los encabezados de las columnas.

LOCAL lcXLSBook AS STRING

lcXLSBook = GETFILE('xls, xlsx', 'Archivo:', 'Aceptar', 0, 'Seleccione una hoja de cálculo')
IF EMPTY(lcXLSBook)
  RETURN .F.
ENDIF

ExcelToCursor(m.lcXLSBook, "xlsResult")

SELECT xlsResult
BROWSE NOWAIT

*!------------------------------------------------------------------------------
*! Procedure : ExcelToCursor
*! Parametros: pcSrcFile -> Nombre del libro de excel
*!             pcCursorName -> Nombre del cursor
*!------------------------------------------------------------------------------
PROCEDURE ExcelToCursor(pcSrcFile AS STRING, pcCursorName AS STRING)
  IF PCOUNT() = 0
    RETURN .F.
  ELSE
    IF VARTYPE("pcSrcFile")#"C"
      RETURN .F.
    ENDIF

    IF !FILE(pcSrcFile)
      MESSAGEBOX("Archivo no encontrado", 16)
      RETURN .F.
    ENDIF

    IF VARTYPE("pcCursorName")#"C"
      RETURN .F.
    ENDIF
  ENDIF

  *** Instanciar MS Excel
  LOCAL oExcel AS Excel.APPLICATION
  m.oExcel = CREATEOBJECT("Excel.application")

  IF VARTYPE(oExcel,.T.)!='O'
    MESSAGEBOX("No se puede procesar el archivo." ;
      + CHR(13) + "Microsoft Excel no está instalado en su ordenador.", 16)
    m.oExcel = NULL
    RELEASE oExcel
    RETURN .F.
  ENDIF

  *** Abrir archivo de Excel
  m.oExcel.Workbooks.OPEN(pcSrcFile)
  m.oExcel.Worksheets(1).ACTIVATE
  m.oExcel.DisplayAlerts = .F.

  LOCAL oSheet AS OBJECT
  m.oSheet = m.oExcel.ActiveSheet

  LOCAL aExcel(1), laStructure(1)
  LOCAL lnCol, lnRow, lnSize, lcCol, lcRow, lcValue, lcCmd

  *** Redimensionar aExcel de acuerdo a las filas y columnas que
  *** contiene el libro de excel abierto
  IF EVALUATE("ALEN(aExcel)") # m.oSheet.UsedRange.COLUMNS.COUNT
    DIMENSION aExcel [1, m.oSheet.UsedRange.Columns.Count]
  ENDIF

  m.lnCol = m.oSheet.UsedRange.COLUMNS.COUNT
  m.lnRow = m.oSheet.UsedRange.ROWS.COUNT

  *** Pasar los valores del libro de excel
  *** a la matriz redimensionada aExcel
  TEXT TO lcCmd TEXTMERGE NOSHOW PRETEXT 1+2
  aExcel = m.oExcel.ActiveWorkbook.ActiveSheet.Range(m.oSheet.Cells(1,1), m.oSheet.Cells(<<m.lnRow>>,<<m.lnCol>>)).value
  ENDTEXT
  &lcCmd

  *** Cerrar la instancia MS Excel
  m.oExcel.QUIT()
  m.oExcel = NULL
  RELEASE oExcel, oSheet

  *** Procedimiento para determinar los tipo de datos por
  *** columnas y crear la estructura del cursor
  m.lnRow = ALEN(aExcel,1)
  m.lnCol = IIF(ALEN(aExcel,2)>0, ALEN(aExcel,2), 1)

  *** La matriz laStructure bidimensional
  *** almacena la estructura del cursor
  *** Columna 1 -> Nombre de la columna
  *** Columna 2 -> Tipo de datos
  *** Columna 3 -> Largo
  *** Columna 4 -> Decimal
  *** Columna 5 -> Acepta valores null
  DIMENSION laStructure(m.lnCol,5)

  FOR i = 1 TO m.lnCol
    m.lnSize = 1
    m.lcCol = LTRIM(STR(i))
    laStructure(i,1) = aExcel(1,i)
    laStructure(i,2) = VARTYPE(aExcel(2,i))
    DO CASE
      CASE laStructure(i,2) = "C" && Character, Memo, Varchar, Varchar (Binary)
        FOR j = 1 TO m.lnRow
          m.lcValue = IIF(m.lnCol = 1, TRANSFORM(aExcel(j)), TRANSFORM(aExcel(j,i)))
          m.lnSize = MAX(m.lnSize, LEN(TRANSFORM(aExcel(j,i))))
          IF AT(CHR(13), m.lcValue) > 0
            laStructure(i,2) = "M" && Memo
          ENDIF
        ENDFOR

        IF laStructure(i,2) = "C"  && Character, Varchar
          IF lnSize < 10
            laStructure(i,3) = 10
          ELSE
            laStructure(i,3) = lnSize
          ENDIF
          laStructure(i,4) = 0
        ELSE            && Memo, Blob
          laStructure(i,3) = 4
          laStructure(i,4) = 0
        ENDIF

      CASE laStructure(i,2) = "D" OR laStructure(i,2) = "T" && Date, DateTime
        laStructure(i,3) = 8
        laStructure(i,4) = 0

      CASE laStructure(i,2) = "L" && Logical
        laStructure(i,3) = 1
        laStructure(i,4) = 0

      CASE laStructure(i,2) = "N" && Numeric, Float, Double, o Integer
        laStructure(i,3) = 12
        laStructure(i,4) = 2

      OTHERWISE
    ENDCASE
    laStructure(i,5) = .T.
  ENDFOR

  *** Crear el cursor
  CREATE CURSOR &pcCursorName FROM ARRAY laStructure

  *** Insertar en el cursor los valores desde aExcel
  LOCAL lCellValue
  m.lcRow = ""

  FOR i = 1 TO m.lnRow
    FOR j = 1 TO m.lnCol
      IF !EMPTY(m.lcRow)
        m.lcRow = m.lcRow + ", "
      ENDIF

      lCellValue = EVALUATE([aExcel(i,j)])
      DO CASE
        CASE VARTYPE(lCellValue) = "C" && Character, Memo, Varchar, Varchar (Binary)
          IF !EMPTY(lCellValue) OR lCellValue # ""
            m.lcRow = m.lcRow + ['] + EVALUATE([aExcel(i,j)]) + [']
          ELSE
            m.lcRow = m.lcRow + [Null]
          ENDIF

        CASE VARTYPE(lCellValue) = "D" OR VARTYPE(lCellValue) = "T" && Date, DateTime
          m.lcRow = m.lcRow + [{] + EVALUATE([aExcel(i,j)]) + [}]

        CASE VARTYPE(lCellValue) = "N" && Numeric, Float, Double, o Integer
          m.lcRow = m.lcRow + ALLTRIM(STR(EVALUATE([aExcel(i,j)])))

        OTHERWISE
          m.lcRow = m.lcRow + EVALUATE([aExcel(i,j)])
      ENDCASE
    ENDFOR

    IF i > 1
      TEXT TO cSQL TEXTMERGE NOSHOW PRETEXT 1+2
    Insert Into <<pcCursorName>> Values (<<lcRow>>)
      ENDTEXT
      EXECSCRIPT(cSQL)
    ENDIF

    m.lcRow = ""
  ENDFOR

  *** Liberar variables
  RELEASE pcSrcFile, laStructure, lnSize, lcValue, lcCmd
  RELEASE lCellValue, aExcel, lnCol, lnRow, lcCol, lcRow, cSQL, i, j

  SELECT &pcCursorName
  GO TOP

  *** Retornar el cursor
  RETURN SETRESULTSET(pcCursorName)
ENDPROC

Hector Urrutia

18 de noviembre de 2017

Obtener la ruta (path) en formato largo

En alguna ocasión puede que obtengamos un nombre de archivo en formato corto, por ejemplo "c:\Docum~1\Usuario\Misdoc~1\" y convertirlo a "c:\Documents and Settings\Usuario\Mis documentos\". A continuación una función API para llegar a ello.

DECLARE integer GetLongPathName IN WIN32API ;
 string @ lpszShortPath, string @ lpszLongPath, integer cchBuffer

#define MAXPATH 267
STORE SPACE(MAXPATH) TO lpszLongPath
lcPath = "C:\DOCUME~1\epalma\Misdoc~1\"
lnLen = GetLongPathName(m.lcPath,@lpszLongPath,MAXPATH)
if lnLen > 0
 ? SUBSTR(lpszLongPath,1,lnLen)
else
 ? Ruta Inválida'
endif

Çetin Basöz
MS Foxpro MVP, MCP

12 de noviembre de 2017

Calcular el dígito de verificación para Rapipago y Pagofacil (Argentina)

En Argentina existen al menos dos redes de cobranzas extrabancarias (Rapipago y Pagofacil) que permiten el pago de facturas, servicios, etc. a través de formularios con códigos de barras que se emiten con una estructura particular para cada caso.

En primer lugar, a la cadena a codificar se le deben agregan 1 ó 2 dígitos de verificación al final. El algoritmo (en código VFP) para la generación de estos dígitos es el siguiente:

CLEAR 

lc = "1234567890123456789012345678901234567890"
? "Sin dígito de verificación...: " + lc
? "Con 1 dígito de verificación.: " + GenerarDigitoVerificador(lc, 1)
? "Con 2 dígitos de verificación: " + GenerarDigitoVerificador(lc, 2)

FUNCTION GenerarDigitoVerificador(tcCadena, tn)
  *---
  * Parámetros
  *   tcCadena = Cadena a generar el/los dígito/s de verificación
  *   tn = Cantidad de dígito/s de verificación (1 ó 2)
  * Retorno:
  *   Caracter: La cadena mas el/los dígito/s de verificación
  *---

  IF EMPTY(tn) OR NOT INLIST(tn, 1, 2)
    tn = 1
  ENDIF

  tcCadena = ALLTRIM(tcCadena)

  LOCAL lnLen, lnIni, lnSum, lcSeq, lcRet
  lnLen = LEN(tcCadena)
  lcSeq = "1" + REPLICATE("3579", CEILING(lnLen/4))
  lnSum = 0
  FOR lnIni = 1 TO lnLen
    lnSum = lnSum + VAL(SUBSTR(tcCadena, lnIni, 1)) * VAL(SUBSTR(lcSeq, lnIni, 1))
  ENDFOR
  lcRet = tcCadena + TRANSFORM(MOD(INT(lnSum / 2), 10))
  IF tn = 2
    tcCadena = ALLTRIM(lcRet)
    lnLen = LEN(tcCadena)
    lcSeq = "1" + REPLICATE("3579", CEILING(lnLen/4))
    lnSum = 0
    FOR lnIni = 1 TO lnLen
      lnSum = lnSum + VAL(SUBSTR(tcCadena, lnIni, 1)) * VAL(SUBSTR(lcSeq, lnIni, 1))
    ENDFOR
    lcRet = tcCadena + TRANSFORM(MOD(INT(lnSum / 2), 10))
  ENDIF
  RETURN lcRet
ENDFUNC

Luego de añadir los dígitos de verificación, ya se puede generar el código de barra Interleved 2 of 5 (I2of5) o Código 128 C, que son los que utilizan ambas empresas.

Si desean realizar toda la tarea con Visual FoxPro, pueden utilizar la clase FoxBarcode que soporta ambas simbologías de códigos de barra. Si utilizan el código I2of5 se debe configurar la propiedad lAddCheckDigit que no genere el dígito de control: (lAddCheckDigit = .F.)

Luis María Guayán

7 de noviembre de 2017

Introducción a Cliente-Servidor

Articulo original: Introduction to Client-Server
https://www.tedroche.com/Present/1999/CS.html
Autor: Ted Roche
Traducido por: Esparta Palma

Introducción

La arquitectura Cliente-Servidor parece ser muy simple en los ejemplos: cree una conexión, cree una vista remota hacia los datos, y estará en el negocio, correcto? . En esta sesión, Ted se introducirá en la potencia detrás del GUI, con la revisión de los comandos y funciones necesarias para crear un sistema Cliente-Servidor real. SQL Pass-throuhg, control manual de transacciones, manipulación de propiedades en conexiones, vistas y cursores, procesamiento en lote, precompilación y muchos otros temas que necesitará para la lucha con un sistema real a ser considerado.

Tecnologías Cliente Servidor y Visual FoxPro

Este pequeño documento no pretende ser una referencia completa hacia todo lo relacionado con Cliente-Servidor. En vez de eso, su intención es de servir como una pequeña introducción hacia los conceptos y las técnicas usadas para realizar una aplicación Cliente-Servidor con Visual FoxPro. Los lectores deberán estar familiarizados con Visual FoxPro y con el uso del contenedor de Base de Datos de FoxPro (DBC) y las vistas trabajando con datos locales. Empezaremos con algunas simples definiciones de los términos, revisaremos por qué deberías estar con Cliente-Servidor, y entonces cavaremos en los comandos y funciones para hacerlo posible. Concluiremos con algunas discusiones acerca de técnicas más avanzadas y cómo debería considerar la integración de Cliente-Servidor en su Framework.

¿Qué es Cliente-Servidor?
Hay una gran cantidad de definiciones acerca de qué es exactamente “Cliente-Servidor”, existen mas o menos estudiantiles, mas o menos robustas. Un vendedor hace algunos años diría: ¿“Cliente-Servidor”?, bueno, eso es cualquier cosa que debo vender ahora. Típicamente, para nuestros propósitos, el elemento clave de Cliente-Servidor es que dos procesos separados van a ser ejecutados, uno con Visual FoxPro, y otro que proveerá datos a Visual FoxPro. Estos dos programas pueden ser ejecutados tanto en la misma máquina como en forma separada, esto con algún tipo de conexión entre ellos (red, puerto serial o Internet) para permitir la comunicación.

¿Por qué Cliente-Servidor?
Muchos clientes ven interesante al esquema Cliente-Servidor por razones que están más allá de los méritos técnicos. Esto es ciertamente válido, pero tenga cuidado con las trampas en las que pueda caer si la solución Cliente-Servidor no fue realizada por una razón técnica primaria. Hay tres razones primordiales a considerar en Cliente-Servidor:
  • Tamaño de Datos: Visual FoxPro, al igual que todas las variantes de xBase anteriores a él, tenia la limitación de 2 GB en cualquiera de sus tablas o archivos. Esta limitación tiene que ver con la manera en que los bloqueos son realizados en los registros individuales y, mientras que es ciertamente factible que este límite sea alcanzado, esto es diferente a cómo normalmente lo hace Microsoft. A pesar de que hay muchos workarrounds para estos límites, estos incrementan los retos con Visual FoxPro en cuanto las tablas se agranden – tiempos prolongados para reindexar en caso de corrupción, por ejemplo. En resumen, si el tamaño se vuelve un factor mayor, considere si el uso de Cliente-Servidor es adecuado.
  • Seguridad: Fundamentalmente, todos los accesos a las tablas FoxPro van a través de la red del sistema operativo, así que los usuarios necesitan tener acceso a los directorios conteniendo las tablas VFP. Cualquiera que pueda tener acceso a las tablas puede, eventualmente, imaginarse como leerlos. También es simple usar un driver ODBC y Excel, o si ellos necesitan usar un editor hexadecimal para romper su esquema de encriptación. Muchos sistemas Cliente-Servidor pueden eliminar esta amenaza en conjunto con la restricción de acceso de los clientes a la interface del servidor, y no necesariamente a todos los datos. Si esta tratando con material altamente confidencial, Cliente-Servidor tiene sentido por razones de seguridad.
  • Bajo Ancho de Banda: Visual FoxPro es el producto para manejo bases de datos escritorio y basado en LAN mas rápido y con mejor mejor desempeño disponible en el mercado hoy en día. Pero VFP obtiene su desempeño fenomenal tomando ventaja del ambiente LAN, pre-obteniendo información de columna, haciendo localmente algún tipo de caching de encabezados de tablas y contenidos de índices. Mientras que el proceso de adquirir esta información es casi imperceptible en un ambiente de red, haciendo lenta la apertura inicial de tablas en milisegundos, esto puede ser un retardo substancial si se está en un “cable delgado” (por ejemplo conexiones Dial-Up, WAN o Internet saturado) entre el cliente y los datos. En estas situaciones, poner los datos, la responsabilidad de hacer las consultas y procesamiento de los datos en el servidor minizará los costos de comunicación y mejorará la velocidad.


¿Por qué NO Cliente-Servidor?
Como se mencionó anteriormente, los clientes pueden estar decepcionados si ellos han seleccionado una arquitectura Cliente-Servidor y sólo encuentran que el producto resultante es menos que satisfactorio. Hay muchas razones a considerar para no escoger Cliente-Servidor:
  • Complicaciones de Recursos: Cliente-Servidor es fundamentalmente mas complejo que un single-tier o aplicación monolítica. Nuevas necesidades de aplicación deben ser aprendidas, con esta terminología extranjera, sintaxis y lenguaje. Connectividad, ODBC y temas de red necesitan ser tomadas en cuenta. Nuevas APIs pueden ser necesitadas y dominadas. Anomalías, desdichas y plantear viejos bugs en la interface del vendedor, además de la búsqueda de driver. Si su equipo es maximizado y agendado, no piense que Cliente-Servidor será una “bala de plata”.>
  • Mantenimiento en Curso: Mientras que los vendedores están tratando de hacer sus aplicaciones mas “Instálelo y Olvídelo”, no existen instalaciones “hands-free”. Un administrador de Base de Datos (DBA, Database Administrator) tiene que estar disponible, al menos por periodos, revisar logs, asegurarse que los respaldos estén trabajando, el espacio en disco sea adecuado, etc. En realidad, esto es un requisito para muchas instalaciones de computadoras, pero los server típicamente necesitan un poco de atención adicional. Asegure el factor de costo de un DBA como parte de su vida en Cliente-Servidor.
  • Inestabilidad de Infraestructura: La percepción entre la gerencia superior es que Cliente-Servidor es inherentemente mas robusto, y que una instalación de este tipo es menos propensa a caídas que una instalación FoxPro. De hecho, la estabilidad de la red del sistema operativo y sus servidores no tienen relación con el software ejecutándose en ellos, y una inestabilidad no supone que las caídas y requerimientos de las aplicaciones FoxPro para reindexar todos sus archivos van a requerir un servidor que vaya a través de los procedimientos de recuperación. Un mejor uso del esfuerzo en mucho de estos casos es reparar los servidores con problemas.
  • Rendimiento: Como condición de que el suficiente ancho de banda está disponible, el rendimiento por sí solo nunca es razón para considerar al Cliente-Servidor, a menos de que los clientes requieran que la aplicación funcione más lentamente. En hardware equivalente, Visual FoxPro ejecuta muy decentemente en comparación de un software de servidor de datos


¿Cómo funciona Cliente-Servidor?
La diferencia principal entre trabajar con datos locales (datos VFP nativos) y datos cliente servidor es la necesidad de establecer una conexión con el servidor. Después de que la conexión es establecida, una vista puede ser usada para leer y escribir datos. En adición, la equivalencia de las sentencias SQL nativas de VFP pueden ser usadas para proporcionar un control más directo que el usualmente disponible vía Vista. Finalmente, como casi todo en Visual FoxPro, existe otra capa de fino control disponible a los desarrolladores que lo requieran.

Conexiones

Una conexión usa porciones del Microsoft Open Dabatase Connectivity (ODBC) para establecer una conexión de dos vías de comunicación entre Visual FoxPro y el servidor destino. Cada vendedor tiene su propio mecanismo para proveer, instalar, configurar y soportar esos drivers. Una vez establecida, la conexión de FoxPro puede ser configurada. Hay cierta confusión en la aplicación de la terminología VFP con respecto a conexiones. Cuando usted crea o modifica una conexión usando su sintaxis correspondiente, está modificando la definición de una conexión, y no está afectando una conexión existente. Las conexiones son almacenadas en el DBC y las definiciones son usadas cuando son llamadas. Las conexiones FoxPro pueden ser definidas visualmente con el Database Container (DBC) o directamente desde el Command Window con CREATE CONNECTION. Situación similar sucede con los equivalentes a los comandos DELETE, MODIFY, RENAME y DISPLAY CONNECTION. Las conexiones pueden referirse a un ODBC Data Source Name (DSN), definidas con el Applet “Panel de Control ODBC”, o se pueden especificar directamente los parámetros equivalentes. Por código, la conexión podría lucir como el siguiente:

** La primera conexión depende de un DSN pre-establecido
DEFINE Connection cnTest  
DATASOURCE dsnAccessNWind          
DATABASE C:\Access\nWind.MDB

** La segunda conexión, una conexión sin DSN (DSN-Less)
DEFINE Connection cnVFPFile     
CONNSTRING "DRIVER={Microsoft Visual FoxPro};" + ;     
                        "SOURCETYPE=DBF;" + ;          
                   "SOURCEDB=C:\VS98\VFP98\



Finalmente, las conexiones no necesitan ser mantenidas en un DBC. Si el resto de el sistema está usando SQL Pass Through, las conexiones pueden ser creadas al vuelo, usando las funciones SQLConnect() y SQLStringConnect():
** Abrir una conexión usando un DSN, UserID y PassWord
lnHandle = SQLConnect("mySQLServerDSN","SA","")

** Conectar a una tabla via el Driver ODBC driver sin un DSN:
lnHandle = SQLStringConnect("DRIVER={Microsoft Visual FoxPro};"+;
                            "SOURCETYPE=DBF;"+ ;                                                 
                            "SOURCEDB=C:\VS98\VFP98\")



Vistas
Las Vistas Remotas no son muy diferentes a las vistas locales, pero hay un par de áreas que nos pueden preocupar. La primera preocupación es sobre los tipos de datos almacenados en la base de datos remota pueden no compararse exactamente con los tipos de Visual FoxPro. Por ejemplo, no hay concepto de tipo de datos Lógico o Booleano en el esquema de Oracle. Esto puede ser fácilmente resuelto usando el Diálogo “Propiedades del Campo” y especificar el tipo de datos deseado. Use esta opción para mapear campos DateTime a datos tipo Fecha. Los campos Fecha son poco comunes en backends de base de datos, pero las funciones matemáticas de fechas de VFP están en días y las tipo DateTime en segundos. Tenga cuidado de asegurarse que se usan las unidades de medida correctas. La segunda preocupación es uso apropiado de conexiones. En el diseñador de Vistas, bajo las Opciones Avanzadas -> Menú Opciones, en la sección “Query”, verá un checkbox etiquetado “Compartir Conexión” (“Share Connection” para la versión en inglés). Por el simple hecho de que las vistas utilizan la misma conexión, no significa que cada uno intentará compartir la conexión actual, por el contrario, cada vista creará su propia copia de la conexión definida a menos de que ese checkbox esté marcado. Hay una configuración global equivalente, esta se encuentra en Herramientas/Opciones/Datos Remotos, esto no hacía nada en VFP 3 y 5. Finalmente, en VFP 6, esta ocasionaba que la compartición de conexión fuera seleccionada por default cuando estaba marcada. Vea “Vistas y Conexiones” mas adelante para una discusión sobre los temas envueltos.

Al igual que las conexiones, las Vistas pueden ser definidas y manipuladas programáticamente, al igual que visualmente. Hay algunas ventajas reales al definir las vistas programáticamente. Es fácil para todos el ver la definición de la vista. No necesita meterse en problemas con el Diseñador de Vistas cuando las relaciones complejas no son visualizadas correctamente. Finalmente, puede extender facilemente la naturaleza del meta-data del sistema para generar la Vista al vuelo. Revise el GenDBC (incluido con Visual FoxPro en el directorio (HOME()+”Tools\GENDBC”) para algunas ideas al extraer meta-data desde el DBC o usarlo para generar los comando requeridos para regenerar el DBC.

El comando CREATE VIEW crea una vista remota en el contenedor de base de datos actual. El formato de el comando parece un poco extraño, debido a que el comando SQL SELECT contenido dentro del CREATE VIEW no está delimitado de ningún modo – no se le establece comillas o paréntesis o algo. Este comando SQL SELECT describe como obtener los datos para producir la Vista. Recuerde que Visual FoxPro no está dando los datos – una aplicación servidor lo hace. Así,el SQL usado en el comando CREATE VIEW es el SQL y su extensiones nativas al server, no así a Visual FoxPro. Esto significa que debería evitar “FoxProismos” o funciones integradas de FoxPro (TRIM(), STR(), VAL()), a menos de que sepa que son soportadas por el server. Hay una excepción hablando del lenguaje materno del servidor, y esta situación de pedirle al ODBC que traduzca algunas frase en el proceso. Recuerde que se está usando ODBC para conectar Visual FoxPro al driver de base de datos correcto. ODBC también provee una API e incluye la habilidad para traducir la requisición desde un SQL más genérico hacia uno entendible por el backend. Las funciones pedidas al ODBC se desactivan del código SQL al poner llaves (símbolos { } ). Estos códigos pueden incluir peticiones para funciones internas del ODBC, traducciones de ciertos datos en formatos fecha y tiempo, y hasta extensiones específicas soportadas por algunos drivers. Revise la documentación de su driver ODBC llamando el Applet ODBC y selecionando ayuda.
* Ejemplo de una vista definida por código:

CREATE VIEW l_Customers_State ;
   REMOTE CONNECTION cnOracle SHARED AS ;
   SELECT * ;
     FROM Customer ;
    WHERE Customer.State = &pcState ;
    ORDER BY Customer.Name
Vistas y Conexiones
La razón por la cual usted necesita entender cuantas conexiones establecer con el servidor es por que cada conexión consume recursos, recursos que tienden a ser preciosos para el rendimiento del server y tienden a limitar la escalabilidad del sistema entero. También, algunos vendedores tienen licencias de sus servidores por conexión, en vez de usuarios, así que esto podría convertirse en un problema de licencias. Entonces, ¿Por qué no compartir toda la comunicación con el server sólo con una conexión? Bien, hay algunas restricciones que hacen esto mas complicado al usar sólo una conexión. Primero, las conexiones son definidas en el DBC y sólo pueden ser usadas por vistas en el DBC. Si tiene múltiples DBCs en su aplicación (y hay una o muchas buenas razones para no hacer esto), entonces estará manteniendo múltiples conexiones. Segundo, mientras que los comandos SQL Pass Through pueden compartir una conexión con una vista, consultando su conexión y entonces usar el mismo manejador, una vista no compartirá una conexión [Nota del Editor: Esto ha cambiado con la llegada de Visual FoxPro 8, en esta versión si es posible compartir conexiones entre SPT y las Vistas Remotas]. Finalmente, los comandos están diseñados para tomar algún tiempo o que requiera de más de un simple comunicación de “Pedir-Responder” entre el Cliente y el Servidor, serán propensos a “bloquear la línea telefónica” y necesita estar en su propia conexión. Dichos comandos incluyen peticiones progresivas, comandos por lotes, comandos precompilados y muchas otras situaciones. Es estos casos, planee una o mas conexiones para los procesos en lotes, y también planee una conexión compartida, si es posible, una para vistas y otra para sentencias cortas en el lenguaje SQL. SQL Pass-Through (SPT) Como se mencionó en secciones previas, toda la funcionalidad que puede ser obtenida usando el Contenedor de Base de Datos y las herramientas visuales también está disponible programáticamente con el lenguaje. Como siempre, esto es una de los puntos destacados de Visual FoxPro, los cuales están bien implementados aquí:

Función Propósito
SQLTables() Retorna una lista de tablas o vistas para una conexión específica. Útil para utilerías de “caja negra”
SQLColumns() Retorna especificaciones de campos en uno de varios formatos, útil para comparar capacidades de VFP con base de datos externas.
SQLConnect(), SQLStringConnect() Descritos anteriormente, son usados para establecer una conexión con el servidor
SQLExec() Pasa comandos al server vía ODBC, estos serán ejecutados.
SQLCommit(), SQLRollback() Provee la funcionalidad para completar o abortar una transacción. La transacción debe ser empezada en modo manual, utilizando SQLSetProp() descrita más abajo.
SQLSetProp(), SQLGetProp() Lee y establece propiedades de una conexión existente.
DBSetProp(), DBGetProp() Lee y establece propiedades de un Contenedor de Base de Datos con respecto a vistas, conexiones, tablas, etc. Estos son los únicos valores persistentes entre sesiones.
CursorSetProp(), CursorGetProp() Establece o lee propiedades en un cursor abierto, incluyendo el buffering.


Cliente Servidor Avanzado
Puede que los detalles de estos tópicos estén lejos de lo que es apropiado cubrir en una sesión introductoria, exponer estos conceptos puede darle algunas ideas sobre de lo que puede considerar estos productos en aplicaciones más avanzadas:

Batch SQL: Múltiples estatutos SQL pueden ser enviados al servidor, con procesamiento contínuo en el cliente, y ocasionalmente revisar el estatus del procesamiento.
Progressive Fetching: Muy conveniente cuando se descarga un conjunto de datos potencialmente grande, este método retorna el control a FoxPro después de que una cantidad predeterminada de registros han sido devueltos, y automáticamente obtiene registros adicionales en medida de que se vayan necesitando. Un ejemplo excelente sería un Grid para tomar la lista de clientes, donde el operador podría especificar un criterio de búsqueda, y el cliente podría estar listado si está dentro del criterio. El operador podría recorrer la página en la lista, y los registros adicionales podrían ser obtenidos así sean necesitados.
Precompilación: La función SQLPREPARE() permite al servidor recibir una sentencia SQL a ejecutar, y realizar sus funciones sobre ella – revisión, análisis, desarrollo del plan de trabajo, etc. - y entonces ejecutar la operación mucho más rápido que cuando es llamado.
Entodos estos tres casos, estas funciones requerirán una conexión separada, ya que ellas “bloquearán la linea telefónica” como fue mencionado anteriormente en la sección de Vistas y Conexiones. Sin embargo, estas funciones proveen esta necesidad de conexión.

Sugerencias en el aprendizaje de Cliente-Servidor
Las aplicaciones Cliente-Servidor pueden verse como algo muy complicad, pero no son muy difíciles si se aprende una parte a la vez. Recomiendo aprender a usar la funcionalidad básica de ODBC con Visual FoxPro a través de la experimentación directa con otra tabla o base de datos en su máquina, usando el driver ODBC de VFP. Una vez que gana experiencia, intente con conexiones sin DSN (DSN-Less), conexión directas usando SPT, e intenta cancelar y aceptar varios cambios a los datos. Después de que se haya familiarizado con los comandos y funciones básicas con Visual FoxPro, mire en el software del server. Aprenda cómo instalarlo y configurarlo, si es necesario, y aprenda que componentes son requeridos para instalar conectividad con clientes y driver. Muchos productos vienen con una sencilla ventana de comandos o interface interactiva, úsela para verificar que la conectividad está trabajando correctamente. Puede ser bastante frustrante el tomar algo desconocido y buscar por uno mismo que es lo que no funciona en el sistema, más aún si no hay manera de saber que sucede debido a la mala especificación de protocolo de red, direcciones o puertos incorrectos, o si falta algo por configurar.

Soporte del Framework
Todos los desarrolladores deberían estar usando un framework consistente para desarrollar sus aplicaciones; en vez de empezar de la nada y reinventar la rueda, un framework puede proveer un buen punto de inicio para desarrollo, y un siempre creciente nivel de sofisticación en el servicio proveido. Una de las características clave que debería proveer un framework debería ser las rutinas de servicio para obtener y guardar datos. Visual FoxPro es ideal proveyendo soporte para frameworks de servicios de datos debido a esto hay mucha consistencia y ricos modelos para trabajar con datos. Seleccionando uno de estos modelos y usándolos consistentemente con su framework proveerá un robusto mecanismo de datos.

Conclusión Visual FoxPro provee excelentes herramientas para obtener un control completo de Cliente-Servidor, tanto en variedad gráfica, como a través de la línea de comando. La tecnología Cliente-Servidor es apropiada para tamaños de datos extremadamente grandes, para situaciones de seguridad de alto riesgo, y para ambientes con comunicaciones con bajo ancho de banda desde el cliente al servidor. Además de que el Diseñador de Vistas y el Diseñador de Conexiones pueden permitir un completo diseñado gráficamente, Visual FoxPro también provee las herramientas y funciones para mezclar diseños programáticos y gráficos(o hasta basado en metatablas). Esta combinación hace fácil de aprender combinando el poder en la implementación.

Acerca del Autor. Ted Roche es un Desarrollador Certificado por Microsoft y es el director de desarrollo en Blackstone Incorporated, una compañía catalogada como Microsoft Certified Solution Provider en Waltham, Massachussets, USA, donde el desarrolla aplicaciones complejas usando herramientas visuales y “BackOffice” de Microsoft. Él es un contribuidor de seis libros de FoxPro, los últimos esfuerzos de Ted han sido como coautor de “Hacker's Guide to Visual FoxPro 6.0” disponible en Hentzenwerke Publishing, así como también en librerías de prestigio. Ted ha dado mas de una docena de conferencias profesionales en Canada, Europa y USA, ha sido coautor de “Advisor Answers” (Respuestas Advisor), una columna para la revista FoxPro Advisor. Contacte a Ted vía telefónica (781) 663-7400, o vía email en tedroche@compuserve.com

Derechos Reservados © 1999-2003 por Ted Roche bajo licencia Creative Commons Attribution - ShareAlike 1.0 - vea http://creativecommons.org/licenses/by-sa/1.0/ para mas detalles.

1 de noviembre de 2017

Escuchar un informe

Artículo original: Listening to a report (https://msdn.microsoft.com/en-us/library/ms947682.aspx)
Autor: Doug Hennig
Traducido por: Ana María Bisbé York


Microsoft ha abierto la arquitectura del motor de informes en Visual FoxPro 9 al comunicar el motor con la clase base ReportListener. Al subclasear ReportListener, los desarrolladores VFP pueden crear sus propias salidas personalizadas. Este mes, Doug Hennig, nos presenta la nueva clase  ReportListener y nos muestra como solucionar problemas del mundo real.

Estoy seguro de que ya se ha dado cuenta, de que el área que ha recibido mayor impulso en VFP 9.0 es el sistema de  informes. Tanto el Diseñador de informes como el motor de informes (responsable de ejecutar los informes) han recibido grandes mejoras y se han dotado de características nuevas muy interesantes.

Antes de VFP 9.0, el motor de informes era monolítico: controla todo el control de los datos, posicionamiento de objetos, generación, e impresión. El nuevo motor de informe de VFP 9.0 divide la responsabilidad de la generación de informes entre el motor de informes, que controla la generación, y la salida. VFP 9 incluye ambos, el nuevo motor de informes  y el viejo, así que puede ejecutar sus informes en cualquiera de los dos motores. Microsoft se refiere al motor nuevo como salida "asistida por objetos"

Utilizar la salida asistida por objetos

Existen tres formas para decirle a VFP que emplee el nuevo motor de informes:

Instanciar un ReportListener (sea una clase base o una subclase) y especificar su nombre en la nueva cláusula OBJECT del comando REPORT o LABEL. Este es el proceder más flexible porque puede especificar exactamente qué clase listener va a utilizar; pero requiere que modifique los comandos REPORT o LABEL existentes en su aplicación.

loListener = createobject('MyReportListener')
report form MyReport object MyReportListener

Especificar el tipo de listener utilizando la cláusula OBJECTTYPE. Existen varios tipos de listeners integrados: 0 significa impresora, 1 significa presentación preliminar, 4 significa XML y 5 significa salida HTML. Puede definir y utilizar tipos personalizados

report form MyReport object type 1 && preview

Utilizar el nuevo comando SET REPORTBEHAVIOR 90 antes de ejecutar el informe, usualmente, se colocará cerca del inicio de la aplicación para que todos los informes utilicen el nuevo motor. Al especificar TO PRINTER en una cláusula del comando REPORT FORM hace que VFP utilice el objecttype = 0 que tiene integrado, de igual forma, si indica PREVIEW hace que VFP utilice el type = 1. Esta es sin duda, la opción más conveniente ante las otras mencionadas, pero no le brinda el control que necesita al instanciar su propio listener. Para restablecer el motor anterior utilice SET REPORTBEHAVIOR 80.

Al ejecutar un informe utilizando alguno de los dos últimos métodos, es llamada la aplicación especificada en la nueva variable de sistema _REPORTOUTPUT  (de forma predeterminada ReportOutput.APP en la carpeta raíz de VFP) para determinar qué clase listener se debe instanciar para el tipo especificado. Sin embargo, debido a que es una aplicación VFP, puede sustituir su propia aplicación estableciendo para ello el valor correspondiente en _REPORTOUTPUT.

Asegúrese de distribuir ReportOutput.APP (o su aplicación que la sustituye) a sus usuarios para que sus aplicaciones puedan utilizar salida asistida por objeto. Nota: En tiempo de ejecución fuera del entorno de desarrollo de VFP _REPORTOUTPUT debe ser establecida explícitamente en su código para apuntar a ReportOutput.APP o su sustituta. Lo mismo se aplica a las variables de sistema. _REPORTPREVIEW (ReportPreview.APP) y, si es necesario, en su aplicación en tiempo de ejecución _REPORTBUILDER (ReportBuilder.APP). Existen otras vías para controlar estos requerimientos, tales como colocar el código desde ReportOutput.APP en su proyecto. Un conjunto de tópicos muy útiles se pueden encontrar en el índice del archivo Ayuda de VFP bajo el título: "Report Output Application."

Dentro de ReportListener

Debido a que ReportListener es una clase base de VFP, se puede subclasear para implementar cualquier comportamiento que se desee. Antes de que pueda crear sus propios listeners, necesita entender qué propiedades, métodos y eventos (PEMs) están disponibles. Por cuestiones de espacio, voy a referirme solamente a las PEMs más importantes, vea la Ayuda de VFP para completar los detalles de todo el conjunto.

Nota de la traductora: Vea el artículo "Extender los informes en tiempo de Ejecución", donde el propio Doug Hennig detalla estas PEMs.

Una de las propiedades más importantes es ListenerType. Esta propiedad dice al report listener cómo debe ser la salida. El valor predeterminado es -1, lo que indica que no se produce salida alguna. Se configura con 0 para impresora o con 1 para salida a una ventana de presentación preliminar. Especificar 2 ó 3 produce resultados interesantes: el informe es ejecutado y las páginas se generan en la memoria; pero en realidad no se produce ninguna salida. Puede utilizar estos valores cuando desea un control sobre el tipo de salida a crear. Por ejemplo, con el ListenerType = 2, VFP genera la primera página y llama al método OutPutPage, luego genera la siguiente página y vuelve a llamar al método OutputPage, y así sucesivamente. Utilizar el 3 para ListenerType provoca que todas las páginas se generen en memoria; pero el método OutputPage no se llama automáticamente para cada página, permitiendo llamar a OutputPage para solicitar desde la memoria las páginas en el orden en que se deseen.

Antes de que se ejecute un informe, el motor de informe abre una copia del informe como un cursor de sólo lectura nombrado FRX en una sesión de datos privada. El ID de esta sesión de datos se guarda en la propiedad FRXDataSession del ReportListener. Si necesita acceder a los datos que serán mostrados en el informe, la propiedad CurrentDataSession indica qué sesión de datos utilizar.

La propiedad CommandClauses contiene una referencia a un objeto que contiene propiedades con información acerca de cómo se ejecutará el informe. Por ejemplo, la propiedad Preview = .T. si el informe se va a mostrar con vista preliminar y la propiedad OutputTo es 1 si el informe se va a imprimir.

El motor de informe dispara eventos del report listener cuando se ejecuta el informe. Existen además otros métodos disponibles que puede llamar si es necesario. Algunos de los eventos y métodos más importantes se muestran en la tabla 1

Tabla 1. Algunos de los métodos y eventos de ReportListener.

EventoDescripción
BeforeReportSe dispara antes de que se ejecuta el informe
AfterReport Se dispara después de que se ejecuta el informe
EvaluateContents Se dispara antes de que se genera un campo
AdjustObjectSize Se dispara antes que se genere una figura o forma
Render Se dispara cuando es generado un objeto
OutputPage Se llama para obtener determinada página en un dispositivo determinado.
CancelReport Se llama para cancelar el informe

EvaluateContents, AdjustObjectSize, y Render son especialmente útiles porque permiten cambiar algo acerca de los objetos antes de que se generen. Junto con otros parámetros (los veremos luego), estos eventos reciben el número de registro para el objeto actual en el cursor FRX. Puede encontrar ese registro en el cursor para determinar si el objeto debe ser generado de forma diferente a lo normal.

_ReportListener

La carpeta FFC del directorio raíz de VFP contiene una nueva biblioteca de clases en VFP 9.0

_ReportListener.VCX.  Esta biblioteca contiene varias subclases ReportListener. Puede considerar utilizar algunas de estas como punto de partida para su propias subclases ReportListener porque estas agregan funcionalidades muy útiles para su clase base.

Una de las mejoras más útiles es el soporte para encadenar varios listeners junto con el mecanismo de sucesores. Al establecer la propiedad Successor de uno de los listeners igual a una referencia a otro informe permite que ambos interactúen con el proceso de generación de informes. Esto significa que puede escribir pequeños listeners que hagan justamente una cosa y encadenar varios para obtener los diferentes efectos unidos. La propiedad IsSuccessor le dice si el listener es el primero, el líder, (es aquel con el que se comunica el motor de informes, porque es el especificado en la cláusula OBJECT de un comando REPORT o LABEL.)

_ReportListener brinda además un grupo de métodos útiles. SetFRXDataSession establece la sesión de datos del cursor FRX. SetCurrentDataSession establece la sesión de datos de los datos del informe. ResetDataSession restablece el ID de la sesión de datos a aquel en el que está el listener.

Ahora que tiene ya esta introducción, es hora de ver algunos ejemplos prácticos.

Establecer formatos dinámicamente.

Una de las primeras cosas en las que yo pensé utilizar un listener fue para establecer formatos dinámicamente. Estoy seguro de que ya han hecho esto antes: Su cliente quiere que un campo sea impreso en rojo bajo determinadas condiciones y en negro, bajo otras. Esto se podía hacer antes de VFP 9.0 creando dos copias para el mismo campo, una en rojo y otra en negro, con condiciones excluyentes en Imprimir cuando, algo como Cantidad >= 100 y Cantidad < 100 y se superponen en el mismo sitio en el informe. Esto trabaja, es cierto; pero es muy difícil de mantener, específicamente si tiene muchos de estos campos en el informe.

Con un report listener, puede cambiar el formato de un campo cuando se está ejecutando el informe en lugar de hacerlo en el Diseñador de informes. La clave para esto es el evento EvaluateContents, el que se dispara justo antes de que se genere cada campo. A este evento se pasa el número de registro del objeto actual del cursor FRX  y una referencia a un objeto que contiene propiedades con información acerca del campo. (vea la Tabla 2)

Tabla 2. Propiedades del objeto oObjPropierties pasado a EvaluateContents.

PropiedadTipoDescripción
FillAlpha N Color (alpha) o transparencia del color de relleno. Rango de valores desde 0 para transparente hasta 255 para opaco.
FillBlue N Porción azul del valor RGB() para el color de relleno
FillGreen N Porción verde del valor RGB() para el color de relleno
FillRed NPorción roja del valor RGB() para el color de relleno
FontName C Nombre de la fuente
FontSize NTamaño de la fuente
FontStyle NUn valor que representa el estilo de fuente. Los valores son: 1 (negrita), 2 (itálica), 4(subrayada) y 128 (tachada)
PenAlpha NColor del pincel (alpha)
PenBlue NPorción azul del valor RGB() para el color de pincel
PenGreen NPorción verde del valor RGB() para el color de pincel
PenRed NPorción roja del valor RGB() para el color de pincel
Reload LIguale a .T. para notificar al motor de informe que ha modificado una o más propiedades.
Text CTexto a mostrar para el objeto campo.
Value VariosValor real del campo a mostrar

DynamicFormatting.PRG, incluido en la descarga que se acompaña, define tres clases.

DynamicListener define qué es lo que debe hacer un listener dinámico y dos subclases: DynamicForeColorListener y DynamicStyleListener, cambian el color  y el estilo respectivamente, del campo que tenga una directiva en su campo memo USER. (Puede acceder al campo memo del objeto a generar desde la ficha Other (Otros) del diálogo Propiedades). La directiva en este ejemplo es una de las siguientes:

*:LISTENER FORECOLOR = ColorExpression
*:LISTENER STYLE = StyleExpression

ColorExpression  es una expresión que evalua a un valor RGB, algo como IIF(Cantidad > 50, RGB(255, 0, 0), RGB (0, 0, 0)), lo que significa utilizar Rojo si la cantidad es mayor que 50 y negro si no lo es. StyleExpression  es una expresión que evalúa un valor de estilo (vea la propiedad FontStyle  en la Tabla 2), algo como IIF(Cantidad > 50, 1, 0), lo que significa utilizar Negrita si la cantidad es mayor que 50 y normal si no lo es.

La primera tarea que debe hacer el listener es identificar qué campos tienen directivas. En lugar de hacerlo cada vez que se evalúa el campo, DynamicListener lo hace en el método BeforeReport. Selecciona la sesión de datos del cursor FRX al llamar a SetFRXDataSession, luego recorre el cursor, buscando registros con la directiva adecuada (especificado en la propiedad .cDirective) en el campo memo USER, y colocando la siguiente expresión la directiva del elemento del registro en el arreglo.

Debido a que DynamicListener hereda las características del sucesor de _ReportListener (que llama automaticamente al método BeforeReport  para todos los listeners sucesores), cada una de nuestras subclases de DynamicListener van a tener su propia matriz aRecords de los registros FRX que se correspondan con la propiedad .cDirective. Así, cada listener puede determinar fácilmente sobre qué campo debe accionar durante la ejecución del informe.

La siguiente tarea es aplicar la directiva cuando sea necesario. EvaluateContents verifica si el elemento actual de la  matriz tiene una expresión y si es así, la evalúa. Luego llama al método ApplyDirective, el que es abstracto en DynamicListener; pero implementado en sus dos subclase. Por ejemplo DynamicForeColorListener establece las propiedades adecuadas del color a toObjProperties e iguala la propiedad Reload  a .T., de tal forma que el motor de informes sabe si el formato del campo cambió.

He aquí otra tarea de mantenimiento: asegúrese de que el ListenerType está definido correctamente. El valor predeterminado, -1, provoca que no haya salida alguna, y no cambia ni siquiera especificando PREVIEW o TO PRINTER en los comandos REPORT o LABEL. Entonces, el método LoadReport establece el valor adecuado de ListenerType si es necesario.

He aquí el código para estas clases:

define class DynamicListener as _ReportListener of ;
  home() + 'ffc\_ReportListener.vcx'
dimension aRecords[1]
&& una matriz con información de cada registro en el FRX
cDirective = ''
&& la directiva que esperamos encontrar

* Si no se ha indicado ListenerType, se determina en dependencia
* de si el informe será impreso o visualizado (preview).
function LoadReport
  with This
    do case
      case .ListenerType <> -1
      case .CommandClauses.Preview
        .ListenerType = 1
      case .CommandClauses.OutputTo = 1
        .ListenerType = 0
    endcase
  endwith
  dodefault()
endfunc

* Antes de ejecutar el informe, recorremos el FRX y
* guardamos información sobre cada campo en el que esperamos 
* una directiva en su campo memo USER en la matriz aRecords.
function BeforeReport
  dodefault()
  with This
    .SetFRXDataSession()
    dimension .aRecords[reccount()]
    scan for .cDirective $ USER
      .aRecords[recno()] = strextract(USER, ;
        .cDirective + ' =', chr(13), 1, 3)
    endscan for .cDirective $ USER
    .ResetDataSession()
  endwith
endfunc

* Si el campo que se va a generar tiene una directiva, se aplica.
function EvaluateContents(tnFRXRecno, toObjProperties)
  local lcExpression, ;
    luValue
  with This
    lcExpression = .aRecords[tnFRXRecno]
    if not empty(lcExpression)
      luValue = evaluate(lcExpression)
      .ApplyDirective(tnFRXRecno, ;
        toObjProperties, luValue)
    endif not empty(lcExpression)
    * Si tenemos un sucesor, le damos participación también.
    if vartype(.Successor) = 'O'
      .Successor.EvaluateContents(tnFRXRecno, ;
        toObjProperties)
    endif vartype(.Successor) = 'O'
  endwith
endfunc

* Método abstracto para aplicar nuestra directiva.
function ApplyDirective(tnFRXRecno, ;
  toObjProperties, tuValue)
endfunc

enddefine

define class DynamicForeColorListener ;
  as DynamicListener
cDirective = '*:LISTENER FORECOLOR'

* Aplicar la directiva.
function ApplyDirective(tnFRXRecno, ;
  toObjProperties, tuValue)
  local lnPenRed, ;
    lnPenGreen, ;
    lnPenBlue
  if vartype(tuValue) = 'N'
    lnPenRed = bitand(tuValue, 0x0000FF)
    lnPenGreen = bitrshift(bitand(tuValue, ;
      0x00FF00), 8)
    lnPenBlue = bitrshift(bitand(tuValue, ;
      0xFF0000), 16)
    with toObjProperties
      if .PenRed <> lnPenRed or ;
        .PenGreen <> lnPenGreen or ;
        .PenBlue <> lnPenBlue
        .PenRed = lnPenRed
        .PenGreen = lnPenGreen
        .PenBlue = lnPenBlue
        .Reload = .T.
      endif .PenRed <> lnPenRed ...
    endwith
  endif vartype(tuValue) = 'N'
endfunc

enddefine

define class DynamicStyleListener as DynamicListener
  cDirective = '*:LISTENER STYLE'

* Aplicar la directiva.
function ApplyDirective(tnFRXRecno, ;
  toObjProperties, tuValue)
  if vartype(tuValue) = 'N'
    toObjProperties.FontStyle = tuValue
    toObjProperties.Reload = .T.
  endif vartype(lnStyle) = 'N'
endfunc

enddefine

El programa TestDynamicFormatting.PRG muestra cómo encadenar estos listeners, de forma tal que ambos se utilicen en la generación del informe.

use _samples + 'Northwind\Orders'
loListener = newobject('DynamicForeColorListener', ;
  'DynamicFormatting.prg')
loListener.Successor = ;
  newobject('DynamicStyleListener', ;
  'DynamicFormatting.prg')
report form TestDynamicFormatting.FRX preview ;
  object loListener

La Figura 1 muestra el resultado de la ejecucón de este programa. En algunos registros, Shipped Date se muestra en rojo y en otros casos en negro. Esto se debe a que tiene la siguiente directiva en su campo memo USER

*:LISTENER FORECOLOR = iif(SHIPPEDDATE > ORDERDATE + 10, rgb(255, 0, 0), rgb(0, 0, 0))


Figura 1

El campo Ship Via aparece a veces en negrita y algunas veces normal, porque tiene la siguiente directiva en su campo memo USER:

*:LISTENER STYLE = iif(SHIPVIA = 3, 1, 0)

(Observe que aunque este campo es numérico, muestra valores como "Fedex," "UPS," o "Mail"  debido a la expresión que hay en el campo.

¿Qué más puede hacer?

Pues casi todo lo que desee. En artículos futuros, mostraré otros listeners que permitan obtener imágenes rotadas, salidas HTML con una tabla de contenidos, y otros muchos tipos de salidas.

Resumen

Microsoft superó todas las ligas de la extensibilidad en VFP 9.0 de muchas formas, incluidas en el generador de informes. Gracias a su clase base, que podemos subclasear, ReportListener permite crear una salida propia, personalizada. Háganme saber, por favor de cualquier listener interesante que ha creado o que ideas se le ocurren para hacer con listeners.