26 de diciembre de 2002

Validar CIF (Código de Identificacion Fiscal) [Solo España]

Función para validar CIF (Código de Identificación Fiscal)
? ValCif("01234567D")

*----------------------------------------------------------------------
* Jose Maria Arrabal Alcañiz
* Funcion para validar CIF (Código de Identificacion Fiscal)
* Entrada: Cadena
* Salida : Cadena con CIF calculado
*----------------------------------------------------------------------
FUNCTION ValCIF
  PARAMETER cdni
  PRIVATE A
  LOCAL impar,par,simpar,SPAR
  A = UPPER(ALLTRIM(CHRTRAN(cdni,",.-","")))
  IF LEN(A) # 9
    WAIT WINDOW "Error en la longitud del CIF" TIME 3
    RETURN ""
  ENDIF
  cNumero = SUBSTR(A,2,7)
  IF PADL(VAL(cNumero),7,"0") # cNumero
    WAIT WINDOW "Error en la contrucción del CIF" TIME 3
    RETURN
  ENDIF
  cLetra = LEFT(A,1)
  *
  * Sumar cifras pares y las impares las multiplicamos por 2
  *
  SPAR = 0
  simpar = 0
  par = 0
  impar = 0
  FOR nI = 1 TO 7 STEP 2
    impar = VAL(SUBSTR(cNumero,nI,1))*2
    impar = INT(impar/10) + impar%10
    par = VAL(SUBSTR(cNumero,nI+1,1))
    simpar = simpar+impar
    SPAR = SPAR+par
  NEXT
  R = 10-(SPAR+simpar)%10
  cCif = cLetra+cNumero
  IF cLetra $ "ABCDEFGH"
    cCif = cCif+CHR(ASC('0')+R)
  ELSE
    cCif = cCif+SUBSTR("JABCDEFGHI",R+1,1)
  ENDIF
  IF A # cCif
    MESSAGEBOX("El CIF "+A+" introducido es incorrecto "+CHR(13)+;
      "El correcto sería "+cCif+CHR(13)+;
      "Compruebe que los datos son correctos",16)
  ENDIF
  RETURN cCif
ENDFUNC

*----------------------------------------------------------------------
Jose Maria Arrabal Alcañiz

22 de diciembre de 2002

Obtener Fecha y Hora del Servidor

Si desea saber la hora del servidor sin importar el sistema operativo que use, aquí tenemos, basándonos en las recomendaciones de Alex Feldstein (MS MVP), una función para obtener la fecha y hora del servidor (tipo DateTime) que funcionará si usa Novell Netware, Microsoft Windows (cualquier versión), incluso Linux (si activa el demonio SAMBA).

Lo único que se necesita es tener derechos de escritura en la carpeta, folder o recurso compartido del servidor.
ltFecha = ServerTime("F:/")
Pasandole de Parametro un recurso compartido:
ltFecha = ServerTime("//SERVIDOR/VALUACION")
Si no se pasan parametros, tomara por default la fecha del equipo donde se ejecute el programa:
ltFecha = ServerTime()
Espero que les sea de utilidad.

Espartaco Palma Martínez

******************************************************************
* ---- Server DateTime ----- Fecha y Hora del Servidor
* ---- Espartaco Palma Martinez esparta@NO_SPAMsofthome.net
* ---- FUNCTION: ServerTime   
* ---- RETURNS: Server DateTime, NULL if cannot get DateTime
* ---- RETORNA: Fecha y Hora Server, NULL si no puede obtenerlo 
* ---- PARAMETERS: tcPath - TYPE: Character 
* ---- Basandose en las recomendaciones de Alex Feldstein (MS MVP)
* ---- sobre como obtener la fecha y hora del servidor.
* ---- DATE: 03/DIC/2002 For Use in Visual FoxPro.
* ---- Sample:
* ---- ltFecha = FechaHora("F:/")
* ---- ltFecha = FechaHora("//SERVER/VALUACION/")
* ---- NOTE: If you are using minor versions of VFP7, use FoxTools
* ---- NOTA: Si esta usando versiones menores a VFP7, use FoxTools
******************************************************************
FUNCTION ServerTime (tcPath)
LOCAL ltFileTime
tcPath = IIF(TYPE('tcPath') # 'C', SPACE(0),ADDBS(tcPath))
lcFile = tcPath+SUBSTR(SYS(2015), 4) +".tmp"
IF STRTOFILE("Getting Hour/Obteniendo la Hora",lcFile) > 0
   ltFileTime = FDATE(lcFile,1)
   ERASE (lcFile)
   RETURN ltFileTime
ENDIF 
RETURN NULL
******************************************************************

19 de diciembre de 2002

Formulario Triangular

Muchos de nosotros estamos cansados de los tipicos formularios rectangulares, ahora podemos hacer un formulario triangular de una manera simple ...

Coloca un formulario y escribe en el INIT:
#DEFINE C_ALTERNATE 1
#DEFINE C_WINDING 2

declare integer CreatePolygonRgn in gdi32 ;
   string@ ppoints, integer npoints, integer nfillmode
declare integer SetWindowRgn in user32 ;
   integer hWnd, integer hRgn , integer bRedraw

cPoints = num2dword(0)+num2dword(0);
   +num2dword(THIS.Width+(sysmetric(13)*1));
   +num2dword(THIS.Height+sysmetric(9)+(sysmetric(12)*2));
   +num2dword(0)+num2dword(THIS.Height+sysmetric(9)+(sysmetric(12)*2))

set library to foxtools.fll

lnw = _WFindTitl(THIS.Caption)
lnh = _WhToHWnd(lnw)
lnr = CreatePolygonRgn(@cPoints, 3, C_WINDING)

SetWindowRgn(lnh, lnr, 1)
return
Luego, crea un .PRG llamado num2dword y coloca lo siguiente:
procedure Num2dword
lparameter tnNum
local c0,c1,c2,c3
lcresult = chr(0)+chr(0)+chr(0)+chr(0)
if tnNum < (2^31 - 1) then
   c3 = chr(int(tnNum/(256^3)))
   tnNum = mod(tnNum,256^3)
   c2 = chr(int(tnNum/(256^2)))
   tnNum = mod(tnNum,256^2)
   c1 = chr(int(tnNum/256))
   c0 = chr(mod(tnNum,256))
   lcresult = c0+c1+c2+c3
else
   * no es un numero valido para DWORD
endif
return lcresult
Mauricio Henao Romero

13 de diciembre de 2002

Impedir el cambio de dimension de una columna en un grid

Es muy fácil impedir la re-dimension de una columna de un grid, solo basta con:
THISFORM.Grid1.SetAll("Resizable", .F., "Column")
Mauricio Henao Romero

8 de diciembre de 2002

Ejecutar comandos DOS en forma oculta

DECLARE LONG WinExec IN kernel32 ;
   STRING lpCmdLine , ;
   LONG nCmdShow

cComando=" regsvr32.exe micontrol.ocx"

WinExec(cComando, 0)

  • 0 - no se muestra (se ejecuta, pero no se hace visible)
  • 1 - la Ventana se Muestra Normal
  • 2 - minimizado
  • 3 - Maximizado
  • 4 - Tamaño Normal, pero no le entrega el Foco al Programa/Comando que se ejecuta

Esto se puede implementar mejor, colocando este código en una función y pasando por parámetros el comando a ejecutar y el forma de visualización. Los comandos a ejecutar pueden ser de DOS o WINDOWS.

Horacio M. Kohler (HK)

5 de diciembre de 2002

Exportar favoritos

Exportar la carpeta favoritos de IE a una página HTML.

Probado en W98, 2000 y XP

x = createobject("Shell.UIHelper.1")
x.ImportExportFavorites(.f.,"c:/Favoritos.Html")
x = .null.
release x

Tatis

2 de diciembre de 2002

Un servidor de base de datos económico con VFP y COM+

Publicado originalmente en UTMag/Rapozine
Introducción

La idea de este artículo vino a mí contestando una pregunta en un grupo de noticias que ya había respondido varias veces de diferente manera. La pregunta era acerca de cómo usar Visual FoxPro como un servidor de base de datos. Muchas veces quien hace la pregunta originalmente no llega a tener incluso lo suficientemente claros los conceptos involucrados en este tema como para entender lo que realmente quiere.

De todas maneras, las respuestas normalmente varían desde alentarlos a cambiar a un verdadero motor de base de datos relacional como SQL Server, y cuando la complejidad o los costos de licenciamiento son una preocupación, algunas otras alternativas limitadas como MSDE. A veces se proponen arquitecturas mixtas basadas en Linux/MySql u otros productos de código abierto, pero generalmente veo esto excesivo para alguien que quiere evitar instalar SQL Server sólo por temor a lo desconocido.

Así que, en primer lugar, quiero dejar algo en claro: estoy de acuerdo en que un servidor de base de datos de primera clase es la verdadera solución. De hecho, los que usamos en mi empresa es SQL Server (y también Oracle). Si tiene al menos un par de años de experiencia con VFP, usar SQL Server no es tan difícil. Pero hay circunstancias en las que migrar a otro motor no es una opción válida. Tal vez ya tenga un montón de aplicaciones que no quiera -o no pueda- modificar, corriendo sobre tablas libres o DBCs de VFP. Lo que necesita es descargar ciertas tareas intensivas; necesita una solución remota en VFP.

Las soluciones del pobre en el período Jurásico

Desde los tiempos del DOS (en la era del FoxBase+), me apoyé en algún tipo de proceso del lado del servidor para optimizar tareas largas, manteniendo la carga de trabajo de los clientes lo más baja posible. En aquellos días, podíamos simplemente tener una tabla con un nombre de procedimiento y un campo de estado. Cuando uno quería correr uno de estos procedimientos, abría la tabla, buscaba el registro apropiado, y si el estado estaba "desocupado", lo cambiaba a "ejecutar".

Del lado del servidor, un programa estaba esperando en un bucle infinito, verificando el contenido de la tabla con cierta frecuencia. Cuando se detectaba un estado "ejecutar", era cambiado a "corriendo" y se disparaba el proceso correspondiente. Al terminar, el estado volvía a "desocupado" y el bucle continuaba.

Por supuesto, ésta no era una solución perfecta. Sólo se podía ejecutar un proceso a la vez, el bucle de espera consumía tiempo de procesador, etc. Pero piensen que el más barato de los motores de bases de datos reales costaba como mínimo decenas de miles de dólares.

Después de la revolución industrial

Al igual que los mamíferos, los sistemas operativos evolucionan. Windows llegó (presencié la aparición de Windows 2.0 ¡buah!), y pudimos moldear un poco nuestro código para que fuese más piadoso. Pero las cosas seguían sin ser ideales.

Con el tiempo aparecieron algunas soluciones fantásticas como DDE, y aprendimos que la buena teoría no siempre da buenos resultados. Después vinieron OLE, las primeras versiones de COM, una buena promesa con DCOM, y finalmente tuvimos COM+ (la historia sigue y llega hasta .Net, pero nos detendremos aquí).

Desde hace algunas versiones, VFP nos permite producir servidores COM en forma de archivos EXE o DLL. Mientras los servidores EXE son una buena solución para componentes complejos como interfaces compartidas, procesadores de automatización y cosas así, los componentes DLL son muy buenos para lógica de negocios, o implementaciones abstractas (no visuales).

Para ir al grano, hoy tenemos una manera mejor de realizar procesamiento remoto que nuestra solución original, y son los componentes COM+. Podemos implementarlos utilizando MTS (Microsoft Transaction Server) bajo NT 4 con el Option Pack 4 instalado, pero voy a utilizar su equivalente en Windows 2000 (y XP), rebautizado Servicio de Componentes.

Este servicio es un administrador de componentes que se encarga del caché, la optimización de recursos, la coordinación de transacciones entre componentes, y más. Para ponerlo en términos simples, es un espacio que alberga sus DLLs para compartirlas con aplicaciones externas en una forma controlada y eficiente. Para el propósito de este artículo, es también muy importante que hace muy sencillo el compartir estos componentes a través de la red.

También usaré directamente sintaxis y características de Visual FoxPro 7. Entiendo que algunos desarrolladores no puedan afrontar el cambio a SQL Server, pero no puedo encontrar motivo para no cambiar a la última versión de VFP, ya que es absolutamente compatible hacia atrás, y su costo es rápidamente amortizado por el incremento de productividad que permite.

Poniendo el genio dentro de la botella

Las bases de nuestro servidor de base de datos barato son sencillas. Veamos primero todo el código:
DEFINE CLASS Executor AS Session OLEPUBLIC

PROCEDURE SqlCommand( tcCommand as String ) as String

        local lcReturn
        lcReturn = ""

        Close Databases all

        Set Path To c:\develop\vfp\miniserver

        &tcCommand

        If Empty( Alias() ) or Reccount() = 0
        * Nada que hacer

        Else
        CursorToXML( Alias(), "lcReturn", 1, 1 + 8, 0, "1")
        endif

        Return lcReturn
ENDPROC

ENDDEFINE
¿Eso es todo? Bueno, básicamente si. ¿Qué es lo que hace? Sólo recibe un comando SQL (puede ser un select, update, delete, etc) y lo ejecuta. Lo importante es que, como veremos más adelante, el código es corrido en el servidor, y solamente los resultados vuelven al cliente (¿ya habían oído sobre esta idea?).

Leámoslo de nuevo, línea por línea:
DEFINE CLASS Executor AS Session OLEPUBLIC
Se define la clase como Session por sus ventajas intrínsecas, que van más allá de los límites de este artículo, y -si no las conocía- descubrirá por si mismo al probarlo. También es declarada como OLEPUBLIC para ser accesible como un componente COM al compilarse.
PROCEDURE SqlCommand( tcCommand as String ) as String
Nuestro único método (por ahora) acepta una string como parámetro (el comando SQL) y devuelve otra como resultado (veremos su contenido una líneas más abajo).
local lcReturn
lcReturn = "" 
Definimos e inicializamos una variable local. Por favor, no se duerma todavía...
 Close Databases all
 
 Set Path To c:\develop\vfp\miniserver
Esto es sólo por si acaso. Cerramos todas las áreas de trabajo y nos aseguramos que la ubicación de nuestros datos sea alcanzable. Para este ejemplo está fija, pero por supuesto, trate de hacerla configurable.
&tcCommand
Esta línea ejecuta el comando recibido. No, no hace ningún control de errores. Esa es su tarea para el hogar.
If Empty( Alias() ) or Reccount() = 0
  * Nothing to do
  Else
  CursorToXML( Alias(), "lcReturn", 1, 1 + 8, 0, "1" )
  endif
Si el comando SQL produjo algún cursor como resultado (como una típica sentencia SELECT) se lo convierte en una string XML. Esta es una forma sencilla (y a la moda) de pasar datos por la tubería entre servidores COM y los bordes del cliente. También podría usar ADO, un archivo ASCII delimitado por comas, o un DBF escrito en un directorio determinado. Cada método tiene sus pro y sus contra, pero el concepto sigue siendo el mismo.
 Return lcReturn
ENDPROC

ENDDEFINE
Devolvemos los resultados (al menos una string vacía), y todo ha terminado.
Ahora debemos probarlo. Guárdelo como MiniServer.prg y desde la ventana de Comandos, úselo:
loServer = NewObject( "Executor", "MiniServer.prg" )

messageBox( loServer.SqlCommand( "select * from products into cursor Parts") )
si todo salió bien, verá un Messagebox con el comienzo de un documentó XML como el de la Figura 1.

Figura 1: La primer prueba


Puede verlo más bonito haciendo:
loServer = NewObject( "Executor", "MiniServer.prg" )

lcXml = loServer.SqlCommand( "select * from products into cursor Parts")

XMLToCursor( lcXml, "Results" )
Browse
Por ahora, esto no es más que una forma complicada de hacer un simple SELECT, ¿no es cierto? Así que construyamos la DLL y probemos de nuevo. Genere un proyecto (lo llamaremos Miniserver.pjx en nuestro ejemplo), y compílelo como una DLL de subproceso múltiples (multi-threaded). Por ahora sólo acepte mi palabra de que en casi cualquier situación es preferible a una DLL de subproceso único (single-threaded). Hace apenas unos días hallé un caso en que no es así, pero esa es otra historia.

Puede hacer las mismas pruebas, esta vez instanciando la DLL en lugar de la clase local:
loServer = CreateObject( "miniServer.Executor" )

lcXml = loServer.SqlCommand( "select * from products into cursor Parts")

XMLToCursor( lcXml, "Results" )
Browse
Note que al escribir "loServer.", el Intellisense mostró sólo el método Executor() en lugar de todos los nativos de VFP. Esta es una de las ventajas de usar una clase Session para crear un componente COM.

Pero hasta ahora estamos corriendo el cliente y el servidor en la misma máquina. Movamos la DLL a otra parte. Puede hacer las siguientes pruebas en su PC de desarrollo sin problemas, pero lo aliento a hacerlo en otra, sólo para que aprecie cuán fácil es construir un entorno de procesamiento distribuido con VFP y COM+.

Creando un paquete COM+

Tenga cuidado con un detalle. La ruta que fije en la DLL debe apuntar al directorio en que realmente residen sus datos dentro del servidor en el que correrá su componente. Veremos más adelante porqué es tan importante indicar explícitamente esta ruta.

Ahora, copie su DLL y TLB (generada tras la compilación) a un directorio en el servidor, y luego seleccione desde el menú de Inicio de Windows, Herramientas Administrativas, Servicio de Componentes. Llegará a la Consola de Servicio de Componentes.

Figura 2: Consola Administrativa de Servicio de Componentes

Primero navegue el árbol de la Consola hasta su máquina, y dentro de ésta, hasta Aplicaciones COM+. Allí haga clic derecho y cree una nueva aplicación. Se disparará un Asistente así que es sumamente sencillo hacerlo:
  1. Haga clic en Siguiente en la pantalla de Introducción
  2. Haga clic en Crear una aplicación vacía
  3. Llámela MiniServer e indíquela como Aplicación de servidor.
  4. Seleccione el usuario que el proceso utilizará al correr. Esto puede ser peliagudo en aplicaciones reales y en un ambiente seguro. En muchos casos puede creara un usuario especial sólo con los derechos para asegurar que el componente puede funcionar según lo previsto. Para nuestro propósito actual, use su nombre de usuario y contraseña.
  5. Haga clic en Finalizar.
Por supuesto, sólo creamos la aplicación, que es un contenedor para una serie de componentes. Así que agreguemos nuestro primer y único componente dentro. Para hacerlo, busque MiniServer dentro de aplicaciones COM+. Expanda la rama y seleccione Componentes. Haga clic derecho y seleccione Nuevo para disparar otro Asistente, y siga estos pasos:
  1. Haga clic en Siguiente en la pantalla de Introducción.
  2. Haga clic en Instalar nuevos componentes.
  3. Navegue hasta el directorio donde copió la DLL y selecciónelo.
  4. Se poblarán dos listas. Arriba se ven los detalles de los archivos, mientras que debajo se ven los diferentes métodos. Sólo mírelos y haga clic en Siguiente.
  5. Haga clic en Finalizar.
Ahora puede navegar toda la jerarquía para verificar como se muestra todo. Finalmente, seleccione Componentes de nuevo (bajo MiniServer), y seleccione en el menú Ver / Estado.

Figura 3: Nuestro flamante componente COM+


Ahora, si quiere usar nuestro poderoso componente e hizo todo esto en su PC de desarrollo, está listo para hacerlo. Si siguió mi consejo y realmente lo instaló en un servidor, aún debe "decirle" a las máquinas clientes como comunicarse con éste. ¿Adivine qué? Hay otro Asistente para hacerlo. Seleccione MiniServer otra vez, haga clic derecho una vez más y seleccione Exportar.
  1. Haga clic en Siguiente en la pantalla de Introducción.
  2. Ingrese la ruta y nombre completo para el archivo de Microsoft Installer que este proceso va a generar. Usemos el mismo directorio donde ubicamos la DLL y en honor a la creatividad, llamémoslo Miniserver.msi. Luego seleccione Proxy de Aplicación, ya que esta opción creará un setup para que nuestros clientes sepan cómo instanciar el componente remotamente (la otra opción le permite generar una instalación para reproducir este paquete en otro servidor).
  3. Haga clic en finalizar. Le prometo que no usaremos más Asistentes en el resto del artículo.
Haga la prueba

Ahora puede ejecutar este archivo MSI y rápidamente preparará cualquier terminal para utilizar su componente. Para probarlo, simplemente copie y pegue este pequeño cliente de ejemplo:
Close Databases all
Clear

Local loServer as MiniServer
Local lcXml as string, lcFilter as String

loServer = CreateObject( "miniServer.Executor" )
lcFilter = InputBox( "Filtro", "Ingrese las primeras letras a buscar", "B" )

lcXml = loServer.SqlCommand( "select * from products where prod_name like '" ;
  + Alltrim( lcFilter ) +"%'" )

If Empty( lcXml )
   * Nada que hacer
Else
 XMLToCursor( lcXml, "Results" )
 Browse last
endif
Lo que hace este programa es realmente trivial. Primero cierra todas las áreas de trabajo y limpia el escritorio. Luego declara algunas variables y crea la instancia del componente. A continuación utiliza la función InputBox para pedir una letra de filtro. Antes de confirmarla, tiene una buena oportunidad para ir al servidor y echar una mirada a la consola de Servicio de Componentes. Si hizo todo bien, verá el contador mostrando una instancia activa, y la famosa bolilla girando, que muestra que el componente está en ejecución.

Ahora ingrese una letra y pulse Enter para continuar. Si todo está bien, el componente recibirá el comando SELECT, lo ejecutará y devolverá el resultado en XML. El programa convierte esta cadena nuevamente en un cursor y la muestra en un browse.

Pero tal vez las cosas no fueron tan bien, y usted obtuvo un error como el de la Figura 4.

Figura 4: La DLL no encuentra los datos

Lo que sucedió es que la ruta especificada en la clase no existe en el servidor. No importa si puso los datos en el mismo directorio que la DLL. Cuando ésta se instancia dentro de Servicio de Componentes, su directorio por defecto es System32 dentro de la carpeta principal de Windows. Pero podría que tampoco se de este caso, así que siempre debe asegurarse de establecer las rutas apropiadas.

Si tiene que arreglar esto, edite la clase, regenere la DLL y reemplácela en el servidor. Mientras no haga cambios en su interfaz, esto basta. Si más tarde agrega métodos o cambia parámetros, luego de copiar la nueva versión, reinstale la DLL repitiendo el segundo Asistente (hacerlo siempre como Nuevo Componente es más seguro, así que ignore las otras opciones), y luego ejecute el tercero para regenerar la instalación Cliente.

Tenga en cuenta también que en el servidor debe tener instalados los Runtimes de VFP. Como en todos los casos, el código dentro de su DLL no es código de máquina ni mucho menos. Por lo tanto, necesita las librerías de tiempo de ejecución. Puede instalarlas como lo hace para cualquier otra aplicación.

Conclusión

Por supuesto sólo hemos arañado la superficie de lo que podemos hacer con COM+. La idea principal de este ejemplo es brindar una manera de ejecutar comandos SQL básicos (select, update, delete, etc). Puede mejorar este componente fácilmente con un método para reindexar y compactar su base de datos (si está usando tablas de VFP), ejecutar procesos largos o complejos, y mucho más. Por supuesto, también deberá agregarle control de errores.

El objetivo principal es mostrar cuán fácil es usar esta funcionalidad. No hace falta que lea libros enteros o haga un largo curso para poder poner esta solución en marcha, utilizando VFP 7, XML y COM+ para construir su primer aplicación distribuida en menos de media hora.

Martín Salías (Buenos Aires, Argentina) es Director de Investigación y Desarrollo en Merino Aller, una empresa de software ERP y MRP para el mercado latinoamericano y español. Lleva veinte años como desarrollador y ha usado todas las versiones de FoxPro desde FoxBase+. Es Consultor de Universal Thread y co-Editor de UTMag/Rapozine.


15 de noviembre de 2002

Como saber si el usuario tiene permisos administrativos

Para saber si el usuario logueado actualmente tiene permisos administrativos en la PC.

Declare IsNTAdmin IN advpack.dll ;
   LONG dwReserved, ;
   LONG lpdwReserved

? IsNTAdmin(0,0)

Jorge Mota

7 de noviembre de 2002

Saber los Captions de todos los forms abiertos

Este código nos muestra el Czption de cada uno de los formularios que actualmente estén abiertos, se puede usar para ver si lo queremos cerrar o no, dependiendo del Caption del formulario.
Local Caption_F, Ciclo
if _screen.formcount=0
    ? 'Ningun Formulario Abierto.'
    RETURN 
endif
dimension CAPTION_F [ _SCREEN.FORMCOUNT]
**Obtenemos los captions, y los metemos a la matriz
for Ciclo = 1 to _SCREEN.FORMCOUNT 
    caption_F[ciclo]=ALLTRIM(_screen.forms(ciclo).caption)
endfor

***mostramos los captions encontrados.
for Ciclo = 1 to _SCREEN.FORMCOUNT
    ? 'Numero ' + STR(CICLO) + ' Caption: ' + caption_F[ciclo]
endfor
Jorge Mota

30 de octubre de 2002

.NET for Visual FoxPro Developers

.NET for Visual FoxPro Developers 
by Kevin McNeish (English)



Title: .NET for Visual FoxPro Developers
Author: Kevin McNeish
Edited by: Cathi Gero
ISBN: 1-930919-30-1
Length: 550 pages
Press date: September, 2002
Ebook format: .PDF

Resumen

Si tienes curiosidad acerca de lo que ofrece .Net, este libro proporciona una visión fuerte de .NET Framework y los lenguajes C# y Visual Basic. Le ayudara a evaluar estas nuevas tecnologías a través de la vision de Visual FoxPro. Si ya están "vendidos" y están listos para aprender detalles acerca de cómo utilizar .NET en sus proyectos de desarrollo de software, este libro ofrece un montón de información de "Como", "Paso a paso" y "Mejores prácticas" que le ayudará escalar la curva de aprendizaje de .NET y marchar rápidamente.

El primer capítulo establece el escenario para el resto del libro por responder preguntas como "¿Qué es .Net"? y "¿Por qué debería estar interesado en .NET"?

En los otros capítulos se ven los detalles de los lenguajes C# y Visual Basic .NET para ayudarle a decidir qué es lo mejor para Uds.

Mas información: http://www.hentzenwerke.com/catalog/netvfp.htm

29 de octubre de 2002

Campos calculados en tablas SQL Server 2000

Para ahorrarse un poquitin de trabajo. Cuando almacenamos unicamente el valor de una llave foranea en una tabla, posteriormente debemos hacer una vista o hacer una relacion en un informe para desplegar regularmente campos de descripcion.

Se preguntaran que tiene que ver esto con los campos calculados, por ejemplo al definir una tabla en SQL Server, podemor crear un campo calculado de esta manera, ejemplo:

CREATE TABLE [tbl_empleados] (
	[id_empleado] [char] (10) COLLATE Modern_Spanish_CI_AS NOT NULL ,
	[nombres] [char] (60) COLLATE Modern_Spanish_CI_AS NULL ,
	[cuota_facturacion] [numeric](10, 2) NULL 
	CONSTRAINT [PK_tbl_empleados] PRIMARY KEY  NONCLUSTERED 
	(
		[id_empleado]
	)  ON [PRIMARY] 
) ON [PRIMARY]

Almacenamos en ella una lista de empleados con su codigo, nombre y cuota de facturacion, posteriormente necesitariamos una tabla donde almacenar las horas laboradas y hacer un calculo del valor con relacion al tiempo trabajado multiplicado por su cuota de facturacion, hariamos entonces una tabla parecida a esta:

CREATE TABLE [tbl_registro_de_empleados] (
	[id_empleado] [char] (10) COLLATE Modern_Spanish_CI_AS NOT NULL ,
	[nombres] [char] (60) COLLATE Modern_Spanish_CI_AS NULL ,
	[cuota_facturacion] [numeric](10, 2) ,
	[horas_trabajadas] [numeric](10, 2) ,
	[valor_a_pagar] as  cuota_facturacion*horas_trabajadas ,

Esta tabla esta bien, podriamos almacenar el codigo del empleado, sus horas trabajadas, almacenar el valor por hora y el campo calculado nos daria el resultado de multiplicar las horas por su cuota de facturacion. Sin embargo, cuando hagamos un informe, necesitariamos hacer una relacion con el archivo de empleados, para saber su nombre, y alli es donde entra a funcionar lo que les quiero dar a conocer, en SQL 2K, se implementaron las UDF (funciones definidas por el usuario), podriamos usar un select e incluir una funcion que creemos que nos devuelva el nombre del usuario en base al registro seleccionado y esto tambien funciona, sin embargo, algo que probe y funciono de maravilla, fue el crear un campo calculado en una tabla que haga referencia a una funcion y que almacene automaicamente el valor que nos devuelve la funcion por cada registro introducido, evitando asi, el hacer relaciones de mas y sobre todo, el mantener actualizada la data en la tabla, e aqui el ejemplo de como realizar un campo calculado que llame a una funcion para devolver data de otra tabla.

Primero creamos la funcion:

CREATE FUNCTION fn_nombre_empleado (@id_empleado char(10))
RETURNS char(60) AS  
BEGIN 
declare @nombre char(60)

select @nombre=ltrim(rtrim(nombres)) from tbl_empleados where id_empleado=@id_empleado

return @nombre

END

Luego redefinimos nuestra tabla y le agregamos un campo calculado que se llame nombre del empleado la tabla quedaria de esta manera:

CREATE TABLE [tbl_registro_de_empleados] (
	[id_empleado] [char] (10) COLLATE Modern_Spanish_CI_AS NOT NULL ,
	[nombres] [char] (60) COLLATE Modern_Spanish_CI_AS NULL ,
	[cuota_facturacion] [numeric](10, 2) ,
	[horas_trabajadas] [numeric](10, 2) ,
	[valor_a_pagar] as  cuota_facturacion*horas_trabajadas ,
	[Nombre] as  .dbo.fn_nombre_empleado(tbl_registro_de_empleados.id_empleado))

Con esto obtendriamos que cada vez que se ingrese un codigo de empleado, la tabla nos despegaria el nombre del mismo y podriamos hacer informes mucho mas rapido y sin tener que almacenar la data o hacer relaciones.

Espero que les sirva.

Fernando España

26 de octubre de 2002

Ejemplo basico de ADO

Ante algunas consultas sobre la forma de conectarse y acceder a SQL Server utilizando ADO, acá va un ejemplo básico de su uso...
* EjemploADO.prg
* Algunos ejemplos de uso de ADO desde VFP
* Jose M. Marcenaro - 2001/11/12
* -------------------------------------------
* Para tener intellisense de estos objetos (VFP 7)
* agregué previamente la Type Library de ActiveX Data Objects
* al Intellisense Manager (Solapa Types / Type Libraries)
LOCAL loConn AS ADODB.CONNECTION
LOCAL loCmd AS ADODB.COMMAND
LOCAL loRs AS ADODB.Recordset
LOCAL lcConnString AS STRING

* conexion por seguridad integrada de Windows
lcConnString = "Provider=SQLOLEDB;Data Source=(local);Initial Catalog=Northwind;Integrated Security=SSPI"

* conexion por usuario / password de SQL Server
* lcConnString = "Provider=SQLOLEDB;Data Source=MSSQL;Initial Catalog=Northwind;User Id=sa;Password=pass;"

* abro la conexion
loConn = CREATEOBJECT("ADODB.Connection")
loConn.CursorLocation= 3 && adUseClient
loConn.OPEN(lcConnString)

* realizo un select simple
loRs = loConn.Execute("SELECT * From Products")
MostrarRecordset( loRs)
loRs.CLOSE

* creo un stored procedure para poder invocarlo despues
ON ERROR xx=0  && ignorar si ya existe el SP
loConn.Execute( "";
  +"create procedure Probar (@Inicial varchar(10)) as ";
  +"    select * from Products where ProductName like @Inicial+'%'")
ON ERROR

* invoco al SP recien creado
loRs = loConn.Execute("exec Probar 'M'")
MostrarRecordset( loRs)
loRs.CLOSE

* lo invoco mediante un objeto Command (acceso a coleccion parámetros)
loCmd = CREATEOBJECT("ADODB.Command")
loCmd.ActiveConnection = loConn
loCmd.CommandText = "Probar"
loCmd.CommandType = 4 && adCmdStoredProc
loCmd.PARAMETERS.REFRESH && obtiene parametros del SP
loCmd.PARAMETERS.ITEM(";@Inicial") = 'L'
loRs = loCmd.Execute()
MostrarRecordset( loRs)
loRs.CLOSE

RETURN

********************************************************
PROCEDURE MostrarRecordset( loRs AS ADODB.Recordset)

  LOCAL ln, li, lcStr
  lcStr = ""

  FOR ln = 1 TO 10  && primeros 10 registros como máximo
    IF loRs.EOF then
      EXIT FOR
    ENDIF
    FOR li=1 TO MIN(loRs.FIELDS.COUNT, 3) && primeros 3 campos como máximo
      lcStr = lcStr+TRANSFORM(loRs.FIELDS.ITEM(li).VALUE)+","
    NEXT
    lcStr = lcStr+CHR(13)
    loRs.MoveNext
  NEXT
  MESSAGEBOX(lcStr)

ENDPROC
Jose Marcenaro
Microsoft MVP
da Vinci - Argentina

22 de octubre de 2002

Verificar la ortografía en VFP con Word

Con el siguiente código podemos verificar la ortografía de cualquier texto en VFP usando el corrector ortográfico de MS Word.

Este código de Trevor Hancock está en el Artículo Q271819 de la Base de Conocimientos de Microsoft.

Para probarlo, solo hay que pegar el código en un nuevo .PRG y ejecutarlo desde VFP.
************ START CODE ***********
PUBLIC oform1

oform1=CREATEOBJECT("form1")
oform1.SHOW
RETURN

**************************************
*
DEFINE CLASS form1 AS FORM

  HEIGHT = 250
  WIDTH = 375
  SHOWWINDOW = 2
  AUTOCENTER = .T.
  BORDERSTYLE = 2
  CAPTION = "VFP - Word Spell Checking"
  MAXBUTTON = .F.
  NAME = "Form1"

  ADD OBJECT edttexttocheck AS EDITBOX WITH ;
    HEIGHT = 163, ;
    LEFT = 23, ;
    TOP = 21, ;
    WIDTH = 332, ;
    NAME = "edtTextToCheck"

  ADD OBJECT cmdcheckspelling AS COMMANDBUTTON WITH ;
    TOP = 207, ;
    LEFT = 115, ;
    HEIGHT = 27, ;
    WIDTH = 149, ;
    CAPTION = "Check Spelling", ;
    NAME = "cmdCheckSpelling"

  PROCEDURE findword
    *~~~~~~~~~~~~~~~~~~~~~
    * PROCEDURE FindWord
    *
    * AUTHOR: Trevor Hancock, , Microsoft Corporation
    * CREATED : 08/22/00 11:50:32 AM
    *
    * ABSTRACT: Locates an installation of MS Word using the FindExecutable API
    *           Creates a file with a .doc extension, checks the association on that
    *           file using FindExecutable, then deletes the file. FindExecutable returns
    *           the full, null terminated path to the application associated with
    *           .doc files (in this case).
    * ACCEPTS: Nothing
    * RETURNS: Full path to the application associated with .doc files on this machine.
    *~~~~~~~~~~~~~~~~~~~~~

    LOCAL lcPath, lcResult, lcFileName, llRetVal, ;
      lcCurDir, lnFileHand, lcWordPath

    lcPath = SPACE(0)
    lcResult = SPACE(128)
    llRetVal = .F.
    *!* Determine the DIR this form is running from. JUSTPATH() and ADDBS()
    *!* could be used here instead (if using VFP6), but this code will work 
    *!* in any VFP version.
    lcCurDir = SUBSTR(SYS(16,0),ATC([ ],SYS(16,0),2)+1)
    lcCurDir = SUBSTR(lcCurDir,1,RAT([],lcCurDir))

    lcFileName = lcCurDir + SYS(3) + [.doc]

    *!* Create a file with a .doc extension.
    *!* Could use STRTOFILE() here in VFP6.
    lnFileHand = FCREATE(lcFileName,0)
    = FCLOSE(lnFileHand)

    DECLARE INTEGER FindExecutable IN shell32 STRING @lcFilename, ;
      STRING @lcPath , STRING @lcResult

    *!* Determine the file association on .DOC files
    IF FindExecutable(@lcFileName, @lcPath, @lcResult) > 32
      *!* Strip off trailing chr(0)
      lcWordPath = UPPER(SUBSTR(lcResult,1,LEN(ALLTR(lcResult))-1))
      IF [WINWORD] $ lcWordPath
        llRetVal = .T.
      ENDIF
    ENDIF
    *!* Clean up after ourselves
    ERASE (lcFileName)
    RETURN llRetVal
  ENDPROC

  PROCEDURE DESTROY
    IF TYPE([goWord]) = [O]
      IF TYPE([goWordDoc]) = [O]
        goWordDoc.SAVED = .T.
        goWordDoc.CLOSE
      ENDIF
      goWord.QUIT
    ENDIF
    RELEASE goWord, goWordDoc
  ENDPROC

  PROCEDURE INIT
    *--- English
    * THIS.edtTextToCheck.VALUE = "Thhis text has mistakees in it. We will seend " +  ;
    * "it to Word and have it cheked."

    *-- Español
    THIS.edtTextToCheck.VALUE = "Ezte tecto esta escrito kon herrores ppara " +  ;
      "que Word lo chequee."

  ENDPROC

  PROCEDURE cmdcheckspelling.CLICK
    *~~~~~~~~~~~~~~~~~~~~~
    * PROCEDURE cmdcheckspelling.CheckSpelling
    *
    * AUTHOR: Trevor Hancock, Microsoft Corporation
    * CREATED : 08/22/00 12:03:46 PM
    *
    * ABSTRACT: Automates MS Word to check the spelling of text in
    *                 THISFORM.edtTextToCheck
    * ACCEPTS: Nothing
    * RETURNS: Nothing
    *~~~~~~~~~~~~~~~~~~~~~

    IF TYPE([goWord]) # [O] && Check if you have already instantiated Word

      IF !THISFORM.FindWord() && You don't have Word up, so let's locate it.
        MESSAGEBOX([Microsoft Word is either not installed or is incorrectly registered.], + ;
          0,[Word Start-Up Failed])
        RETURN .F.
      ENDIF

      *!* Change the mouse pointer for all form controls to indicate processing (opening Word)
      WITH THISFORM
        .cmdCheckSpelling.MOUSEPOINTER = 11
        .edtTextToCheck.MOUSEPOINTER = 11
        .MOUSEPOINTER = 11
      ENDWITH

      PUBLIC goWord, goWordDoc && Public vars for Word and Document1 in Word.
      goWord = CREATEOBJECT([WORD.APPLICATION]) && Create Word
      WITH goWord
        .WINDOWSTATE= 0  && wdWindowStateNormal (needs to be Normal before you can move it)
        .MOVE(1000,1000) && Move the window out of view
        goWordDoc = .Documents.ADD
      ENDWITH

      *!* Change mouse pointers back
      WITH THISFORM
        .cmdCheckSpelling.MOUSEPOINTER = 0
        .edtTextToCheck.MOUSEPOINTER = 0
        .MOUSEPOINTER = 0
      ENDWITH

    ENDIF

    WITH goWordDoc
      .Content.TEXT = ALLTRIM(THISFORM.edtTextToCheck.VALUE)
      .ACTIVATE
      IF .SpellingErrors.COUNT > 0
        .CHECKSPELLING
      ELSE
        =MESSAGEBOX([Spell check complete. No errors found],0,[Spell Check])
      ENDIF
      *!* For some reason, Word likes to make itself visible here. Keep it hidden...
      goWord.VISIBLE = .F.
      THISFORM.edtTextToCheck.VALUE = .Content.TEXT
    ENDWITH
  ENDPROC

ENDDEFINE
*
**********************************
*!*********** END CODE ***********

18 de octubre de 2002

COM+ con VFP 7.0 - Parte 3

Publicado originalmente en FoxTalk.
Traducción de Jorge Espinosa.

Las transacciones son una parte importante de cualquier mecanismo de actualización de datos. La parte 3 de Craig Berntson nos introduce en la Coordinación de Transacciones Distribuidas y explica como usar las transacciones bajo COM+.

Transacciones

Sin ellas no podemos estar seguros que los datos están siendo escritos en todas las tablas involucradas en la actualización. Antes de introducirnos en como trabajan las transacciones en COM+ hagamos una revisión rápida de transacciones.

Revisión de Transacciones

Joe quiere transferir $100 de su cuenta de pagos a su cuenta de cheques. El camina hacia el cajero, inserta su tarjeta, presiona el botón para empezar la transferencia. Durante la escena, esta transferencia puede se lograda de dos maneras. La primera manera es que el balance de la cuenta de pagos de Joe puede ser reducida por $100 y el balance de su cuenta de cheques incrementada por $100. La segunda opción es que el balance de su cuenta de cheques puede ser incrementada y el balance de su cuenta de pagos puede ser decrementada. Pero que pasa si allí sucede una caída del sistema entre las dos actualizaciones? Bajo la primera escena Joe no estaría feliz. El perdió $100. En el segundo ejemplo Joe está muy feliz. El es $100 mas rico pero el banco tiene ahora una pérdida de $100. Lo que nosotros queremos es esto, en el evento de la caída mencionado, o ambas cuentas deben se actualizadas o ninguna debe ser actualizada. El propósito de la transacción es asegurar que esto pase. Las transacciones deben seguir la regla ACID, que es, atomicidad, consistencia, aislamiento y durabilidad.

La Atomicidad significa que o todas o ninguna de las actualizaciones se ejecuten. La Consistencia significa que si una transacción falla los datos son retornados al mismo estado que tenían antes de empezar la transacción. El aislamiento significa que una transacción no sepa que otra transacción se está haciendo. Finalmente, la Durabilidad significa que el estado de la transacción se guarda, no importa que pase en el sistema.

Esto es manejado generalmente a través de un log de transacciones.

Podemos implementar transacciones en nuestros datos de VFP al usar los comandos BEGIN TRANSACTION / END TRANSACTION / ROLLBACK. El pseudo código muestra un procedimiento de ejemplo:
BEGIN TRANSACTION
LlSavingsOk = UpdateSavings(-100)
LlCheckingOk = UpdateChecking(100)
IF llSavings and llChecking
 END TRANSACTION
Else
 ROLLBACK
ENDIF
Las transacciones en VFP solo trabajan con datos de VFP, y las transacciones de VFP carecen de la regla de la durabilidad de ACID. No hay ningún log de transacciones.

Si estuviésemos usando SQL Server necesitaríamos usar un mecanismo diferente. Podemos usar comandos de ADO para llamar a SQL server cuando empieza o termina una transacción o podemos codificar en un stored procedure.

Sin embargo, que pasaría si la cuenta de pagos estuviese en SQL Server y los datos de la cuenta de cheques estuviesen en Oracle? Las transacciones de Sql Server solamente trabajarían en Sql Server, y las transacciones de Oracle solamente trabajarían con Oracle. Necesitamos una sola transacción que trabaje con ambas base de datos. Aquí es donde entra la Coordinación de Transacción Distribuida (DTC).

Usando el DTC

El DTC nos permite tener transacciones que crucen base de datos. Esto significa que tenemos datos en ambos, Sql Server y Oracle, que actualizaríamos por la misma transacción. Bajo MTS y VFP 6, los datos de Fox no podrían participar en la transacción DTS.(Esto cambia bajo COM+ y VFP7. Veremos acerca de esto mas adelante). Volviendo al DTC.

El DTS usa un commit en dos fases. Básicamente, en la primera fase, el DTS pregunta a la base de datos si puede hacer una actualización. Si todos los datos responden que si a esa pregunta, comienza la segunda fase, en el que el DTS le dice a la base de datos que debe actualizar los datos. Si alguna de las base de datos responde que no el DTS le dice a las bases de datos que se hace un Roll Back de la actualización.

Veamos como las transacciones son manejadas bajo MTS:
DEFINE CLASS Math AS SESSION OLEPUBLIC
FUNCTION Multiply(tnNum1, tnNum2)
LOCAL lnResult, LomTX, LoContext

** Creamos una referencia al objeto MTS
LoMtx = CREATEOBJECT("MTXAS.APPSERVER.1")

** Creamos una referencia al objeto Contexto
LoContext = loMTX.GetObjectContext()

lnResult = tnNum1*tnNum2

** Cierra la transaccion si existe y le avisa a MTS que ha finalizado con el uso del componente

LoContext.SetComplete()

** Si existiera un error podriamos abortar la transacción y avisarle a MTS que finalizamos
** Con el componente usando loContext.SetAbort()

RETURN lnResult
ENDFUNC
ENDDEFINE
Note que cuando nosotros hacemos Commit o abortamos la transacción, MTS también descargará el componente.

Esto significa que si el próximo comando en nuestra aplicación necesita el componente, tendremos que hacer otro CREATEOBJECT() en el cliente. Veamos como cambia esto en COM+:
 
DEFINE CLASS Math AS SESSION OLEPUBLIC
FUNCTION Multiply(tnNum1, tnNum2 as Number);
 AS Number HELPSTRING "Multiplica ;
  Dos números y devuelve el resultado"

LOCAL lnResult, LomTX, LoContext, loContextState, ;
 LlDone, llGetDone, lnTxnState, lnGetTxnState

LlDone = .T.
LlGetDone = .F.
LnTxnState = 1
LnGetTxnState = 0

** Creamos una referencia al objeto MTS
LoMtx = CREATEOBJECT("MTXAS.APPSERVER.1")

** Creamos una referencia al objeto Contexto
LoContext = loMTX.GetObjectContext()

** Crea una interfaz al estado del Contexto
LoContextState = GETINTERFACE(loContext, "iContextState")

lnResult = tnNum1*tnNum2

** Establece el seteo de la Transacción (Consistencia)
** 0 = Commit,   1 = Abort

LoContextState.SetMyTransactionVote(lnTxnState)
LoContextState.GetMyTransactionVote(@lnGetTxnState)

** Lleva adelante el seteo de Activacion
** .T. = Desactiva ,   .F. = Deja Activado

loContextState.SetDesctivateOnReturn(llDone)
loContextState.GetDeactivateOnReturn(@llGetDone)

RETURN lnResult
ENDFUNC
ENDDEFINE
En este ejemplo, podemos hacer Commit o abortar la transacción con SetMyTransactionVote, pero tenga en cuenta la instancia del componente permanecerá activa hasta que explícitamente llamemos a SetDeactivateOnReturn con .T. como parámetro.

Ahora estará preguntándose donde encaja el DTC en esto COM+ automáticamente llama al DTC por usted

El DTC habla con la base de datos a través del uso de Resource Manager.

MTS y COM+ ya vienen con Resource Managers para Oracle y SQL Server, pero no para VFP. Es por esto que los datos de VFP no podrían tomar parte en una transacción MTS.

Resulta que Resource Managers es muy difícil para implementar. Entonces, por esto es que COM+ es llamado Compensating Resource Managers(CRMs). CRMs son mas fáciles de implementar que un Resource Managers. Al usar un CRM podemos tener nuestros datos involucrados en una transacción DTC.

Un CRM consiste en dos partes, la CRM Worker y la CRM Compensator. Esto corresponde a las dos fases del commit del DTC. Un detalle en discusión sobre el CRM está fuera del alcance de este capítulo. Sin embargo, VPF 7 viene con un ejemplo de aplicación CRM. Encontrará esto en las carpetas SAMPLES\COM+\CRM bajo la instalación de su VFP 7.

Configurando transacciones COM+

Como muchos de los rasgos en COM+, las transacciones son manejadas en tiempo de ejecución. Abrir el Component Service Manager (applet) y elegir uno de sus componentes. Click derecho en el componente y seleccione propiedades, seleccione el tab de transacciones. Verá cinco opciones de transacciones.(figura1 y tabla1)

También notará el ckeck box "Override global transaction timeout value". Cuando seleccione Required o Required new transaction, este ckeckbox estará habilitado. Entonces puede ingresar el número de segundos que correrá la transacción antes del time out.

Esto fue un acercamiento a las transacciones. En la parte 4 daremos un vistazo a las ventajas de COM+ con Queued Components.



Figura 1.

Tabla 1. Tipos de transacciones soportados por componentes COM+.
SeetingDescripción
DisabledLa transacción no se necesita. Elija esta cuando no necesite la sobrecarga de trabajo que implica generar una transacción, por ejemplo un componente que no tiene que actualizar ningún dato.
Not SupportedImpide a un componente usar la transacción, sin tener en cuenta el estado transaccional de la llamada al componente.
SupportedEl componente participa en la transacción si una está activa.
RequiredEl componente participa en la transacción si una está activa. Si no hay ninguna transacción activa, genera una nueva.
Requires NewUna transacción nueva es generada siempre.

Craig Berntson es Microsoft Certified Solution Developer y fue nombrado cuatro veces Microsoft Most Valuable Profesional. Disertante de varias conferencias de FoxPro en todo EEUU y eventos de Microsoft en Salt Lake. Es Presidente del Salt Lake City Fox Users Group y actualmente ingeniero de software senior para 3m Health Information Systems.
Jorge A. Espinosa, Buenos Aires (Capital Federal), Argentina, es Analista Programador y MCP. Dedicado al desarrollo de sistemas desktop desde el año 1987; siempre en el entorno xBase y en Visual Fox Pro desde la version 3.0. Hoy Gerente de Sistemas en Droguería Saporiti SACIFIA.

15 de octubre de 2002

Conferenciantes PortalFox España 2003

Aqui teneis la primera lista provisional de conferenciantes ... puede sufrir algun cambio. Ordenados alfabéticamente por apellido:

Miguel Egea (portalsql.com)

Nacío en Calasparra (Murcia) en 1969, Estudió Informática en la Universidad de Castilla la mancha y comenzó su vida laboral como Freelance desarrollando aplicaciones comerciales para sectores tan dispares como la peluquería o la banca. Fundó el departamento de informática de Laboratorios Munuera en Murcia y desarrolló sus primeras aplicaciones con Bases de Datos Access hace ya más de 10 años. Desde hace ya bastantes años está trabajando con SQL-SERVER desde su versión 6.5 hasta la actual 2.000. En este periodo ha dirigido y colaborado en numerosos desarrollos en la consultora Sinergia Tecnológica (http://www.sinergiatec.com) que está integrada en el grupo IT-Deusto (http://www.itdeusto.com). En Sinergia Tecnológica dirige varios proyectos de desarrollo de software en plataformas Microsoft con bases de datos diversas como Sql-Server, Oracle, ... Desde mayo de 2002 es MVP en SQL-SERVER por su colaboración en la comunidad On-Line de Microsoft. También pertenece a PASS Spanish Group, la asociación de profesionales de SQL-Server. Es el webmaster de www.portalsql.com.


Miguel Angel Hernaiz (gnomo.net)

Nacido en 1964 en Madrid, lleva desde el año 1981 metido entre pantallas y teclados (luego vendrían los ratones). Autodidacta de nacimiento, empezó en el mundo de los mini y grandes mainframes: S36, S38, Kienzle, AS400... Hasta que llegó a sus manos un PC de IBM. Descubrió Dbase, Clipper, las comunicaciones por modem... Alaska Xbase, Multimedia, Internet, Vfoxpro, Html, Sql server, Oracle... Gracias a Internet empezó a colaborar con el resto de desarrolladores de VFP, en chats, foros, News, y forma parte del VFUG y UniversalThread desde sus inicios, allá por el 96. Junto con Fernando y Juan Carlos, tuvo la gran oportunidad de elevar su nivel en OOP y plataformas cliente-servidor. Colabora con multiples empresas de Analista, Programador y Tecnico de Sistemas y Aplicaciones. Ha publicado en revistas y foros, y se empeña en que el nivel de los que le rodean sea más y mas alto cada día. Ahora anda por Barcelona entre servidores WEB y MAIL, .NET, SQL Server y Desarrollo de sitios Web, con VFP como capa de datos y reglas de negocio, por supuesto..


Pedro Hernandez Muñoz (www.pedrohernandez.com)

Nacido hace 36 años en Almelo ( Holanda ) , inició su andadura en el campo informático allá por el año 80 estudiando Cobol cuando tenia 14 años en Valencia. A partir del año 87 inició la programación xBase con dBase y Clipper, lo que le permitió colaborar durante un tiempo con una empresa que aún hoy es líder en el tema de la enseñanza y edición de libros, en la que también escribió artículos en sus publicaciones. Tras esta experiencia se traslada a Madrid y comienzo a desarrollar proyectos de aplicaciones siempre con FoxPro y Visual FoxPro para diversas empresas, editando en el año 1998 el libro “Visual FoxPro 5. Desarrollo de Aplicaciones” en la editorial McGrawHill distribuido en España y Latinoamérica.


Luis Maria Guayan (Vicente Trapani, S.A.)

37 años, nació en Tartagal, Salta (Argentina) y desde hace mas de 15 años que reside en Tucumán (Argentina). Comenzó a programar en lenguajes xBase en el año 1990 (dBase, FoxPlus y Clipper). Con estas herramientas realizó sus primeros trabajos profesionales como desarrollador independiente. En el año 1994 ingresó a trabajar a tiempo completo en el Área Sistemas de Vicente Trapani S.A., una empresa de Tucumán (Argentina) dedicada a la producción y exportación del limón y todos sus derivados. Allí comenzó a desarrollar con FoxPro 2.6 para Windows y todas las versiones posteriores hasta Visual FoxPro 7. Actualmente es el Responsable del Área Sistemas. Casi la totalidad del software desarrollado por el Área Sistemas de Vicente Trapani S.A. está realizado en FoxPro y Visual FoxPro. Desde el año 1997 participa en los Grupos de Noticias de Visual FoxPro en español. Es allí que a principios del año 2000 se contactó con Pablo Roca para colaborar con PortalFox y sumando esfuerzos poder contar con un sitio para todos los desarrolladores en Visual FoxPro de habla hispana. En Febrero del año 2002 fue nombrado Microsoft MVP (Most Valuable Professional) en Visual FoxPro, por sus colaboraciones en las distintas comunidades "on-line".


Fernando Guerrero (www.callsql.com)

Fernando nació en Santo-Tomé (Jaen) en el 1959, y desde el 1976 está directamente involucrado en el desarrollo de aplicaciones informáticas de un modo u otro. Actualmente ocupa el puesto de Principal Technologist y SQL Server Product Consultant en QA, la empresa de formación en Tecnología de Información líder en el Reino Unido, donde se encarga de desarrollar e impartir nuevos cursos, así como aplicar sus conocimientos en proyectos de consultoría y mentoring, en particular en tecnologías .NET y SQL Server. Fernando es un ponente habitual en conferencias del sector, como TechEd, PASS, VBUG, VBITS, MCTCon, SQLLive, VSConnections, SQL Server Magazine Live, y DevWeek. Escribe para SQL Server Magazine y SQL Server Professional, y es el coautor del libro "Microsoft SQL Server 2000 Programming by Example" (QUE Corporation 2001, ISBN 0-7897-2449-9, ISBN 9879460634 para la version en castellano), y está escribiendo en estos momentos otros libros sobre VB.NET y SQL Server 2000. Fernando es Ingeniero Técnico de Obras Públicas (Hidrología) y Diplomado Universitario en ICCP, MCDBA, MCSE+I, MCSD, MCT y SQL Server MVP (Most Valuable Professional) desde el año 2000.


Jose Enrique LLopis (FUTURA Systems & software)

Nacido en Alicante, 45 años y comencé a trabajar con los sistemas IBM-360. De ahí pasé sucesivamente por los sistemas 370, 32, DEC Rainbow , SECOINSA S30/5 y S20, etc .... Me entretuve con los PDP's y VAX/MicroVAX de Digital y a continuación me sumergí en los ordenadores personales. De ahí pasé a los UNIX, primero sobre los IBM RT6150/51 y después sobre plataforma PC. En 1984 entré en la Caja de Ahorros Provincial de Alicante y de ahí pasé a la Caja del Mediterráneo siempre con sistemas personales y departamentales siendo el responsable por ejemplo del diseño de la red de servidores central para toda la empresa. Comencé a trabajar en fox desde FoxBase, pasando por Foxpro hasta ahora que utilizo VFP7. Hoy día además de mi trabajo en la CAM soy socio de una empresa consultora especializada en productos para el mercado inmobiliario llamada FUTURA Systems & software.


Les Pinter (Pinter Consulting)

Nació en Houston, Texas, pero pasó gran parte de su adolescencia en México. Regresó a Texas para continuar sus estudios superiores. Mientras estudiaba para un doctorado en economía en la Universidad Rice, formó una empresa para desarrollar y comercializar un procesador de texto llamado La Varilla Mágica. En 1980, vendió la Varilla Mágica a un joven de 23 años de Seattle llamado Bill Gates, para formar la base de Microsoft Word. Continuó a trabajar en el área de las microcomputadoras, siguiendo la carrera de desarrollador de base de datos a partir de 1982.
En 1985 Les empezó a trabajar con un nuevo producto llamado FoxBASE, y después de varios años inició la publicación de una revista mensual, The Pinter FoxBASE Letter, en Menlo Park, California. Poco después, Fox Software anunció su nueva generación, FoxPro. Después de otros dos años, salió Visual FoxPro, un lenguaje plenamente orientado a objetos, y Microsoft compró la compañía Fox Software.
Desde entonces, Les publicó su revista mensual por más de 10 años en California, y por 4 años en Moscú. Tiene equipos de programadores en México y en Rusia que colaboran en proyectos de base de datos para varios clientes en los Estados Unidos. Ha participado como ponente en conferencias de Microsoft, Advisor Media, DevConnections y otras, y ha presentado seminarios y cursos en varias universidades en Rusia, México y Cuba. Es autor de seis libros y más de 270 artículos sobre varios temas de la informática. Su próximo libro, Desarrollo de Aplicaciones de Base de Datos con Visual Basic.NET y Visual FoxPro 7, estará disponible a fines de 2002. Les es también piloto privado, y tocaba guitarra en varios conjuntos musicales durante los años 60.


Fernando Puyuelo (Informática Borsan)

Nacido en Villafranca del Bierzo, León. Tiene 34 años. Licenciado en Ciencias Físicas, Especialidad Cálculo Automático en la Universidad Complutense de Madrid. Comenzó a trabajar en el año 1992 en Informática Borsan,S.L., empresa dedicada al desarrollo de software, y de la que actualmente es Director Técnico y socio. Comenzó el desarrollo de aplicaciones en Clipper, pero muy pronto comenzó el desarrollo de las mismas en el incipiente lenguaje que parecía iba a ser el sustituto a Xbase y Clipper, Foxpro. Después de evaluar la versión FOX 20, comenzó a trabajar con la versión Foxpro 2.5 DOS en la realización de un programa de generación de estadísticas. Ha pasado por todas las versiones de desarrollo de Foxpro, hasta llegar a la actual VFP 70. Actualmente la empresa maneja aplicaciones desarrolladas en todas las versiones de windows, incluyendo Fox 26, dada en muchas ocasiones las lentas ganas de migrar de los usuarios, pero poco a poco traspasando todo a VFP70. Actualmente desarrollando un nuevo programa de gestión de Sociedades de Tasación, terreno en el cual la empresa es lider en el mercado español, en un modelo de tres capas, usando como no, VFP. Fernando ha participado en todos los congresos y eventos relativos a foxpro desde 1994. Impulsor de los primeros chat en castellano, así como de la primera fase de la creación de las news de Microsoft en torno a Visual Foxpro. Miembro activo de la comunidad desde sus comienzos..


Pablo Roca (Clavo Congelados, S.A.)

Tiene 42 años, nacido en Palma de Mallorca (España). Pero toda la vida ha vivido en La Coruña. Dedicado al diseño y desarrollo de aplicaciones desde hace 22 años. Los inicios fueron en FORTRAN y tarjetas perforadas allá por el año 1980 en un viejo mainframe de la Universidad de Vigo. Ha trabajado desde técnico de sistemas en Fujitsu España, hasta el puesto que ocupa actualmente como Director de Informática en Clavo Congelados. Impartió diversas conferencias tanto en grupos de usuarios, como en reuniones de informática y colaboró en la presentación del Visual Foxpro 5 junto con Microsoft. Durante unos años fué colaborador de MacWorld España, donde escribia artículos y comparativas de bases de datos. Sus inicios en Foxpro fueron desde los tiempos de Fox Software, con el Foxbase, hasta hoy en día que está con el VFP. Creador de PortalFox, que es la comunidad en línea más grande de habla hispana sobre Visual Foxpro. Es nombrado en Octubre de 2001 como Microsoft MVP (Most Valuable Professional) de VisualFoxpro por su aportación a la comunidad.


Alberto Rodriguez (FoxPress)

Nacido en Palencia (España). 42 años. Licenciado en CC.Económicas y PDG del IESE. Empezó a trabajar en Informática con FoxBase+. Ha desarrollado aplicaciones de Gestión para Empresas, Hospitales y Seguros publicadas en las principales revistas del sector (www.flasof.com). Mentor de FoxPress (www.fpress.com) revista con 10 años de antiguedad y dedicada a Visual FoxPro. Organizador de 5 Congresos sobre Fox. Ha dado cursos y publicado artículos con cuyo código trabajan los desarrolladores de FoxPro de todo el mundo. Es Gerente de la empresa Flash Software.


Ramon Seoane (Molduras del Nororeste)

Licenciado en Informática por la Universidad de La Coruña. Amplia experiencia como programador desarrollando aplicaciones de tipo industrial con autómatas, de comunicaciones con toda clase de dispositivos, de gestión comercial, de producción, etc. En el año 1999 obtengo el título de MCP en Visual Basic y en el 2000 el de Visual Fox. Como responsable del departamento de Sistemas de la empresa Molduras del Noroeste, he llevado a cabo la ardua labor de llenar los escritorios de cabecitas de zorro, haciéndose más popular que el cocodrilo de Lacoste. He utilizado Visual Fox como cliente de SQL Server realizando una aplicación ERP enteramente con dichas herramientas, incluso cuando no había un sólo libro sobre el tema y había que echarle algo de imaginación. Fiel defensor del Fox desde sus orígenes, he impartido cursos desde aquel Dbase hasta el Visual Foxpro 6.0 y participado en el IV Congreso de desarrolladores de Fox comentando las posibilidades de la FOXISAPI aprovechando la información que amablemente Rick Strahl me permitión incorporar.


Jorge Serrano Pérez (portalvb.com)

Nació en Madrid en 1973. Ingeniero Técnico en Informática de Gestión por el ICAI y Microsoft MVP. Su experiencia profesional se extiende por el mundo de la gestión, de la informática y de las telecomunicaciones, especializado en el desarrollo web y de aplicaciones de gestión y de negocio fundamentalmente, posee conocimientos extensos sobre Visual Basic dónde comenzó con la versión 1.0 de este producto, ASP, C#, ADO .NET y SQL Server principalmente, si bien sus conocimientos se extienden en un amplio abanico de aplicaciones de diferentes características y fabricantes. Entre otras cosas, es el propietario, webmaster y administrador de PortalVB.com (http://www.portalvb.com), el primer portal del mundo de habla hispana dedicado a la tecnología .NET con gran cantidad de información para el informático, desarrollador, administrador de sistemas y estudiante, es autor y colaborador de diferentes libros para la editorial Anaya Multimedia (http://www.anayamultimedia.es), de artículos técnicos en la revista técnica Windows TI Magazine (http://www.windowstimag.com), y colaborador de MSDN Latinoamérica (http://www.microsoft.com/latam/msdn) y de MSDN España (http://www.microsoft.com/spain/msdn).

10 de octubre de 2002

Conferencia VFP España 2003

Aquí tenéis mas información ...

Se celebrará del 19 al 21 de Febrero de 2003 en La Coruña (España). Aunque el 19 serán sesiones introductorias (VFP, SQL Server y NET), lo que posiblemente hará que la mayoría de la gente venga solo el 20 y 21.

El costo está todavía sin cerrar del todo pero andará en torno a 100 Euros e incluirá una o dos comidas. Actualmente ya tenemos 10 conferenciantes, bastantes MVPs de SQL Server, VFP y NET.

Lo que si anticipo, que os tendréis que apurar cuando se abra el registro, ya que se calculan problemas de plazas (a pesar de que el salón admita unas 400 personas).

Posibles conferencias:

  • Webservices (I) introducción
  • WebServices (II) intermedio
  • Novedades del VFP8
  • Visual FoxPro y productos de terceros (Mabry, Xceed, Leadtools, PDF ...)
  • Introduccion a NET Framework
  • ADO.NET o ASP.NET (con VFP)
  • VFP e internet (WebConnection, voodoo)
  • Control de proyectos
  • Introduccion a VFP
  • Automatización de Office con VisualFoxpro
  • Consultas en Visual FoxPro
  • VFP y programacion orientada a objetos
  • VFP con SQL Server
  • VFP y NET
  • Introduccion a SQL Server
  • Administracion de SQL Server
  • Optimizacion de SQL Server
  • COM en VisualFoxpro -
  • Acceso a Datos (vistas, SQL Passtrough, CursorAdapter) -
  • Bases de datos en VFP (buffering, reglas de validacion, procedimientos almacenados, database events)
  • 101 trucos en VFP
  • Control de Errores en VFP (TRY .. CATCH)
  • Event Binding
  • Diseño multicapa (objetos de negocio, ...)
  • Visual FoxPro Toolkit para .NET
  • Intellisense con VFP
  • .....


Revisión 15/10/2002

Las conferencias se haran en dos bloques, dia 19 solo alumnos y como dia extra para profesionales, seria de pago para los profesionales fundamentelmente por el tema de darles de comer.

Y dias 20 y 12, para ambos (profesionales y alumnos), para estos dos dias tenemos 12 horas de sesiones, la posibilidad de sesiones simultaneas está un tanto limitada, ya que la segunda sala, no tiene nada que ver (tanto en calidad como en espacio, es de 150 plazas). Por tanto se dará solo una sesión al tiempo. Lo que si haremos es en dicha aula complementaria, una vez que los conferenciantes finalizen se iran alla, para ampliar información, comentar trucos, etc ... la tendremos como un aula de charlas.

Dia 19 Sesiones:


  • Introducción a SQL Server - Fernando Guerrero/Miguel Egea
  • SQL Server (1) - Fernando Guerrero/Miguel Egea
  • SQL Server (2) - Fernando Guerrero/Miguel Egea
  • Introducción a VFP (1) - Pedro Hernandez
  • Introducción a VFP (2) - Pedro Hernandez
  • Introducción a VFP (3) - Pedro Hernandez
  • Introducción a NET(1) - Jorge Serrano
  • Introducción a NET(2) - Jorge Serrano


Debido a que tenemos solo posibilidad de 12 sesiones, mas la keynote, este seria un boceto mas aproximado:

  • Webservices - Pablo Roca
  • VFP y NET - Alberto Rodriguez
  • VFP e internet - Jose Enrique Llopis
  • Office con VFP - Luis Maria Guayan
  • VFP y OOP - Ramon Seoane
  • VFP y SQL Server - Ramon Seoane
  • Acceso a Datos (vistas, SQL Passtrough, CursorAdapter) - Luis Maria Guayan
  • Control de Errores en VFP (TRY .. CATCH) - Les Pinter
  • XML - Les Pinter
  • Optimizacion de SQL Server - Fernando Guerrero/Miguel Egea
  • ASP.NET con VFP - Jorge Serrano
  • Sesiones offline - Fernando Puyuelo/Miguel Angel Hernaiz


En la que mas tengo dudas es en la de XML, quizas seria mejor profundizar mas en otra area que dedicar 1 hora entera a XML.

Necesito opiniones sobre estas conferencias, alguna idea adicional sobre conferencias, etc ...

Pablo Roca

25 de septiembre de 2002

Convertir Números a Letras

Usuarios de Visual Foxpro®: Espero que les sea util esta rutina, convierte números hasta 999,999,999,999.99 y con gusto la comparto con Uds.
Function Convnum(Total)
  * Autor: Anselmo Antonio Ortiz Alcocer
  * Corrreo: ortizanst@hotmail.com
  * 26/06/2001
  Dimension aUnidades(9), aDecenas(14), aCentenas(10)
  aUnidades(1) = 'UN'
  aUnidades(2) = 'DOS'
  aUnidades(3) = 'TRES'
  aUnidades(4) = 'CUATRO'
  aUnidades(5) = 'CINCO'
  aUnidades(6) = 'SEIS'
  aUnidades(7) = 'SIETE'
  aUnidades(8) = 'OCHO'
  aUnidades(9) = 'NUEVE'
  aDecenas(1) = 'DIEZ'
  aDecenas(2) = 'ONCE'
  aDecenas(3) = 'DOCE'
  aDecenas(4) = 'TRECE'
  aDecenas(5) = 'CATORCE'
  aDecenas(6) = 'QUINCE'
  aDecenas(7) = 'VEINTE'
  aDecenas(8) = 'TREINTA'
  aDecenas(9) = 'CUARENTA'
  aDecenas(10) = 'CINCUENTA'
  aDecenas(11) = 'SESENTA'
  aDecenas(12) = 'SETENTA'
  aDecenas(13) = 'OCHENTA'
  aDecenas(14) = 'NOVENTA'
  aCentenas(1) = 'CIEN'
  aCentenas(2) = 'DOSCIENTOS'
  aCentenas(3) = 'TRESCIENTOS'
  aCentenas(4) = 'CUATROCIENTOS'
  aCentenas(5) = 'QUINIENTOS'
  aCentenas(6) = 'SEISCIENTOS'
  aCentenas(7) = 'SETECIENTOS'
  aCentenas(8) = 'OCHOCIENTOS'
  aCentenas(9) = 'NOVECIENTOS'

  vTotal = str(int(Total), 12)

  Do case
    Case empty(val(vTotal))
      Texto = 'CERO PESOS'

    Case val(vTotal) = 1
      Texto = 'UN PESO'

    Otherwise
      tCientos     = obt_cant(substr(vTotal,10,3))
      tMiles       = obt_cant(substr(vTotal,7,3))
      tMillones    = obt_cant(substr(vTotal,4,3))
      tMilMillones = obt_cant(substr(vTotal,1,3))

      tCientos = tCientos
      tMiles = iif(empty(tMiles), '', ;
               iif(tMiles='UN', '', tMiles + ' ') + 'MIL ')
      tMillones = iif(empty(tMillones), '', ;
               tMillones + ' MILLON' + iif(tMillones='UN', ' ', 'ES ') +;
               iif(empty(tMiles + tCientos), 'DE', ''))
      tMilMillones = iif(empty(tMilMillones), '', ;
               iif(tMilMillones='UN', '', tMilMillones + ' ') + 'MIL ' +; 
               iif(empty(tMillones), 'MILLONES ', ' ') +;
               iif(empty(tMillones + tMiles + tCientos), 'DE', ''))

      Texto = strtran(tMilMillones + tMillones + tMiles + tCientos, '  ', ' ') + ' PESOS'
  Endcase
  
Return Texto + iif(!empty(Total), ' CON ' + ;
   strtran(transform(int((total - int(total)) * ;
   100), '**'), '*', '0') + '/100 M.N.', '')

Function obt_cant(valor)
  Public Unidades, Decenas, Centenas

  If empty(val(valor))
    Return ''
  Endif

  Store '' to tUnidades, tDecenas, tCentenas
  Unidades = int(val(substr(valor,3,1)))  &&          123
  Decenas  = int(val(substr(valor,2,1)))  && vTotal = 999
  Centenas = int(val(substr(valor,1,1)))  &&          ^^^
  valor = int(val(valor))

  tUnidades = iif(!empty(unidades), aUnidades(Unidades), '')

  If !empty(decenas)
    If decenas = 1
      tDecenas = iif(val(right(str(valor,3),2)) >= 10 and ;
      val(right(str(valor,3),2)) <= 15, aDecenas(val(right(str(valor,3),2)) - 9), 'DIECI' + tUnidades)
      tUnidades = ''
    Else
      tDecenas = aDecenas(decenas + 5)
      if !empty(unidades)
        tDecenas = left(tDecenas, len(tDecenas) - 1) + 'I'
      Endif
    Endif
  Endif

  If !empty(centenas)
    tCentenas = aCentenas(centenas)
    If valor > 100
      If centenas = 1
        tCentenas = tCentenas + 'TO '
      Else
        tCentenas = tCentenas + ' '
      Endif
    Endif
  Endif
  
Return tCentenas + tDecenas + tUnidades
Anselmo Antonio Ortiz Alcocer

23 de septiembre de 2002

Problemas con tu OCX ?

¿Tienes problemas con las OCX's que funcionan perfectamente en tu máquina y cuando haces el ejecutable, pero que no funcionan en el equipo del usuario final?

El problema es que las ocx están diseñadas para que ellas mismas se registren durante el evento constructor. Se llama a una función que tienen todas las ocx llamada DLLRegisterServer. El problema está en que Power Builder no llama a esta función. Incluso si ejecutas la utilidad que viene con windows REGSRV, la ocx falla al registrarse a si misma. Para corregir este problema, en el objeto donde tu estás usando la ocx, en el evento constructor llama a la funcion DLLRegisterServer.

Declara una función externa local en el objeto:
Function long DllRegisterServer()Library "ocxname.OCX"

y en el evento Constructor:
LONG ll_RC ll_RC = DllRegisterServer()

Ramón Rodríguez Martínez

17 de septiembre de 2002

Búsquedas sencillas

Autor: Les Pinter

De cúantas maneras quieren tus usuarios hacer consultas? Probablemente demasiadas.

No te cuento cuántas veces mis usuarios me han dicho "quiero poder buscar lo que sea." Pero cuando les digo que esa posibilidad no viene incluido en el lenguaje, sino que va a costar dinero, y que dependiendo de como lo haga puede ser increiblemente lento, usualmente piden otra cosa - algo rápido y barato.

Afortunadamente hay por lo menos dos maneras de añadir un grupo de consultas a cualquier pantalla. Se hace en unos cuantos minutos, y dado el precio, la mayoría de los usuarios descubrirán que hace todo lo que quieren - o al menos, todo lo que están dispuestos a pagar. Lo cual es precisamente lo que queríamos saber en primer término...

1 - Usa un marco de página con una Listbox cuyas columnas pueden ser ordenadas en la última página.

Este truco se aprovecha del hecho de que en el caso de las ListBox con RowSourceType = 2 (Alias), el puntero de la tabla se mueve a medida que el usuario cambia el registro seleccionado en la ListBox. (RowSourceType = 2 quiere decir usa los primeros n campos de la tabla como fuente de datos para las columnas del control, mientras que RowSourceType = 6 (Campos) significa que vas a especificar los nombres y el orden de los campos que aparecen en las columnas del control. Al pasar el usuario por los registros mostrados en el control, el puntero de la tabla o el cursor se mueve correspondientemente. Así que el puntero de la tabla siempre está de acuerdo con el muestreo. Pero, me preguntas, esto ¿cómo me ayuda con las búsquedas?

Consideremos qué debe lograr una consulta. Se usa una consulta para localizar algo rápidamente. Tradicionalmente, usando SQL, hacemos una consulta que le muestra al usuario todo lo que se asemeja a lo que busca, y le dejamos escoger el registro exacto de esa selección reducida. Pero esto implica una TextBox para permitir que escriban lo que quieren buscar, lo cual puede contener errores ortográficos; y ¿buscamos mayúsculas o minúsculas? ¿Hay cupo en la pantalla para entrar la clave? ¿Donde pondremos la lista de candidatos a seleccionar? Y ¿qué haremos con los registros que no hacen juego con lo que entró el usuario pero sí son lo que está buscando?

En vez de hacer todo esto, aprovechemos el hecho de que una tabla FoxPro con todos sus registros ya está disponible. Sencillamente vamos a mostrar una grid con las columnas más importantes que le permitirá al usuario seleccionar visualmente, cambiando el orden de una u otra columna hasta ver lo que estaba buscando. Y lo mejor es que se puede hacer esto con sólo unas cuantas líneas de código.

Como siempre, FoxPro proporciona un mecanismo muy sencillo para hacer esto. Primero, añadimos un método llamado ColumnSort al formulario. Si usas una clase base para todos tus formularios, práctica que recomiendo sin reservaciones, añade esta clase a la clase de formulario que usas como plantilla. La sentencia PARAMETER debe ser la primer línea de código en el método:
* ColumnSort method
PARAMETER IndexTagName
WITH THISFORM
SELECT (.DataEnvironment.InitialSelectedAlias)
SET ORDER TO ( IndexTagName )
.LockScreen   = .T.
.PageFrame1.Pages[.PageCount].MyList1.Refresh
.LockScreen   = .F.
ENDWITH
Yo siempre escribo el nombre de la tabla o alias principal de cada formulario en la propiedad InitialSelectedAlias del Entorno de Datos del mismo. Esto me permite escribir código genérico para habilitar métodos como BorrarRegistro, ProximoRegistro, etc.

Luego, como lo implica el códogo, arriba, añade un marco de página (PageFrame) al formulario y pon una ListBox en la última página. Como RowSourceType usa 6, y como RowSource el nombre de la tabla principal, más los nombres de las columnas que aparecen en la tabla, e.g. "CLIENTES.Nombre,Telefono,Ciudad,Estado,Contact", usando los nombres que aparecen en los encabezados de las columnas de la ListBox. Se usa la propiedad ColumnCount de la ListBox para precisar el número de columnas, y el número de píxeles de anchura de cada columna, separadas con comas en la propiedad ColumnWidth. Se suele usar Max(anchura del valor más ancho que aparecerá en la columna,anchura del título que aparece en el encabezado) como la anchura para cada columna.



Después de poner una etiqueta encima de cada columna, usa lo que sigue en el evento Click de cada una de las etiquetas:
THISFORM.SetIndex ( THIS.Caption )
El método SetIndex cambia el orden de la tabla y refresca el contenido que aparece en la lista. (Si tu índice no tiene el mismo nombre que la etiqueta, sigue leyendo - no es difícil manejar tales casos.)
PROCEDURE SetIndex
PARAMETERS   IdxName
WITH THISFORM
SELECT (.DataEnvironment.InitialSelectedAlias )
SET ORDER TO ( IdxName )
.PageFrame1.Page2.MyList1.Requery
ENDWITH
Finalmente, esto es el código DblClick de la ListBox:
PROCEDURE mylist1.DblClick
WITH THISFORM
.LockScreen   = .T.
.PageFrame1.ActivePage   = 1
.PageFrame1.Page1.Refresh
.LockScreen   = .F.
ENDWITH
Esto activa la primera página, la cual muestra el contenido de los demás campos del registro seleccionado mediante el puntero de la ListBox.

Como indicamos, esto asume que se usan TAGS de índice cuyos nombres son exactamente los mismos que las capciones de las etiquetas que sirven de encabezados para la ListBox en la última página. Si prefieres usar nombres de índices que no se pueden usar como TAGS de índice (por ejemplo, capciones que contienen espacios), el método SetIndex debe incluir una forma de usar la capción para encontrar el nombre del TAG, a saber:
SET ORDER TO TAG ( ;
IIF(IdxName=[Nombre del Cliente],[Nombre], ;
IIF(IdxName=[Apellido del Cliente],[Apellido] , IdxName)))

Un Doble-Click en la lista nos lleva a la primera página:



Qué pasa si hay muchísimos registros?

Desafortunadamente, este método es demasiado lento cuando se usa con tablas grandes en una máquina con memoria limitada. En una P266 con 32MB de memoria, trabaja bien hasta llegar a los 10,000 registros. Pero si a FoxPro le falta memoria para guardar el índice actual en la memoria, se vuelve lenta la búsqueda - cosa que los usuarios acostumbrados a la rapidez de FoxPro no toleran muy bien. Por esto, para efectuar búsquedas de tablas con muchos registros, es mejor crear una pantalla de búsqueda separada.

2 - Usa una pantalla de consulta para retornar la clave deseada

Para lograr esto, añade al forulario un botón de comando que a su vez abre otro formulario que contiene una ListBox para mostrar los resultados de la consulta. Pon una combobox y una textbox al fondo de la pantalla, para permitirle al usuario precisar las características que quiere usar para localicar registros. El combobox especifica el nombre del campo a usar, y el textbox el valor a buscar en el campo especificado.

El evento Valid del textbox es entonces responsable por cargar todos los registros que contienen el valor especificado en el campo seleccionado. El usuario usa una DobleClick en uno de los registros que aparecen en la lista para seleccionar un valor y retornar su código. El código del botón de consulta del formulario que abrió la pantalla de consulta entonces usa el valor retornado para encontrar y mostrar el registro seleccionado.



Dentro del formulario de consulta, el código Valid del combobox asegura el formateo correcto de la clave entrada por el usuario:
THISFORM.ActiveTag = THIS.Value
WITH THISFORM.MyText1
DO CASE
  CASE THIS.Value = "Name"
     .Format = "K"
     .InputMask = "!!!!!!!!!!"
     THISFORM.KeyField = [UPPER(LastName)]
     THISFORM.OrderField = [LastName]
  CASE THIS.Value = "Company"
     .Format = "K"
     .InputMask = "!!!!!!!!!!"
     THISFORM.KeyField = [UPPER(Company)]
     THISFORM.OrderField = [Company]
  CASE THIS.Value = "Zip"
     .Format = "K"
     .InputMask = "#####"
     THISFORM.KeyField = [Zip]
     THISFORM.OrderField = [Zip]
  CASE THIS.Value = "Phone"
     .Format = "K"
     .InputMask = "(###)########"
     THISFORM.KeyField = [Phone]
     THISFORM.OrderField = [Phone]
  CASE THIS.Value = "CustNo"
     .Format = "K"
     .InputMask = "#####"
     THISFORM.KeyField = [CustNo]
     THISFORM.OrderField = [CustNo]
ENDCASE
.Visible  = .T.
ENDWITH
THISFORM.Input = SPACE(30)
KEYBOARD [{RightArrow}]
El código del método Valid de la TextBox carga y muestra todos los registros que cumplen con los requisitos del usuario:
WITH THISFORM.MyList1
.RowSourceType  = 0
.Clear
ENDWITH

WITH THIS
* Crea la clave para el número de teléfono,
*  quitándole todo lo que no sea un dígito:
IF .Value  = [(]
   IF LEN(TRIM(.Value)) > 5
    .Value = LEFT(THIS.Value,8) + [-] ;
     + IIF(SUBS(THIS.Value,9,1)=[-], ;
           SUBS(THIS.Value,9), ;
           [-]+SUBS(THIS.Value,9))
   ENDIF
ENDIF
Key = TRIM(.Value)
SELECT CUSTOMER
SET ORDER TO TAG ( THISFORM.Mycombo1.Value )
SET EXACT OFF   && Importante! Permite claves parciales
* will not match if SET("EXACT") = "ON"
SEEK Key
DO WHILE ( NOT FOUND() ) AND ( LEN(Key) > 0 )
   Key = LEFT ( Key, LEN(Key)-1 )
   SEEK Key
ENDDO
ENDWITH

IF ( NOT FOUND() ) OR ( LEN(Key) = 0 )
   IF ( NOT FOUND() )
      MessageBox ( "No match", 64, "My App" )
   ENDIF
   THISFORM.Init
   RETURN
ENDIF
WITH THISFORM
.LockScreen   = .T.
.MousePointer = 11
ENDWITH
WITH THISFORM.MyList1
.RowSourceType = 0
cmd = [SELECT lastname,firstname,company,phone,zip,custno] ;
    + [ FROM CUSTOMER] ;
    + [ WHERE ]+THISFORM.KeyField+[ = "]+Key+["] ;
    + [ INTO CURSOR Matches] ;
    + [ ORDER BY ] + THISFORM.OrderField
&cmd
.RowSource     = "Matches"
.RowSourceType = 2
.Requery
.Refresh
.Selected[1]   = .T.
ENDWITH
WITH THISFORM
.SetAll  ( "ForeColor", RGB(0,0,0),"MyLabel")
* SORT NO LONGER APPLIES
.LockScreen    = .F.
.MousePointer  = 1
ENDWITH
Yo le cambio el color de los encabezados de las columnas para indicar visualmente que los datos ya están ordenados:

He aquí el código del evento DblClick de la ListBox:
THISFORM.Release
Y el código del método UNLOAD del formulario es esto:
RETURN THISFORM.MyList1.Value
En el código Click del botón de comando de Búsqueda del formulario original hay que tener esto:
DO FORM Search TO m.CustCode
IF NOT EMPTY ( m.CustCode )
   SEEK m.CustCode
   THISFORM.Refresh
ENDIF
Existen decenas de variaciones que pueden ser derivadas de este método básico. En mi ejemplo, deshabilité el mecanismo de consulta una vez que hayan seleccionado un campo de búsqueda de la ComboBox. Si quieren efectuar otra consulta, tienen que cerrar el formulario y volverlo a seleccionar, del mismo formulario, de otro, o del menú. En algunas aplicaciones sería preferible permitirle al usuario quedarse en la misma pantalla y hacer cuantas consultas quiere antes de seleccionar un registro y retornar - por ejemplo, para buscar padrones en una tabla de facturas antiguas para una auditoría.

Se pueden construir consultas sofisticadas para otros casos. Por ejemplo, para seleccionar todos los registros padres que tiene al menos un registro hijo con determinada característica - todas las facturas en las cuales una de las cosas compradas fué un podador de césped, por ejemplo - se puede crea una subconsulta:
SELECT &FldList. FROM INVOICES ;
 WHERE INVNO IN                     ;
 ( SELECT INVNO FORM INDETAIL  ;
    WHERE [LAWNMOWER] $ UPPER(DESC) ) ;
    ORDER BY INVDATE DESC INTO CURSOR Matches
También se pueden crear subconsultas que incluyen una lista determinada de valores - por ejemplo, las facturas de los tres clientes más importantes:
SELECT &FldList. FROM INVOICES   ;
WHERE CustNo IN ("00102","00352","00601")

14 de septiembre de 2002

Calcular la edad de una persona

Función para calcular la edad de una persona.

*-----------------------------------------------------
* FUNCTION Edad(tdNac, tdHoy)
*-----------------------------------------------------
* Calcula la edad pasando como parámetros:
*  tdNac = Fecha de nacimiento
*  tdHoy = Fecha a la cual se calcula la edad.
*          Por defecto toma la fecha actual.
*-----------------------------------------------------
FUNCTION Edad(tdNac, tdHoy)
  LOCAL lnAnio
  IF EMPTY(tdHoy)
    tdHoy = DATE()
  ENDIF
  lnAnio = YEAR(tdHoy) - YEAR(tdNac)
  IF GOMONTH(tdNac, 12 * lnAnio) > tdHoy
    lnAnio = lnAnio - 1
  ENDIF
  RETURN lnAnio
ENDFUNC

Luis María Guayán

11 de septiembre de 2002

Campos "Autoincrementales" en VFP (anteriores a la versión 8)

Ejemplo de como podemos implementar campos autoincrementales en nuestras tablas de Visual FoxPro® contenidas en una Base de Datos.
Nota: Esto es para versiones anteriores a VFP8, ya que esta versión ya posee nativamente la característica de campos autoincrementales en sus tablas.
Para lograr esto seguimos los siguientes pasos:
  • Creamos una Base de Datos de ejemplo: "EjemploDBC.DBC"
  • Creamos dos Tablas de ejemplo: "Ejemplo1.DBF" y "Ejemplo2.DBF"
  • Creamos una Tabla que almacenará los valores Id's Autoincremetales de cada tabla que lo necesite. Esta tendrá un registro por tabla: "Tabla_Ids.DBF"
  • Creamos un Procedimiento Almacenado: "NuevoId(tcAlias)"

El siguiente código crea la DBC y DBF's.

CREATE DATABASE EjemploDBC
SET DATABASE TO EjemploDBC

*-- Tabla de Ejemplo
CREATE TABLE Ejemplo1 ;
  (uId I NOT NULL DEFAULT NuevoId("Ejemplo1"), ;
  cCampo C(10) NOT NULL)

*-- Tabla de Ejemplo
CREATE TABLE Ejemplo2 ;
  (uId I NOT NULL DEFAULT NuevoId("Ejemplo2"), ;
  nCampo N(10,2) NOT NULL)

*-- Tabla con los nombres de tablas y sus Id's
CREATE TABLE Tabla_Ids ;
  (NombreTabla C(30) NOT NULL, ;
  uId I NOT NULL)
INDEX ON UPPER(NombreTabla) TAG NomTab

*-- Por cada tabla que requiera un campo
*   Id Autoincremental añado un registro
INSERT INTO Tabla_Ids VALUES("Ejemplo1", 0)
INSERT INTO Tabla_Ids VALUES("Ejemplo2", 0)

CLOSE DATABASES
RETURN

Para crear el Procedimiento Almacenado, modificamos la Base de Datos "EjemploDBC.DBC" con:
MODIFY DATABASE EjemploDBC
y en la ventana de Procedimientos Almacenados (Menú -> Base de datos -> Modificar procedimientos almacenados) copiamos la siguiente función:

FUNCTION NuevoID(tcAlias)
  LOCAL lcAlias, lnID, lnAreaAnt, lcReprAnt
  lnID = 0
  lnAreaAnt = SELECT()
  lcReprAnt = SET('REPROCESS')
  SET REPROCESS TO AUTOMATIC
  lcAlias = UPPER(ALLTRIM(tcAlias))
  IF NOT USED("Tabla_Ids")
    USE EjemploDBC!Tabla_Ids IN 0
  ENDIF
  SELECT Tabla_Ids
  IF SEEK(lcAlias, "Tabla_Ids", "NomTab")
    IF RLOCK()
      REPLACE uId WITH Tabla_Ids.uId + 1 IN Tabla_Ids
      lnID = Tabla_Ids.uId
      UNLOCK
    ENDIF
  ENDIF
  SELECT (lnAreaAnt)
  SET REPROCESS TO lcReprAnt
  RETURN lnID
ENDFUNC

Las tablas "Ejemplo1.DBF" y "Ejemplo2.DBF" contienen el campo uId que automaticamente se incrementa por el Procedimiento Almacenado cada vez que hacemos un INSERT.

Ahora miremos el ejemplo de como funcionan:

IF NOT USED("Ejemplo1")
  USE Ejemplo1 IN 0
ENDIF 
IF NOT USED("Ejemplo2")
  USE Ejemplo2 IN 0
ENDIF 
FOR ln = 1 TO 100
  INSERT INTO Ejemplo1 (cCampo) ;
    VALUES(SYS(2015))
  INSERT INTO Ejemplo2 (nCampo) ;
    VALUES(RAND()*1000)
ENDF

Note que no se incluye el campo uId en la lista de los campos. El campo uId contine un valor por defecto. Este valor por defecto es realmente una llamada al procedimiento almacenado (NuevoId()) que retorna un número único que se agrega al campo uId de la tabla pasada como parámetro cuando añadimos un nuevo registro.
Nota: Parte del código de este artículo fue tomado y ligeramente modificado del artículo de la MSKB http://support.microsoft.com/kb/316910/es

21 de agosto de 2002

¿Cómo saber si el equipo está conectado a Internet?

Una simple llamada a esta función puede resolverlo.

Tomado del news de microsoft en ingles;

? IsInternetActive()

***********************************
Function IsInterNetActive (tcURL)
***********************************
* PARAMETERS: URL, no olvidar pasar la URL completa, con http:// al inicio
* Retorna .T. si hay una conexion a internet activa
*Tekno
*Wireless Toyz
*Ypsilanti, Michigan
***********************************
tcURL = IIF(TYPE("tcURL")="C" AND !EMPTY(tcURL),tcURL,"http://www.yahoo.com")

  DECLARE INTEGER InternetCheckConnection in wininet;
                    STRING lpszUrl,;
                    INTEGER dwFlags,;
                    INTEGER dwReserved

    RETURN ( InternetCheckConnection(tcURL, 1, 0) == 1)
ENDFUNC
***********************************
Esparta Palma

19 de agosto de 2002

Preview en forma maximizada y con diálogo de impresión

Preview en forma maximizada y con diálogo de impresión, mucho más fácil y en una sola línea...

Esta línea de código me pareció muy buena, lo único que solo funciona de la versión 6.0 en adelante...

KEYBOARD '{CTRL+F10}' PLAIN CLEAR
REPORT FORM tureporte NOCO TO PRINTER PROMPT PREV

Chris Ascencio

16 de agosto de 2002

REGISTRAR Y DESREGISTRAR UN ARCHIVO OCX O DLL

Excelente cuando distribuimos aplicaciones.
DECLARE INTEGER DLLSelfRegister IN "Vb6stkit.DLL" ;
  STRING lpDllName

*-- Esta es la ruta donde esta el archivo
nombredll="s:/sistemas/ocx/hwnd.ocx"

liRet = DLLSelfRegister(NombreDll)

IF liRet = 0 Then
  SelfRegisterDLL = .T.
  MESSAGEBOX ("Registrado ocx")
ELSE
  SelfRegisterDLL = .F.
  MESSAGEBOX ("Error- No Registrado ocx")
ENDIF
Jorge Mota

15 de agosto de 2002

Print Preview en ventana Maximizada versión OOP

Solucion OOP del problema de la ventana Preview Maximizada.

Cómo ya muchos sabran, las ventanas de Preview del reporteador no están maximizadas, por lo que para remediarlo se ha utilizado la instrucción define window, pero a continuación les comparto algo que para no variar encontré en los foros en ingles del news de microsoft.
*-----------------------------
* Fred Taylor
* Microsoft Visual FoxPro MVP
* News: microsoft.public.fox.vfp.reports.printing
* Subject: Re: Report preview window size control
* Date: 05/19/2002
*-----------------------------
oForm = CREATEOBJECT("Form")
WITH oForm
   .Caption = "Tu Titulo del Preview"
   .WindowState = 2    && Maximized
   .Show()

   REPORT FORM tureporte PREVIEW WINDOW (.Name)
   .Release()
ENDWITH

Esparta Palma

14 de agosto de 2002

ACTIVAR Y DESACTIVAR EL PROTECTOR DE PANTALLA

Útil cuando no queramos que el protector se active cuando estamos programando.

DECLARE LONG SystemParametersInfo IN "user32";
  LONG uAction,;
  LONG UParam,;
  LONG lpvParam,;
  LONG fuWinIni

SPI_SETSCREENSAVEACTIVE = 17
SPIF_SENDWININICHANGE = 2

*-- para impedir que se active :
=SystemParametersInfo (SPI_SETSCREENSAVEACTIVE, .F., 0, SPIF_SENDWININICHANGE)

*-- Para volver a restaurarlo como estaba antes de nuestra intervención :
=SystemParametersInfo (SPI_SETSCREENSAVEACTIVE, .T., 0, SPIF_SENDWININICHANGE)

Jorge Mota

13 de agosto de 2002

Autoregistrar OCX

Como autoregistrar OCX y DLLs. Tomado del news de microsoft en inglés.
------------------------------------------
DECLARE LONG DllRegisterServer IN [archivo.ocx]
IF DllRegisterServer() = 0
    * OK
ELSE
    * Not OK
ENDIF
Este método también puede ser usado con archivos COM DLL.

Eric den Doop

12 de agosto de 2002

VACIAR LA CARPETA DE DOCUMENTOS RECIENTES

Util cuando no queramos que vea el usuario que archivo estuvimos abriendo.

DECLARE LONG SHAddToRecentDocs IN "Shell32";
  LONG lFlags,;
  LONG lPv

=SHAddToRecentDocs (0, 0)

Jorge Mota

8 de agosto de 2002

WebRAD: Building Database Applications on the Web with Visual FoxPro and Web Connection

WebRAD: Building Database Applications on the Web with Visual FoxPro and Web Connection 
by Harold Chattaway, Randy Pearson & Whil Hentzen.



Title: WebRAD: Building Database Applications on the Web with Visual FoxPro and Web Connection
Author: Harold Chattaway, Randy Pearson & Whil Hentzen
Edited: Barbara Peisch
ISBN: 1-930919-07-7
Length: 502 pages
Press date: June, 2002
Ebook format: .PDF

Table of Contents

1. Introduction
2. What's In Web Connection
3. Your First Web Connect App - Installing Web Connection
4. Your First Sample Application
5. How the Internet Works
6. Complete Web Development Environment
7. Server Hardware and Hosting
8. Configuring Server Software
9. How a Web Page Works
10. Getting in Tune - Overcoming Conceptual Hurdles
11. Managing Your Configuration
12. A Web Connection Application from Start to Finish
13. Identifying Users and Managing Session Data
14. Com vs File Based Operation
15. 101 Things You Want to Know How To Do
16. Extending the Framework
17. Advanced Trouble Shooting and Maintenance
18. Asynchronous Services
19. XML and Distributed Applications
20. SOAP and Web Services
21. Web Connection Add on Tools
22. Version Updates
23. Making Your Site Successful

Para mas información: http://www.hentzenwerke.com/catalog/webrad.htm

7 de agosto de 2002

CAMBIAR LA ETIQUETA DE UNA UNIDAD DE DISCO

Ejemplo para cambiar la etiqueta de un disco.

DECLARE LONG SetVolumeLabel IN "kernel32" ;
  STRING lpRootPathName,;
  STRING lpVolumeName

=SetVolumeLabel("D:/","PROBANDO")

Jorge Mota

2 de agosto de 2002

COM+ con VFP 7.0 - Parte 2

Publicado originalmente en FoxTalk.
Traducción de Jorge Espinosa.

En la primera parte de esta serie hemos revisado COM y MTS y nos hemos introducido en los servicios de COM+ en Windows 2000.También le he mostrado como crear una nueva aplicación COM+ e instalar el componente en el servidor. Ahora, en la parte 2, le mostraré cómo hacer que el cliente use el componente en el servidor, la seguridad y el manejo de errores.

¿El cliente siempre tiene razón?

En el articulo anterior, le he mostrado cómo instalar su aplicación en el servidor. Pero esto, no sirve de mucho si usted no puede usar el componente desde el cliente. Lo bueno es, que Windows 2000 hace que esto sea muy fácil. Yendo un poco hacia atrás, al componente que hemos creado e instalado, le damos un vistazo al código:

DEFINE CLASS Math AS SESSION OLEPUBLIC
FUNCTION Multiply(tnNum1, tnNum2)
LOCAL lnResult, loMtx, loContext 
*Creamos una referencia al Objeto MTS
loMtx=CREATEOBJECT("MTXAS.APPSERVER.1") 

*Creamos la ref. al Contexto
loContext=loMtx.GetObjectContext()
lnResult=tnNum1*tnNum2

*Si hay alguna transacción abierta hacemos el Commit y avisamos al MTS
*que terminamos de usar el objeto.
loContext.SetComplete()
RETURN lnResult
ENDFUNC
ENDDEFINE
He llamado a este el más tonto de los componentes COM. Todo lo que hace es multiplicar dos números. Sin embargo, manteniendo este simple código de ejemplo, nos permite concentrarnos en los aspectos COM+ del mismo.

Ahora, necesitamos hacer la instalación en el cliente. Volvamos a Component Services Manager. Expanda el árbol debajo de COM+ Applications y seleccione MyFirstCOMApp. Esta es la aplicación que hemos creado e instalado en el artículo anterior. Ahora, clic derecho sobre MyFirstCOMApp y seleccionar Export. El asistente de COM Application Export aparecerá (Figura 1). En primera instancia le solicitará el path completo y el nombre del archivo para su aplicación. Ingrese C:\COMApps\MyFirstCOMAppProxy. Luego asegúrese de haber seleccionado "Application Proxy-Install on other machines to enable access to this machine". (La otra opción, "Server Application", es utilizada cuando quiere instalar el componente en otro servidor). clic en Next para finalizar.


El asistente creará dos archivos MyFirstCOMAppProxy.MSI.CAB y MyFirstCOMAppProxy.MSI. Estos archivos pueden ser copiados e instalados en la computadora cliente. Estos archivos NO contienen el componente. Usted no necesita de él en el cliente. Estos archivos contienen información acerca de los métodos públicos y propiedades que expone el componente y punteros hacia el servidor que lo aloja de modo que Windows pueda encontrarlo e instanciarlo.

Nuevamente, usted nunca instala el componente en el cliente. En su lugar instala un proxy. Su aplicación "piensa" que el componente esta alojado y corriendo localmente, pero no es así. Tome nota de esto. Un componente instalado sobre un servidor de aplicaciones nunca corre en el cliente. Por alguna razón, este es un concepto difícil de entender para algunas personas.

Usted instancia el componente servidor exactamente de la misma forma que lo hace localmente. Pruebe esto en su ventana de comandos:

ox = CREATEOBJECT("MyCom.Math")
? ox.Multiply(3, 4) && devolvera 12
ox = NULL
¿Porque ésto funciona?. VFP hace un llamado a Windows, solicitando instanciar el componente. Windows toma información del componente desde la Registry y encuentra que el componente se aloja en un servidor de aplicaciones. Windows entonces instancia el proxy del componente y hace una llamada al servidor para instanciar el componente. VFP no sabe que esta "hablando" con el proxy; "piensa" que lo hace directamente con el componente. Cuando usted llama al método Multiply, el proxy envía el llamado al Server vía DCOM y el resultado es pasado al proxy, quien a su vez se lo pasa a VFP.

¿Se siente inseguro?

Ahora que el componente está instalado en el servidor y el proxy en el cliente, permítame mostrarle cuan fácilmente podemos permitir o prohibir accesos a los componentes.

COM+ utiliza seguridad basada en roles. Un rol es un tipo de usuario. Por ejemplo, en un banco podría tener cajeros, gerentes, oficiales de cuenta, personal de servicios de clientes, etc. Cada una de esas personas cumple un rol. Si quiere prohibir a los cajeros hacer préstamos, en COM+ podría setear seguridad sobre el componente "PRESTAMO" para prohibir esto. Lo bueno es que esto es una opción de configuración que se puede cambiar utilizando el Component Service Manager.

Los roles están basados en los usuarios y grupos de Windows, así el primer paso en setear el esquema de seguridad es establecer los grupos de seguridad de Windows. Puede ser confuso para entender donde encajan los roles en las jerarquías de grupos y usuarios. Los archivos de ayuda COM+ dicen que : " Los Roles son categorías de usuarios que han sido definidos para la aplicación con el propósito de determinar los permisos de acceso para los recursos de la aplicación. El desarrollador asigna los roles (como categorías de usuarios simbólicas) para la aplicación". Esto se asemeja a un grupo de usuarios de Windows, tome esto con cuidado, piense en el rol como un grupo de usuarios para una aplicación específica.

Ahora volviendo sobre nuestro ejemplo del banco, tenemos cuatro grupos: cajeros, gerentes, oficiales de cuenta y servicios al cliente. Avanzamos y creamos estos en el servidor utilizando el User Manager de Windows NT o la herramienta Computer Management de Windows 2000. Ingrese el primer grupo de usuario y luego los otros tres.

Una vez que estos grupos han sido creados volvemos sobre MyFirstCOMApp en Component Services Manager. clic sobre roles, clic derecho y seleccione New Role. Ingrese el primer rol, cajero, y clic OK (figura 2). No es necesario nombrar los roles igual a los grupos de usuarios de Windows pero esto hace que la administración sea mucho mas fácil. Ahora cree los tres roles restantes gerente, oficial de cuenta y servicios al cliente.


Luego necesitamos identificar los usuarios en cada rol. Expanda el árbol debajo de cajero. Verá una carpeta llamada Users. clic sobre esa carpeta, clic derecho y seleccione New User. Ubique en la lista de grupos y usuarios de Windows y seleccione el grupo de usuarios Cajero, clic sobre Add. Lo mismo para gerentes y servicios al cliente. clic en Ok. Podrá ver cada uno de los grupos de seguridad de Windows agregados en el rol cajero en Components Services (figura 3).


Cuando un usuario es agregado al sistema este debe ser agregado a su correspondiente grupo de Windows que automáticamente lo agregará dentro de su rol correspondiente. Entonces tenemos identificados los roles, pero aún no le hemos indicado a Components Services sobre el uso de seguridad. clic sobre MyFirstCOMApp, clic derecho y seleccionamos Properties, allí seleccionamos la solapaSecurity. Habilitamos la opción "Enforce access checks for this application" y clic en Ok (figura 4). Ignore la otra opción por ahora que será tratada en breve.


Ahora clic derecho sobre MyComm.math y seleccione Properties, luego la solapa Security. Usted verá que está habilitada la opción"Enforce component level access checks". También verá la lista de seguridad para los roles. Simplemente seleccione el rol que tendrá acceso a este componente (figura 5).


Usted puede desplegar y asignar el acceso de seguridad a la interfaz o método en el nivel que usted desee. Esto le permite a usted tener un mismo componente con varias interfaces, cada una conteniendo diferentes niveles de seguridad. Si un usuario no tiene el permiso apropiado para el uso del componente, se mostrará un mensaje de error indicando que el componente no puede ser instanciado.

Ahora retomemos al cuadro de diálogo de Aplication Security (figura 4), allí hay algunas opciones adicionales que necesitamos discutir. La primera es el nivel de seguridad. Esta controla cuando COM+ valida la seguridad de usuario. Con la primera opción, "Perform access checks only at the process level", el chequeo del rol no será hecho a nivel de componente, interfaz o método. En la segunda opción,"Perform access checks at the process and component level", la seguridad es chequeada cuando se produce una llamada al componente. Casi siempre se debería utilizar la segunda opción.

No obstante, la primera opción es útil cuando usted ya tiene validado al usuario.

El siguiente seteo es "Authentication level for calls". Éste tiene seis opciones descriptas en la tabla 1.

Viendo a través de la lista cada opción toma progresivamente mas seguridad, note que el mayor nivel de seguridad está en validar el usuario. La opción por defecto es Packet.



Tabla 1. Niveles de autenticación

NivelDescripción
NoneSin autenticación.
ConnectChequea la seguridad sólo cuando el cliente se conecta al componente.
CallChequea la seguridad al comienzo de cada llamada.
PacketChequea la seguridad y valida que todos los datos fueron recibidos.
Packet IntegrityChequea la seguridad y valida que ningún dato fue modificado en tránsito.
Packet PrivacyChequea la seguridad y encripta el paquete.

Finalmente tenemos "Impresonation level". Este es el seteo de niveles de autoridad que los componentes tienen con otros procesos. Estos son cuatro niveles diferentes como describe la tabla 2. Por defecto es Impersonate.

Tabla 2. Niveles de Impersonamiento

NivelDescripción
AnonymousEl segundo proceso no sabe nada acerca del cliente.
IdentifyEl segundo proceso puede identificar quién es el cliente.
ImpersonateEl segundo proceso puede impersonar al cliente, pero sólo por procesos en el mismo servidor.
DelegateEl segundo proceso puede impersonar al cliente en todas las instancias.

Bien, hemos discutido sobre seguridad declarativa basada en roles, la cual es definida y manejada en tiempo de ejecución. También puede usar la seguridad programática. Esto le permite expandir su código basado en los niveles de acceso del usuario. Por ejemplo, un oficial de cuentas solo podría estar autorizado a prestar $50.000. Después que el gerente hace la aprobación para autorizar el préstamo.

Do case
   Case IsCallerInrole("Loan Officer")
       lnMaxLoanAmt = 50000

   Case IsCallerInRole("Manager")
       LnMaxLoanAmt = 100000

   OTherWise 
       LnMaxLoanAmt = 10000000000000
EndCase
Utilizar ambos, seguridad basada en roles combinado con seguridad programática, puede soportar cualquier esquema de seguridad que usted necesite.

Manejo de errores

Una de las preguntas que con mas frecuencia de ve en los foros es, "Como mostrar un error de vuelta al usuario?". Si usted piensa sobre esto la respuesta no parece fácil. Usted NO puede mostrar un cuadro de diálogo con el error de su componente. Este está corriendo en una computadora distinta de la que contiene la interfaz de usuario. VFP posee la función COMRETURNERROR(), para enviar el mensaje de error de vuelta al cliente. COMRETURNERROR() usa dos parámetros. El primero es el nombre del módulo donde ocurre el error. El segundo es el mensaje que muestra al usuario. Veámoslo en el código.

Define Class ErrorExample as SESSION OLEPUBLIC
   Procedure Error(tnError, tcMetodo, tnLine)
 Local lcMensaje

 LcMensaje = "Este es mi mensaje de error")

 COMRETURNERROR(tcMethod, lcMensaje)
   EndProc

   Function CauseError
 Error 15
 Return "Esto nunca debería ser mostrado"
   EndFunc

Enddefine   
Ahora compile el código en una DLL e instancie lo siguiente.

Ox = CREATEOBJECT("MyError.ErrorExample")
? ox.CauseError()
Usted debe esperar "Esto nunca debería ser mostrado" para mostrar en el escritorio de VFP. Sin embargo, aparece un cuadro de dialogo con el mensaje de error.

>Puede retornar cualquier información que quiera en el parámetro del mensaje. Podría querer mejorar el método de error al capturar información adicional utilizando la función AERROR( ) o escribiendo información en el log de errores. Esta es una advertencia: El método de error no se disparará si el error ocurre en el método Init.

Conclusión

Hemos cubierto instalación, seguridad y manejo de errores. En la próxima oportunidad discutiremos transacciones y veremos como COM+ y VFP 7 permite incluir datos de VFP en las transacciones, algo que no se podía hacer con MTS y VFP 6.


Craig Berntson es Microsoft Certified Solution Developer y fue nombrado cuatro veces Microsoft Most Valuable Profesional. Disertante de varias conferencias de FoxPro en todo EEUU y eventos de Microsoft en Salt Lake. Es Presidente del Salt Lake City Fox Users Group y actualmente ingeniero de software senior para 3m Health Information Systems.
Jorge A. Espinosa, Buenos Aires (Capital Federal), Argentina, es Analista Programador y MCP. Dedicado al desarrollo de sistemas desktop desde el año 1987; siempre en el entorno xBase y en Visual Fox Pro desde la version 3.0. Hoy Gerente de Sistemas en Droguería Saporiti SACIFIA.