viernes, noviembre 18, 2011

VFP: Clausula READWRITE para VFP 6

Como sabemos, los cursores creados por el comando SELECT INTO en VFP son solo lectura. A partir de VFP 7 se incluyo una clausula llamada READWRITE que nos permite indicar si queremos que el cursor sea de lectura-escritura, lo cual es sumamente útil en infinidad de situaciones.

Sin embargo, para los que por algún motivo aun debemos seguir trabajando con versiones anteriores a VFP 7, convertir un cursor a lectura-escritura representa todo un reto.  El truco mas usado para lograr esto en VFP 6 es usar la clausula NOFILTER para asegurarnos que VFP creara un cursor fisico temporal, hacer una copia fisica del cursor temporal con otro nombre, cerrar el cursor original y luego abrir el cursor temporal con el nombre original del cursor:

SELECT * FROM mitabla INTO CURSOR Q1 NOFILTER
SELECT Q1
COPY FILE (DBF("Q1")) TO ("Q1.TMP")
USE IN Q1
SELECT 0
USE ("Q1.TMP") ALIAS Q1

Pero esta solución dista mucho de ser perfecta, especialmente si el cursor original contaba con columnas cuyo nombre superara los 10 caracteres. En estos casos, el nombre de esas columnas era truncado lo cual causaba molestos problemas con las referencias a esas columnas en el código.

Hace un tiempo ayudé a un amigo a encontrar una forma de solventar este problema, y me encontré con que, como siempre, VFP me daba todas las herramientas necesarias para lograrlo; era solo cuestión de ponerlas a trabajar juntas.

En este caso, la ayuda llegó de la mano de los arrays.  Yo sabía desde siempre que VFP contaba con una función que tomaba la estructura de un DBF o cursor y la almacenaba en un array.  Lo que no sabía es que tanto el comando CREATE CURSOR como INSERT INTO contenían la clausula FROM ARRAY.

El resultado?  una función que recibe el nombre de un cursor solo-lectura y lo convierte en lectura-escritura respetando los nombres largos de columna.

Aqui les dejo la función por si les resulta útil (NOTA Nov 19: limpié y mejoré un poco el código, ademas de corregir un problema cuando el cursor original estaba vacío).

* openForUpdate()
* Función que recibe un cursor de S/L y lo convierte en L/E respetando las columnas con nombres largos
*
* Autor: Victor Espina
*
PROCEDURE openForUpdate(pcCursor)
 *
 * Obtenemos el schema del cursor
 LOCAL ARRAY aSchema[1]
 AFIELDS(aSchema, pcCursor)

 * Obtenemos el contenido del cursor
 LOCAL ARRAY aRows[1]
 LOCAL nRowCount
 SELECT * FROM (pcCursor) INTO ARRAY aRows
 nRowCount = _TALLY

 * Cerrar el cursor original, lo recreamos como un cursor de L/E
 * y lo llenamos con los datos originales (si habia)
 SELECT (pcCursor)
 USE
 CREATE CURSOR (pcCursor) FROM ARRAY aSchema
 IF nRowCount > 0 
  INSERT INTO (pcCursor) FROM ARRAY aRows
 ENDIF
 GO TOP 
 *
ENDPROC

martes, noviembre 15, 2011

VFP: Crear un buffer rapidamente

Primero, aclaremos conceptos. Llamo "buffer" a un objeto muy liviano cuya unica función es contener data temporal en forma de propiedades. En muchos sentidos vendria a ser como una versión light de un STRUCT, solo que sin la posibilidad de incluir métodos; solo propiedades.

Por ejemplo, si quiero pasar información sobre una persona de un form a otro, me seria muy útil pasar un solo parámetro que contuviera toda la información de esa persona.  Una forma de lograr esto es crear una clase "Persona" que contenga las propiedades para almacenar la información sobre una persona, pero muchas veces queremos lograr lo mismo sin la sobrecarga de crear una clase que en si misma no hace nada.

Ese objeto "temporal" que sirve para almacenar múltiples informaciones relacionadas, es lo que llamo "buffer".  La función crearBuffer hace justamente eso: crea un buffer con una lista de propiedades dada y, opcionalmente, inicializa esas propiedades con los datos suministrados; todo en una misma instrucción.

Ejemplo:

LOCAL oPersona
oPersona = crearBuffer("nombre,apellido,cedula,fechaNac,cargo","Victor","Espina","12345678",{18-11-1970})

?oPersona.Nombre --> "Victor"
?oPersona.Apellido --> "Espina"


Práctico, cierto?  aqui les dejo el código fuente.

*-- crearBuffer
*   Funcion para crear un buffer de datos e inicializarlo. Compatible con VFP 5 o superior
*
*   Autor: V Espina
*
*   Ejemplo:
*   oBuff = CFDBuffer("Nombre,Apellido","Victor","Espina")
*   ?oBuff.Nombre -> "Victor"
*   ?oBuff.Apellido -> "Espina"
*
PROCEDURE crearBuffer
LPARAMETERS pcItemList,p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16,p17,p18,p19
 *
 LOCAL oBuff,i,cProp
 oBuff=CREATEOBJECT("Custom")

 LOCAL cPropName,uPropValue,nCount
 LOCAL ARRAY aProps[1]
 nCount = ALINES(aProps,STRT(pcItemList,",",CHR(13)+CHR(10)))
 FOR i=1 TO MIN(nCount,20)
  cPropName = aProps[i]
  uPropValue = EVALUATE("P" + ALLTRIM(STR(i - 1)))
  oBuff.AddProperty(cPropName, uPropValue)
 ENDFOR
 
 RETURN oBuff
 *
ENDPROC

lunes, noviembre 07, 2011

VFP: Como crear un dll en .NET y usarlo desde VFP


Alguien una vez dijo que no es cuestion de cambiarse de un lenguaje a otro sino de usar la herramienta adecuada al problema que se desea resolver. C# es excelente para muchas cosas en las que VFP no es tan bueno, por lo que el poder crear un DLL en C# y usarlo en VFP se convierte en algo muy poderoso.

Aqui les dejo todo un conjunto de tips para lograr justamente esto:

1. En tu clase C# debes incluir el namespace System.Runtime.InteropServices;

2. La clase que deseas accesar desde VFP debe estar declarada como public y tener estas directivas:

[ClassInterface(ClassInterfaceType.AutoDual)]
[ProgId("className")]

donde "className" es el nombre OLE de la clase, es decir, el que usaras en el CREATEOBJECT en VFP.

3 Los metodos de la clase que quieras accesar desde VFP deben estar declarados como public y no ser estaticos.

4. Los metodos publicos de la clase no pueden tener sobrecargas, es decir, multiples versiones del mismo metodo con diferentes parametros.

5. Si un metodo devuelve un tipo estructurado, este tipo debe ser creado como class (no como struct) y cumplir con todas las condiciones indicadas en los puntos 1, 2, 3 y 4.

6. Si un metodo devuelve un array de cualquier tipo (ej, string[]), VFP lo recibe como un tipo ARRAY y no como un objeto (como lo es en .NET), por lo que no podremos acceder a las propiedaes del array, tal como Lenght, pero si a sus items:

aItems = myClass.MyMethod()
FOR EACH oItem IN aItems
 ?oItem.Property
ENDFOR

o

aItems = myClass.MyMethod()
FOR i = 1 TO ALEN(aItems,1)
oItem = aItems[i]
 ?oItem.Property
ENDFOR

7. Antes de compilar la clase, debes ir a las propiedades del proyecto, Application, boton Assembly Information y marcar la casilla "Make assembly COM-visible"

8. Debes firmar la DLL. Para esto, sigue los pasos indicados aqui

9. Una vez compilada la dll, la misma debe ser registrada de la siguiente forma antes de poder ser usada en VFP:

Windows 32 bits:
C:\WINDOWS\microsoft.net\framework\v2.0.50727\regasm mylib.dll /register /codebase /tlb

Windows 64 bits
C:\WINDOWS\microsoft.net\framework64\v2.0.50727\regasm mylib.dll /register /codebase /tlb