23 de diciembre de 2004

Obtener la version con que fue compilado un EXE de VFP

Deseas saber con que version fue creado un .EXE, a continuacion una funcion creada por Rick Bean...

El siguiente codigo fue obtenido de los newsgroups de VFP en ingles:

**************************************************
* Subject. Re: Identifying the versions of VFP used to build an EXE or DLL
* Sender: Rick Bean rgbean@unrealmelange-inc.com
* Date: 04/11/2004 8:01
* newsgroup: microsoft.public.fox.programmer.exchange
**************************************************

Hay que hacer notar que funcionara con ejecutables desde FoxPro para Windows, hasta el VFP9b.

**************************************************
* Funcion: GetFoxEXEVersion
* Obtiene la version en que fue compilado un EXE
* Autor: Rick Bean rgbean@unrealmelange-inc.com
* Ejemplo: ?GetFoxEXEVersion(GETFILE("EXE"))
**************************************************

* GetFoxEXEVersion.prg
FUNCTION GetFoxEXEVersion
LPARAMETERS p_cEXEName
DIMENSION VersionInfo[8,3]
VersionInfo[1,1] = "FPW 2.5"
VersionInfo[2,1] = "FPW 2.6"
VersionInfo[3,1] = "VFP 3.0"
VersionInfo[4,1] = "VFP 5.0"
VersionInfo[5,1] = "VFP 6.0"
VersionInfo[6,1] = "VFP 7.0"
VersionInfo[7,1] = "VFP 8.0"
VersionInfo[8,1] = "VFP 9.0"
VersionInfo[1,2] = "foxw"
VersionInfo[2,2] = "foxw"
VersionInfo[3,2] = "VisualFoxProRuntime.3"
VersionInfo[4,2] = "VisualFoxProRuntime.5"
VersionInfo[5,2] = "VisualFoxProRuntime.6"
VersionInfo[6,2] = "VisualFoxProRuntime.7"
VersionInfo[7,2] = "VisualFoxProRuntime.8"
VersionInfo[8,2] = "VisualFoxProRuntime.9"
VersionInfo[1,3] = "00D1"
VersionInfo[2,3] = "0111"
VersionInfo[3,3] = ""
VersionInfo[4,3] = "3228"
VersionInfo[5,3] = "1418"
VersionInfo[6,3] = "162C"
VersionInfo[7,3] = "1638"
VersionInfo[8,3] = "10EC" && beta 1
LOCAL lnii, lcVersion, lnHandle, lcKeyName
lnHandle = FOPEN(p_cEXEName, 0)
IF lnHandle < 0
 RETURN "Unable to Open file"
ENDIF

lcVersion = "(unknown)"
FOR lnii = 1 TO 8
 IF !EMPTY(VersionInfo[lnii,3])
  = FSEEK(lnHandle, EVALUATE("0x"+VersionInfo[lnii,3]))
  lcKeyName = VersionInfo[lnii,2]
  IF FGETS(lnHandle, LEN(lcKeyName)) == lcKeyName
     lcVersion = VersionInfo[lnii, 1]
     EXIT
  ENDIF
 ENDIF
ENDFOR
=FCLOSE(lnHandle)

RETURN lcVersion

Espero les sea de utilidad.

Espartaco Palma Martinez

17 de diciembre de 2004

Como se mueve el ratón


Originalmente publicado en FoxTALK Febrero de 1998
Traducción de Ariel Gimenez


Como se mueve el ratón
Jim Booth
Si tu aplicación necesita contemplar las acciones del ratón, puedes tanto llamar a un exterminador o usar los eventos que Visual FoxPro nos dá para capturar las acciones del mouse y sus botones. Desde que mis usuarios se enojaron al ver ese tipo del traje metalico destruyendo sus ratones, prefiero usar los eventos enVisual FoxPro.
Allí hay un número de eventos que pueden informarnos de las actividades del ratón. MouseMove, MouseDown, MouseUp, Click, RightClick, DoubleClick, MiddleClick, y el MouseWheel son los eventos que nos interesan.
Echemosle una mirada al MouseMove primero.  El archivo de ayuda nos dice que este evento se dispara cuando se mueve el ratón sobre un control.   Esto es correcto, y debido a que constantemente se dispara mientras el ratón se esta moviendo sobre el control puede ser un problema para la performance, si no es utilizado correctamente.  Recuerda cuando escribes codigo en el MouseMove que este correra muy frecuentemente y por consiguiente este debe ser corto y conciso.
El MouseMove recibe cuatro(4) parámetros.   Estos son nButton, nShift, nXCoord, y nYCoord respectivamente.  Estos parámetros nos cuentan sobre la posición del puntero del ratón, el estado de los botones del ratón, y el estado de las teclas shift, ctrl y alt .  Sus valores son:
El parámetro nButton posee un valor entre 0 y 7 y se refiere al estado de los botones del ratón.   El valor 0 significa que no hay botones presionados mientras que los valores de 1, 2, y 4 cada uno se refiere a que los botones izquierdo, derecho, y medio se encuentran presionados respectivamente. Los otros valores  (3,5,6,7) nos informa de las combinaciones de botones que estan presionados.   El parámetro nButton posee un valor que es la suma de los valores de los botones presionados, así que si el boton izquierdo y derecho están presionados entonces nButton será igual a 3, o la suma de 1 (botón izquierdo) y 2 (botón derecho).
El parámetro nShift nos dá una información similar sobre las teclas Shift, CTRL, y Alt .   Los valores para este parámetro son manejados similarmente a el parámetro nButton en el cual Shift es 1, CTRL es 2, y Alt es 4.   Ninguna es 0 y si mas de una tecla de estas esta presionada entonces nShift será la suma de los valores de las mismas.
El parámetro nXCoord y nYCoord nos informan de la posición horizontal y vertical del ratón respectivamente.   Los valores de este parámetro son tomados con respecto al formulario y no del control, así que cuando el mouse ingresa a la parte de arriba del control el nYCoord NO es 0 o 1 sinó el número de pixel desde el formulario mismo.
El formulario de ejemplo Mice.scx muestra el uso del evento MouseMove de un botón para actualizar algunos otros controles.   los otros controles nos van mostrando el estado de las cosas mientras el ratón se mueve sobre el command button.   Un extracto del código del MouseMovecode del botón esta listado abajo, este extracto es el código que determina si el puntero del ratón esta entrando, saliendo, o moviendose dentro del command button.  Hace esto chequeando el nXCoord del puntero del ratón para ver si este cae dentro de los 3-pixels del borde  arriba, abajo, izquierda, o derecha del limite del botón. Si el puntero se encuentra en este perímetro entonces chequea si el ratón se encontraba previamente sobre el botón o no(en el ejemplo estamos usando el valor corriente del option button group, pero en código real esto debería ser manejado con una propiedad del botón mismo) y entonces setear el valor entrante o saliente del OptionButton group en el formulario para reflejar esto.   Hay también código en el MouseMove del formulario que setea el valor del  OptionButton group en 0 indicando que el ratón no se encuentra sobre el boton.
LPARAMETERS nButton, nShift, nXCoord, nYCoord
WITH THISFORM
       IF ((nXCoord >= THIS.Left AND nXCoord <= THIS.Left+3) OR ;
          (nXCoord <= THIS.Left + THIS.Width AND ;
           nXCoord >= THIS.Left + THIS.Width-3)) OR ;
          ((nYCoord >= THIS.Top AND nYCoord <= THIS.Top+3) OR ;
          (nYCoord <= THIS.Top + THIS.Height AND ;
           nYCoord >= THIS.Top + THIS.Height-3))
                 DO CASE
                     CASE .optEntering.Value = 0
                            * estamos entrando
                            .optEntering.Value = 1
                     CASE .optEntering.Value = 2
                            * estamos saliendo
                            .optEntering.Value = 3
              ENDCASE
       ELSE
* estamos sobre el control
              .optEntering.Value = 2
       ENDIF
* mas código aqui
ENDWITH
El formulario de ejemplo está disponible para los suscriptores de FoxTALK en el area de downloads en http://www.pinnaclepublishing.com/ft.  El formulario de ejemplo muestra como leer el estado de los botones del ratón, las teclas shift/ctrl/alt, y las coordenadas x e y del ratón, y también el manejo de la entrada y salida del control.  si usted no es suscriptor puede suscribirse desde el mismo sitio web de FoxTALK.
 

Jim Booth

9 de diciembre de 2004

¿Cómo y cuándo usar subconsultas...? SELECT-SQL

Alguna vez se ha preguntado como sacar esa consulta SQL que le han pedido?, intentas proyectar tablas con FULL JOIN, INNER JOIN, LEFT JOIN y todavia no se obtienen los resultados deseados...

A veces hay que recurrir a las subconsultas para llegar a nuestro objetivo... Quizás es típico el estar buscando las formas de ejecutar consultas SELECT-SQL para obtener un conjunto de datos específico, esto porque ya estás cansado de crear cursores hechos con CREATE CURSOR y rellenados mediante un ciclo SCAN ... ENDSCAN (ok, podrías hacerlo con DO WHILE NOT EOF(), pero eso ya está pasado de moda, además de que resulta ser poco eficiente debido a que no utilizarás a su máximo la optimización RushMore, una de las ventajas del uso de Visual FoxPro). Haz consultado con algunos de tus colegas y te responden que efectivamente, debe haber alguna manera de hacerlo por medio de sentencias SQL. Y tu sexto sentido casi te lo puede gritar!!

A continuación expondremos un caso típico en los cuales será necesario hacer unos cuantos "trucos" para obtener el tan ansiado cursor que no nos deja en paz:

La gerencia te pide que expongas en un reporte un resumen de las compras y ventas de los artículos en el último mes, donde compares lado a lado, cuantos se vendieron, cuantos se compraron por artículo, quedando quizas algo por el estilo:

ID Articulo Descripcion Compras Ventas
001 Camisa XXX XXX
002 Pantalon XXX XXX
003 Blusa XXX XXX

Parece algo sencillo y no muy difícil de realizar, pero es tipico que en el diseño de tu base de datos hayas dejado por separado una entidad para compras y una entidad para ventas, por lo que tendriamos los siguientes entidades:



Lo primero que se nos podría ocurrir es una consulta donde proyectaramos "todos" vs "todos" para obtener conjunto de datos deseados:

SELECT Ventas.IDArticulo, SUM(compras.Cantidad) as SumaCompras,;
    SUM(ventas.cantidad) AS SumaVentas ;
  FROM Compras ;
    FULL JOIN Ventas ON Ventas.IdArticulo = Compras.IdArticulo ;
  GROUP BY Ventas.idarticulo ;  
INTO CURSOR cResumen

A primera vista parece buena la idea, sumamos todas las cantidades de compras, todas las cantidades de ventas de una mezcla de todos los registros, no? Pero sabras que pasa?: No funciona, ya que obtendremos datos que nada tienen que ver, primeramente porque se estan cualificando las tuplas de la tabla ventas contra las tuplas de la tabla compras, pero nótese que hay algunos IDs que están en una, pero que no están en la otra, dandonos el siguiente resultado:



Podras comentar, "Ya casi", solo me faltó el codigo 002 que es de la tabla compras que no estaba en la tabla ventas, hmmm si, en efecto es asi, puedes seguir intentando cambiar las clausulas por LEFT JOIN, RIGTH JOIN, cambiar Ventas.Articulo = Compras.IdArticulo por Compras.IdArticulo = Ventas.Articulo en lo correspondiente a la cláusula ON (de los operadores de proyeccion), pero obtendrás diferentes resultados que seguirán sin ser los que esperabas.

Inclusive, puedes llegar a pensar que tal vez el operador de igualdad sale sobrando, e intentas una más para ver si funciona:

SELECT ventas.idArticulo,;
   SUM(ventas.cantidad) AS TotalVentas,;
   SUM(compras.cantidad) AS TotalCompras ;
  FROM ventas,compras ;
  GROUP BY ventas.idArticulo ;
  INTO CURSOR cReporte

Y tenemos el siguiente resultado:



Que sucedió?, lamentablemente al hacerlo de esta forma se está utilizando implicitamente un operador de igualdad entre las entidades (INNER JOIN), y eso sin tomar en cuenta que la suma no son lo que deberia tener, por lo que tampoco obtenemos lo que queremos.

Entonces? Qué es lo que debemos hacer?, la solución es sencilla, el uso de subconsultas. Resulta ser que el algebra de conjunto (que es la teoría que sustenta a la práctica de el lenguaje SQL) tiene un pequeño "truco" para normalizar este pequeño desperfecto en lo que parece no tener solución. Esto se le llaman subconsultas (mas adelante veremos que otros nombres se les conoce).

En el parrafo anterior hice mención a un tema que es base de esto, la normalización, en vez de intentar proyectar las columnas que serán calculadas (en nuestro ejemplo se sumaron, pero bien pudieron haber sido contadas) para llegar al resultado inmediato, debemos crear un conjunto de datos intermedio, el cual, nos servirá para ahora si proyectarlo correctamente:

SELECT idArticulo, 000000 as nCompras, Cantidad as nVentas ;
       FROM Ventas ;
   UNION ;
SELECT idArticulo, Cantidad as nCompras , 000000 as nVentas ;
       FROM Compras ;
 INTO CURSOR cResumen

Con el query anterior obtendremos un conjunto de datos normalizados como el que sigue:



Ya se va viendo mejor?, claro!, ahora a éste conjunto de datos sólo le faltaría hacer una agrupación y suma, para que quede como lo deseamos:

SELECT idArticulo, SUM(nCompras) as TotalCompras ,;
         SUM(nVentas) as TotalVentas ;
  FROM cResumen ;
  GROUP BY idArticulo ;
  INTO CURSOR cReporte

Quedando como sigue:



Ahora si, llegamos a los resultados deseados (puedes hacer la suma manual, no me he equivocado :-), como comentaba en el inicio del artículo a veces es *NECESARIO*. Cabe destacar que en las consultas he obviado las clásulas WHERE, ya que es demasiado obvio que siempre deberán incluirse en cada una de la consultas que hemos unido (con la claúsula UNION).

Aprovecho este momento de tu atención para demostrar la manera en que también podrá ser realizado con la VFP9 (la próxima versión del producto), en la que se puede simplificar aún más esta sentencia SQL:

SELECT codigo,;
    SUM(cResumen.nCompras) AS TotalCompras ,;
    SUM(cResumen.nVentas ) AS TotalVentas ;
  FROM (SELECT codigo, ;
               CAST(0 as Int) AS nCompras, Cantidas as nVentas ;
            FROM Ventas ;
          UNION ;
        SELECT Codigo,;
               Cantidad as nCompras, CAST(0 as Int) AS nVentas ;
            FROM Compras ) cResumen ;
  GROUP BY cResumen.Codigo ;
  INTO CURSOR cReporte

Aquí se hará uso de lo que se le denomina sentencias anidadas, en donde tenemos varias partes clave.

En primer lugar se puede apreciar que se está realizando una sentencia anidad en al utilizar como cursor de procedencia ... otra consulta!! FROM ( SELECT ... ), esto nos dá la ventaja de poder anidar tantas consultas sean necesarias.

Como segundo punto a favor estamos utilizando una de las nuevas funciones: CAST(), con ella prescindiremos de los trucos que hemos estado usando para "forzar" que las columnas calculadas de VFP tengan un tipo de datos, ancho o precisión que se requiera, en vez de tener que utilizar por ejemplo 000000 para forzar que fuera un entero de ancho 6, o usar $0 para que sea de tipo Moneda, y un sin fin de etceteras.

Con la combinación de ambas, obtenemos una consulta mas compacta, te podrán preguntar ¿En qué nos beneficia eso?, en que dicha consulta, si en dado momento desea pasarse a un servidor de base de datos (por ejemplo MS-SQLServer), la transición sea más ligera, ya que desde hace mucho tiempo estos manejan consultas anidadas y funciones de conversión explícita.

Quizás no debemos de dejar pasar el hecho que a veces puede ser contraproducente el tener todo en un una sola consulta, ya que todo dependerá de cómo estén formados tus datos, asi por ejemplo, pudiera ser necesario que separes en varias consultas para darle mayor interactividad a tu aplicación, por ejemplo:

WAIT WINDOW "Buscando Ventas en el periodo... Espere" NOWAIT

SELECT idArticulo, 000000 as nCompras, Cantidad as nVentas ;
   FROM Ventas ; 
   WHERE BETWEEN(Fecha, ldInicio, ldFinal) ;
   INTO CURSOR cVentas ;

WAIT WINDOW "Buscando Compras en el periodo... Espere" NOWAIT

SELECT idArticulo,  Cantidad as nVentas,000000 as nCompras ;
   FROM Compras ; 
   WHERE BETWEEN(Fecha, ldInicio, ldFinal) ;
   INTO CURSOR cCompras ;

WAIT WINDOW "Realizando resumen de Compra ventas... Espere" NOWAIT

SELECT * FROM cCompras ;
  UNION ;
SELECT * FROM cVentas ;
 INTO CURSOR cResumen

Asi pues, entre más pasos intermedios dejes, puedes enviar más mensajes a tu usuario indicándo qué algo se está haciendo, ya que en cuanto el número de datos empiece a crecer puede ser una posibilidad a que se vaya tardando cada vez más, por ejemplo, si lo pruebas con 1000 datos... será rapidísimo, pero en cuanto llegues a los millones de registros, se tardará, y muchos usuarios sienten que dos o tres segundos es un mundo, y mientras se tarda los puedes animar un poquito, así no se desesperarán y empiecen a "tronar" tu aplicación porque "ya no respondía" ... El cuánto las separes o unas te lo dejo a tu criterio, ya que siempre dependerá de tu caso específico.

Sea cual sea la solución tomada, debemos resumir lo siguiente: Es un error común el pensar que todo se puede hacer con una sola consulta SELECT-SQL, a veces hay que utilizar subconsultas (o consultas anidadas), y no solo proyecciones directas.

Espero que este pequeño tutorial les sea de utilidad.

Espartaco Palma Martínez

6 de diciembre de 2004

Reduciendo el tiempo de desarrollo


Usando la Orientación a Objetos para Reducir el Tiempo de Desarrollo
Por Jim Booth
Traducción de Sergio E. Aguirre

Introducción
Visual FoxPro es Orientado a Objetos. ¿Qué es lo que hace eso por mí? ¿Cómo la orientación de objetos puede hacer mi vida más fácil? Todos sabemos que hay una curva de aprendizaje a subir para entender qué nos han dicho sobre objetos y que hay una recompensa al subir esa curva. Este artículo investigará uno de las recompensas, una reducción para nuestro tiempo de desarrollo.

Tiempo de Desarrollo
El tiempo de desarrollo es siempre muy importante y los pasos que tomamos para reducir este tiempo son muy valiosos. La orientación a objetos nos promete que nuestro trabajo será reusable y que cada proyecto que nosotros desarrollamos tomará menos tiempo que el anterior. ¿Cómo nos cobramos esa promesa?
Como es el caso en todas las situaciones, la solución a un problema nunca se encuentra hasta que el problema sea entendido y definido claramente. El tiempo de desarrollo puesto bajo un microscopio nos muestra que está dividido en varias tareas diferentes. Las mayores son el análisis, diseño, implementación, prueba, y entrenamiento del usuario. ¿Cuáles son estas tareas y cómo influyen éstas en el tiempo global de desarrollo?

Análisis
Todo el desarrollo de la aplicación empieza con el análisis del problema comercial a ser resuelto. Un análisis completo es crítico para una solución exitosa. Nos dicen que en el desarrollo orientado a objetos, la fase de análisis ocupará un porcentaje más grande del tiempo de desarrollo total que en las prácticas de programación estructuradas del pasado. ¿Qué podemos hacer para reducir el tiempo requerido para esta fase de un proyecto?
Buscando patrones en problemas comerciales reduciremos bastante el tiempo destinado al análisis. Catalogando estos patrones cuando los identifiquemos nos permitirán descubrirlos más fácilmente en el futuro. Con un catálogo de patrones en nuestras manos podremos reconocer rápidamente similitudes a los problemas comerciales anteriores y rápidamente podremos registrar los requerimientos.
Hay un peligro en cualquier esfuerzo para reducir el tiempo de análisis es el de producir un análisis incompleto. Cuando cortamos esquinas sobre el análisis de un problema de negocio nosotros potencialmente abrimos el proyecto a una cantidad de problemas que van a consumir tiempo. Es posible que no se descubran requisitos perdidos hasta que el proyecto esté bien encaminado para su finalización y que esos requisitos no descubiertos nos obliguen a abandonar un grupo grande de trabajo debido a los cambios en el diseño.
Si el problema de negocio no es entendido claramente por los diseñadores y programadores de una aplicación, es probable que la aplicación no satisfaga las necesidades del usuario. Las aplicaciones que no reúnan los requisitos de los usuarios son abandonadas o escritas de vuelta. Es asustadiza cómo verdadera la vieja frase: "Nunca hay bastante tiempo para hacerlo correctamente, pero siempre hay bastante tiempo para hacerlo encima."
Aparte de reconocer, usar, y catalogar los patrones vistos durante el análisis, ésta es una de las dos áreas del proceso de desarrollo que no debe abreviarse.

Diseño
El diseño es la abstracción de una solución, en otras palabras es la descripción general de la solución a un problema sin los detalles. Así como pueden categorizarse problemas de negocio en patrones que se repiten, los diseños también exhiben patrones. Patrones vistos en la fase del análisis pueden ser trazados en el diseño. Catalogando y reusando patrones de diseño podemos reducir el tiempo requerido para crear el diseño de la solución.
La fase de diseño es el segundo paso de desarrollo que no debe abreviarse. Es sorprendente qué fácil y rápida pueden lograrse las metas cuando esta fase se define claramente. La fase de diseño es la planificación de la solución, sin un buen diseño podemos seguir un gran número de puntos muertos. Estos puntos muertos consumirán mucho más tiempo que el desarrollo del propio diseño en sí.
Comparando la construcción de una aplicación con una vacaciones en familia. ¿Si, en el momento de las vacaciones, usted metió a su familia dentro del automóvil y empezó a manejar para cruzar el país y usted no tenía ningún mapa y ninguna ruta planeada, piensa usted que en esas condiciones alcanzará su destino? ¿Cuántos malos giros podría tomar usted en el viaje? ¿Cuánto tiempo le tomaría estar al otro lado del país? Construir una aplicación sin un buen diseño es algo muy parecido.


Implementación
La implementación es la ejecución del plan de diseño. Esta parte es donde nosotros escribimos el código que hace realmente el trabajo. Este artículo se enfoca en técnicas para reducir el tiempo de la fase de implementación del desarrollo de una aplicación.
Empezando con un legítimo framework pueden reducir el codificado de la aplicación quitando la necesidad de tratar con las actividades rutinarias de las interacciones del componente.
Reusar código previamente exitoso ha sido una meta para programadores desde hace mucho tiempo. La orientación a objetos promete, de nuevo, permitir la reusabilidad de nuestro código, a pesar de que la orientación a objetos nos permite reusar no más que código. Nosotros podemos reusar cosas reales, widgets. Estos widgets pueden ser tan simples como un cuadro de texto que maneja entradas de fecha, o tan complejo como un formulario que maneja la administración de los datos. Un framework es un widget reusable muy complejo comprendido por muchos objetos que proporcionan un servicio a la aplicación.
Usted puede reconocer widgets útiles durante el desarrollo de un proyecto. Una vez que usted se encuentra haciendo la misma cosa por segunda vez, deténgase y pregúntese si esto podría ser manejado por un widget reusable.

Prueba
Las pruebas pueden extenderse a lo largo de un proyecto y también después de su finalización. Probamos cosas cuando las construimos y entonces de nuevo las probamos cuando el proyecto está completo para cerciorarnos que ellas trabajan bien juntas. Cuando estamos reusando widgets previamente probados, ahorramos un poco de tiempo porque estos elementos reusables ya se han probado individualmente, quedándonos solamente por probarlos en unión con los demás elementos.


Formación
La formación del usuario es uno de los aspectos pasados por alto a menudo, y centro del costo, del desarrollo de la aplicación. Una nueva aplicación no hace nada bueno para el negocio hasta que los usuarios puedan hacerla trabajar. Mediante la capitalización sobre componentes reusables promovemos una interface consistente para nuestras aplicaciones. Esto, a su vez, reduce el tiempo de formación requerido para que los usuarios se sientan cómodos con el nuevo sistema porque ellos ya están familiarizados con la interface del usuario.

Los Principios de la Reusabilidad Promovida en la POO
El desarrollo de sistemas orientados a objetos tiene un conjunto de principios y reglas que prescriben la manera en que deben hacerse las cosas. Se oye de todos a menudo que la teoría es buena pero el pragmatismo se apodera del trabajo hecho. Esta es una actitud infortunada para los desarrolladores de software que usan herramientas orientadas a objetos. No se fundaron los principios de orientación a objetos en algún cuarto de la parte trasera de una universidad, ellos se descubrieron durante el desarrollo de un proyecto real a través del análisis de cosas que no funcionaban.
Estos principios y reglas son pautas hacia el éxito y ellos necesitan ser respetados y entendidos. Siguiendo las reglas, éstas nos protegen de cometer errores que otros ya han cometido antes. Es verdad que cualquier regla puede romperse, sin embargo también es verdad que antes de romper una regla uno debe:
  1. Saber que la regla existe.
  2. Saber lo que es la regla.
  3. Saber por qué las reglas existen
  4. Saber los problemas potenciales relacionados con el rompimiento de la regla.



Si usted sabe todas estas cosas y todavía escoge romper la regla, prosiga y rómpala. ¿Qué tiene de malo todo esto? Significa que es necesario saber lo que son las reglas y principios del desarrollo orientado a objetos. Significa que la "teoría" no es dispensable.
Las siguientes secciones discuten algunos de estos principios y examinan, a través de los ejemplos, cómo estos promueven la reusability y reducen el tiempo de desarrollo.

Delegación
La delegación es el proceso donde un objeto pasa la responsabilidad de una acción a otro objeto diferente. Esto normalmente se hace debido a las responsabilidades naturales de los dos objetos en cuestión. Por ejemplo, considere que un botón de comando es responsable de cerrar un formulario. ¿La responsabilidad del botón es reaccionar al usuario cuando este pulse el botón, pero es el botón el que tiene la responsabilidad de cerrar el formulario? Quizás el propio formulario está mejor preparado para realizar su propia acción de cierre. A través de la delegación podemos llevar el paso de la responsabilidad del botón, que es de cerrar el formulario, al propio formulario.
Ejemplo del botón cmdExit
El código del evento click del botón delega la responsabilidad al formulario mediante una llamada al THISFORM.Release() quitando así la responsabilidad del botón para manejar los detalles de cerrar el formulario.
Con el uso apropiado de la delegación separaremos la funcionalidad de una operación en particular y asignaremos la responsabilidad al objeto más apropiado. Esto proporciona la habilidad de ejecutar independientemente pedazos de esa funcionalidad, que a su vez reduce los requerimientos para los procedimientos monolíticos (métodos) que son esencialmente grandes estructuras DO CASE. Estos procedimientos monolíticos son una molestia mayor durante la fase de mantenimiento de un proyecto.

Herencia
Definido como la posibilidad que tiene una clase de incorporar toda o una parte de otra clase en su propia definición, la herencia probablemente es la mayor propiedad del desarrollo orientado a objetos y, al mismo tiempo, el más usada. La herencia es el proceso por el que una subclase recibe conductas y datos de su clase antecesora. La idea es que mientras más extenso es el árbol de herencia la definición de las clases se vuelven más especializadas.
Mientras que la herencia un rasgo poderoso de los lenguajes orientados a objetos, también es limitando. Siempre que intentemos modificar una clase en la jerarquía también necesitaremos determinar lo que este cambio afectará a todas las clases descendientes. Cuando un árbol de herencia crece hasta llegar a ser muy grande la habilidad de reusar cualquiera de las clases individuales dentro de ese árbol será más difícil.
Sin embargo puede usarse la herencia para mantener el polimorfismo. Definiendo propiedades y métodos públicos de una clase entera de objetos en la cima del árbol de herencia podemos estandarizar la interface de programación y asegurarnos que las clases sean intercambiables.

Polimorfismo

Polimorfismo: La capacidad que tiene una funcionalidad de aplicarse en distintas formas. Hay dos tipos de polimorfismo:
Inherente: La funcionalidad es heredada por cada subclase.
Ad hoc: Las distintas funcionalidades de tipos diferentes tienen el mismo nombre.
Definiendo una clase abstracta que introduzca todos los métodos y propiedades públicas de un árbol de la clase entera nosotros podemos mantener consistente la interface de un programador y asegurarnos que todas las subclases sean intercambiables. En otras palabras, si todas las clases de cuadros de texto tienen el mismo conjunto de propiedades y métodos públicos serán intercambiables. Nosotros podemos reemplazar fácilmente un tipo de cuadro de texto por otro sin preocuparnos por algún otro objeto que se refiera a una propiedad o método que el nuevo cuadro de texto no tenga. Éste es el polimorfismo inherente.
Ejemplos del txtBase, txtCalculator, y txtDate
El txtBase define la interface pública para todos los cuadros de texto. El txtCalculator y txtDate heredan de la clase txtBase manteniendo así estándar la interface de programación para esos cuadros de texto y permitiéndoles ser intercambiados sin la necesidad de alterar cualquier código fuera de los cuadros de texto.
El polimorfismo ad hoc puede verse en la clase botón de cmdExit. Sobre Delegación, nosotros mencionamos que el botón pasa la responsabilidad al formulario para la acción de cierre. Esto es cumplido por el código del evento click del botón que llama al método Release del formulario. Los diferentes formularios pueden tomar acciones diferentes en sus métodos Release respectivos y pueden significar que el código en el evento click del botón de salida está aprovechándose del polimorfismo ad hoc para proporcionar reusabilidad. El botón cmdExit puede ser arrojado en cualquier formulario y funcionará eficazmente.


Encapsulación
La encapsulación puede definirse como esconder la aplicación de un objeto. Cuando una clase contiene todos los datos y código requeridos para que pueda funcionar sin la necesidad de depender de cualquier objeto externo al runtime, decimos que está encapsulada. Esta encapsulación permite usar la clase sin tener en cuenta el entorno en donde se la coloca. La clase no requiere nada específico de su entorno y no crea ningún efecto en el lado del entorno. La encapsulación es muy eficaz haciendo clases reusables.

Ejemplo del cntProgress
Se definen todas las propiedades y métodos necesarios como parte de la clase haciendo la clase independiente de cualquier objeto externo al runtime. Esto permite que la clase cntProgress sea virtualmente puesta en cualquier formulario sin tener en cuenta que pueda o no haber en ese formulario.

Independencia
La independencia es un paso para lograr encapsulación. Haciendo una clase independiente requiere que cualquier referencia a las propiedades o métodos de otro objeto sea verificada antes de hacer dicha referencia. Esto es llamado a veces "programación defensiva". Nosotros podemos hacer una clase independiente no asumiendo nada sobre el entorno en el que esa clase puede encontrarse. No asumiendo el significado de verificar que para la existencia de cualquier cosa nosotros necesitamos antes tratar de usarla.
Ejemplo del tmrTimeOutForm
El evento timer verifica la existencia de una propiedad llamada TimedOut en el formulario contenedor antes de intentar hacer una referencia a ella. Esto permite poner el cronómetro en un formulario sin tener en cuenta si ese formulario tiene o no una propiedad llamada TimedOut.

Usando Colecciones
Las distintas clases de base de Visual FoxPro tienen colecciones de sus objetos miembros, entre éstos el Formulario, Container, Grid, PageFrame, Page, etc. Esta colección de propiedades hacen posible referirse al objeto miembro contenido sin saber algo de ellos, como sus nombres o el número de ellos que existen en el objeto. Usar estas colecciones nos hacen posible escribir código que trabajará con una variedad de configuraciones del objeto contenedor.

Ejemplo del grdRefresh
La colección de Columnas es usada en los eventos Init y BeforeRowColChange para permitir que la clase funcione normalmente sin tener en cuenta el número, o orden, de las columnas en tiempo de ejecución.
Además de usar las colecciones que Visual FoxPro nos proporciona nosotros podemos crear también nuestras propias colecciones. Agregando propiedades de array a nuestras clases que mantienen referencias a una colección de objetos pueden permitirnos manejar una variedad de situaciones sin "codificar duro" las referencias de objetos.


Referencia Indirecta
El uso de la referencia indirecta a los objetos quita la dependencia en los nombres de esos objetos. Dependiendo de nombres de algo en una definición de una clase limita la reusabilidad de esa clase. En lugar de codificar los nombres de cosas en una clase es mejor usar las palabras claves THIS, THISFORM, y THISFORMSET. Una de las preguntas más comúnes con la que yo tuve que tratar durante una clase de capacitación es: "¿Cómo consigo el nombre de mi formulario para que yo pueda referirme a él?". Más allá del interrogatorio normal descubrimos que el estudiante quiere cambiar una propiedad del formulario desde uno de los controles contenidos dicho formulario. Usando THISFORM hace que esta pregunta sea discutible, no se necesita el nombre del formulario. De hecho el formulario puede tener su nombre cambiado y el código todavía trabajará correctamente.
Otro problema que puede surgir es que un objeto necesite comunicarse con su contenedor. Yo he visto a mucha gente escribir código en un textbox de una columna en un grid de esta manera:
THISFORM.Grid1.Refresh()
El problema con el anterior segmento de código es que fallará si usted coloca el grid en una página de un PageFrame. Falla porque la ubicación del grid ha cambiado de THISFORM.Grid1 a THISFORM.PageFrame1.Page1.Grid1. Las código anterior hace que sea imposible de reusar el grid a menos que se coloque en exactamente el mismo lugar. Reescribiendo el anterior código usando la propiedad PARENT del objeto hace que el código sea movible.
THIS.Parent.Parent.Refresh()
Este código todavía se refiere al grid sin embargo se dirige al grid desde el textbox en lugar del formulario permitiendo poner al grid en cualquier parte sin afectar el funcionamiento del código escrito en el textbox.


Composición
La composición es la construcción de una clase compleja mediante la combinación clases más simples en una clase contenedora. A través de la composición usted puede crear una sola clase que tenga muchos controles en ella. Arrojando esta sola clase en un formulario le dará una interface relativamente compleja sin la necesidad de construir cada parte de esa interface para cada formulario que lo usa.

Ejemplo del cntProgress
La clase barra de progreso es una clase compleja compuesta por varias clases simples puestas en una clase contenedora.
NOTA: la composición, mientras puede mejorar la reusabilidad en ciertas situaciones, también puede presentar problemas. La composición tardía es mucho mejor que la composición temprana. Las designaciones tardías y tempranas para la composición se refieren al lugar del árbol de la clase contenedora donde es poblada con sus objetos miembros. Si esta población toma lugar justo antes que la clase sea usada para instanciar un objeto se llama composición tardía, si esto toma lugar antes de que una subclase del contenedor sea creada se llama composición temprana. La composición temprana causa problemas con la flexibilidad de la clase compuesta haciendo que los cambios al objeto miembro sean más difícil de lograr.

Mediación
La mediación permite que un objeto pueda manejar las comunicaciones para otros objetos. Se ve a menudo en contenedores que mantienen objetos externos con una interface para acceder a las conductas de los objetos contenidos.

Ejemplo del cntProgress
Todas las comunicaciones de los objetos externos han terminado en métodos del contenedor cntProgress. Para aumentar el ancho del shape dentro del contenedor son llamados los métodos del contenedor.

Usando un Framework Estándar
Haciendo estándar un framework para todo el desarrollo de la aplicación pueden documentarse y cumplirse los contratos entre las distintas clases. Todo los sistemas orientados a objetos derivan su conducta a través de objetos que se comunican con otros objetos. Sin la comunicación de objetos no hay ninguna actividad en el sistema.
Como con la comunicación humana, los objetos deben estar de acuerdo en ciertas convenciones. Si dos personas intentan comunicarse y no están de acuerdo primero con en el idioma que van a usar, existe la posibilidad que no va ocurrir ningún intercambio de ideas porque las dos personas no se pueden entender. Lo mismo pasa con los objetos. Ellos deben estar de acuerdo en el "idioma" o sintaxis que usarán sus comunicaciones. Este acuerdo está llamado un contrato entre los objetos. Establecer los contratos para las comunicaciones de objetos es una de tantas cosas que un framework hace por nosotros. Haciendo estándar un framework a ser usado para todo el trabajo de desarrollo de la aplicación nos aseguramos que los contratos de comunicación entre los objetos serán consistentes.
Siempre que empecemos a crear una definición de una clase reusable nosotros debemos decidir si esta clase requerirá los contratos del framework o funcionará libremente sin tener en cuenta el framework. Si nosotros decidimos que la clase debe funcionar fuera del framework, entonces tenemos que tomar medidas para asegurarnos que la clase no tenga ninguna dependencia en los contratos del framework.

Resumen
Los principios del desarrollo orientado a objetos, como Herencia, Polimorfismo, Encapsulación, Mediación, y Composición, sostienen la creación de clases reusables. Aplicando estos principios eficazmente las clases que vamos a crear pueden ser usadas muchas veces sobre una variedad de settings diferentes. Sin embargo, este reusabilidad no viene gratis, nosotros debemos crear activamente nuestras clases para que sean reusables prestando mucha atención a nuestro diseño y codificado.
Hay grados de reusabilidad, como reusable dentro de los límites de un framework particular contra reusable sobre los framework múltiples. El grado más alto de reusabilidad, es el que nosotros debemos planear para la clase que vamos a crear.
Envíe un email a Jim Booth con preguntas o comentarios sobre esta información.

18 de noviembre de 2004

Saber si un número es par

Tres funciones distintas para saber si un número entero es par.

1. Utilizando el operador %
FUNCTION EsPar(tnEntero)
  RETURN tnEntero % 2 = 0
ENDFUNC
2. Utilizando el operador ^
FUNCTION EsPar(tnEntero)
  RETURN -1 ^ tnEntero = 1
ENDFUNC
3. Utilizando BITTEST()
FUNCTION EsPar(tnEntero)
  RETURN NOT BITTEST(tnEntero,0)
ENDFUNC
Nota del editor: Las funciones 1. y 2. fueron tomadas del Grupo de Noticias de VFP en español, de respuestas enviadas por Luis María Guayán y Dario David Puccio respectivamente. La función 3. fue tomada de la ayuda de VFP de la función BITTEST().

5 de noviembre de 2004

Ventajas y desventajas del uso de formularios basados en VCX o SCX

Autor: Drew Speedie
Traducido por: Ana María Bisbé York


Fragmento de la sesión VFP Tips and Tricks presentada por el autor en la Conferencia DevEssentials 2004
Existen ventajas y desventajas para crear/guardar formularios VFP en .SCXs o .VCXs. He aquí algunas de ellas:


Formularios basados en VCX: VENTAJAS
  • Fácilmente subclaseable
  • Muchos formularios pueden estar contenidos en un fichero VCX/.VCT (desventaja para desarrollo en grupos)
  • Posibilidad de utilizar propiedades y métodos protegidos (Protected) y ocultos (Hidden)
  • Puede sustituir una subclase DataEnvironment definida por el usuario (programa .PRG) en tiempo de ejecución.
  • Control completo sobre la apertura de tablas/vistas
Formularios basados en VCX: DESVENTAJAS
  • No puede utilizar DataEnvironment visual nativo
  • Como no puede utilizar DataEnvironment visual nativo, debe llenar manualmente las propiedades ControlSources y RecordSources
  • Más dificultad para ejecutar/probar, son necesarios 2 ó 3 comandos
  • No existe una vía directa para devolver un valor de un formulario modal
  • No tiene disponible el Asistente de formulario (de todas formas no recomendamos utilizar asistentes nativos...)
  • Si varios formularios están contenidos en una librería .VCX/.VCT, el desarrollo en equipo se hace más difícil, debido a que es más complicado que más de un desarrollador trabajen con formularios diferentes guardados en el mismo archivo .VCX
Formularios basados en SCX: VENTAJAS
  • Puede utilizar DataEnvironment visual nativo
  • Si se utiliza DataEnvironment, puede llenar ControlSources y RecordSources a través de los cuadros desplegables
  • Es fácil de ejecutar/probar, con un comando o con la opción Ejecutar en la barra de herramientas estándar.
  • Una vía simple, directa para devolver un valor de un formulario modal.
  • Puede sustituir una subclase DataEnvironment definida por el usuario (programa .PRG) en tiempo de ejecución.
  • Tiene disponible el Asistente de formulario (de todas formas no recomendamos utilizar asistentes nativos...)
  • El desarrollo en equipo es menos problemático porque cada formulario está siempre guardado en su propio archivo .SCX/.SCT
Formularios basados en SCX: DESVENTAJAS
  • Un poco más difíciles de subclasear; la intención es utilizarlo en el nivel instanciado.
  • Sólo un formulario por archivo .SCX/.SCT (muchos archivos individuales), pero puede ser una ventaja en el desarrollo de proyectos en equipo (vea arriba)
  • No se pueden utilizar propiedades y métodos protegidos (Protected) y ocultos (Hidden)
  • No existe control real sobre la apertura de tablas/vistas en el DataEnvironment (aunque, si lo desea, lo puede realizar en el Form.Load(); pero perderá los beneficios de utilizar un DataEnvironment nativo, mientras tiene otros ventajas de formularios basados en SCX)
Existe una vía de tomar lo mejor de ambos mundos, particularmente para tomar las ventajas de poder agregar cursores a un formulario basado en .SCX DataEnvironment, donde los nombres de tablas y nombres de campos están disponibles en varias interfaces del Diseñador de formularios:
  1. Crea un formulario basado en .SCX
  2. Agregue cursores al DataEnvironment – estos cursores nunca serán abiertos en tiempo de ejecución, así sean tablas/vistas cuando estás utilizando objetos de negocio o DataEnvironment de usuario.
  3. Establecer la propiedad DataEnvironment.AutoCloseTables a .F.
  4. Establecer la propiedad DataEnvironment.AutoOpenTables a .F.
  5. Establecer manualmente el entorno de datos:
    • Agregar código manual en el Form.Load()
    • Agregar código en el Form.Load() para el lazo entre el DataEnvironment (ignorado en tiempo de ejecución) y ejecutar el código equivalente al objeto Cursor y sus propiedades en los objetos Relation.
    • Implementar su objeto DataEnvironment sustituto/de usuario
    • Liberar el control de los datos a su objeto de negocio
Esta técnica permite aprovechar todas las ventajas de los formularios basados en .SCX, mientras acomoda todas las consideraciones que típicamente fueron creadas para formularios basados en .VCX. Incluso en un desarrollo n-capas, si su capa intermedia proporciona cursores a la capa de presentación, puede agregar los cursores al DataEnvironment donde están disponibles en tiempo de diseño, y establecer simplemente la propiedad Alias al alias proporcionado en la capa intermedia.

Espero que les haya resultado de utilidad.

Saludos,

Ana María Bisbé York

2 de noviembre de 2004

Menú dinámico

Hola a Todos, esto se me ocurrió hace tiempo y recién lo he podido implementar, mi idea era generar un menú que se encuentre definido en una tabla dbf y luego llamarlo desde un formulario de nivel superior, para que no se necesite compilar nuestra aplicación si queremos cambiar algo en el menú, ojalá les sirva de utilidad.

****************************************
* MODO DE USO :
* COPIAR EL TEXTO EN UN PRG EN
* CUALQUIER CARPETA EJ: MENU.PRG
* DO MENU.PRG
****************************************
* INDICACIONES :
* EL CODIGO SE ENCARGA DE CREAR UN FORM DE NIVEL SUPERIOR, GENERA EL
* MENU Y HACE LA LLAMADA AL MISMO LA PRIMERA VEZ QUE SE EJECUTA EL
* CODIGO, VERIFICA SI EXISTE LA TABLA DE MENU, EN LA CUAL SE DEFINEN LOS
* NIVELES DE PROFUNDIDAD DE CADA OPCION DEL MENU, LO CUAL LO PUEDES
* MANEJAR SEGUN REQUERIMIENTO LAS OPCIONES DE LOS MENUS NO VAN A
* FUNCIONAR AL NO EXISTIR LOS FORMULARIOS EN LA RUTA, ESO LO DEBES
* CAMBIAR SEGUN REQUERIMIENTO.

IF !FILE("MENU.DBF")
  CREATE TABLE MENU (MODULO C(2),;
    CODIGO C(14),;
    DESCRIPC C(50),;
    COMMAND C(50))
  SELECT MENU
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","01000000000000","Tablas","")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","01010000000000","Clientes","DO FORM CLIENTES.SCX")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","01020000000000","Proveedores","DO FORM PROVEEDORES.SCX")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","01030000000000","Maestras","ACTIVATE POPUP P010301")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","01030100000000","Trabajadores","ACTIVATE POPUP P01030101")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","01030101000000","Empleados","DO FORM EMPLEADOS.SCX")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","01030102000000","Obreros","ACTIVATE POPUP P0103010201")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","01030102010000","Obreros Clasificados","DO FORM CLASIFICA.SCX")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02000000000000","Procesos","")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02010000000000","Calcular Encuesta","DO FORM CALCULAR.SCX")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02020000000000","Tipos De Empleados","ACTIVATE POPUP P020201")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02020100000000","Obreros","DO FORM OBREROS.SCX")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02020100000000","Empleados","DO FORM EMPLEADOS.SCX")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02020100000000","Contratistas","ACTIVATE POPUP P02020101")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02020101000000","Luz Del Sur","ACTIVATE POPUP P0202010101")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02020101010000","Electricistas","ACTIVATE POPUP P020201010101")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02020101010100","Juan Perez","DO FORM JUAN.SCX")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02020101010200","Jose Chavez","DO FORM JOSE.SCX")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("02","03000000000000","Ayuda","")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("02","03010000000000","Acerca De","do form acercade.scx")
  INDEX ON modulo+codigo TAG ORDEN
  CLOSE ALL
ENDIF
IF USED('TABMENU')
  USE IN TABMENU
ENDIF

oForm = CREATEOBJECT('MyFormSample')
oForm.SHOW
READ EVENTS

DEFINE CLASS MyFormSample AS FORM
  NAME = 'FrmPrincipal'
  CAPTION = 'Formulario Principal'
  SHOWWINDOW = 2
  WINDOWSTATE = 2
  cNameMenuMP = ""
  PROCEDURE LOAD
    =MESSAGEBOX("ejecutando load")
    THISFORM.Previo()
      \LPARAMETER oFormRef,getMenuName
      \
      \LOCAL cMenuName
      \IF TYPE("m.oFormRef") # "O" OR LOWER(m.oFormRef.BaseClass) # 'form' OR m.oFormRef.ShowWindow # 2
      \   RETURN
      \ENDIF
      \m.cMenuName = IIF(TYPE("m.getMenuName")="C",m.getMenuName,SYS(2015))
      \IF TYPE("m.getMenuName")="L" AND m.getMenuName
      \   m.oFormRef.Name = m.cMenuName
      \ENDIF
      \DEFINE MENU (m.cMenuName) IN (m.oFormRef.Name) BAR
      \*
    SELECT TABMENU
    nNumberMenuPrin = 0
    SELECT TABMENU
    SCAN FOR INT(VAL(SUBSTR(CODIGO,3)))=0
      nNumberMenuPrin = nNumberMenuPrin + 1
      cDescripcion = ALLTRIM(TABMENU.descripc)
      =MESSAGEBOX("Creando Menú " + cDescripcion)
      cNamePad = "PAD" + PADL(ALLTRIM(STR(nNumberMenuPrin)),3,'0')
      \DEFINE PAD <<cNamePad>> OF (m.cMenuName) PROMPT "<<cDescripcion>>" COLOR SCHEME 4 KEY ALT+T, ""
    ENDSCAN
    SELECT LEFT(CODIGO,2) AS GRUPO FROM TABMENU GROUP BY 1 ORDER BY 1 INTO CURSOR _CrsOpciones
    nMenuTotal = RECCOUNT("_CrsOpciones")
    IF USED('_CrsOpciones')
      USE IN _CrsOpciones
    ENDIF
    nNumberMenuPrin = 0
    SELECT TABMENU
    SCAN FOR INT(VAL(SUBSTR(CODIGO,3)))=0
      nNumberMenuPrin = nNumberMenuPrin + 1
      cDescripcion = ALLTRIM(TABMENU.descripc)
      cNamePad   = "PAD" + PADL(ALLTRIM(STR(nNumberMenuPrin)),3,'0')
      cNamePopup = "P" + LEFT(TABMENU.CODIGO,2) &&& + PADL(ALLTRIM(STR(nNumberMenuPrin)),2,'0')
      \ON PAD <<cNamePad>> OF (m.cMenuName) ACTIVATE POPUP <<cNamePopup>>
    ENDSCAN

    * DEFINIENDO POPUS POR NIVELES
    nBarNivel = 0
    SELECT TABMENU
    GO TOP
    FOR nNiveles = 2 TO 6
      nSubStrCero = ((nNiveles+1)+nNiveles)
      nLeft = nSubStrCero-1
      nLeftNivelFactor = IIF(nNiveles=2,2,nNiveles*2)
      FOR nMenuPrin = 1 TO nMenuTotal
        SELECT * FROM TABMENU WHERE LEFT(TABMENU.CODIGO,2) = PADL(ALLTRIM(STR(nMenuPrin)),2,'0') AND ;
          INT(VAL(SUBSTR(TABMENU.CODIGO,nSubstrCero))) = 0 AND ;
          RIGHT(LEFT(TABMENU.CODIGO,nLeft),2) <> '00';
          INTO CURSOR _CrsConsultaSQL
        IF RECCOUNT('_CrsConsultaSQL')>0
          IF USED('_CrsConsultaSQL')
            USE IN _CrsConsultaSQL
          ENDIF
          nBarNivel = 0
          lEsPrimero = .T.
          SELECT TABMENU
          SCAN FOR LEFT(TABMENU.CODIGO,2)=PADL(ALLTRIM(STR(nMenuPrin)),2,'0') AND ;
              INT(VAL(SUBSTR(TABMENU.CODIGO,nSubstrCero))) = 0 AND ;
              RIGHT(LEFT(TABMENU.CODIGO,nLeft),2) <> '00'
            cTagNivelNamePad = LEFT(TABMENU.codigo,nLeftNivelFactor) &&& SUBSTR(TABMENU.codigo,3,2)
            IF lEsPrimero
              cPadNameNivel = "P" + cTagNivelNamePad
              \
              \DEFINE POPUP <<cPadNameNivel>> MARGIN RELATIVE SHADOW COLOR SCHEME 4
              lEsPrimero = .F.
            ENDIF
            cTagBar = ALLTRIM(TABMENU.DESCRIPC)
            nBarNivel = nBarNivel + 1
            \DEFINE BAR <<nBarNivel>> OF <<cPadNameNivel>> PROMPT "<<cTagBar>>"
          ENDSCAN
        ELSE
          IF USED('_CrsConsultaSQL')
            USE IN _CrsConsultaSQL
          ENDIF
        ENDIF
        nBarNivel = 0
        SELECT TABMENU
        SCAN FOR LEFT(TABMENU.CODIGO,2)=PADL(ALLTRIM(STR(nMenuPrin)),2,'0') AND ;
            INT(VAL(SUBSTR(TABMENU.CODIGO,nSubstrCero)))=0 AND ;
            RIGHT(LEFT(TABMENU.CODIGO,nLeft),2)<>'00'
          cTagBar = ALLTRIM(TABMENU.DESCRIPC)
          cTagCom = ALLTRIM(TABMENU.COMMAND)
          nBarNivel = nBarNivel + 1
          IF "ACTIVATE POPUP"$cTagCom
            \ON BAR <<nBarNivel>> OF <<cPadNameNivel>> <<cTagCom>>
          ELSE
            \ON SELECTION BAR <<nBarNivel>> OF <<cPadNameNivel>> <<cTagCom>>
          ENDIF
        ENDSCAN
      NEXT
    NEXT
    \ACTIVATE MENU (m.cMenuName) NOWAIT
    SET TEXTMERGE TO
    SET TEXTMERGE OFF
    COMPILE (THISFORM.cNameMenuMP+".MPR")
  ENDPROC
  PROCEDURE Previo
    SET TEXTMERGE ON
    cFileMenu = CURDIR() + "MENU.DBF"
    IF !FILE(cFileMenu)
      =MESSAGEBOX("No existe el archivo de Menú ...!"+CHR(13)+cFileMenu,16,"Error al cargar Form")
      RETURN .F.
    ENDIF
    USE (cFileMenu) IN 0 SHARED ALIAS TABMENU ORDER ORDEN
    THISFORM.cNameMenuMP = GETENV("TEMP")+"\"+"_" + RIGHT(SUBSTR(SYS(2015), 3),3) + RIGHT(SUBSTR(SYS(2015), 3),3) + RIGHT(SUBSTR(SYS(2015), 3),1)
    SET TEXTMERGE TO (THISFORM.cNameMenuMP+".MPR") NOSHOW
  ENDPROC
  PROCEDURE INIT
    DO (THISFORM.cNameMenuMP+".MPX") WITH THISFORM,.F.
    THISFORM.RESIZE()
  ENDPROC
  ADD OBJECT CmdSalir AS MyButtonQuit  WITH ;
    CAPTION = "\<Salir"
ENDDEFINE


DEFINE CLASS MyButtonQuit AS COMMANDBUTTON
  HEIGHT = 30
  WIDTH = 130
  TOP = 50
  LEFT = 100
  NAME = 'CmdSalir'
  CAPTION = '\<Salir'

  PROCEDURE CLICK
    IF MESSAGEBOX("¿Está seguro que desea Salir?",36,THISFORM.CAPTION) = 6
      CLEAR EVENTS
      THISFORM.RELEASE
    ENDIF
  ENDPROC
ENDDEFINE

PD: Se reciben sugerencias y comentarios. Muchas Gracias.

Jesús Rojas Cárdenas (Lima - Perú)

26 de octubre de 2004

VFP 9.0. Mejoras en diseñador de formularios y clases

Autor: Drew Speedie
Traducido por: Ana María Bisbé York


Resumen

Esta sesión intenta documentar y demostrar todas las características de VFP 9.0 que mejoran, o en cualquier caso afectan, la forma en que se diseñan formularios y clases. Por tanto, el énfasis está hecho en el IDE de VFP, y el diseñador de clases y formularios en particular, pero tenemos otras características de VFP 9.0 que están directamente relacionadas con la productividad mientras creamos formularios y clases.

Mejoras en Ficha Propiedades (Properties)

El área donde VFP 9.0 tiene más cambios relativos al diseño de formularios y clases es sin dudas la ficha Propiedades, así que voy a comenzar por aquí. La Figura1 muestra una parte de la ficha Propiedades mejorada, donde lo primero que salta a la vista son los iconos que preceden las Propiedades, Eventos, y Métodos (PEM). Lo que no está mostrado en la Figura 1 es que el icono indica además la visibilidad de cada PEM. El icono para PEMs ocultos incluye un candado y el icono para PEMs protegidos, incluye una llave.


Figura 1: Ficha Propiedades - lo primero que se observa es el icono que identifica cada elemento

Estos iconos ya son familiares porque se han visto en la lista desplegable de miembros de IntelliSense, el cuadro desplegable para el método, del editor del método Snippet en VFP 8.0 y otros entornos de desarrollo orientados a objeto. Incluir los iconos lo hace más productivo ya que la clave visual identifica instantáneamente cada PEM por el tipo como se ven en la lista.

Fuente personalizada en la ficha Propiedades

Una vez que comienza a agregar propiedades personalizadas y establecer explícitamente PEMs, verá otras características que son bienvenidas. Para comenzar, la ficha Propiedades admite personalización de la fuente y los colores (quizás ha notado que la fuente en la figura 1 es Tahoma). Como se ve en la figura 2 hay además otras características nuevas, las iremos viendo por turnos comenzando con la personalización de la fuente.


7 de octubre de 2004

Lo que nunca te contó tu madre sobre instanciar y destruir formularios

Sesión original: What Your Mother Never Told You About Form Instantiation and Destruction (DevEssentials 2004)
Autor: Drew Speedie
Traducido por: Ana María Bisbé York



Resumen

Esta sesión intenta ayudar a entender mejor la secuencia normal de eventos en VFP cuando los formularios se instancian y se destruyen ... existe mucho más los eventos Init y Destroy. Dotado de este conocimiento, puede depurar problemas e implementar buenas técnicas como las que vamos a demostrar aquí.

Todos los ejemplos se pueden probar en VFP 8 o VFP 9, porque no se utiliza código específico para VFP 9. La mayoría de los ejemplos se aplican a todas las versiones de VFP; pero algunos de los ejemplos utilizan las funciones BINDEVENT()s y las características de la clase DataEnvironment que fueron agregadas en VFP 8.

La mayoría de los ejemplos se pueden ejecutar desde la interfaz DEMO.APP, aunque algunos deben comenzar con CLEAR ALL/CLOSE ALL, y deben ejecutarse desde la ventana de comandos. DEMO.APP es el único archivo incluido con la presentación. Una vez que selecciona los botones Run (Ejecutar) o Source (Fuente) desde la interfaz, los archivos con código fuente para ese ejemplo se copian al disco, en la carpeta donde se encuentra DEMO.APP. A partir de ahí, puede ejecutar los ejemplos desde la ventana de comandos. La mayoría pueden ser ejecutados directamente desde la interfaz DEMO.APP.

Las bases LISAG (Load-Init-Show-Activate-GotFocus) y QRDU (QueryUnload- Release- Destroy- Unload)

Para implementar exitosamente los escenarios de instanciación y destrucción de un formulario, lo primero que debe entender es la secuencia nativa de los eventos.

Instanciación

Como se demuestra en LISAG_QRDU.SCX, aquí está la lista de eventos que ocurren durante la instanciación:
  1. Evento Form.DataEnvironment.OpenTables (Tablas/vistas en el Entorno de datos DataEnvironment no están en uso USE() o abiertas)
  2. Evento Form.DataEnvironment.BeforeOpenTables (Tablas/vistas en el Entorno de datos DataEnvironment no están en uso USE() o abiertas)
  3. Evento Form.Load (Tablas/vistas en el Entorno de datos DataEnvironment están en uso USE() o abiertas)
  4. Evento Init de cualquier objeto cursor en DataEnvironment
  5. Evento Form.DataEnvironment.Init
  6. Evento Init de cada miembro del formulario que es instanciado
  7. Evento Form.Init
  8. Evento Form.Show
  9. Evento Form.Activate
  10. Evento When del primer control del formulario en el orden de tabulación (tab order)
  11. Evento Form.GotFocus
  12. Evento GotFocus del primer control del formulario en el orden de tabulación
Destrucción

1 de octubre de 2004

Calcular Digito de Verificacion de NIT (Colombia)

Rutina para calcular el digito de verificacion de NIT (para Colombia)
********************************************************
* FUNCION         : Nit_DV
* FUNCION         : Devuelve el Digito de Verificacion de un Nit (COLOMBIA)
* PARAMETROS      : Numero de Nit a Calcular
* RETORNO         : Digito de Verificación
* AUTOR           : Nelson Cortes (nelson_cortes@tutopia.com)
********************************************************
FUNCTION Nit_DV(_Nit)
    LOCAL _TipoRet, lnRetorno, Arreglo_PA, WSuma, WDato, WDig_Ver, I
    _TipoRet = VARTYPE(_Nit)
    DO CASE
    CASE _TipoRet == "C"
        _Nit = ALLTRIM(_Nit)
    CASE _TipoRet == "N" OR _TipoRet == "Y"
        _Nit = ALLTRIM(STR(_Nit))
    OTHERWISE
        =MESSAGEBOX("El valor de entrada NIT no se ha podido procesar.",0+48,_SCREEN.cNomApp)
        RETURN
    ENDCASE
    DIMENSION Arreglo_PA(15)
    Arreglo_PA(1) = 71
    Arreglo_PA(2) = 67
    Arreglo_PA(3) = 59
    Arreglo_PA(4) = 53
    Arreglo_PA(5) = 47
    Arreglo_PA(6) = 43
    Arreglo_PA(7) = 41
    Arreglo_PA(8) = 37
    Arreglo_PA(9) = 29
    Arreglo_PA(10) = 23
    Arreglo_PA(11) = 19
    Arreglo_PA(12) = 17
    Arreglo_PA(13) = 13
    Arreglo_PA(14) = 7
    Arreglo_PA(15) = 3
    lnRetorno = 0
    WDato=RIGHT(SPACE(15)+ALLTRIM(_Nit),15)
    WSuma=0
    WDig_Ver=0
    FOR I = 1 TO 15
        WSuma=WSuma+(VAL(SUBSTR(WDato,I,1))*Arreglo_PA(I))
    ENDFOR
    WSuma=MOD(WSuma,11)
    IF(WSuma=0 .OR. WSuma=1)
        lnRetorno = WSuma
    ELSE
        lnRetorno = 11 - WSuma
    ENDIF
    IF _TipoRet == "C"
        RETURN ALLTRIM(STR(lnRetorno))
    ELSE
        RETURN lnRetorno
    ENDIF
ENDFUNC
Oscar Arley Yepes Aristizabal

30 de septiembre de 2004

WAIT WINDOWS CENTRADO CONTROLADO POR ARGUMENTOS

Un WAIT WINDOW centrado.
? WaitWindowCentrado("Microsoft Visual FoxPro...",0)

FUNCTION WaitWindowCentrado
  LPARAMETERS pcmensaje, pnmodo, pnsegundos, lcarea
  IF pcount()  = 0 OR TYPE("pcmensaje") # "C"
    RETURN("")
  ENDIF
  IF TYPE("pnmodo") # "N"
    pnmodo = 0
  ENDIF
  IF TYPE("pnsegundos") # "N"
    pnsegundos = 1
  ENDIF
  IF TYPE("lcarea") # "C"
    lcarea = "_SCREEN"
  ENDIF
  LOCAL lnfila AS INTEGER, lncolumna AS INTEGER
  LOCAL lnold_scale, lcmodo AS CHARACTER, lcresp AS CHARACTER

  DO CASE
    CASE pnmodo = 0
      lcmodo = ""
    CASE pnmodo = 1
      lcmodo = "NOWAIT"
    CASE pnmodo = 2
      lcmodo = "TIMEOUT pnsegundos"
    OTHERWISE
      lcmodo = ""
  ENDCASE
  lnold_scale = &lcarea..SCALEMODE
  &lcarea..SCALEMODE = 0
  lnfila = &lcarea..HEIGHT / 2
  lncolumna = ( &lcarea..WIDTH / 2 ) - LEN(pcmensaje) / 2
  WAIT WINDOW pcmensaje TO lcresp AT lnfila, lncolumna &lcmodo
  &lcarea..SCALEMODE = lnold_scale
  RETURN(lcresp)
ENDFUNC
NOTA: El 1er parametro es el mensaje, el segundo fijese en el DO CASE, el tercero va junto cuando 'pnmodo' es igual a 2, y el ultimo parametro puede ser _SCREEN o THISFORM por ejemplo.

Jose Matute

14 de septiembre de 2004

Trabajar con fechas y horas en Visual FoxPro


Autor: Luis María Guayán

Este artículo describe las funciones nativas de Visual FoxPro y diversas funciones definidas por el usuario para el tratamiento de expresiones de tipo Date y DateTime.

Introducción

Visual FoxPro posee muchas funciones nativas que generan y manipulan expresiones de tipo Date y DateTime. En este artículo veremos las características de algunas de estas funciones y una recopilación de funciones definidas por el usuario publicadas en PortalFox que nos permitirán obtener diversos resultados a partir de este tipo de expresiones.

Funciones DATE() y DATETIME()

La función DATE() nos retorna la fecha actual del sistema si se ejecuta sin argumentos. Para evitar complicaciones con fechas ambiguas es conveniente que siempre utilicemos la función DATE(nAnio,nMes,nDia) con sus argumentos para crear expresiones de tipo Date.
dFecha = DATE()            && Fecha actual del sistema
dFecha = DATE(2000,03,01)  && 1 de Marzo de 2000 
La diferencia entre dos expresiones de tipo Date nos retorna el número de días transcurridos entre ambas fechas.
? DATE(2004,12,31) - DATE(2004,1,1) 
Si se suman o restan cantidades numéricas de días a una expresión de tipo Date, el resultado es otra expresión de tipo Date.
? DATE() + 7   && La fecha actual mas 7 días
? DATE() - 15  && La fecha actual menos 15 días 
La función DATETIME() nos retorna la fecha y hora actual del sistema si se ejecuta sin argumentos. Al igual que la función DATE() y para evitar complicaciones con fechas ambiguas es conveniente que siempre utilicemos la función DATETIME(nAnio,nMes,nDia,nHoras,nMinutos,nSegundo) con todos sus argumentos para crear expresiones de tipo DateTime.
tFHora = DATETIME()                     

&& Fecha y hora actual del sistema
tFHora = DATETIME(2000,03,01,16,15,30)  && 1 de Marzo de 2000, 16:15:30 horas 
La diferencia entre dos expresiones de tipo DateTime nos retorna el número de segundos transcurridos entre ambas fechas y horas.
? DATETIME(2004,12,31,23,59,59) - DATETIME(2004,12,31,6,0,0)
NOTA: Debido a un bug con el redondeo en las fracciones de segundos en variables del tipo DateTime es conveniente redondear las diferencias de estas variables con ROUND(lnSeg,0)
? ROUND(DATETIME(2004,12,31,23,59,59) - DATETIME(2004,12,31,6,0,0), 0)
Si se suman o restan cantidades de segundos a una expresión de tipo DateTime, el resultado es una expresión de tipo DateTime.
? DATETIME() + 60    && Fecha y hora actual mas 60 segundos
? DATETIME() - 3600  && Fecha y hora actual menos 3600 segundos
Si deseamos generar expresiones Date y DateTime "vacías" lo hacemos de la siguiente manera:
dFecha = {}      && Expresion Date 

vacia
dFecha = {//}    && Expresion Date vacia
tFHora = {/:}    && Expresión DateTime vacia
tFHora = {//::}  && Expresión DateTime vacia 
En estos últimos ejemplos, se debe tener en cuenta que la mayoría de los servidores de bases de datos no trabaja con el concepto de fechas "vacías" como lo hace Visual FoxPro. En estos casos se debe utilizar fechas nulas debido a que se tendrán fechas incoherentes y/o no esperadas.
dFecha = .null.
tFHora = .null.

Funciones con expresiones Date y DateTime

Se pueden convertir expresiones de tipo Date a DateTime y viceversa con las funciones DTOT() y TTOD() respectivamente. En el caso de la función DTOT() (al igual que si omitimos los argumentos de horas en la función DATETIME(nAnio,nMes,nDia)) la hora se sustituye por 00:00:00 (12:00:00AM).
dFecha = DATE(2004,06,03)
tFHora = DATETIME(2004,05,18,11,30,45)
? DTOT(dFecha)
? TTOD(tFHora) 
Las siguientes funciones de Visual FoxPro sirven para poder extraer algunos valores de las expresiones Date y DateTime, como el año, el mes, el día, la semana, etc.
dFecha = DATE(2004,06,03)
tFHora = DATETIME(2004,05,18,11,30,45)
? YEAR(dFecha)     && Año de una expresión Date o DateTime
? MONTH(tFHora)    && Mes de una expresión Date o DateTime
? DAY(dFecha)      && Día de una expresión Date o DateTime
? HOUR(tFHora)     && Horas de una expresión DateTime
? MINUTE(tFHora)   && Minutos de una expresión DateTime
? SEC(tFHora)      && Segundos de una expresión DateTime
? DOW(dFecha)      && Número del día de la semana de una expresión Date o DateTime
? WEEK(dFecha)     && Número de semana del año de una expresión Date o DateTime
? QUARTER(tFHora)  && Trimestre del año de una expresión Date o DateTime 
Todas las funciones anteriores retornan valores numéricos.
Otras funciones de Visual FoxPro retornan cadenas de caracteres a partir de expresiones Date o DateTime, como por ejemplo:
dFecha = DATE(2004,06,03)
tFHora = DATETIME(2004,05,18,11,30,45)
? CMONTH(dFecha)  && Nombre del mes de una expresión Date o DateTime
? CDOW(tFHora)    && Nombre del día de la semana de una expresión Date o DateTime
? DMY(tFHora)     && Cadena con el formato día-mes-año de una expresión Date o DateTime
? MDY(tFHora)     && Cadena con el formato mes-día-año de una expresión Date o DateTime
? TTOC(tFHora)    && Convierte una expresión DateTime a caracter 
? DTOC(dFecha)    && Convierte una expresión Date a caracter 
Para retornar una cadena de caracteres solo con la hora de una variable DateTime se utiliza la función TTOC() con el segundo parámetro igual a 2.
? TTOC(DATETIME(),2) 
En Visual FoxPro se puede retornar una fecha con "n" meses anteriores o posteriores de una expresión Date o DateTime con la función GOMONTH().
? GOMONTH(DATE(),3)        

&& Tres meses después a partir de la fecha actual
? GOMONTH(DATETIME(),-12)  && Doce meses anteriores a partir de la fecha y hora actual 

Otras funciones con argumentos de expresiones Date y DateTime


10 de septiembre de 2004

Meses entre 2 fechas

Con esta simple rutina puedes determinar cuantos meses hay entre 2 fechas.
FUNCTION MesesEntreFechas(pdFecha2, pdFecha1)
  LOCAL lnAnio AS INTEGER, lnMes AS INTEGER
  lnAnio = YEAR(pdFecha2) - YEAR(pdFecha1)
  lnMes = MONTH(pdFecha2) - MONTH(pdFecha1)
  RETURN ((lnAnio * 12) + lnMes)
ENDFUNC
Jose Matute

31 de agosto de 2004

Obtener el IP de la maquina facilmente

Obtener la dirección IP local de la maquina.

loSock = CREATEOBJECT('MSWinsock.Winsock.1')
? loSock.LocalIP
loSock = .NULL.

José Temporini

30 de agosto de 2004

Truncar un número

Función que trunca un número en "n" posiciones decimales.

Ejemplo:
? Truncar(123.456789,2)
-> 123.45

*-------------------------------------------
* FUNCTION Truncar(tnNro, tnDec)
*-------------------------------------------
* Trunca un número en "n" posiciones decimales
* USO: Truncar(nNumero, nDecimales)
* PARAMETROS: 
*   tnNro = Número a truncar
*   tnDec = Número de cifras decimales a truncar
* RETORNO: Numérico
*-------------------------------------------
FUNCTION Truncar(tnNro, tnDec)
  LOCAL ln
  IF EMPTY(tnDec)
    tnDec = 0
  ENDIF
  ln = 10 ^ tnDec
  RETURN ROUND(INT(tnNro * ln) / ln, tnDec)
ENDFUNC
*-------------------------------------------

Luis María Guayán

23 de agosto de 2004

Soy un programador de FoxPro

Artículo original: http://fox.wikis.com/wc.dll?Wiki~VFPArticleByBurtRosen
Autor: Burton J. Rosen
Traductor: Esparta Palma



Me siento como si estuviera parado en un cuarto con personas, anunciando "mi nombre es Burt, y soy un programador de FoxPro". Sabe alguien si hay algún programa de "20 pasos" llamado PAF (Programadores Anónimos de FoxPro)?

Ahora mismo, muchos de ustedes se estarán preguntando "Que es FoxPro, y porqué debería importarme?". FoxPro son varias cosas, pero los atributos principales son 2 partes. Primero, FoxPro es un lenguaje de programación (la herramienta básica usada para desarrollar un software para computadoras), y segundo, FoxPro es una base de datos. Una vez más te preguntarás: Por qué me debería importar?

Si está involucrado en el uso de computadoras, le debería importar. Si está involucrado en la compra de software, le debería importar aún más. Si está involucrado en la contratación de personal para desarrollar software, éste debería ser un tema más importante para usted. Otra vez, me preguntará, ¿Por qué?.

Muchas aplicaciones de negocio computarizadas están en tres cosas: formularios, imágenes o datos (información). Si está tratando con formuarios, usted probablemente está usando un procesador de textos (como Microsoft Word) o una hoja de cálculo (como Microsoft Excel). Si está tratando con imágenes, tal vez alguien está proveyendo las imagenes, o ya sabe que software usar. En cualquier caso, el software de graficas está más allá del alcance de esta discusión.

Mi preocupación aquí, es por aquellos que están tratando con datos. Cuando enfrentamos con un problema de 'datos', muchas gente escogerá uno de esos tres métodos para sobrellevarlo. Ellos también pegarán los datos en un documento de Word (la peor solución posible), o lo pondrán en una hoja de cálculo, o lo pondrán en Microsoft Access (un sistema de base de datos de nivel inicial).

Por que es importante cuál herramienta usar? Ha tratado de insertar un tornillo usando un martillo? Puede hacerlo (a veces), pero el resultado no siempre será limpio. Alternativamente, puede meter un clavo usando desarmador. Usualmente con el resultado de un desarmador roto. Es importante usar una herramienta que sea apropiada para el proyecto actual. Si está procesando datos, necesita usar un programa que esté diseñado para el procesamiento de datos (llamado Sistema de Base de Datos).

Las herramientas de bases de datos mas populares de Microsoft son Acess y SQL Server. Access es un sistema orientado al usuario, está satisface razonablemente bien a las aplicaciones de bajo nivel. Esta es una herramienta que alguien puede usar para solventar rápida y en sucio, un problema de datos. Hay muchos términos clave que use anteriormente. Primero que nada, Access trabaja bien para el uso en un solo lugar (éste no escala bien para el uso de muchas personas). En segundo lugar, dije que sería "rapido y en sucio". Si el problema de datos es un tema de una sola vez, Access probablemente sea aceptable. Pero no recomendaría usar Access para un sistema que será usado regularmente, por meses o años.

En el extremo alto, Microsoft tiene SQL Server. Éste es un ambiente de desarrollo diseñado para las aplicaciones de niveles corporativos empresariales. Las personas que desarrollan aplicaciones grandes, para grandes corporativos usarán bases de datos SQL Server, Oracle o IBM DB2.

Pero, que deberían hacerlas personas organizaciones que están enmedio para soluciones de datos? Ellos necesitan soluciones reales para problemas reales que son muy grandes para Access, pero que no tienen el presupuesto o el equipamento para soportar SQL Server o sus competidores. Cuáles son sus soluciones? Es ahí donde entra FoxPro. FoxPro, o paa ser un poco más exactamente técnico, Visual FoxPro (VFP), es la base de datos con un lenguaje de programación integrado. Satisface idelamente a problemas de datos de rango medio. Se puede acomodar fácilmente en una base de datos pequeña que funciona en el escritorio con varias
docenas de expedientes. También puede manejar bases de datos con millones de registro es una red ampliamente distribuida. La buena noticia es que este tambien maneja todos los tamaños que están enmedio y los maneja muy bien.

Así pues, se puede preguntar: ¿Cuál es el problema?, cuando digo a la gente que soy un programador de FoxPro, la reacción más común que obtengo es "Oh, todavía existe?". La genete piense que FoxPro desapareció al menos hace 10 años, y que todo lo que queda son programas heredados que no vale la pena re-codificar. La verdad es que Microsoft estará liberando la versión 9 de VFP en Septiembre. VFP es una herrmienta de desarrollo que sigue viva y es próspera, y en mi opinión, es el ambiente de desarrollo más poderoso y versatil que hay en el mercado hoy en día.

Así que la próxima vez que esté buscando una solución de base de datos, busque algo escrito en VFP. Creo encontrará soluciones de gran alcance, con precio razonablem dispuestas de gran flexibilidad y desempeño. Si necesita una aplicación desarrollada especialmente para usted, pregunte por un desarrollador de FoxPro.

Actualmente, yo me levantaría y confesaría "Soy un programador de FoxPro, Y ESTOY ORGULLOSO DE ELLO".

BurtRosen

Este artículo fué publicado originalmente en CWCE Magazine ( www.cwce.com)
(c) 2004 by Burton J. Rosen

18 de agosto de 2004

Saber si una aplicación ya está activa (2)

Una manera más para saber si una aplicación ya está activa, esto para evitar cargar dos veces la misma.

Cortesía del MVP turco: Çetin Basöz
*** En el PRG inicial de tu aplicación***
If AppAlreadyRunning()
  Messagebox('Another instance is already running.')
...

Function AppAlreadyRunning
Local hsem, lpszSemName
#Define ERROR_ALREADY_EXISTS 183
Declare Integer GetLastError In win32API
Declare Integer CreateSemaphore In WIN32API ;
 string @ lpSemaphoreAttributes, ;
 LONG lInitialCount, ;
 LONG lMaximumCount, ;
 string @ lpName
lpszSemName = "CadenaUnicadetuAplicacion"
hsem = CreateSemaphore(0,0,1,lpszSemName)
Return (hsem # 0 And GetLastError() == ERROR_ALREADY_EXISTS)
PS:FindWindow() works with ClassNames too.

Çetin Basöz
MS Foxpro MVP, MCP
Welcome to FoxyClasses

16 de agosto de 2004

Guardar archivo HTML como cadena

Con esta rutina puedes simular el efecto de la función "Guardar cómo ... Archivo de Texto", de los navegadores web (IE, Netscape, etc..)

Local m.myHTML,m.parsed
myHTML=[This< br> is an < font color="red">< b>example< /b >< /font >< Hr>x2&nbsp&lt&nbspx3]
*myHTML= FileToStr(GetFile('HTM'))
parsed = ''
oParser = Createobject('myParser',m.myHTML,@m.parsed)
? m.Parsed
*StrToFile(m.Parsed,'ParseResult.txt')

Define Class myParser As Form
  Add Object oWB As OleControl With ;
    OleClass = 'Shell.Explorer'

  Procedure Init
  Lparameters tcHTML, tcParse
  lcTemp = Sys(2015)+'.htm'
  Strtofile(tcHTML,lcTemp)
  With This.oWB
    .Navigate2('file://'+Fullpath(lcTemp))
    Wait window 'Parsing...' nowait
    Do While .ReadyState # 4 && Wait for ready state
    EndDo
  Endwith
  Erase (lcTemp)
  tcParse = This.oWB.Document.body.innerText
  Wait clear
  Return .f.
Endproc
Enddefine

Çetin Basöz
MS Foxpro MVP, MCP 
Welcome to FoxyClasses

13 de agosto de 2004

Enviar email con Adjuntos utilizando CDO

Un código bastante sencillo (y gratis) cortesía de Pepe Llopis, extraido de los newsgroup de microsoft:

Necesitas un XP profesional o superior (2000, 2003, etc) y tener configurado el servicio SMTP

    iMsg = CreateObject("CDO.Message")
    iMsg.From   = ALLTRIM(Thisform.txtEmail.Value)
    iMsg.Subject  = ALLTRIM(Thisform.txtAsunto.Value)
    iMsg.To   = ALLTRIM(lcMailAddress)
    IF !EMPTY(Thisform.txtAdjunto1.Value)
          iMsg.AddAttachment(ALLTRIM(Thisform.txtAdjunto1.Value))
    ENDIF
    WaitWCtr("Dando formato al mensaje ....",.T.)
    lcMessage = ALLTRIM(Thisform.txtPlantilla.Value)
    lcMessage = "file://"+FULLPATH(ALLTRIM(lcMessage))
    iMsg.CreateMHTMLBody( lcMessage,0)
    WaitWCtr("Enviando mensaje....",.T.)
    iMsg.Send
    RELEASE iMsg

Espero les sea de utilidad.


-----------------------------------
Espartaco Palma Martínez

12 de agosto de 2004

Hacer respaldo de la base de datos de MS-SQLServer (programáticamente)

Dos formas de lograrlo, utilizando código de VFP.

Con SQLDMO:
LOCAL loServer, loBackup

loServer   = CREATEOBJECT('SQLDMO.SqlServer')
loServer.Connect("MiServidor","MiUsuario", "Mi password")

loBackup = CREATEOBJECT('SQLDMO.Backup')
WITH loBackup
 .Database     = "LabasedeDatos"
 .Files        = '[C:BackupsNombreRespaldo]'
 .Initialize   =  .T.
 .BackupSetName   = "MiRespaldo"
 .BackupSetDescription = "MiDescripcion"
 .SQLBackup(loServer)
ENDWITH

Y con ADO:
LOCAL lcSQLScript, loConnection

loConnection = CREATEOBJECT('ADODB.CONNECTION')
loConnection.Open("Provider=SQLOLEDB.1;User ID=MiUsuario"+;
 +";PWD=MiPassword"+;
 +";Persist Security Info=False"+;
 +";Initial Catalog=LaBasedeDatos"+;
 +";Data Source=MiServidor")

lcSQLScript = ;
"BACKUP DATABASE [LaBasedeDatos] TO  DISK = N'C:BackupsNombreRespaldo' "+;
"WITH  INIT ,  NOUNLOAD ,  NAME = N'MiRespaldo',  NOSKIP ,  STATS = 10,  "+;
"DESCRIPTION = N'MiDescripcion',  NOFORMAT "

loConnection.Execute(lcSQLScript)

Espero les sea de utilidad.

Espartaco Palma Martínez

26 de julio de 2004

Hexadecimal a Binario

Función que transforma un número hexadecimal en binaro.
? Hex2Bin("123ABC")

FUNCTION Hex2Bin(tcHex)
  LOCAL lcRet, lnDec, lnI
  lcRet = ""
  FOR lnI = 1 TO LEN(ALLTRIM(tcHex))
    lnDec = EVALUATE("0x"+SUBSTR(tcHex,lnI,1))
    lcRet = lcRet + ;
      IIF(BITTEST(lnDec,3),"1","0") + IIF(BITTEST(lnDec,2),"1","0") + ;
      IIF(BITTEST(lnDec,1),"1","0") + IIF(BITTEST(lnDec,0),"1","0")
  ENDFOR
  RETURN lcRet
ENDFUNC

NOTA: Otra forma mas directa de pasar un número Hexadecimal a Decimal:
? EVALUATE("0x" + "123ABC")

Luis María Guayán