30 de abril de 2016

Actualizar aplicaciones

Permite actualizar la Aplicacion tanto en el equipo local como actualizar el servidor donde esta la aplicacion a distribuir.

Poner este codigo en archivo de inicio principal (prg).

Luego de Generar nuestro ejecutable, el instalador ademas de todos los archivos necesarios, tiene que instalar en cada maquina el Archivo ACTUALIZA.BAT y/o UPLOAD.BAT

El Archivo Upload.bat es identico al Actualiza solo cambia la direccion de la copia.

***** Comprueba Nueva Version  *******

AGETFILEVERSION(ServerApp,"\\miservidor")
Version_Mayor = VAL(SUBSTR(ServerApp(4),1,1))
Version_Menor = VAL(SUBSTR(ServerApp(4),3,AT(ServerApp(4),".",1) + 1))
Version_Mantension = VAL(SUBSTR(ServerApp(4),5,2) )

AGETFILEVERSION(MiApp,"C:\Archivos de programa\CDP\CDP.exe")
MiVersion_Mayor = VAL(SUBSTR(miapp(4),1,1))
MiVersion_Menor = VAL(SUBSTR(miapp(4),3,AT(miapp(4),".",1) + 1))
MiVersion_Mantension = VAL(SUBSTR(miapp(4),5,2) )
*
IF !(MiVersion_Mayor = Version_Mayor.AND.MiVersion_Menor = Version_Menor ;
.AND.MiVersion_Mantension = Version_Mantension)
  **** Actualizar
  ** Si soy yo copia al servidor
  IF UPPER(ALLTRIM(SUBSTR(SYS(0),AT("#",SYS(0),1) + 1,LEN(SYS(0))))) = "miUsername"
    MESSAGEBOX("Las Versiones Son: ServerApp -> " + ServerApp(4) + CHR(13) + ;
      "Local App. ->" + miApp(4) + CHR(13) + "Actualizando el Servidor")

    RUN /N "C:\Archivos de programa\CDP\UPLOAD.BAT"
  ELSE

    SET DEFAULT TO "C:\Archivos de programa\CDP"
    RUN /N "ACTUALIZA.BAT"
    QUIT
  ENDIF
ENDIF

***** Fin Comprobacion de Actualizacion

Contenido del archivo ACTUALIZA.BAT

@ECHO OFF
@ECHO Actualizando Sistema xxxxxxx
@ECHO AUTOR.: Ludwig Corales M.
@ECHO Actualizado al : %DATE%
@xCopy \\soft\CDP.EXE "C:\Archivos de programa\CDP" /C /R /Y

***** Fin Contenido **********

***** Contenido del archivo UPLOAD.BAT ******

@ECHO OFF
@ECHO Actualizando Sistema xxxxxxx
@ECHO AUTOR.: Ludwig Corales M.
@ECHO Actualizado al Servidor : %DATE%
@xCopy "C:\Archivos de programa\CDP\CDP.exe" \\soft\  /C /R /Y

***** Fin Contenido **********

Notar que se comparan siempre tanto las versiones mayores, menores y sub versiones, para ver si se actualiza o no.

Atte.

Ludwig Corales Marti

26 de abril de 2016

Enlazar con el evento Valid

Artículo original: Binding to the Valid event
http://www.foxpert.com/KnowlBits_200701_1.htm
Autor: Christof Wollenhaupt
Traducido por: Ana María Bisbé York


Como otras muchas cosas en Visual FoxPro, enlazar con el evento Valid no es tan evidente como uno pudiera desear. Al enlazar a un control en un grid, Visual FoxPro llama al objeto delegado, solamente cuando el control grid contiene código definido por el usuario en el evento Valid, en algún punto de la jerarquía. Este código podría no hacer nada y puede ser solamente un comentario. Visual FoxPro necesita el código del usuario como un lugar de enlace, así que suficiente conque exista.

Si intenta enlazar un evento Valid de un textbox utilizando el objeto Text1 predeterminado o un textbox regular sin ningún código especial en el evento Valid, su código no será llamado nunca. Este comportamiento ya está documentado (bien, oculto, en la sección Remarks (Observaciones) de BindEvent: "Ciertos eventos como When y Valid necesitan que haya código en el evento para que pueda ocurrir el enlace."

El siguiente ejemplo demuestra este comportamiento. No importa cómo navegue en el grid o qué valores cambie, el único evento Valid que verá siempre es el de la columna 2:

*==========================================================
* Enlazar al evento Valid de un control grid es posible
* solamente si emplea una subclase del control con código en el evento Valid
*==========================================================

Public goForm
Create Cursor Test (cField1 C(10), cField2 C(10))
Insert into Test Values ("one","two")
Insert into Test Values ("three","four")
Go top
goForm = CreateObject("Form")
goForm.AddObject("grd","MyGrid")
goForm.grd.Visible = .T.
goForm.Left = 200
goForm.Show()

Define Class MyGrid as Grid
  ColumnCount = 2

  Procedure Init
    This.Columns(2).RemoveObject("Text1")
    This.Columns(2).AddObject("Text1","MyTextbox")
    This.Columns(2).Text1.Visible = .T.
    BindEvent( ;
      This.Columns(1).Text1, "Valid", ;
      This, "ColumnValid" )
    BindEvent( ;
      This.Columns(2).Text1, "Valid", ;
      This, "ColumnValid" )
  EndProc

  Procedure ColumnValid
    Local laSource[1]
    AEvents(laSource,0)
    Activate Screen
    ? Sys(1272,laSource[1])
  EndProc
EndDefine

Define Class MyTextbox as TextBox
  Procedure Valid
  *
  EndProc
EndDefine

23 de abril de 2016

Reloj Analógico con VFP 9.0

Una clase que que contiene un objeto Shape con su nueva propiedad PolyPoints en Visual FoxPro 9.0 que nos permite dibujar las agujas de un reloj analógico con formas poligonales.

El código (para VFP 9.0) de la clase RelojAnalogico contenida en un formulario es el siguiente:

PUBLIC goMiForm
goMiForm = CREATEOBJECT("MiForm")
goMiForm.SHOW(1)
RETURN

DEFINE CLASS MiForm AS FORM
  HEIGHT = 240
  WIDTH = 240
  AUTOCENTER = .T.
  CAPTION = "Reloj Analógico"
  NAME = "frmRelojAnalogico"
  ADD OBJECT RelojAnalogico1 AS RelojanAlogico WITH ;
    TOP = 20, ;
    LEFT = 20, ;
    NAME = "RelojAnalogo1"
ENDDEFINE

DEFINE CLASS relojanalogico AS CONTAINER
  WIDTH = 200
  HEIGHT = 200
  BACKSTYLE = 0
  BORDERWIDTH = 0
  NAME = "RelojAnalogico"
  ADD OBJECT shpEsfera AS SHAPE WITH ;
    TOP = 0, ;
    LEFT = 0, ;
    HEIGHT = 200, ;
    WIDTH = 200, ;
    CURVATURE = 99, ;
    BACKCOLOR = RGB(255,255,255), ;
    NAME = "shpEsfera"
  ADD OBJECT shpAgujas AS SHAPE WITH ;
    TOP = 0, ;
    LEFT = 0, ;
    HEIGHT = 200, ;
    WIDTH = 200, ;
    BORDERWIDTH = 1, ;
    BACKCOLOR = RGB(200,255,200), ;
    POLYPOINTS = "This.aPoly", ;
    NAME = "shpAgujas"
  ADD OBJECT tmrTimer AS TIMER WITH ;
    INTERVAL = 1000, ;
    NAME = "tmrTimer"
  PROCEDURE Hora
    LOCAL ln, lnAng, lnCos, lnSen, ;
      lnCoI, lnCoD, lnSeI, lnSeD
    ln = SECONDS()
    *--- Posicion aguja segundos
    lnAng = INT(ln % 60) * 6
    lnCos = COS(DTOR(lnAng -90))
    lnSen = SIN(DTOR(lnAng -90))
    THIS.shpAgujas.aPoly(10,1) = 48 * lnCos + 50
    THIS.shpAgujas.aPoly(10,2) = 48 * lnSen + 50
    *--- Posicion aguja minutos
    lnAng = INT((ln / 60) % 60) * 6
    lnCos = COS(DTOR(lnAng-90))
    lnSen = SIN(DTOR(lnAng-90))
    lnCoI = COS(DTOR(lnAng-90-7))
    lnSeI = SIN(DTOR(lnAng-90-7))
    lnCoD = COS(DTOR(lnAng-90+7))
    lnSeD = SIN(DTOR(lnAng-90+7))
    THIS.shpAgujas.aPoly(2,1) = 30 * lnCoI + 50
    THIS.shpAgujas.aPoly(2,2) = 30 * lnSeI + 50
    THIS.shpAgujas.aPoly(3,1) = 45 * lnCos + 50
    THIS.shpAgujas.aPoly(3,2) = 45 * lnSen + 50
    THIS.shpAgujas.aPoly(4,1) = 30 * lnCoD + 50
    THIS.shpAgujas.aPoly(4,2) = 30 * lnSeD + 50
    *--- Posicion aguja horas
    lnAng = ((ln / 3600) % 12)* 30
    lnCos = COS(DTOR(lnAng -90))
    lnSen = SIN(DTOR(lnAng -90))
    lnCoI = COS(DTOR(lnAng-90-10))
    lnSeI = SIN(DTOR(lnAng-90-10))
    lnCoD = COS(DTOR(lnAng-90+10))
    lnSeD = SIN(DTOR(lnAng-90+10))
    THIS.shpAgujas.aPoly(6,1) = 20 * lnCoI + 50
    THIS.shpAgujas.aPoly(6,2) = 20 * lnSeI + 50
    THIS.shpAgujas.aPoly(7,1) = 35 * lnCos + 50
    THIS.shpAgujas.aPoly(7,2) = 35 * lnSen + 50
    THIS.shpAgujas.aPoly(8,1) = 20 * lnCoD + 50
    THIS.shpAgujas.aPoly(8,2) = 20 * lnSeD + 50
    THIS.shpAgujas.REFRESH
  ENDPROC
  PROCEDURE INIT
    THIS.Hora
  ENDPROC
  PROCEDURE shpEsfera.INIT
    THIS.HEIGHT = THIS.PARENT.HEIGHT
    THIS.WIDTH = THIS.PARENT.WIDTH
  ENDPROC
  PROCEDURE shpAgujas.INIT
    THIS.HEIGHT = THIS.PARENT.HEIGHT
    THIS.WIDTH = THIS.PARENT.WIDTH
    THIS.ADDPROPERTY("aPoly[10,2]")
    FOR lnI = 1 TO 10
      STORE 50 TO ;
        THIS.aPoly[lnI,1], ;
        THIS.aPoly[lnI,2]
    ENDFOR
  ENDPROC
  PROCEDURE tmrTimer.TIMER
    THIS.PARENT.Hora
  ENDPROC
ENDDEFINE

Pueden descargar el proyecto con el formulario y la librería de clases visuales en el siguiente enlace:

RelojAnalogico.zip (4,41 KB)

19 de abril de 2016

Enlazar Eventos en Tiempo de Ejecución, uso de BINDEVENT

A partir de VFP8 ya puedes enlazar eventos en tiempo de ejecución, de que nos servirá esto?... Exploraremos un ejemplo práctico.

PUBLIC oForm
oHandler = CREATEOBJECT("myHandler")
oForm = CREATEOBJECT("MyForm",oHandler)
oForm.Show()
DEFINE CLASS myForm as Form 
  Width = 400
  Height = 225
  ADD OBJECT myGrid as Grid WITH Width=400, HEIGHT=180, TOP=5
  ADD OBJECT myCmdButton AS CommandButton ;
          WITH Top=190, Left=40,Caption="Bind",;
               Height=30              
  PROCEDURE INIT
  LPARAMETERS toHandler
     This.AddProperty("oHandler",toHandler)
  ENDPROC                
  PROCEDURE LOAD
     OPEN DATABASE (HOME(2)+"\Data\TestData")
     USE customer 
  ENDPROC
  PROCEDURE UNLOAD
     USE IN "Customer"
     CLOSE DATABASES ALL
  ENDPROC
  PROCEDURE myCmdButton.Click
     Thisform.EnlazaEvento() 
  ENDPROC
  PROCEDURE EnlazaEvento      
  * Recorremos los Objetos contenidos en el Grid
  FOR EACH loObjects IN Thisform.myGrid.Objects       
    FOR EACH loControls IN loObjects.Controls
      DO CASE 
       CASE UPPER(loControls.BaseClass)="HEADER"
       ** Enlazamos el evento Doble Click de los Headers
       ** Hacia un metodo del objeto Handler                                  
          BINDEVENT(loControls,"DblClick",this.oHandler,"DobleClick")
       CASE UPPER(loControls.BaseClass)="TEXTBOX"
       ** Enlazamos el evento Doble Click de los textbox
       ** Hacia otro metodo del objeto Handler                                                                                         
          BINDEVENT(loControls,"DblClick",this.oHandler,"MuestraValor")   
       ENDCASE
    ENDFOR   
  ENDFOR      
  ENDPROC
ENDDEFINE
DEFINE CLASS myHandler as Custom
   PROCEDURE DobleClick
       AEVENTS(laEventos,0)
       MESSAGEBOX("Lllamado desde:"+laEventos[1].Parent.ControlSource)
   ENDPROC
   PROCEDURE MuestraValor
      AEVENTS(laEventos,0)
      MESSAGEBOX(EVALUATE(laEventos[1].ControlSource))
   ENDPROC
ENDDEFINE

Con lo anterior hemos enlazado los eventos tanto de los Headers, como de los TextBox (en lo respectivo al evento Doble Click) del Grid, estos no ejecutaban ninguna acción en una primera instancia, pero cuando ejecutamos el botón "Bind" pudimos (en tiempo real) modificar el comportamiento de los eventos Doble Click sin necesidad de haberles agregado al Grid como clases personalizadas (único modo en versiones anteriores a VFP8).

En entregas posteriores veremos otras cosas interesantes sobre esta nueva función de Visual FoxPro 8, espero les sea de utilidad.

Esparta Palma

16 de abril de 2016

Construyendo Objetos Multicapa en VisualFoxPro (n-tier)

Autor: Jim Booth

Traducción de Sergio E. Aguirre


El modelo N-Tier

El término N-tier se refiere a los varios niveles de responsabilidad en el diseño de un sistema. La N en N-Tier puede ser cualquier número de 2 en adelante. Un diseño muy común es el modelo 3-Tier. En el modelo 3-tier la aplicación es dividida en 3 capas de responsabilidades distintas, la interface del usuario, la lógica de negocio, y la base de datos. Cada una de estas capas puede ser implementada usando uno o más objetos que se dedican a las responsabilidades de esa capa.

Interface del usuario

La capa interface del usuario contendría todos los aspectos visuales del sistema. Cualquier cosa involucrada en la interacción con el usuario del sistema es manejada por esta capa. Todos los diálogos, cuadros de mensajes, formularios, informes, y otros componentes de interacción con el usuario residirían en la capa interface de usuario de un sistema.

Lógica de Negocio

La capa lógica de negocio tiene la responsabilidad de determinar la forma en que vienen los datos y cómo deben estructurarse para la interface del usuario. También aplica toda regla de validación a los datos provenientes de la interface del usuario antes de mandar estos datos a la base de datos.

La capa lógica de negocio no tiene ningún componente de la interface del usuario en ella como tampoco tiene la responsabilidad de actuar recíprocamente con el usuario. Los problemas percibidos con los datos deben ser comunicados a la capa interface del usuario a través de valores devueltos por métodos y la capa interface del usuario debe mostrar los mensajes al usuario.

Manejo de la Base de Datos

La base de datos es la responsable de manejar el dominio de validación sobre los datos, de actualizar y recuperar los datos en las tablas. Las reglas en la base de datos deben restringirse a sólo aquellas que son de directa aplicación del dominio de validación. "Reglas de Negocio" no son parte de las reglas de la base de datos, en cambio ellas fueron puestas en vigor en la capa lógica de negocio.

Otras Capas

3-Tier no es el único diseño N-Tier. N puede ser cualquier número. Algunas de las cosas que podrían ser consideradas para las capas adicionales son, Interface del Sistema Operativo, Interface de la Red, y los Múltiples Niveles de Capas de Lógica de Negocio.

Por ejemplo, usted puede diseñar un sistema para un banco donde el objeto de lógica de negocio para una Cuenta necesita tener varios formatos diferentes dependiendo de la sección del banco que está usando los datos. En este caso usted puede tener un objeto de lógica de negocio para Cuenta que es genérico para el banco entero, y tiene otros objetos de lógica de negocio que son específicos para las secciones particulares (usando el objeto Cuenta genérico y agregando o restringiendo rasgos basados en los requisitos de la sección).

Ventajas / Desventajas del Diseño N-Tier

Las ventajas del diseño de un sistema N-Tier son múltiples. La siguiente lista muestra algunas de las ventajas.

  1. Usted puede modificar la lógica de negocio sin hacer cambios a la interface del usuario o a la base de datos.
  2. Si lo construyó correctamente, el objeto de lógica de negocio puede ser usado por múltiples interfaces del usuario.
  3. Aisla el conocimiento requerido en cualquier capa dada a esa capa.

Algunas de las desventajas son:

  1. El diseño del sistema es más complejo.
  2. El enlace de datos inherente de Visual FoxPro es indisponible.
  3. La huella de memoria de la aplicación aumenta.

Con estas desventajas, ¿Por qué querría alguien construir un sistema N-Tier? La respuesta es una sola palabra, scalability. El diseño N-Tier puede descascarar a los sistemas sumamente grandes sin compromisos. Por grande estamos refiriéndonos al número de usuarios, el número de diferentes componentes de la interface del usuario, el tamaño de la base de datos, la estructura de la red, y todos los otros problemas de tamaño para una aplicación.

Usando el diseño N-Tier, usted puede diseñar un sistema que pueda manejar múltiples intefaces del usuario divergentes sin tener que volver a escribir la lógica de negocio para cada interface construida. La lógica de negocio puede ser compartida por múltiples interfaces del usuario. Mediante la posibilidad de hacer subclases, las clases de lógica de negocio pueden personalizarse para manejar diferentes servidores de base de datos.

El diseño N-Tier no es apropiado para todos los proyectos, pero cuando se necesita de él es un concepto de diseño sumamente poderoso.

Construyendo Aplicaciones N-Tier en Visual FoxPro

Visual Foxpro puede ser usado para construir cualquiera de las capas comúnes del modelo N-Tier. Visual FoxPro tiene las herramientas para construir interfaces del usuario destacadas. La base de datos nativa de Visual FoxPro es rápida y robusta. Sin embargo, para cada una de estas dos capas hay otras herramientas que también lo hacen bien o mejor. ¿Si la interface necesita imitar a una hoja de cálculo, Excel no sería una mejor opción? Si la base de datos necesita seguridad agregada en la base de datos del servidor, no serían una opción mejor SQL Server o Oracle?

La capa en la que Visual FoxPro tiene ventajas es la capa media, o capa de lógica de negocio, del modelo N-Tier. Debido a la construcción en Lenguaje de Manejo de Datos (DML) de Visual FoxPro, es el primer candidato para manipular datos de un servidor y presentarlos a una interface. También, la posibilidad de crear clases OLE públicas con Visual FoxPro permite interfaces divergentes y bases de datos para usar el mismo objeto de media capa para comunicarse entre sí.

Las Responsabilidades de un Objeto de Lógica de Negocio

Las responsabilidades de un objeto de media capa varían ampliamente. Cosas como la aplicación de la regla de negocio, separación de la interface del usuario y el origen de la base de datos, y proporcionando una sola capa para el acceso a datos para los múltiples servidores de la base de datos está entre las posibles responsabilidades.

Como con muchas otras cosas en el desarrollo orientado a objetos, el diseño del sistema dicta las reales funciones proporcionadas por un objeto de media capa.

Aplicando las reglas de negocio

En cualquier sistema de base de datos hay reglas que controlan que son datos válidos y que son datos inválidos. Estas reglas pueden ser divididas en el dominio de validación y reglas de negocio. Un dominio describe todos los valores posibles que pueden encontrarse en la entidad o atributo al que el dominio aplica. Por ejemplo, el dominio para un campo ciudad puede incluir todos los nombres posibles de ciudades del mundo.

Las reglas de negocio son un subconjunto de un dominio. Las reglas de negocio llevan más allá el límite de valores posibles para ser sólo parte del dominio completo. Con el ejemplo del campo ciudad, quizás nuestra compañía se localiza en Alemania y tiene clientes sólo alemanes. En este caso el dominio para el campo ciudad será todas las ciudades del mundo, pero las reglas de negocio limitarían al campo a sólo ciudades en Alemania.

Mientras que las bases de datos son muy buenas en la aplicación de dominios, ellas pueden ser demasiadas restrictivas si ponen en vigor reglas de negocio. El objeto de lógica de negocio de media capa es un candidato ideal para poner en vigor las reglas de negocio. Visual FoxPro está especialmente preparado para este trabajo porque su motor local de datos permite la creación de diseños de metadata para describir las reglas específicas a ser puestas en vigor. Esto le permite al programador crear reglas de negocios para el manejo de datos que puedan cambiar con el tiempo sin requerir cualquier modificación de código.

Trasladando Datos para la Interface del Usuario

En el diseño 3-Tier la interface del usuario está separada del origen de datos por la capa media, o la capa de la lógica de negocio. Esta separación le permite al programador construir una interface del usuario independiente y capas de almacenamiento de datos. Los perfeccionamientos futuros del sistema pueden incorporar nuevas interfaces del usuario o nuevas tecnologías de almacenamiento de datos sin un cambio que provoque la necesidad de cambiar el otro.

El objeto de media capa realiza el papel de traductor de datos en el formato encontrado en el sistema de almacenamiento de datos a un formato que puede ser usado por la interface del usuario. También traduce los datos desde la interface del usuario a un formato que puede ser guardado por la base de datos.

Con este diseño un cambio en la base de datos requiere sólo que el objeto de media capa sea reforzado, así como un cambio en la interface del usuario también sólo requiere que el objeto de media capa sea reforzado.

Usando Clases de Visual FoxPro para construir un Objeto de Lógica de Negocio

Los ejemplos se pueden descargar aquí.

Exploremos ahora en algo de código para ver una de las muchas maneras que usted puede diseñar un objeto de media capa en Visual FoxPro. La clase de lógica de negocio que nosotros crearemos se va a llamar customer y proporcionará acceso a los datos de ejemplo Fitch Mather que vienen con Visual FoxPro 6.0. La tabla que usaremos es la tabla Stores (Tiendas).

El primer problema con el cual yo traté era qué clase de base usar para crear el objeto customer. Yo escogí usar la clase de base form porque proporciona una sesión de datos privada que protegerá los datos de otras instancias del objeto customer.

A la clase customer yo agregué una propiedad llamada oRDS para usarse como una referencia a un Control de Datos RDS. Yo usé RDS como la metodología de acceso a datos para que la clase customer pueda ser fácilmente modificada para acceder a otros sistemas de base de datos. El control de Datos RDS se crea en el Init de la clase Customer. El código del método Init se describe debajo.

 
* Creo el Control de Datos RDS
This.oRDS = CreateObject("rds.datacontrol")
* Verificamos si la creación tuvo éxito 
If Type("This.oRDS") <> "O" 
   * Si es no devuelvo .F. 
   Return .F. 
Else 
   * Si tuvo éxito fijo algunas propiedades del datacontrol
   With This.oRDS 
      * Establezco el nombre del origen de Datos
      .Connect = "dsn=dsnFitchMather" 
      * Prepare la declaración SQL para ejecutarla
      .SQL = "Select * from stores" 
      * Fijo la ejecución síncrona
      .ExecuteOptions = adcExecSync 
      * Fijo el saque en el fondo 
      .FetchOptions = adcFetchBackground 
      * Ejecuto la consulta 
      .Refresh 
   EndWith 
EndIf 

Los comentarios en el código anterior son autoexplicativos. Una vez que el objeto customer existe tiene el control de Datos RDS dentro de él y el Control de Datos RDS está sacando los datos.

NOTA:

Las constantes referidas en el código anterior se toman de un archivo llamado adcvbs.h. Los contenidos de este se listan debajo.

*-------------------------------------------------------------------- 
* Microsoft ADC 
* (c) 1997 Microsoft Corporation. All Rights Reserved.
* ADO constants include file for VBScript
*-------------------------------------------------------------------- 
*---- enum Values ---- 
#Define adcExecSync 1 
#Define adcExecAsync 2 
*---- enum Values ---- 
#Define adcFetchUpFront 1 
#Define adcFetchBackground 2 
#Define adcFetchAsync 3 
*---- enum Values ---- 
#Define adcReadyStateLoaded 2 
#Define adcReadyStateInteractive 3 
#Define adcReadyStateComplete 4 

A esta clase yo he agregado varios métodos que se listan en la siguiente tabla.

Método

Propósito

GetValue

Usado para obtener el valor de un campo

SetValue

Usado para establecer el valor de un campo

MoveFirst

Mueve al primer registro en el RecordSet

MoveLast

Mueve al último registro

MoveNext

Mueve al próximo registro

MovePrev

Mueve al registro anterior

Requery

Refresca el Control de Datos mediante la re-ejecución de la consulta SQL. El método requery está para hacer que la sintaxis del objeto customer sea similar a la sintaxis nativa de VFP nativa para el requerying de una vista.

RevertChanges

Descarta los cambios pendientes de los datos

SaveChanges

Graba los cambios pendientes de los datos

Las siguientes secciones presentarán el código que está en estos métodos.

GetValue

LPARAMETERS PcField
* Verifico por un parámetro válido 
IF NOT EMPTY(pcField) AND VARTYPE(pcField) = "C" 
   * Verifico si es un nombre de campo válido para este objeto
   IF LOWER(pcField) $ "store_name~store_add1~store_addr2~store_addr3~" + ;
      "store_city~store_id~store_desc~store_phone1~" + ; 
      "store_state~store_type~store_zip" 
      * Demanda de un campo válido para este objeto, así que devuelvo el valor 
      RETURN THIS.oRDS.Recordset.Fields(pcField).Value 
   ENDIF 
ENDIF 
* Demanda de un campo inválida 
RETURN .NULL. 

SetValue

LPARAMETERS PcField, pxValue
IF NOT EMPTY(pcField) AND VARTYPE (pcField) = "C" 
   IF LOWER(pcField) $ "store_name~store_add1~store_addr2~store_addr3~" + ; 
      "store_city~store_id~store_desc~store_phone1~" + ; 
      "store_state~store_type~store_zip" 
      IF VarType(pxValue) = "C" 
         pxValue = ALLTRIM(pxValue) 
      ENDIF 
      THIS.oRDS.Recordset.Fields(pcField).Value = pxValue 
      RETURN .T. 
   ENDIF 
ENDIF 
RETURN .F.

MoveFirst

ThisForm.oRDS.RecordSet.MoveFirst
RETURN 1 

MoveLast

ThisForm.oRDS.RecordSet.MoveLast
RETURN 1

MoveNext

LOCAL LnRet
lnRet = 1 
ThisForm.oRDS.RecordSet.MoveNext 
If ThisForm.oRDS.RecordSet.Eof 
   lnRet = -1 
   ThisForm.oRDS.RecordSet.MoveLast 
EndIf 
RETURN lnRet 

MovePrev

LOCAL LnRet
lnRet = 1 
ThisForm.oRDS.RecordSet.MovePrevious 
If ThisForm.oRDS.RecordSet.Bof 
   lnRet = -1 
   ThisForm.oRDS.RecordSet.MoveFirst 
EndIf 
RETURN lnRet

Requery

ThisForm.oRDS.Refresh

RevertChanges

THISFORM.Requery()

SaveChanges

ThisForm.oRDS.SubmitChanges()
THIS.Requery()

Usted puede preguntarse por qué yo he creado todos estos métodos para hacer cosas que podría haber hecho refiriéndome directamente al Control de Datos RDS. La respuesta es que esto proporciona una interface de desarrollo para el objeto de negocio que es independiente de la naturaleza del objeto de datos. Yo puedo hacer subclases de esta clase y escribir código que con datos locales de VFP, o ADO en lugar de RDS, o ODBC a través de vistas remotas. Ninguno de éstas modificaciones requeriría que cualquier código, en el nivel de UI, sea cambiado en absoluto.

Usando el Objeto de Negocio

El proyecto también incluye un formulario de VFP que demanda esta clase de negocio. El nombre del formulario es Customer.scx. Examinemos el código de este formulario que usa la clase de negocio.

En este formulario se agrega una propiedad llamada oBusObj. En el evento Load del formulario el código será el siguiente.

THISFORM.oBusObj = NewObject("Customer")

Esto crea una instancia del objeto customer y almacena una referencia a él en la propiedad oBusObj del formulario. En el Refresh del textbox número de tienda este es el código:

THIS.Value = THISFORM.oBusObj.GetValue("store_id")

Este código llama al GetValue del objeto de negocio y fija la propiedad Value del textbox para ser el valor devuelto por el método. El evento Valid para el mismo textbox es:

THISFORM.oBusObj.SetValue("store_id",THIS.Value)

Que escribe el Valor devuelto a la fuente de datos del objeto de negocio.

El evento Click para el botón Top es:

THISFORM.oBusObj.MoveFirst()
THISFORM.Refresh()

¿Está empezando a ver un patrón aquí? ¿Puede apreciar lo fácil que es usar este objeto de negocio para manejar el acceso de datos? El otro código en el formulario es similar salvo que se llaman métodos del objeto de negocio.

Creando un servidor ActiveX desde las clases de Visual FoxPro

Así que, ¿Cuál es el gran trato? ¿Por qué es mejor usar un objeto separado para manejar el acceso a datos cuándo el formulario tiene que un entorno de datos maravilloso y controles que pueden enlazar directamente los datos?

La respuesta a estas mentiras es la palabra scalability. Scalability es la habilidad de un sistema de crecer incluir nuevos rasgos con el tiempo, grandes volúmenes de datos, interfaces del usuario adicionales, y otros perfeccionamientos. Si usted demanda el entorno de datos de un formulario de VFP por acceder a los datos, entonces usted se limita a usar VFP para construir la interface del usuario o usted necesitará crear las mismas capacidades de acceso a datos en alguna otra herramienta.

¿Qué pasa si las mismas necesidades de acceso a datos estén disponibles a VFP y a Microsoft Excel? La respuesta es hacer a la clase customer como una clase OLE Pública y construir un COM DLL con ella incluida. Para hacer una clase OLE Pública, abra el diseñador de clases y luego elija Información de clase... en el menú Clase. Verifique que esté marcada la casilla OLE público.

Para construir el COM DLL abra el proyecto y escoja la opción Información del proyecto... del menú Proyecto, seleccione la etiqueta Servidores y establezca sus opciones de la clase (estas opciones están bastante bien documentadas en el archivo de ayuda). Guarde esas opciones y entonces escoja Generar y seleccione la opción Servidor COM (dll) y pulse el botón Aceptar. Esto va a generar el COM DLL con su clase COM servidor en él y registrará la DLL en su máquina. Para otros para usar la clase DLL necesitará ser instalada y registrada en sus máquinas, esto puede hacerse como parte del proceso de Instalación para su aplicación.

Una vez que usted ha hecho esto puede modificar sus formularios de VFP para que usen la clase COM. Simplemente cambie el evento Load para que sea:

THISFORM.oBusObj = NewObject("BusObj.Customer")

Donde BusObj es el nombre del archivo DLL que usted creó.

Usando Múltiples Interfaces del Usuario en el Diseño N-Tier

La ventaja de este diseño es que el mismo objeto de negocio puede ser usado por múltiples interfaces del usuario diferentes permitiendo una definición de clase para controlar el acceso a datos para cada UI que su sistema usa. Aunque su aplicación sólo pueda limitarse a formularios de VFP al principio, siguiendo este diseño N-Tier le permitirá, de manera más fácil, agregar otras interfaces al sistema en el futuro (haciendo el sistema escalable).

Usando Microsoft Excel para la Interface del Usuario

Aquí hay un ejemplo de una macro de Microsoft Excel que usa la misma clase de negocio para llenar con datos una hoja de cálculo.

Option Explicit
Public Dummy As Variant
Public oCustomer As Object
Sub nTier()
   ' nTier Macro
   '
   Dim lnRet As Integer
   Set oCustomer = CreateObject("BusObj.Customer")
   ActiveSheet.Cells(1, 1) = "Store ID"
   ActiveSheet.Cells(1, 2) = "Store Name"
   ActiveSheet.Cells(1, 3) = "Store City"
   lnRet = Refresh()
   ' frmRefresh.Show
End Sub
Public Function Refresh()
   ' nTier Macro
   ' Refreshes the contents of the business logic object and the sheet.
   Dim lnRet As Integer
   Dim lnRow As Integer
   lnRet = oCustomer.MoveFirst()
   lnRow = 2
   Do While lnRet > 0
      ActiveSheet.Cells(lnRow, 1) = oCustomer.GetValue("store_id")
      ActiveSheet.Cells(lnRow, 2) = oCustomer.GetValue("store_name")
      ActiveSheet.Cells(lnRow, 3) = oCustomer.GetValue("store_city")
      lnRet = oCustomer.MoveNext()
      lnRow = lnRow + 1
   Loop
   Refresh = 1
End Function

Esta macro creará una instancia de la clase customer y entonces llenará las filas y columnas de la hoja con datos del objeto de negocio.

Resumen

Hay muchas charlas alrededor del diseño de sistemas N-Tier. Algunas personas son fuertes defensoras de usar N-Tier para todo, en cambio otras personas sienten que el N-Tier es excesivo en muchos lugares. Mi opinión es que algo que yo pueda hacer para mejorar con el tiempo un sistema para mi cliente es bien esfuerzo bien hecho.

El diseño N-Tier me da la capacidad de manejar datos a través del uso de clases de las que se pueden hacer subclases y pueden ser especializadas dentro de VFP y al mismo tiempo pueden hacer que esas clases estén disponibles a otras herramientas de desarrollo que manteniendo el lugar solo modificando el acceso de datos. Esta sola situación es uno de los mayores beneficios logrados a través del diseño N-Tier. Si el cliente cambia el servidor de la base de datos a otra, hay sólo un lugar para hacer los cambios necesarios y todas las interfaces del usuario se actualizarán.

Envíe un email a Jim Booth con preguntas o comentarios sobre esta información.

12 de abril de 2016

Enlazar eventos de ventana

Autor: Doug Hennig
Traducido por: Ana María Bisbé York


Una característica disponible en otros entornos de desarrollo; pero ausente en VFP es la capacidad de capturar eventos de Windows. VFP 9 extiende la función BINDEVENT() para permitir que su propio código sea llamado cuando Windows pasa cierto mensaje a ventanas de VFP. Esto tiene un amplio rango de usos, algunos de ellos los examina en este documento, Doug Hennig.

Windows comunica eventos al pasar mensajes a las aplicaciones. Aunque VFP expone algunos de estos mensajes a través de eventos en objetos de VFP, tales como MouseDown y Click, muchos mensajes no están disponibles para desarrolladores VFP.

Un requerimiento común es la capacidad de detectar un intercambio de aplicación (application switch). Por ejemplo, creo una aplicación que entre en GoldMine, un popular sistema administrador de contactos que muestra información adicional sobre el contacto actual. Si el usuario activa GoldMine, se mueve a un contacto diferente y luego vuelve a mi aplicación, sería bueno refrescar la pantalla para que muestre información sobre el nuevo contacto. Desafortunadamente, no había manera de hacerlo en versiones anteriores de VFP; tenía que confiar en un cronómetro (timer) que verificara constantemente los contactos que estaban mostrándose actualmente en GoldMine.

VFP 9 extiende la función BINDEVENT() añadida en VFP 8 para que soporte mensajes de Windows. La sintaxis para su uso es:

bindevent(hWnd, nMessage, oEventHandler, cDelegate) 

En este caso, hWnd es el controlador de Windows para la ventana que recibe el evento, nMessage es el número del mensaje de Windows y oEventHandler y cDelegate son los objetos y métodos que se disparan cuando se recibe el mensaje por la ventana. A diferencia de los eventos VFP, sólo un controlador puede enlazar una combinación particular de hWnd y nMessage. Al especificar un segundo objeto controlador de evento o método delegado provoca que el primer enlace sea reemplazado por el segundo. VFP no verifica valores válidos de hWnd o nMessage; incluso si es no válido, no ocurre nada porque la ventana especificada no puede recibir el mensaje especificado.

Puede especificar _Screen.hWnd o _VFP.hWnd para hWnd de forma tal que atrape mensajes enviados a hWnd de la aplicación o un formulario para aquellos mensajes enviados al formulario. Los controles de VFP no tienen controlador de Windows, pero lo hace un control ActiveX, así que usted también puede enlazarlos.

Existen cientos de mensajes de Windows. Ejemplos de algunos mensajes son:

WM_POWERBROADCAST (0x0218), el que es enviado cuando ocurre un evento de encendido (power) como puede ser batería baja o intercambio a modo standbay; WM_THEMECHANGED (0x031A), el que indica que ha cambiado el tema de Windows XP; y WM_ACTIVATE (0x0006), que se inicia cuando se dispara para o desde de una aplicación. (Los mensajes de Windows se referencian usualmente por un nombre que comienza con WM_.) La documentación para casi todos los mensajes de Windows está disponible en http://msdn.microsoft.com/library/en-us/winui/winui/windowsuserinterface/windowui.asp. Los valores para las constantes WM_ están en el archivo WinUser.H que es parte de la plataforma SDK, la que se puede bajar desde www.microsoft.com/msdownload/platformsdk/sdkupdate.

El método controlador de eventos acepta cuatro parámetros: hWnd, el controlador para la ventana que recibe el mensaje; nMessage, el número de mensaje de Windows y dos parámetros enteros, el contenido de los cuales varía en dependencia de los mensajes Windows (la documentación para cada mensaje describe los valores de esos parámetros). El método debe devolver un valor Entero, que contiene un valor resultante. Uno de los valores posibles devueltos es BROADCAST_QUERY_DENY (0x424D5144, el que representa la cadena "BMQD") que advierte que el evento puede ocurrir.

Si desea que un mensaje sea procesado de forma normal, es decir que haga algo que el controlador de eventos debe hacer, debe llamar al controlador de mensajes de Windows de VFP en su método controlador de eventos; es algo como DODEFAULT() en código de un método VFP. Su método controlador de eventos probablemente devolverá el valor que fue devuelto por el controlador de eventos de Windows de VFP. He aquí un ejemplo de un controlador de eventos que hace esto (no hace otra cosa):

lparameters hWnd, ;
  Msg, ;
  wParam, ;
  lParam
local lnOldProc, ;
  lnResult
#define GWL_WNDPROC -4
declare integer GetWindowLong in Win32API ;
  integer hWnd, integer nIndex
declare integer CallWindowProc in Win32API ;
  integer lpPrevWndFunc, integer hWnd, integer Msg, ;
  integer wParam, integer lParam
lnOldProc = GetWindowLong(_screen.hWnd, GWL_WNDPROC)
lnResult = CallWindowProc(lnOldProc, hWnd, Msg, wParam, lParam)
return lnResult 

Por supuesto, el controlador de eventos no necesita declarar las funciones API de Windows ni llamar a GetWindowLong a cada momento; puede colocar este código en el método Init de la clase, guardando el valor devuelto de GetWindowLong en una propiedad de usuario, y luego utilizar esta propiedad en la llamada a CallWindowProc en el controlador de eventos. El resto de los ejemplos muestran este proceder.

Para determinar cuáles mensajes han saltado, utilice AEVENTS(ArrayName, 1). Esto llena la matriz específicada con una fila para enlazar y cuatro columnas, conteniendo los valores de los parámetros pasados a BINDEVENT().

Puede desenlazar eventos utilizando UNBINDEVENT(hWnd [, nMessage ]). Al omitir el segundo parámetro desenlaza todos los mensajes para la ventana especificada. Pase sólo 0 para desenlazar todos los mensajes de todas las ventanas. Los eventos son automáticamente desenlazados además la siguiente vez que ocurre el mensaje después que el evento controlando el objeto es destruido.

¿Qué sería una nueva versión de VFP sin nuevas funciones SYS()? El equipo VFP agregó 3 funciones SYS() relativas a eventos en VFP 9:

  • SYS(2325, wHandle) devuelve el wHandle (un envoltorio interno VFP para hWnd) para la ventana cliente de la ventana de la que es pasado wHandle como un parámetro. (Una ventana cliente es una ventana dentro de una ventana, por ejemplo, _Screen es una ventana cliente de _VFP.)
  • SYS(2326, hWnd) devuelve el wHandle para la ventana especificada con hWnd.
  • SYS(2327, wHandle) devuelve el hWnd para la ventana especificada con wHandle. La documentación para estas funciones indican que son para escenarios BINDEVENT() utilizando el VFP API Library Construction Kit. Sin embargo, puede además utilizar el hWnd para la ventana cliente de una ventana de IDE de VFP, como verá en el siguiente ejemplo.

Enlazar con eventos de ventanas de IDE VFP

TestWinEventsForIDE.PRG, incluido en esta descarga mensual, demuestra el enlace de eventos con ventanas del IDE VFP. Haga lcCaption igual al título de la ventana del IDE que desea enlazar eventos a (el siguiente código utiliza la ventana Comandos), luego ejecutar el programa. Active y desactive la ventana, muévala, redimensiónela, etc. Debe ver los eventos de la ventana mostrados en la pantalla. Al finalizar, escriba RESUME y escriba Enter en la ventana Comandos para limpiarla. Para probar con una ventana cliente de la ventana IDE, elimine el comentario del código indicado.

Puede enlazar además otros eventos, agregando sentencias BINDEVENT() de este código, utilice las constantes en WinEvents.H para el valor de los eventos deseados. Note que TestWinEventsForIDE.PRG trabaja solamente con las ventanas no ancladas del IDE, entonces antes de que ejecute este programa, haga clic derecho en la barra de título de la ventana que desea probar y asegúrese de que la propiedad Dockable está establecida a OFF.

He aquí el código para este PRG:

#include WinEvents.H
lcCaption      = 'Command'
loEventHandler = createobject('IDEWindowsEvents')
lnhWnd         = ;
  loEventHandler.FindIDEWindow(lcCaption)
* Quite los comentarios a este código para recibir eventos 
* en lugar de los usuarios de windows 
*lnhWnd         = ;
  loEventHandler.FindIDEClientWindow(lcCaption)
if lnhWnd > 0
  bindevent(lnhWnd, WM_SETFOCUS,      loEventHandler, ;
    'EventHandler')
  bindevent(lnhWnd, WM_KILLFOCUS,     loEventHandler, ;
    'EventHandler')
  bindevent(lnhWnd, WM_MOVE,          loEventHandler, ;
    'EventHandler')
  bindevent(lnhWnd, WM_SIZE,          loEventHandler, ;
    'EventHandler')
  bindevent(lnhWnd, WM_MOUSEACTIVATE, loEventHandler, ;
    'EventHandler')
  bindevent(lnhWnd, WM_KEYDOWN,       loEventHandler, ;
    'EventHandler')
  bindevent(lnhWnd, WM_KEYUP,         loEventHandler, ;
    'EventHandler')
  bindevent(lnhWnd, WM_CHAR,          loEventHandler, ;
    'EventHandler')
  bindevent(lnhWnd, WM_DEADCHAR,      loEventHandler, ;
    'EventHandler')
  bindevent(lnhWnd, WM_KEYLAST,       loEventHandler, ;
    'EventHandler')
  clear
  suspend
  unbindevents(0)
  clear
else
  messagebox('La ventana ' + lcCaption + ;
    ' no ha sido encontrada.')
endif lnhWnd > 0
define class IDEWindowsEvents as Custom
  cCaption = ''
  nOldProc = 0
  function Init
    declare integer GetWindowLong in Win32API ;
      integer hWnd, integer nIndex
    declare integer CallWindowProc in Win32API ;
      integer lpPrevWndFunc, ;
      integer hWnd, integer Msg, ;
      integer wParam, integer lParam
    declare integer FindWindowEx in Win32API;
      integer, integer, string, string
    declare integer GetWindowText in Win32API ;
      integer, string @, integer
    This.nOldProc = GetWindowLong(_screen.hWnd, ;
      GWL_WNDPROC)
  endfunc
  function FindIDEWindow(tcCaption)
    local lnhWnd, ;
      lnhChild, ;
      lcCaption
    This.cCaption = tcCaption
    lnhWnd        = _screen.hWnd
    lnhChild      = 0
    do while .T.
      lnhChild = FindWindowEx(lnhWnd, lnhChild, 0, 0)
      if lnhChild = 0
        exit
      endif lnhChild = 0
      lcCaption = space(80)
      GetWindowText(lnhChild, @lcCaption, len(lcCaption))
      lcCaption = upper(left(lcCaption, ;
        at(chr(0), lcCaption) - 1))
      if lcCaption = upper(tcCaption)
        exit
      endif lcCaption = upper(tcCaption)
    enddo while .T.
    return lnhChild
  endfunc
  function FindIDEClientWindow(tcCaption)
    local lnhWnd, ;
      lnwHandle, ;
      lnwChild
    lnhWnd = This.FindIDEWindow(tcCaption)
    if lnhWnd > 0
      lnwHandle = sys(2326, lnhWnd)
      lnwChild  = sys(2325, lnwHandle)
      lnhWnd    = sys(2327, lnwChild)
    endif lnhWnd > 0
    return lnhWnd
  endfunc
  function EventHandler(hWnd, Msg, wParam, lParam)
    ? 'La ventana ' + This.cCaption + ;
      ' recibió el evento #' + transform(Msg)
    return CallWindowProc(This.nOldProc, hWnd, Msg, ;
      wParam, lParam)
  endfunc
enddefine

El programa comienza instanciando la clase IDEWindowsEvents. Llama al método FindIDEWindow que toma un controlador a la ventana de la que se ha guardado el título en la variable lcCaption. Emplea entonces BINDEVENT() para enlazar ciertos eventos desde la ventana deseada al método EventHandler de la clase. Estos eventos incluyen la activación, desactivación, redimensionamiento, y movimiento de la ventana, y oprimir teclas dentro de la ventana.

El método Init de la clase IDEWindowsEvents declara las funciones Windows API utilizadas por la clase. Esto también determina el valor utilizado para llamar al mensaje del controlador de Windows VFP y lo guarda en la propiedad nOldProc; este valor es utilizado por el método EventHandler para asegurar que ha ocurrido un control normal de eventos. El método FindIDEWindow utiliza una pareja de funciones Windows API para buscar la ventana del IDE de VFP especificada. Esto lo hace buscando cada ventana hija de _VFP para ver si su título coincide con el título pasado como un parámetro. FindIDEClientWindow hace algo similar; pero utiliza las nuevas funciones SYS() para tomar el controlador para la ventana cliente de la ventana especificada.

Al ejecutar TestWinEventsForIDE.PRG, encontrará que no todos los eventos ocurren para todas las ventanas del IDE o de usuarios. Por ejemplo, no verá los eventos keypress para la ventana Propiedades. Esto es probablemente debido a la forma en que VFP implementa ventanas, las que son algo diferentes de otras aplicaciones Windows.

Nota: No utilizará este tipo de código en una aplicación típica, se dirige a los desarrolladores que deseen agregar este comportamiento al IDE VFP. El siguiente ejemplo es algo que puede utilizar en una aplicación final de usuario.

Enlazar la ventana aplicación y eventos de disco

WindowsMessagesDemo.SCX (vea Figura 1) demuestra el gancho en eventos Activate y Deactivate y eventos Shell de Windows, tales como insertar o eliminar un disco compacto o USB. El código siguiente muestra un uso interesante de eventos de Windows: El código registra _VFP para recibir un subconjunto de eventos shell de Windows como un evento de usuario de Windows.


Figura 1

El método Init de este formulario controla la configuración necesaria. Como con TestWinEventsForIDE.PRG declara varias funciones APIs de Windows y almacena el valor para el controlador de evento de Windows VFP en la propiedad nOldProc. La llamada a SHChangeNotifyRegister dice a Windows que registre _VFP para recibir eventos de disco, eventos de insertar o desactivar dispositivos de media o unidades de disco, utilizando un mensaje de usuario WM_USER_SHNOTIFY. (Los elementos en letras mayúsculas en este ejemplo son constantes definidas en WinEvents.H o ShellFileEvents.H.)

Este código enlaza luego los eventos activate del formulario y los cambios de dispositivos y el mensaje de usuario definido para _VFP por el método HandleEvents en el formulario.

Nota: La llamada a SHChangeNotifyRegister requiere Windows XP o posterior. Si utiliza un sistema operativo anterior, comente la asignación This.nSHNotify

local lcSEntry
* Declaramos las funciones API de Windows que vamos a utilizar.
declare integer GetWindowLong in Win32API ;
  integer hWnd, integer nIndex
declare integer CallWindowProc in Win32API ;
  integer lpPrevWndFunc, integer hWnd, integer Msg, ;
  integer wParam, integer lParam
declare integer SHGetPathFromIDList in shell32 ;
  integer nItemList, string @szPath
declare integer SHChangeNotifyRegister in shell32 ;
  integer hWnd, integer fSources, integer fEvents, ;
  integer wMsg, integer cEntries, string @SEntry
declare integer SHChangeNotifyDeregister in shell32 ;
  integer
* Tomamos un controlador para el controlador de eventos de Windows en VFP.
This.nOldProc = GetWindowLong(_screen.hWnd, ;
  GWL_WNDPROC)
* Nos registramos para recibir cierto evento shell  
* como un evento de usuario de Windows.
lcSEntry = replicate(chr(0), 8)
This.nShNotify = SHChangeNotifyRegister(_vfp.hWnd, ;
  SHCNE_DISKEVENTS, SHCNE_MEDIAINSERTED + ;
  SHCNE_MEDIAREMOVED + SHCNE_DRIVEADD + ;
    SHCNE_DRIVEREMOVED, WM_USER_SHNOTIFY, 1, @lcSEntry)
* Enlazamos con el evento de Windows que estamos interesados.
bindevent(This.hWnd, WM_ACTIVATE,      This, ;
  'HandleEvents')
bindevent(_vfp.hWnd, WM_DEVICECHANGE,  This, ;
  'HandleEvents')
bindevent(_vfp.hWnd, WM_USER_SHNOTIFY, This, ;
  'HandleEvents')
* Ocultamos la ventana principal de VFP para que sea más fácil ver lo que ocurre
_screen.Visible = .F.

El método HandleEvents controla los eventos registrados. Utiliza una sentencia CASE para determinar qué evento ocurrió y actualizar el título de la etiqueta de estado adecuadamente en el formulario. Ciertos tipos de eventos tienen "subeventos" como se ha indicado por el parámetro wParam; esto es utilizado para determinar qué evento ocurrió exactamente. Por ejemplo, cuando ocurre un evento WM_ACTIVATE, wParam especifica si la ventana ha sido activada o desactivada, y si la activación ocurrió por el conmutador de tareas (task switching) (como Alt+Tab) o por hacer Clic en la ventana.

Controlar el evento de usuario shell es un poco más complicado que otros eventos. En ese caso, lParam especifica el evento wParam y contiene la dirección donde se guarda la ruta para el disco que fue insertado o removido. Entonces, SYS(2600), se utiliza para copiar el valor desde la dirección, el método de usuario BinToInt (no se muestra aquí) convierte el valor en un entero, y la función API de Windows SHGetPathFromIDList proporciona el camino actual desde el entero. Finalmente, el método llama al método
HandleWindowsMessage, el que simplemente llama a CallWindowProc para tomar el comportamiento normal de controlador de eventos. He aquí el código para HandleEvents:

lparameters hWnd, ;
  Msg, ;
  wParam, ;
  lParam
local lcCaption, ;
  lnParm, ;
  lcPath
do case
* Controla un evento de activar o desactivar.
  case Msg = WM_ACTIVATE
    do case
* Controla un evento de desactivar.
      case wParam = WA_INACTIVE
        This.lblStatus.Caption = 'Window deactivated'
* Controla un evento activar (activar una tarea o hacer clic en una barra de título)
      case wParam = WA_ACTIVE
        This.lblStatus.Caption = ;
          'Window activated (task switch)'
* Controla un evento activar (hace clic en el área de usuario de una ventana)
      case wParam = WA_CLICKACTIVE
        This.lblStatus.Caption = ;
          'Ventana activada (clic)'
    endcase
* Controla un evento de cambio de dispositivo (device).
  case Msg = WM_DEVICECHANGE
    do case
      case wParam = DBT_DEVNODES_CHANGED
        This.lblStatus.Caption = 'DevNodes cambiado'
      case wParam = DBT_DEVICEARRIVAL
        This.lblStatus.Caption = 'Dispositivo integrado'
      case wParam = DBT_DEVICEREMOVECOMPLETE
        This.lblStatus.Caption = 'Dispositivo retirado ' + ;
          'complete'
    endcase
* Controla un evento de notificación de shell de usuario.
  case Msg = WM_USER_SHNOTIFY
    do case
      case lParam = SHCNE_DRIVEADD
        lcCaption = 'Disco agregado'
      case lParam = SHCNE_DRIVEREMOVED
        lcCaption = 'Disco retirado'
      case lParam = SHCNE_MEDIAINSERTED
        lcCaption = 'Media insertada'
      case lParam = SHCNE_MEDIAREMOVED
        lcCaption = 'Media retirada'
    endcase
    lnParm = This.BinToInt(sys(2600, wParam, 4))
    lcPath = space(270)
    SHGetPathFromIDList(lnParm, @lcPath)
    lcPath = left(lcPath, at(chr(0), lcPath) - 1)
    This.lblStatus.Caption = lcCaption + ': ' + lcPath
endcase
return This.HandleWindowsMessage(hWnd, Msg, wParam, ;
  lParam)

Ejecute el formulario como se indica en las instrucciones. Haga clic en otra ventana o el escritorio y nuevamente en el formulario para eventos activar o desactivar. Agregue o elimine un dispositivo de algún tipo, como disco USB o una cámara digital, para ver que evento ocurrió.

Existen algunos usos prácticos para el tipo de código mostrado en este formulario. Por ejemplo, el GoldMine integrado que he mencionado al inicio del artículo puede refrescarse cuando recibe un activate. Al adjuntar la cámara digital a mi PC con un cable USB, el software que viene con la cámara se activa y me pregunta por las imágenes a bajar. Una aplicación inmobiliaria o de medicina con tratamiento de imágenes puede podría hacer algo similar con imágenes de casas o de medicina.

Otros usos

Existe gran cantidad de otros usos para eventos de Windows. Por ejemplo, puede desear prevenir Windows para terminar sesión bajo ciertas condiciones, como son unos procesos de importación que no han sido completados. En este caso puede enlazar con el mensaje WM_POWERBROADCAST y devuelve BROADCAST_QUERY_DENY si se detiene la descarga.

Utilizo Microsoft Money para hacer mis financias domésticas y me ha gustado siempre el hecho que cuando descargo un dato de mi banco, Money lo sabe inmediatamente y muestra el diálogo apropiado. El tipo de comportamiento es ahora posible en una aplicación VFP, en lugar de elegir constantemente un directorio para ver si un archivo ha sido agregado (o removido o renombrado o lo que sea), su aplicación puede notificar tan pronto como ocurra y toma la acción apropiada.

Resumen

Poder enlazar con eventos de Windows es una mejora increíble a VFP; permite hondar casi dentro de cualquier cosa que ocurra en Windows. Espero ver muchos usos interesantes de esto en cuánto la comunidad de VFP comience a aprender sobre estas capacidades.


9 de abril de 2016

Determinar el tiempo de encendido del PC

Esta es una rutina que nos permitira saber el tiempo que el PC tiene encendido.

DECLARE INTEGER GetTickCount IN kernel32.DLL
lnSeconds = GetTickCount() / 1000
lnHours = INT (lnSeconds/3600)
lnSeconds = lnSeconds - lnHours * 3600
lnMinutes = INT (lnSeconds/60)
lnSeconds = lnSeconds - lnMinutes * 60
? "Encendido desde hace: " ;
   + TRANSFORM(lnHours) + " horas " ;
   + TRANSFORM(lnMinutes) + " minutos " ;
   + TRANSFORM(INT(lnSeconds)) + " segundos."
Mauricio Henao Romero

6 de abril de 2016

Crear una hoja de Excel con SubTotales

Con el siguiente código y utilizando Automation, podemos crear una hoja Excel con SubTotales. Cortesía de Çetin Basöz, MVP de VFP.

OPEN DATABASE (HOME(2) + "Northwind\Northwind.dbc")
SELECT o.CustomerId, o.OrderId, ProductId, UnitPrice, Quantity ;
  FROM Orders o inner JOIN OrderDetails od ON o.OrderId = od.OrderId ;
  ORDER BY o.CustomerId, o.OrderId ;
  INTO CURSOR crsTemp
lcXLSFile = SYS(5) + CURDIR() + "myOrders1.xls"
COPY TO (lcXLSFile) TYPE XLS
CLOSE DATABASES ALL
DIMENSION laSubtotal[3]
laSubtotal[1] = 4 && Unit_price
laSubtotal[2] = 5 && Quantity
laSubtotal[3] = 6 && Will use later
#DEFINE xlSum -4157
oExcel = CREATEOBJECT("excel.application")
WITH oExcel
  .Workbooks.OPEN(lcXLSFile)
  WITH .ActiveWorkbook.ActiveSheet
    lnRows = .UsedRange.ROWS.COUNT && Get current row count
    lcFirstUnusedColumn = _GetChar(laSubtotal[3]) && Get column in Excel A1 notation
    * Instead of orders order_net field use Excel calculation for net prices
    .RANGE(lcFirstUnusedColumn + '2:' + ;
      lcFirstUnusedColumn + TRANSFORM(lnRows)).FormulaR1C1 = ;
      "=RC[-2]*RC[-1]"
    .RANGE(lcFirstUnusedColumn+'1').VALUE = 'Extended Price' && Place header
    .RANGE('D:'+lcFirstUnusedColumn).NumberFormat = "$#,##0.0000" && Format columns
    * Subtotal grouping by customer then by order
    .UsedRange.Subtotal(1, xlSum, @laSubtotal)
    .UsedRange.Subtotal(2, xlSum, @laSubtotal,.F.,.F.,.F.)
    .UsedRange.COLUMNS.AUTOFIT && Autofit columns
  ENDWITH
  .VISIBLE = .T.
ENDWITH
* Return A, AA, BC etc notation for nth column
FUNCTION _GetChar
  LPARAMETERS tnColumn && Convert tnValue to Excel alpha notation
  IF tnColumn = 0
    RETURN ""
  ENDIF
  IF tnColumn <= 26
    RETURN CHR(ASC("A") - 1 + tnColumn)
  ELSE
    RETURN  _GetChar(INT(IIF(tnColumn % 26 = 0, tnColumn - 1, tnColumn) / 26)) + ;
      _GetChar((tnColumn-1) % 26 + 1)
  ENDIF
ENDFUNC

Çetin Basöz
MS Foxpro MVP, MCP

4 de abril de 2016

LockScreen mejorado

Algunas veces es necesario utilizar APIs de Windows para asegurarnos que LockScreen funcione como debería... Marcia G. Akins nos ofrece una rutina para ello.

***********************************************************************
* Program....: REALLYLOCKSCREEN
* Purpose....: Use the Windows API to force a true screen lock. 
* ...........: Calling this lock function with a handle of 0 unlocks all locked windows
* Developer..: Marcia G. Akins
***********************************************************************
LPARAMETERS tlLock
LOCAL ARRAY laJunk[1]
LOCAL lnHWnd, lnRes
**********************************************************
*** Check that the library has been set up and open it if not already done.
**********************************************************
lnRes = ADLLS( laJunk )
IF lnRes = 0 OR  NOT ( ASCAN( laJunk, 'LockWindowUpdate', 1, -1, 1, 15 ) > 0)
  *** We don't have the function available
  DECLARE INTEGER LockWindowUpdate IN Win32API INTEGER nHandle 
ENDIF
*** Now set the Handle to lock according to the parameter
lnHWnd = IIF( tlLock, ThisForm.HWnd, 0 )
*** And call the function
LockWindowUpdate( lnHWnd )

RETURN

Regards,
Marcia G. Akins

Esparta Palma

2 de abril de 2016

Pasar mas de 27 parámetros a función o procedimiento.

Visual FoxPro tiene un límite de 27 parámetros en el uso de funciones, a continuación una manera de darle vuelta a este pequeño problema... Las funciones y procedimientos de VFP tienen este límite de 27 parámetros, no hay forma de agregar más, pero si usas objetos este límite puede ser sobrepasado fácilmente:

oMyParam = CREATEOBJECT("Empty")

AddProperty(oMyParam,"cClave","1")
AddProperty(oMyParam,"nID",2)
AddProperty(oMyParam,"dFecha",DATE())
AddProperty(oMyParam,"yMonto",$100)

nMyResult = MyFuntion(oMyParam)

FUNCTION myFunction
LPARAMETERS toMyParam

lcClave = toMyParam.cClave
lnID = toMyParam.nID
ldFecha = toMyParam.dFecha
lyMonto = toMyParam.yMonto
......
......

ENDFUNC

Esto también aplica para métodos dentro de un formulario o clase. En el ejemplo he utilizado la clase EMPTY (disponible en VFP8 y posteriores), pero puede sustituirse por cualquiera, se recomienda usar alguna "ligera" y sin interface (Forms, Grids, Pageframe, etc) como puede ser la clase Custom.

También está disponible el utilizar un arreglo, pero no lo recomiendo mucho, esas técnicas son muy anticuadas, tienen sus limitantes (capacidad del array) y molestias (como tener que declarar dicha variable como EXTERNAL).

Espero que les sea de utilidad.

Espartaco Palma Martínez