22 de noviembre de 2014

Domine Visual FoxPro 9.0 SP2 / Expert in Visual FoxPro 9.0 SP2


Español

Comparto con Uds. un libro que termine de escribir de Visual FoxPro 9 SP2. NUEVO: Agregué la versión en inglés

Para leer el libro Domine Visual FoxPro 9.0 SP2 en formato PDF en español (2,16 MB) haga clic aquí.

Atte. Ernesto Fabián Coronel

Agradecimientos:

  • Agradezco a mi familia por siempre, por apoyarme en el tiempo que no les dedique, por estar abocado en mi "proyecto de escribir este libro".
  • A mi esposa Julia e hijos: Igor, Ernesto y Cristina.

English

I share with you a book to finish writing Visual FoxPro 9 SP2. NEW: I added the English version

To read the book Expert in Visual FoxPro 9.0 SP2 in English in PDF format (2.16 MB) click here.

Sincerely, Ernesto Fabián Coronel

Acknowledgments:

  • I thank my family for always, for supporting me in the time not devoted to them, being doomed in my "project of writing this book."
  • My wife Julia and children: Igor Ernesto and Cristina.

15 de noviembre de 2014

Los detalles que tu mamá no te conto sobre HTMLListener

Pues bien, varios de los foxeros que visitan el foro de Microsoft creo que me vieron batallar por cerca de semana y media, por el tema de HTMLListener. Esta es una clase que viene en VFP9 SP2 (no estoy seguro si ya estaba en el SP1), la cual nos da la posibilidad de que nuestros reportes generados por archivos FRX, nos den la salida a formato HTML (entre uno de los tipos de salidas).

Sobre el uso de esta clase podemos encontrar documentación en el HELP de VFP9 SP2, y otro tanto en la Web. Pero lo que no vamos a encontrar es documentación relacionada con algunos problemas que se dan al implementar de esta clase en nuestros proyectos. Encontraran eso sí la exposición del problema pero no la solución.

Estos errores estan relacionados a las constantes: "FRX_OBJTYP_BAND" y "FRX_PLATFORM_WINDOWS". Estas constantes viene declaradas en el archivo de cabecera "foxpro_reporting.h" que se encuentra en la carpeta FFC (donde también se ubica la librería de clases VCX _Reportlistener, a la cual pertenece la clase HtmlListener.

Yendonos al punto, expondré el ejemplo típico que encontraremos en la web.
LOCAL loListener
loListener =.null.

*-- Asegurarse de que no existe el archivo HTML
ERASE SalidaHTML.HTM

*-- Crear la clase Listener
SET CLASSLIB TO HOME() + 'FFC\_REPORTLISTENER'
loListener = CREATEOBJECT('htmlListener')

DO (_reportoutput) WITH 5,loListener

*-- Configurar algunas propiedades
loListener.TargetFileName = 'SalidaHTML'

REPORT FORM  OBJECT loListener
Hasta aquí muy bien, es probable que en su ambiente de desarrollo no tengan ningún problema; pero cuando ya están incluyendo estas líneas en su proyecto, y lo corren desde el EXE, pueda que aun les corra sin problema (aun que yo si los tuve) pero al llevarlo a distribución la cosa cambia. Más adelante expondré que necesitamos ademas un parche de Windows.

Ahora expondré el código que me soluciono el problema de las constantes no encontradas, en el cual veremos que obviamos algunas líneas de código.
LOCAL loListener
loListener =.null.

*-- Asegurarse de que no existe el archivo HTML
ERASE SalidaHTML.HTM

DO (_reportoutput) WITH 5,loListener 

*-- Configurar algunas propiedades
loListener.QuietMode = .T.
loListener.TargetFileName = 'SalidaHTML'
Erase (loListener.TargetFileName)

REPORT FORM  OBJECT loListener
Algunas aclaraciones

Resulta que la línea de código : DO (_reportoutput) WITH 5,loListener al generar el proyecto no incluye el archivo "reportoutput.app" , por lo que nuestro proyecto no incluirá otras librerías de clases necesarias, la solución para ello es hacer que forzosamente "reportoutput.app" sea incluido en el proyecto, de la siguiente manera:
EXTERNAL FILE HOME() + 'reportoutput.app'
En sí el problema es que nunca se incluye en nuestros proyecto las clases que "reportoutput.app" invoca al correrlo, siendo así que entre las cosas que no se generan van las constantes descritas al inicio como errores en corrida.

Yo concluyo que el ejemplo típico forzando la inclusión de "reportoutput.app" en el proyecto debería funcionar, pero con el código que expuse que me dio solución al problema nos ahorramos un par de lineas:
*-- Crear la clase Listener
SET CLASSLIB TO HOME() + 'FFC\_REPORTLISTENER'
loListener = CREATEOBJECT('htmlListener')
Estas no son necesarias ya que "reportaoutput" hace el trabajo por nosotros.

He de manifestar mi agradecimiento a Edwin Duran, que respondió a unos de mis hilos en el foro, enviandome además un conjunto de programas que generaban un archivo PDF a partir de uin reporte FRX, pero que casualmente ahí venia el ejemplo para generar HTML con HtmlListener, que fue el que al final me soluciono el problema, quedando así demostrado como "san foxito" no proveer muchas soluciones útiles para nuestras aplicaciones.

Agrego como nota final en link para bajar la actualización del MSXML4, que es un componente esencial para que ReportListener nos de resultados que esperamos.

*** MSXML 4.0 Service Pack 2 (Servicios principales de Microsoft XML) ***
http://www.microsoft.com/downloads/details.aspx?displaylang=es&FamilyID=3144b72b-b4f2-46da-b4b6-c5d7485f2b42

Saludos,

William Hernández
Santiago de Chile

8 de noviembre de 2014

Automatizando Visual FoxPro

Así como podemos controlar otras aplicaciones desde Visual FoxPro mediante automatización, también podemos controlar Visual FoxPro desde otras aplicaciones utilizando a VFP como un servidor de automatización.

Existen muchos escritos sobre como automatizar aplicaciones como Excel, Word, Outlook, etc. desde Visual FoxPro como cliente de automatización, pero quizás algo que muchos desconozcan, es que Visual FoxPro también es un servidor de automatización. ¿Que quiero decir con esto?. Que cualquier aplicación que permita automatización puede crear una instancia de Visual FoxPro y ejecutar comandos de Visual FoxPro.

Comenzar con lo conocido

Para comenzar utilizaremos a Visual FoxPro como cliente, como lo hicimos ya muchas veces, y crearemos una nueva instancia de Visual FoxPro con la siguiente sentencia:
loVFP = CREATEOBJECT("VisualFoxPro.Application")
Una vez creado el objeto Application de Visual FoxPro, con la ayuda de IntelliSense, una ventana emergente nos mostrará las Propiedades y los Métodos de este objeto al escribir lo siguiente:
loVFP.    
Si en la PC tenemos instaldas mas de una versión de Visual FoxPro, podemos especificar cual versión vamos a instanciar, como lo muestra el siguiente código:
*-- Visual FoxPro 9.0
loVFPx = CREATEOBJECT("VisualFoxPro.Application.9")
? loVFPx.Version
loVFPx.Quit

*-- Visual FoxPro 8.0
loVFPx = CREATEOBJECT("VisualFoxPro.Application.8")
? loVFPx.Version
loVFPx.Quit

*-- Visual FoxPro 7.0
loVFPx = CREATEOBJECT("VisualFoxPro.Application.7")
? loVFPx.Version
loVFPx.Quit

*-- Visual FoxPro 6.0
loVFPx = CREATEOBJECT("VisualFoxPro.Application.6")
? loVFPx.Version
loVFPx.Quit

Conocer los métodos y las propiedades

Algunos de los métodos disponibles del objeto Application de Visual Fox y que podemos ejecutar son:
  • DoCmd
Ejecuta un comando de Visual FoxPro para la instancia de la aplicación Visual FoxPro.
loVFP.DoCmd("USE (HOME(2)+'Northwind\Customers')")
  • Eval
Evalua y retorna el resultado de una expresión en la instancia de la aplicación Visual FoxPro.
? loVFP.Eval("CompanyName")
  • SetVar
Crea una variable y le asigna un valor en la instancia de la aplicación Visual FoxPro.
loVFP.SetVar("lcNombre","PortalFox")
? loVFP.Eval("lcNombre")
  • RequestData
Retorna una matriz que contiene los datos de una tabla abierta en la instancia de la aplicación Visual FoxPro.
laArray = loVFP.RequestData("Customers",2)
DISPLAY MEMORY LIKE laArray
  • DataToClip
Copia como texto al portapapeles un conjunto de registros de una tabla abierta en la instancia de la aplicación Visual FoxPro.
loVFP.DataToClip("Customers",2,3)
? ClipText
  • Quit
Finaliza la instancia de la aplicación Visual FoxPro.
loVFP.Quit
Algunas de la propiedades del objeto Application de VFP y que podemos consultar o modificar son:
? loVFP.Version 
? loVFP.StartMode
loVFP.Caption = "Instanciado de otra aplicacion"
loVFP.Visible = .T.
La variable del sistema _VFP hace referencia al objeto aplicación de la instancia actual de Visual FoxPro, y podemos ejecutar sus métodos y modificar sus propiedades:
_VFP.Caption = "Instancia Actual de VFP"
_VFP.DoCmd("_Screen.BackColor = RGB(255,255,192)")
? _VFP.Version

Instanciando Visual FoxPro desde Excel

El siguiente ejemplo, nos muestra como podemos crear una instancia de Visual FoxPor desde Excel, abrir una tabla, e importar sus datos en una hoja de Excel.

Copie el siguiente código escrito en VBA (Visual Basic for Applications), insertelo en un módulo de Excel y ejecutelo:

Sub ImportarDeVFP()
   Dim loVFP As Object, lnReg As Integer
   Set loVFP = CreateObject("VisualFoxPro.Application")
   loVFP.DoCmd ("OPEN DATABASE (HOME(2)+'Northwind\Northwind')")
   loVFP.DoCmd ("SELECT * FROM Customers INTO CURSOR MiCursor")
   lnReg = loVFP.DataToClip("MiCursor", , 3)
   Range("A1").Select
   ActiveSheet.Paste
   Cells.Select
   Cells.EntireColumn.AutoFit
   loVFP.Quit
   Set loVFP = Nothing
   MsgBox (lnReg & " Registros copiados")
End Sub

Para terminar

Con este último ejemplo podemos ver que a veces es mas simple programar otra aplicación para controlar a Visual FoxPro y obtener datos, que hacerlo todo desde Visual FoxPro. Uds. verán el camino a tomar para dar solución a los requerimientos de los usuarios.

Hasta la próxima.

Luis María Guayán

6 de noviembre de 2014

Verificar si una tabla está abierta

Función modificada de ¿Cómo verificar si una tabla está abierta en exclusiva?

La modificación a esta misma función que permite saber si existe algún usuario (o varios) que tengan abierta la tabla en modo Shared. Esto me fue util cuando intentaba abrir una tabla en modo exclusivo y evitaba los errores. Para esto se agrego el parámetro lModo el cual indica el modo en que se desea abrir la tabla (0 si se desea abrir SHARED y 1 si se desea abrir como exclusivo)

***************************************************
FUNCTION _Exclusivo(tcTabla,lModo)
***************************************************
* Verifica si una tabla esta abierta en  EXCLUSIVO o SHARED 
* por otro/s usuario/s.
*
* Ej. de USO:   _Exclusivo("C:\VFP\MiTabla.DBF",0)
*
* PARAMETROS:
*    tcTabla = Ruta completa del archivo .DBF que se desea abrir 
*    lModo = Indica la manera en que se desea abrir la tabla. Valores posibles 0, 1
*                   0  (o sin parametros) Utilizar cuando se intenta 
*   abrir una tabla en modo SHARED
*   (no exclusivo) y se quiere evitar
*   que se devuelva un error si existe
*   otro usuario utiliza la en exclusivo 
*
*                  1  Utilizar cuando se intenta abrir en modo EXCLUSIVE una 
*                 Tabla y evitar el mensaje de error por que otro usuario 
*                tiene abierta la misma tabla en modo Shared o Exclusivo.
*
* RETORNO:  .T. Si se puede abrir, no se encuentra abierta por otro usuario
* .F. No se puede abrir, se encuentra abierta por otro usuario
*
* Modificada de ¿Cómo verificar si una tabla está abierta en exclusiva?
* Se agrega parametro lModo , por Tomás Cruz - 04.08.2014
***************************************************
IF VARTYPE(lModo)=='L'
lModo = 0
ENDIF 
LOCAL lnHandle, llRet
lnHandle = FOPEN(tcTabla,lModo)
IF lnHandle = -1
   llRet = .F.
ELSE
   llRet = .T.
   =FCLOSE(lnHandle)
ENDIF
RETURN llRet
ENDFUNC

Tomas Cruz

1 de noviembre de 2014

Combo con búsqueda incremental y filtro

Nunca he aportado nada a la Comunidad, sin embargo me he beneficiado muchísimo de lo escrito aquí y en PortalFox, por lo que os dejo un añadido al código de otro compañero que lo he adaptado por necesidades propias para mi trabajo. Espero que nadie se moleste de que utilice / modifique código de otro. Ojala alguno de los "maestros" de FoxPro retoque el que dejo, lo mejore y nos beneficiemos todos.

Son dos Combos, uno que va buscando incrementalmente el texto que escribimos y marcando la parte del registro mas parecido que falta por escribir. El otro combo, es copia de uno que vi hecho en un programa compilado con Delphy, que va buscando la primera aparición del texto introducido y filtrando los registros que contengan ese texto. Nada espectacular pero cómodo para el usuario.


PUBLIC miForm
miForm = CREATEOBJECT("FormularioPruebas")
miForm.SHOW
RETURN

DEFINE CLASS FormularioPruebas AS FORM
  ADD OBJECT ComboNombre AS ComboFiltroCerrado WITH ;
    LEFT = 10, TOP = 10, NAME = "ComboNombre", ;
    MAXLENGTH = 30, WIDTH = 340
  ADD OBJECT ComboNombre2 AS ComboFiltroAbierto WITH ;
    LEFT = 10, TOP = 50, NAME = "ComboNombre2", ;
    MAXLENGTH = 30, WIDTH = 340

  PROCEDURE ComboNombre.INIT
    DODEFAULT()
    THIS.SetAliasControl('Customer')
    THIS.SetCampoFiltro('Customer.Contact_Name')
  ENDPROC

  PROCEDURE INIT
    DODEFAULT()
    IF NOT USED ('Customer')
      USE (HOME(2) + "Tastrade\Data\Customer.dbf") IN 0 SHARED
    ENDIF
    THIS.ComboNombre.ROWSOURCE = 'Customer.Contact_Name'
    THIS.ComboNombre2.ROWSOURCE = 'Customer.Contact_Name'
  ENDPROC
ENDDEFINE

DEFINE CLASS ComboFiltroCerrado AS COMBOBOX
  INCREMENTALSEARCH = .F.
  ROWSOURCETYPE = 2
  SELECTEDITEMFORECOLOR = RGB(255,255,255)
  SELECTEDITEMBACKCOLOR = RGB(200,200,180)
  SELECTEDFORECOLOR = RGB(240,0,0)&&RGB(50,030,240)
  SELECTEDBACKCOLOR = RGB(200,200,180)
  STYLE = 0
  SORTED = .T.

  PROTECTED cadenaIntroducida AS STRING
  PROTECTED inicioSeleccion AS INTEGER
  PROTECTED finalSeleccion AS INTEGER

  PROTECTED aliasControl AS STRING
  PROTECTED campoFiltro AS STRING
  PROTECTED conFiltro AS Boolean

  PROCEDURE INIT
    DODEFAULT()
    THIS.cadenaIntroducida = ''
    THIS.aliasControl = ''
    THIS.campoFiltro = ''
    THIS.conFiltro = .F.
  ENDPROC

  PROCEDURE INTERACTIVECHANGE
    LOCAL codigoCaracter AS INTEGER
    LOCAL tamañoCadena AS INTEGER

    codigoCaracter = LASTKEY()
    IF codigoCaracter = 127
      tamañoCadena = LEN(THIS.cadenaIntroducida)
      IF tamañoCadena > 1
        THIS.cadenaIntroducida = LEFT(THIS.cadenaIntroducida, ;
          LEN(THIS.cadenaIntroducida)-1)
        THIS.BuscaRegistros(codigoCaracter)
      ELSE
        THIS.cadenaIntroducida = ''
        THIS.VALUE = ''
        THIS.SELSTART = 0
        THIS.SELLENGTH = 0
        THIS.QuitaFiltroRegistros()
      ENDIF
    ELSE
      IF BETWEEN(codigoCaracter,32,255)
        THIS.cadenaIntroducida = THIS.cadenaIntroducida + CHR(codigoCaracter)
        THIS.BuscaRegistros(codigoCaracter)
      ENDIF
    ENDIF
  ENDPROC

  PROCEDURE BuscaRegistros(_CodigoCaracter AS INTEGER)
    THIS.QuitaFiltroRegistros()
    FOR i = 1 TO THIS.LISTCOUNT
      IF UPPER(LEFT(THIS.LIST[i],LEN(THIS.cadenaIntroducida))) == ;
          ALLTRIM(UPPER(THIS.cadenaIntroducida))
        THIS.DISPLAYVALUE = THIS.LIST(i)
        THIS.SELSTART = ATC(UPPER(THIS.cadenaIntroducida), ;
          UPPER(THIS.LIST[i]), 1)-1
        THIS.SELLENGTH = LEN(THIS.cadenaIntroducida)
        THIS.FiltraRegistros()
        RETURN
      ENDIF
    ENDFOR
    FOR i = 1 TO THIS.LISTCOUNT
      IF UPPER(THIS.cadenaIntroducida) $ UPPER(THIS.LIST(i))
        THIS.DISPLAYVALUE = THIS.LIST(i)
        THIS.SELSTART = ATC(UPPER(THIS.cadenaIntroducida), ;
          UPPER(THIS.LIST[i]), 1)-1
        THIS.SELLENGTH = LEN(THIS.cadenaIntroducida)
        THIS.FiltraRegistros()
        RETURN
      ENDIF
    ENDFOR
    THIS.cadenaIntroducida= CHR(_CodigoCaracter)
    THIS.VALUE = CHR(_CodigoCaracter)
    THIS.DISPLAYVALUE = THIS.VALUE
    THIS.SELSTART = 0
    THIS.SELLENGTH = 1
  ENDPROC

  PROCEDURE QuitaFiltroRegistros()
    LOCAL aliasTabla
    IF THIS.conFiltro
      aliasTabla =THIS.aliasControl
      SET FILTER TO IN &aliasTabla
      THIS.REQUERY
    ENDIF
  ENDPROC

  HIDDEN PROCEDURE FiltraRegistros()
    LOCAL campo
    LOCAL aliasTabla
    PUBLIC textoSeleccionadoFiltro AS STRING
    IF THIS.conFiltro
      campo = THIS.campoFiltro
      aliasTabla = THIS.aliasControl
      textoSeleccionadoFiltro = THIS.SELTEXT
      SET FILTER TO (textoSeleccionadoFiltro $ &campo) IN &aliasTabla
      THIS.REQUERY
    ENDIF
  ENDPROC

  PROCEDURE SetAliasControl(_Alias AS STRING)
    THIS.aliasControl = _Alias
  ENDPROC

  PROCEDURE SetCampoFiltro(_Campo AS STRING)
    THIS.conFiltro = .T.
    THIS.campoFiltro = _Campo
  ENDPROC
ENDDEFINE

DEFINE CLASS ComboFiltroAbierto AS COMBOBOX
  INCREMENTALSEARCH = .F.
  ROWSOURCETYPE = 2
  SELECTEDITEMFORECOLOR = RGB(255,255,255)
  SELECTEDITEMBACKCOLOR = RGB(200,200,180)
  SELECTEDFORECOLOR = RGB(50,030,240)
  SELECTEDBACKCOLOR = RGB(200,200,180)
  STYLE = 0
  SORTED = .T.

  PROCEDURE INTERACTIVECHANGE
    LOCAL codigoCaracter AS INTEGER
    LOCAL valorDisplay AS STRING
    LOCAL valorNuevoDisplay AS STRING
    LOCAL lnUltimaSeleccion AS INTEGER
    LOCAL lnSeleccionados AS INTEGER
    codigoCaracter = LASTKEY()
    valorNuevoDisplay = ""
    lnUltimaSeleccion = 0
    lnSeleccionados = 0
    IF (codigoCaracter >= 32 AND codigoCaracter <= 126)
      valorDisplay = SUBSTR(THIS.DISPLAYVALUE,1,THIS.SELSTART-1)+(CHR(codigoCaracter))
      valorNuevoDisplay = THIS.DISPLAYVALUE
      FOR i = 1 TO THIS.LISTCOUNT
        IF UPPER(valorDisplay) $ UPPER(SUBSTR(THIS.LIST(i),1,LEN(valorDisplay)))
          THIS.DISPLAYVALUE = THIS.LIST(i)
          THIS.SELSTART = LEN(valorDisplay)
          IF LEN(ALLT(THIS.DISPLAYVALUE)) > LEN(valorDisplay)
            THIS.SELLENGTH = LEN(ALLT(THIS.DISPLAYVALUE))-LEN(valorDisplay)
          ELSE
            THIS.SELLENGTH = 0
          ENDIF
          valorNuevoDisplay = THIS.DISPLAYVALUE
          lnUltimaSeleccion = THIS.SELSTART
          lnSeleccionados = THIS.SELLENGTH
          RETURN
        ENDIF
      ENDFOR
      THIS.DISPLAYVALUE = valorNuevoDisplay
      THIS.SELSTART = IIF(lnUltimaSeleccion > 0, lnUltimaSeleccion, LEN(valorDisplay))
      THIS.SELLENGTH = lnSeleccionados
    ENDIF
  ENDPROC
ENDDEFINE
Un abrazo,

Carlos Joaniquet Tamburini