27 de diciembre de 2014

Abrir tablas o no

Abrir tablas o no abrir tablas. Esa es la pregunta.

por Jim Booth (Publicado originalmente en FoxTALK Diciembre 1998)
Traducido por Roberto Alfredo Moré
¿Se ha preguntado alguna vez si hay algún beneficio abriendo las tablas para un comando SQL SELECT antes de ejecutar el SELECT?. ¿Usa el SELECT las tablas que ya están abiertas?. Interesante pregunta.

¿Qué tan rápido es rápido?

Una simple prueba para averiguar cuál es el efecto de abrir primero las tablas sobre la performance de SQL en Visual FoxPro se muestra en el siguiente ejemplo de código.


LPARAMETERS plCloseThem
DIMENSION laTimes(100)
FOR lnCnt = 1 TO 100
lnStart = SECONDS()
SELECT * FROM BigFile WHERE cState = "NY" INTO CURSOR Result
lnEnd = SECONDS()
laTimes(lnCnt) = lnEnd - lnStart
IF plCloseThem
CLOSE ALL
ENDIF
ENDFOR
* Calcular el tiempo promedio
lnTime = 0
FOR lnCnt = 1 TO 100
lnTime = lnTime + laTimes(lnCnt)
ENDFOR
lnTime = lnTime/100
?lnTime

En el programa de prueba de arriba, la tabla BigFile es una tabla de 200,000 registros con índices sobre el campo cState y sobre DELETED(). El programa puede ser llamado con un parámetro que le indica si cierra o no las tablas entre las corridas del comando SELECT. Ejecuta el SELECT 100 veces y luego promedia el tiempo que toma el SELECT.
En mi máquina (Pentium II 300MHz con 128 MB RAM y un disco rígido SCSI) obtuve un promedio de tiempo de 1.23 segundos cuando las tablas se cierran entre los SELECTS. Si las tablas se dejan abiertas, el tiempo promedio fue de 0.67 segundos, ¡dos veces más rápido!.
Conclusión: Abra sus tablas primero y déjelas abiertas, especialmente si estará ejecutando múltiples SELECTS que acceden a estas tablas.

¿Usa realmente el SELECT las tablas que están abiertas?

No, no las usa. Utiliza un USE AGAIN para abrir la tabla en otra área de trabajo. Para ver esto, usted puede realizar la siguiente prueba:

1. Abra la ventana Data Session desde el menú Window.
2. En la ventana de comandos ejecute un comando SELECT simple como SELECT * FROM AlgunaTabla INTO CURSOR Resultado.
3. En la ventana Data Session haga click sobre el nombre de la tabla origen y observe el eco del comando en la ventana de comandos (será SELECT 1 o algo así). El número es el área de trabajo en que el cursor está abierto.
4. Haga click sobre el cursor Resultado y observe el área de trabajo (podría ser 2).
5. Tipee USE en la ventana de comandos para cerrar el cursor Resultado.
6. Corra nuevamente el comando SELECT.
7. Repita los pasos 3. y 4. nuevamente.
Encontrará que la segunda vez hay un hueco en las áreas de trabajo que usó el comando SELECT. Si usted hace esto cuando no hay ninguna tabla abierta, la primera vez las áreas de trabajo deberían ser 1 para la tabla origen y 2 para el resultado. La segunda vez, serían 1 para la tabla origen y 3 para el resultado.
La razón por la que el resultado está en un área de trabajo diferente la segunda vez es que el SELECT abrió la tabla origen en el área 2 durante el proceso de consulta. Esto fue hecho con una operación USE AGAIN. El beneficio de este comportamiento son dos cosas:

1) el segundo SELECT se ejecutará más rápido y
2) el puntero de registro en el área de trabajo de la tabla origen no se afecta.
¿Por qué es más rápido abrir las tablas?

Porque cuando una tabla está abierta, con SET OPTIMIZE ON, VFP trata de capturar tanto como pueda del índice para proveer a Rushmore con lo que necesita. Así, cada vez que se abre una tabla, se lee en memoria una cierta cantidad de información. Cuando se usa USE AGAIN para una tabla que ya está abierta, esa información ya está en memoria y no necesita ser leída del disco. Usted habrá notado, en el programa de prueba, que no hay un comando USE Bigfile. Esto ilustra otra sutil cualidad del comando SQL SELECT. Si se ejecuta el SELECT y la(s) tabla(s) de origen no está(n) abierta(s), la(s) abrirá y la(s) mantendrá abierta(s). Si la(s) tabla(s) de origen está(n) abierta(s), las usará nuevamente en otra área de trabajo y cerrará dicha área de trabajo cuando se completa el SELECT.

Una nota final.

Hay una preocupación reciente sobre el uso de SYS(2015) para generar nombres únicos de cursor. Existe un informe sobre que SUBSTR(SYS(2015),3) comenzará con un dígito en un futuro cercano. Bueno, esto es cierto, pero es irrelevante. Los cursores creados con el comando SQL SELECT o CREATE CURSOR usan el nombre para definir el alias del área de trabajo donde se ubica el cursor, NO el nombre del archivo en disco usado por el cursor para sus datos. Los nombres de archivo para los cursores serán siempre únicos y se crearán en el directorio temporal del usuario. Los nombres alias son siempre locales al equipo actual y no necesitan ser únicos. Pruebe lo siguiente en una ventana de comandos:

CREATE CURSOR MyCsr (Test C(10))
? ALIAS()
?DBF()

Lo que se obtiene como resultado de Alias() es MyCsr y DBF() es C:Temp2E4H000C.TMP. De aquí se puede deducir que
1) el archivo para el cursor fue creado en el directorio temporal, no en el disco de red, y
2) el nombre utilizado en el comando CREATE CURSOR no es el mismo que el que se usó en el archivo dbf.
No es necesario usar ningún algoritmo para crear un nombre único de un cursor; usted puede usar nombres significativos en su código y no encontrará problemas en un entorno multiusuario.

13 de diciembre de 2014

Introducción a los Patrones de diseño

Artículo original: Introduction to Design Patterns
http://weblogs.foxite.com/andykramek/2006/12/02/introduction-to-design-patterns/
Autor: Andy Kramek
Traducido por: Ana María Bisbé York


¡ Cuánto tiempo !

Hace ya tiempo que no he podido actualizar mi blog y existen varias razones para ello, no solamente se debe a que he estado extremadamente ocupado profesionalmente, trabajando en varios proyectos interesantes. Más recientemente, Marcia y yo viajamos a Frankfurt a hablar en la 13ra European DevCon donde nos unimos a Craig Bernston, Doug Henning, Lisa Slater-Nicholls y Rick Schummer.

Esta fue, como siempre, una G R A N conferencia y mantuvo el misterio de siempre, para todos nosotros los que hemos presenciado este evento, la poca cantidad de personas de EEUU que han estado. Los vuelos son (relativamente) baratos y agradables, y la oportunidad de presenciar una conferencia genuinamente buena combinado con un descanso europeo y recreación, es sencillamente una buena idea. Puede encontrar detalles de lo que se perdió en varios lugares de la red, incluyendo la cobertura en Universal Thread y los blogs de algunos presentes como Craig, Doug y Rick.

Tengo en plan comenzar otra pequeña serie de artículos en mi blog, justo ahora, con el tema de patrones de diseño. Esto es algo que me interesa desde hace ya tiempo (vea "The Revolutionary Guide to Visual FoxPro OOP", Wrox Press, 1996 escrito por Will Phelps, Bob Groomes y yo, con algunos ejemplos iniciales basados en patrones de diseño con VFP), y a juzgar por los comentarios y discuciones que he presenciado en conferencias, no soy el único interesado. La pregunta más común que he escuchado es "¿Cómo utilizo los Patrones de diseño en VFP?" y esto es lo que trataré de enfocar con esta pequeña serie de artículos - al menos para algunos de los patrones más comunes.

¿Porqué me debo preocupar por los Patrones de diseño?

Los patrones de diseño ofrecen un lenguaje estándar para reconocer, definir, y describir soluciones a problemas de la creación de aplicaciones. El conocimiento de los patrones de diseño, hace más fácil entender los sistemas existentes y describir los requerimientos para nuevos sistemas complejos.

Recuerdo que hace unos años, sentado con Paul Maskens en el lobby de Lindner Conference Hotel en Frankfurt, mientras el me explicaba una idea para la columna Kitbox. El estaba describiendo una solución a un problema que había desarrollado y luego de aproximadamente 20 minutos, de pronto, me di cuenta de que estaba hablando de el "patrón de estrategia". El comenzó diciendo que había implementado el patrón de estrategia quise saber inmediatamente qué uso general tenía aquí, y el método que había empleado para solucionarlo. Esto nos ahorró mucho tiempo y esfuerzo y nos permitió concentrarnos en los detalles de la implementación.

Sin embargo, es importante indicar que los patrones de diseño, no son, de por sí, la solución a problemas específicos. Son vías sencillas de identificación de los problemas y una descripción genérica de soluciones, que han sido probadas por la experiencia. La implementación real de los patrones de diseño sigue siendo el trabajo de un desarrollador de aplicaciones.

¿Qué son patrones de diseño?

Antes de comenzar a bucear en ejemplos específicos de cómo implementar patrones de diseño específicos en Visual FoxPro, debo comenzar por definir qué entendemos por "Patrones de Diseño". Se han propuesto varias definiciones y quizás la más reconocida es la que aparece en el trabajo "Design Patterns, Elements of Reusable Object-Oriented Software" (Diseño de patrones, elementos de reutilización de aplicaciones orientadas a objetos) escrito por Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides (comúnmente conocidos por la pandilla de los cuatro o simplemente como "GoF"). Ellos ofrecen, en el primer capítulo de su libro "What is a Design Pattern" (¿Qué es el diseño de patrones?), la siguiente definición:

Un diseño de patrones nombra, abstrae e identifica los aspectos claves de una estructura de diseño común que lo hace útil para la creación de un diseño orientado a objetos reutilizables. El diseño de patrones identifica la participación de clases e instancias, sus roles y colaboraciones, y la distribución de responsabilidades.

Esta es una buena definición, porque encapsula los cuatro elementos de cualquier diseño de patrones.

  • Tiene un nombre. Esto es vital, porque permite a los desarrolladores superar uno de los problemas fundamentales del diseño de aplicaciones – cómo comunicar lo que haces a otros? El ejemplo de Paul y yo discutiendo una idea para un artículo, que he mostrado antes, ilustra este punto perfectamente.
  • Abstrae el problema. Esto es referido por "GoF" como el "objetivo". Nos dice la naturaleza del problema y la solución descrita por el patrón. Considere el problema frecuente de advertir a los usuarios de no crear múltiples instancias de una aplicación. Los usuarios, por lo general, tratan de comenzar nuevas instancias de la aplicación porque, al tener minimizada la pantalla principal, se olvidan que está activa y hace clic en el icono del escritorio, en lugar de de maximizar la instancia existente. Podemos ver inmediatamente que el patrón Singleton es relevante porque su objetivo es "Asegurar una clase que tiene una sola instancia y provee un punto global de acceso a ella".
  • Define una estructura de diseño. Es importante darse cuenta que los patrones de diseño no ofrecen soluciones a los problemas. Ellos describen estructuras que permiten solucionar un problema de manera que resulte más fácil de reutilizar que si se escribe simplemente el código para solucionar el problema en el contexto en el que surge. Todos hemos pasado por estas experiencias de darnos cuenta que hemos solucionado ya un problema particular en algún lugar en nuestro código, pero no podemos re-utilizarlo, porque no hicimos una solución aislada de la situación. El objetivo de los patrones de diseño es ayudarnos a reconocer situaciones como estas y evitarlas.
  • Identifica la distribución de responsabilidades. Esto es, por supuesto, la clave a todos los tópicos de diseño y no está limitado a los patrones de diseño. Después de todo, una vez conocido lo que tiene que hacer una clase (u objeto), escribir el código para hacerlo es relativamente fácil. La ventaja de los patrones de diseño es que una vez que haya reconocido el problema y lo haga corresponder con un patrón, el patrón nos dirá cómo debemos asignar las responsabilidades y por consiguiente nos ayudará a crear rápidamente le "mejor" solución.

No es mi intención ofrecer una revisión exhaustiva de todos los patrones de diseño conocidos (existen libros enteros dedicados a eso). Pero en esta pequeña serie mi plan es cubrir los patrones encontrados más frecuentemente y mostrar cómo puede utilizar el Visual FoxPro para implementar una solución. Cada artículo es independiente y cubrirá un patrón, aunque intentaré en lo posible mantener un hilo único en ellos.

Espero que encuentre útil esta información, y si tiene cualquier comentario, por favor, sea libre de publicarlo.

5 de diciembre de 2014

Cualquier dirección con StreetView !!!

Artículo original: StreetView any address !!!
http://vfpimaging.blogspot.com.br/2014/12/streetview-any-adress.html
Autor: Cesar VFPIMAGING
Traducido por: Luis María Guayán


Acabo de recibir un pedido de un usuario que le permita ver la Vista de la Calle (Google StreetView) de nuestros clientes en los formularios de clientes.

Esto es útil especialmente para los casos de entregas, lo que permite al equipo saber el aspecto de la calle, y planificar mejor las entregas de mercaderías.

He encontrado varios ejemplos, pero todos ellos necesitaba las coordenadas GPS (Global Positioning System - Sistema de Posicionamiento Global), la latitud y la longitud para hacer que funcione.

Después de hacer algunas búsquedas, he encontrado la solución en el BLOG JAYCODE DESIGN, de Jordan Clist.
"A todos nos gusta el servicio Streetview que ofrece Mapas de Google. Naturalmente, la API de Google ofrece la posibilidad de incrustar Streetview dentro de una aplicación web, si le das los datos y coordenadas correctas.
Lo qué no hace de forma natural y fácil es apuntar la cámara StreetView automáticamente a una dirección.
Esto es algo que pensé que sería fácil, pero en realidad era más difícil de lo que parece.
Aunque es bastante simple crear una vista panorámica de la calle en un punto GPS dado, lo que es más complicado es asegurarse de que la cámara StreetView esté apuntando en la dirección correcta (punto de vista, u orientación)"
Por lo que es el trabajo era un poco complicado, ya que el ejemplo original proporcionado trabajó solamente con algunas direcciones ya conocidos por Google. Eso hizo que falle en la mayoría de los casos, debido a la ortografía, o incluso una coma puesta en el lugar equivocado.

Así que usé otra API de Google Maps, la "Google Maps Directions API", que se utiliza originalmente para decirnos las rutas que podemos tomar entre 2 direcciones. Esta API funciona muy bien con las direcciones sin un buen formato, y devuelve la versión de Google de la dirección. Adapté un ejemplo de Mike Gagnon, de su artículo publicado en http://atoutfox.org "Calcular la distancia entre 2 direcciones, y mostrar el recorrido completo"

Después de conseguir esta dirección, lo paso al script  de Jordan Clist y Voila! Funciona !

Copiar y pegar el siguiente código y guardarlo en un archivo .PRG y llamado "GOOGLESTREETVIEW.PRG"

Y para probarlo, escriba en la ventana de comandos:

=GoogleStreetView("Arche du Triumph") 

o, por supuesto, pasando la dirección completa:

=GoogleStreetView("Place Charles de Gaulle, 75008, Paris, France")

Bonito no es así?



1 de diciembre de 2014

Diseñar y crear clases

Artículo original: Designing and Building Classes
http://weblogs.foxite.com/andykramek/2005/03/27/designing-and-building-classes/
Autor: Andy Kramek
Traducido por: Ana María Bisbé York


He aquí otro tema sobre el que he sido preguntado con frecuencia y es precisamente cómo se deben diseñar las clases. Por supuesto, no hay una única respuesta "correcta"; pero he encontrado siempre muy útil al crear una clase nueva, preguntarme estas tres preguntas:

  • ¿Será reutilizado el objeto?
  • ¿La clase va a simplificar la tarea o a complicarla?
  • ¿Vale la pena el esfuerzo?

Ahora, para ganar en claridad, permítanme que me expanda un poco en esto:

Re-usabilidad

Este es, probablemente la razón más común para la creación de una clase y la posibilidad de re-utilizarla, es después de todo, uno de los logros primarios de POO. En su nivel más simple, puede significar tan sencillo como establecer sus preferencias a los objetos - propiedades Font, Color y Style, por ejemplo, de tal forma que todos los objetos de sus clases se creen con la configuración correcta. Las cosas se complican un poco cuando consideramos la funcionalidad. En la práctica, ¿con qué frecuencia hacemos exactamente lo mismo, en exactamente la misma forma, para alcanzar exactamente el mismo resultado? La respuesta es probablemente "no muy frecuentemente" y empezará a asombrarse si la re-usabilidad, es después de todo, realmente tan valiosa. Pasemos al segundo criterio.

Controlar complejidad

Realmente es muy raro que las clases funcionales puedan simplemente ser re-utilizadas "tal cual". Existen casi siempre, diferencias en el entorno, la entrada o salida de datos requeridos y a veces, todos juntos. Esta complejidad aparente puede a veces oscurecer el tema, con la funcionalidad que se desea mantener constante, incluso a través de la implementación, puede diferir en detalle entre las aplicaciones.

Al aplicar las reglas para el diseño de clases resumidas en el siguiente tópico, debe ser más fácil decidir si al utilizar una clase va a facilitar el control de esta complejidad o no. Incluso, si el criterio de una clase pueda facilitar la manipulación de la complejidad, nosotros aun tendremos que considerar el tercer criterio.

Vale la pena el esfuerzo

Un objeto debería ser encapsulado para que contenga en si mismo toda la información necesaria para completar su tarea, e incluso evidente que ningún objeto debe confiar su implementación interna a otro objeto. Claramente esto puede complicarnos mucho la vida. Puede significar que su clase tendrá que verificar docenas de condiciones posibles para determinar (para si) exactamente que el estado del sistema es antes de que pudiera realizar la función asignada. Al considerar la creación de una clase nueva, es importante estar seguros de que realmente vale la pena el esfuerzo de hacer tal cosa.

¿Qué tipo de clase queremos?

Al tener que decidir que realmente deseo crear una clase nueva, la siguiente pregunta a responder es: ¿Qué tipo de clase debo diseñar?" Existen dos modelos fundamentales para el diseño de clases que son el "Mundo Real" o un modelo concreto de "Componente" o modelo abstracto. No hereda uno del otro, son precisamente diferentes.

Modelo Mundo real (concreto)

En este modelo las clases se diseñan para cumplir la funcionalidad requerida en su totalidad. En este modelo se basan usualmente los Objetos de negocio. Por ejemplo, podemos definir a una clase objeto de negocios "persona" que tiene las características comunes (por ejemplo, propiedades para Ojo izquierdo, ojo derecho, nariz, etc) y el comportamiento de las personas (ejemplo Métodos de Respiración, Alimentación, etc). Esta clase persona puede ser sub-claseada para modelar la funcionalidad de tipos especiales de personas tales como "clientes" o "amigos". Es realmente aparente que el modelo real confía demasiado en la herencia y , como un resultado, en tiempo de diseño, está explícitamente definida la funcionalidad  .

Modelo Componente (abstracto)

En este modelo la clase se ocupa más de un comportamiento específico, que de funcionalidad. Cada clase define una función genérica o acción y, de manera aislada, limita la usabilidad. Claramente habrá más confianza en la herencia, en su lugar, el modelo va a depender de la agregación (esencialmente una operación en tiempo de ejecución) y la composición (en tiempo de ejecución y diseño). El equivalente para nuestra clase objeto de negocio Persona es un compuesto de objetos separados: dos instancias para las clases ojos y una instancia para la clase nariz, etc.

Identificar responsabilidades

Habiendo decidido qué modelo va a utilizar. la siguiente tarea es estar absolutamente seguro de que sabe lo que la clase va a hacer. Esto puede sonar obvio; pero hay una trampa sutil en esto. Es muy fácil crear demasiado en una clase, antes de que se de cuenta que lo que ha creado que en realidad no es re-utilizable, porque hace demasiado. El mejor proceder para esto es identificar y categorizar las "Responsabilidades" de la clase antes de que comience a escribir código alguno. Una responsabilidad puede estar definida, de forma muy simple, como algún elemento de funcionalidad que tiene que ser completado.

Categorizar responsabilidades (Las reglas "debe, podría haber y debe ser hecho").

El propósito de la categorización es asegurarse de que puedo identificar correctamente donde en la jerarquía de clases, pertenece cada responsabilidad. Yo hago esto al utilizar tres categorías las que llamo "debe hacer", "podría hacer" y "debe ser hecho"

Las responsabilidades que caen dentro de la primera categoría "debe hacer" son aquellas que derivan de un objeto desde la clase que debe hacer en cada situación y que no puede ser hecho por otro objeto. Esto es, claramente, la responsabilidad de la clase. Debe ser parte de la definición de clase raíz. Por ejemplo, cualquier objeto que mueve el puntero del registro en una tabla, cursor o vista, DEBE chequear las condiciones EOF() y BOF().

Las responsabilidades que entran en la categoría "podría hacer" son indicadores de que la clase puede requerir una o más subclases (o, posiblemente, la cooperación entre objetos de otra clase.) En otras palabras, estas son cosas que es posible que haga la clase; pero que no son precisamente requeridas en cada situación. Típicamente es un fallo reconocer aquí estos elementos que "podría hacer" muy pronto en el proceso de diseño que encamine a una inadecuada colocación de la funcionalidad en la jerarquía de clases y la selección sea hecha. Sin embargo, un objeto que muestre una lista PUEDE inhabilitarse a si mismo una vez que sea hecha la selección. Sin embargo, esto no es la función esencial y no es el comportamiento requerido en cada momento. Esto pertenece a una subclase especializada.

La categoría final (debe ser hecho) es muy importante también. Esta es la categoría que define la suposición de que una clase puede ser exitosa para funcionar correctamente. Los elementos listados definitivamente no son de única responsabilidad de la clase en cuestión pero pueden, aun así, ser hechos de alguna forma. Por ejemplo, para que un control enlace con un origen de datos, el origen de dato DEBE estar abierto antes de que sea instanciado el control. Sin embargo, controlar esta operación claramente no es responsabilidad del control como tal - si solamente porque debe estar hecho antes de que se instancie el control.

Teniendo definidas y categorizadas las responsabilidades de la nueva clase, puede entonces definir su interfaz pública decidiendo qué propiedades y métodos va a requerir y cómo se deben revelar a otros objetos con los que interactúan. Sólo cuando todo esto esté hecho, puede comenzar a pensar sobre el código y aquí hay un par de cosas que es necesario recordar.

Escribir código en los métodos, no en los eventos.

Al escribir el código de las clases, yo creo firmemente en evitar el colocar código directamente en los eventos nativos de Visual FoxPro, siempre que puedo hacerlo. En su lugar, creo métodos de usuario y los llamo desde los eventos. Hacerlo sí no es, admitámoslo, un requerimiento; pero encuentro que te facilita la vida por varias razones:

  • Me permite dar nombres más identificativos a los métodos. Esto puede sonar trivial; pero realmente hace que sea más fácil de leer y mantener el código cuando el código produce algún tipo de salida es llamado por: "This.GenerarSalida()" en lugar de "Thisform.Pageframe1.Page1.CmdButton1.Click()"
  • Me permite cambiar la interfaz, si es necesario con la simple re-colocación de una única línea que llama al método en lugar de de tener que cortar y pegar la parte funcional del código.
  • Me permite partir los métodos complejos en métodos múltiples (y potencialmente reutilizables). El evento actúa meramente como indicador para el código asociado.

Los métodos deben hacer una cosa y sólo una

Los métodos que hacen "de todo" son difíciles de reutilizar. Recuerde: una simple línea de código es siempre reutilizable. Si tengo que oprimir la tecla Re Pag más de una vez para ver mi código, lo más probable es que el método sea demasiado largo y trate de hacer demasiadas cosas. La solución es partir ese tipo de métodos monolíticos en métodos pequeños y mejor enfocados, y utilizar un método de control que los pueda llamar de forma individual si es necesario. (Pregúntese a si mismo cuántas veces se ha visto duplicando código en un método o bloque de código que ya existe en parte de otro método.

Asegúrese de poner la funcionalidad lo más alto posible en la jerarquía de clases

Esto es una trampa en la que he caído muchas veces a través de los años y aun ocasionalmente tropiezo. Debe saber que cuando coloca funcionalidad muy alto porque invierte mucho tiempo en sus subclases tratando de lograr toda una gran funcionalidad que pueda en la clase raíz. Uno de los mayores beneficios de esto "Puede, debe, podría" es el que ayuda a evitar esta trampa.

Utilice métodos plantilla para una interfaz inconsistente.

He constatado que necesitamos evitar colocar la funcionalidad en una jerarquía muy alta. Al crear un contenedor, vacío métodos en un nivel más alto de la jerarquía, puedo asegurar que todas las clases van a compartir una interfaz común. Ya que las subclases comparten la interfaz común, son intercambiables. Incluso, si un método vacío es llamado., no hace daño a nadie, porque todos los métodos de VFP devuelven .T. de forma predeterminada, incluso si no contienen texto.

22 de noviembre de 2014

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


Español

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

Para leer el libro Domine Visual FoxPro 9.0 SP2 en formato PDF en español (2,16 MB) haga clic aquí.
Nota: Para descargar el libro haga en español haga clic derecho aquí y seleccione la opción "Guardar destino como"

Atte. Ernesto Fabián Coronel

Agradecimientos:

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

English

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

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

Note: To download the book done in English right-click here and select "Save Target As"

Sincerely, Ernesto Fabián Coronel

Acknowledgments:

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

15 de noviembre de 2014

Los detalles que tu mamá no te conto sobre HTMLListener

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

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

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

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

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

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

DO (_reportoutput) WITH 5,loListener

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

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

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

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

DO (_reportoutput) WITH 5,loListener 

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

REPORT FORM  OBJECT loListener
Algunas aclaraciones

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

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

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

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

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

Saludos,

William Hernández
Santiago de Chile

8 de noviembre de 2014

Automatizando Visual FoxPro

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

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

Comenzar con lo conocido

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

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

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

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

Conocer los métodos y las propiedades

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

Instanciando Visual FoxPro desde Excel

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

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

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

Para terminar

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

Hasta la próxima.

Luis María Guayán

6 de noviembre de 2014

Verificar si una tabla está abierta

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

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

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

Tomas Cruz

1 de noviembre de 2014

Combo con búsqueda incremental y filtro

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Carlos Joaniquet Tamburini

18 de octubre de 2014

¿Funciona Visual FoxPro 9 en Windows 10?

Artículo original: Does Microsoft Visual FoxPro 9 run on Windows 10?
http://mattslay.com/does-microsoft-visual-foxpro-run-on-windows-10
Autor: Matt Slay
Traducido por: Luis María Guayán

Estoy seguro que muchos desarrolladores empedernidos de FoxPro tienen la curiosidad si Visual FoxPro 9.0 SP 2 se instalará y ejecutará en Windows 10. Bueno, yo quería ser uno de los primeros en descubrirlo, al igual que lo hice antes cuando Windows 8 fue lanzado por primera vez en su primera vista previa.

Por lo tanto vamos a averiguarlo...

En primer lugar, he instalado Windows 10 Preview (64 bits) en una partición de BootCamp en mis 15" MacBook Pro. (No se preocupen por estas cosas de Mac, es sólo Windows corriendo en hardware vivo, como si fuera una computadora Dell o HP). Eso salió muy bien e hice una instalación completa, quitando el Windows 8 que había estado utilizando en esa partición, en vez de actualizar desde Windows 8 a Windows 10.

A continuación, inserté con cuidado el CD de Visual FoxPro 9 que aún tengo desde 2004. En primer lugar, me impulsó a instalar unos "requisitos previos", cosa que hizo sin problemas. Luego seguí con la instalación principal de VFP y escogí todos los valores predeterminados, entonces el CD giraba y paraba un poco, y finalmente, me mostró una pantalla con un lindo mensaje indicando "Instalación completa" y "No hubo ningún error durante la instalación." Hasta ahora se ve bien!


17 de octubre de 2014

Cómo añadir un generador a sus clases


Articulo original: How to add a Builder to your Class
http://weblogs.foxite.com/bernardbout/2014/09/18/how-to-add-a-builder-to-your-class
Autor: Bernard Bout
Traductor: Luis María Guayán

Es posible que haya visto que varias de mis clases vienen con un generador (builder) incorporado que hace mas fácil configurar las propiedades correctas y brinda una visión correcta de cómo se verá el objeto con las propiedades ya establecidas.

Recientemente uno de los chicos de Foxite ha creado una clase Spinner ideal para tocar con dedos grandes y yo le envié un Generador para ello. Me pidió un tutorial sobre cómo crear un generador así, y como también había estado pensando que esto sería útil, me decidí a documentar el proceso aquí. Nosotros vamos a usar la clase Spinner creada por Tony Vignone para esto (todo está incluido en la descarga) o Ud. puede utilizar cualquier otra clase que desee.

Una clase visual personalizada puede tener una serie de propiedades que necesitan configurarse, y es útil que el usuario final tenga todo esto en un solo lugar, en vez de una larga lista. Así que lo primero que hay que hacer es agregar las propiedades que el usuario puede cambiar, en la solapa Favoritos. De esta manera las propiedades que cambian, son independientes de las propiedades internas y que no cambian.

Para esto la clase necesita tener MemberData. Así que este es el primer paso en la creación de la clase.

1. Abra la clase y agregue una propiedad personalizada _memberdata [guión bajo - memberdata]




1.2. En la hoja de propiedades, seleccione cada propiedad para ser agregada a la solapa Favoritos, haga clic derecho sobre ella y en el menú emergente seleccione "Agregar a favoritos"

2. Añada otra propiedad personalizada a su clase y nómbrela _BigSpinnerBuilder. Darle un valor inicial de 0 [cero]. Haga clic derecho sobre ella, y añadirla a la solapa de Favoritos.

3. Haga clic derecho sobre esta nueva propiedad _BigSpinnerBuilder y seleccione "Editor MemberData" en la ventana emergente, y se abre el Editor MemberData. Configurarlo como en la siguiente imagen, haga clic en el icono de lupa y en la ventana de código que se abre, pegar el siguiente código:

nControls = ASelObj(aObjects)
If nControls # 0
   oObject = aObjects(1)
   Set Classlib To (oObject.ClassLibrary)
    IF VARTYPE(_screen.oWiz) = "U"
       _screen.addproperty("oWiz","")
    ENDIF
   _screen.oWiz = Createobject("BigSpinnerBuilder", oObject)
   _screen.oWiz.Show()
EndIf


29 de septiembre de 2014

Buscar registros repetidos

¿Quien no tuvo la tarea de buscar registros repetidos en una tabla de Visual FoxPro?...

Una forma de saber si tenemos registros repetidos en nuestras tablas es utilizar una sentencia SELECT con las cláusulas GROUP BY y HAVING como la siguiente:

OPEN DATABASE (HOME(2) + "Northwind\Northwind")
SELECT TerritoryDescription, ;
  COUNT(*) AS Repetido  ;
  FROM Territories ;
  GROUP BY TerritoryDescription ;
  HAVING Repetido > 1

El resultado de esta consulta nos muestra si existen registros repetidos y la cantidad de veces que se repiten.

Pero hay veces que necesitamos saber exactamente que registros están repetidos, para ello podemos utilizar algunas de las siguientes sentencias SELECT:

1)
OPEN DATABASE (HOME(2) + "Northwind\Northwind")
SELECT * ;
  FROM Territories ;
  WHERE TerritoryDescription IN ;
  (SELECT TerritoryDescription ;
  FROM Territories ;
  GROUP BY TerritoryDescription ;
  HAVING COUNT(*) > 1)

2)
OPEN DATABASE (HOME(2) + "Northwind\Northwind")
SELECT * ;
  FROM Territories T1;
  WHERE EXISTS ;
  (SELECT * ;
  FROM Territories T2 ;
  WHERE T1.TerritoryDescription = T2.TerritoryDescription ;
  GROUP BY T2.TerritoryDescription ;
  HAVING COUNT(*) > 1)

Nota: Las pruebas estan realizadas con la tabla "Territories" de la base de datos "Northwind" que viene en los ejemplos de VFP8 y superior.

Luis María Guayán

1 de agosto de 2014

Manipular valores NULL en VFP

Artículo original: Handling NULL Values in VFP
http://weblogs.foxite.com/andykramek/archive/2005/12/04/1016.aspx
Autor: Andy Kramek
Traducido por: Ana María Bisbé York

El otro día estuve recordando algunos de los problemas que podían surgir en mi código cuando se encontraba un valor NULL inesperado. Esto me hizo retomar temas de comprensión sobre la manipulación de NULL en VFP y sobre esto tratará este blog. Vamos a comenzar considerando los posibles estados en los que puede existir un valor de VFP ( ya sea un campo de una tabla, o una variable). En Visual FoxPro hay en realidad tres condiciones:
  • el valor puede ser igual a algo (.T.)
  • el valor puede no ser igual a algo (.F.)
  • el valor puede estar en un estado en el que su valor no puede ser determinado (NULL)
Los primeros dos casos no tienen problema y podemos controlar estos casos diariamente en nuestro código. Es el tercero el que nos puede causar problemas, sin que lo controlemos. Frecuentemente encuentro útil, pensar NULL pensando que NULL significa "Yo no se". Esto ayuda a tener sentido el comportamiento de los valores nulos cuando son encontrados.

Lo primero a recordar sobre los nulos es que la respuesta a cualquier comparación que involucre un valor NULL es siempre NULL. Consideremos el siguiente código:
luVal = NULL
? luVal > 0
? luVal = "UNA CADENA DE CARACTERES"
? luVal <= DATE()
? luVal = .T.
El valor devuelto en cada caso es realmente NULL. En otras palabras, cuando VFP pregunta cómo un NULL ( igual a "Yo no se") relaciona con algún otro valor, la respuesta siempre es NULL ( igual a "Yo no se"). Ahora puede estar pensando que esto es un ejemplo tonto, porque siempre puede verificar el tipo de variable (o campo) empleando las funciones TYPE() o VARTYPE().

Desafortunadamente TYPE() no está recomendada para verificar valores NULL, ya que devuelve un tipo "L" para una variable que haya sido inicializada como NULL, de esta forma:
luVal = NULL
? TYPE("luVal") && Devuelve "L"
Devolverá el tipo de dato original si la variable está contenida originalmente algún valor no nulo y luego adquiere un NULL, así:
lnTotal = 100
luVal = 10
? TYPE( "luVal" ) && Type = "N"
luVal = IIF( luVal > 20, luVal, NULL ) 
? TYPE( "luVal" ) && Value = NULL pero Type = "N"
lnTotal = lnTotal + luVal && lnTotal = NULL - ni siquiera 100!!!
¿Por qué es malo? Considere qué podría ocurrir si va a utilizar código que acumula valores dentro de un lazo (por ejemplo un SCAN) y un registro contiene un NULL. No sólo el total se pierde, sino que no va a recuperar los valores, porque tan pronto como alcance un valor nulo en el acumulador quedará NULL y todas las sumas subsecuentes van a resultar simplemente NULL. Puede que no reciba un error; pero no va a obtener el resultado deseado.

VARTYPE() tiene un comportamiento definitivamente mejor en este tema. De forma predeterminada devuelve un tipo de "X" si el valor verificado contiene NULL, sin embargo, al pasar el segundo parámetro como .T. puede obtener el tipo de dato original (en otras palabras, imita el comportamiento de TYPE()).

En la práctica, la mejor forma de controlar los nulos es verificarlos explícitamente empleando la función ISNULL() (que devuelve .T. si el valor verificado es nulo) o envolver expresiones que pueden contener nulos dentro de NVL(), de tal forma que cada valor nulo se reemplaza con un valor que sea más fácil de controlar. (De hecho, fue una omisión de la función NVL() lo que me ocasionó problemas esta semana y por tanto provocó este escrito.)

NVL() es simplemente una función que sustituye un valor especificado cuando se encuentra un NULL, así:
luVal = NULL
? NVL( luVal, 0 ) && Devuelve 0, no NULL!
Afortunadamente PUEDE utilizar funciones tales como SUM() y las funciones CALCULATE, o un SQL SUM(), incluso cuando hay valores NULL. Visual FoxPro es lo suficientemente inteligente como para ignorar esos valores - y esto incluso es uno de los beneficios de los valores NULL, al calcular promedios y contar registros - los NULLs se ignoran y no afectan el resultado.

Luego de haber dicho esto, ¿cuál es mi problema? Olvidé que cuando estamos concatenando cadenas recortadas (trimmed) de caracteres, uno de los cuales contiene un NULL, el resultado es NULL. La situación que tuve fue al llamar a un procedimiento almacenado en SQL Server que devolvía un valor en un cursor. Se suponía que el valor era una cadena que contenía hasta 6 caracteres espacios. Esto fue recortado, y provocó que se generara un nombre de archivo al concatenarlo con el ID de usuario que había iniciado sesión. Era algo así:
lcNextSeq = ALLTRIM( cur_result.cNextSeq ) && Este campo era NULL
lcFileName = lcUserID + lcNextSeq
El problema surgió cuando el procedimiento almacenado devolvió NULL, debido a que la operación de inserción previa había fallado y no tenía forma de atrapar el resultado en una función NVL(). No generaba un error; pero el resultado era NULL. Ahora, yo pienso que esto es un bug (quizás se introdujo en el producto desde VFP 6.0 al menos). Este código muestra por qué:
lcVal = ALLTRIM(NULL)
? lcVal && .NULL.
luVal = NULL
? luVal && .NULL.
? "Test" + lcVal && .NULL.
? "Test" + luVal && OPERATOR/OPERAND MISMATCH!!!!
Después de todo, si hay un error cuando una cadena de caracteres se concatena con una que contenga un NULL, entonces, simplemente aplicando una función ALLTRIM() debería no permitir que la operación se ejecute sin devolver error.

La moraleja de esta historia es, por supuesto, ¡ Controle siempre sus NULLs !

Incidentalmente puede estar pensando que como sólo utiliza datos VFP no se admiten nulos en sus tablas, este es el tipo de cosas que no le va a ocurrir. Está es lo cierto, mientras no utilice ni un JOIN en una consulta SQL. Recuerde que en ese tipo de consultas se devuelve un valor NULL como resultado si no hay coincidencia en los registros - independientemente de si sus tablas admiten nulos o no.

9 de julio de 2014

Mas sobre el envio de mensajes de correo electrónico desde Visual FoxPro

Existen muchas herramientas y formas de enviar mensajes de correo electrónico desde Visual FoxPro, y seguramente por ello, existen muchos artículos y códigos escritos sobre este tema. La razón de escribir mas de los mismo, es comentarles sobre mi experiencia y elección personal, de la herramienta que utilizo actualmente, la cual me satisface y cubre todas mis necesidades. Estoy convencido que lo mismo le sucede a muchos desarrolladores y espero que al terminar de leer estas líneas, esto ayude a muchos mas.

The winner is ...

Como indico arriba, esto es "mi experiencia y elección personal", obviamente no digo que sea "la mejor", ni digo tampoco "la peor", por lo tanto no entraré en comparación con otras herramientas, solo hablaré de CDO.

¿Qué es CDO?

CDO (Collaboration Data Objects) es un componente COM (Component Object Model) que simplifica la escritura de código para crear y manipular mensajes de Internet y es parte integrante (Cdosys.dll) de los sistemas operativos Windows 2000 y superiores, siendo esta la primer gran ventaja, ya que no necesitamos descargar, comprar, ni licenciar, ninguna otra herramienta extra. CDO no necesita que tengamos un servidor SMTP local, solo necesita acceso a la Web, a algún servidor SMTP que nos permita enviar los mensajes de correo electrónico.

Usando VFP

Otra ventaja adicional, es que se puede utilizar CDO con lenguajes que soporten COM y Automation, y Visual FoxPro lo soporta. A continuación un sencillo código para enviar un mensaje de correo electrónico con CDO desde VFP. Recuerde configurar correctamente el nombre del servidor SMTP, el puerto SMTP, el nombre de usuario y contraseña.
loCfg = CREATEOBJECT("CDO.Configuration")
WITH loCfg.Fields
  .Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "smtp.mail.com"
  .Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
  .Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
  .Item("http://schemas.microsoft.com/cdo/configuration/sendusername") = "user@mail.com"
  .Item("http://schemas.microsoft.com/cdo/configuration/sendpassword") = "password"
  .Update
ENDWITH

loMsg = CREATEOBJECT ("CDO.Message")
WITH loMsg
  .Configuration = loCfg
  .From = "user@mail.com"
  .To = "user1@mail.com.ar"
  .Subject = "Prueba desde VFP"
  .TextBody = "Este es un mensaje de prueba con CDO desde Visual FoxPro."
  .Send()
ENDWITH

Adjuntando archivos en nuestros mensajes

Para enviar archivos adjuntos en un mensaje de correo electrónico, solo debemos llamar al método AddAttachment() del objeto Message, tantas veces como archivos deseamos adjuntar, con la ruta y el nombre del archivo como parámetros:
WITH loMsg
 .AddAttachment("C:\Imagenes\Foto1.jpg")
 .AddAttachment("C:\Imagenes\Foto2.jpg")
 .AddAttachment("C:\Imagenes\Foto3.jpg")
ENDWITH

Autenficación y cifrado SSL

Algunos servidores SMTP, necesitan autentificación y cifrado SSL para iniciar sesión. Para ello debemos configurar las propiedadades smtpauthenticate y smtpusessl del objeto Configuration con el valor .T..
WITH loCfg.Fields
  .Item("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate") = .T.
  .Item("http://schemas.microsoft.com/cdo/configuration/smtpusessl") = .T.
ENDWITH

Mensajes a múltiples destinatarios

Para enviar el mensaje de correo a mas de un destinatario, solo debemos separar las direcciones de correos electrónicos, con el caracter "," (coma) en la propiedad To. También podemos agregar una copia para otro destinatario, como así también una copia oculta, configurando las propiedades Cc y Bcc respectivamente.
WITH loMsg
 .To = "user1@mail.com, user2@mail.com"
 .Cc = "user3@mail.com"
 .Bcc = "user4@mail.com"
ENDWITH
Podemos hacer que aparezca el nombre completo del remitente y/o del destinatario, anteponiendo el nombre a la dirección de correo electrónico, y encerrando ésta última entre "<" y ">"
WITH loMsg
 .From = "Jose Fox <jose@fox.org>"
 .To = "Usuario Uno <user1@mail.com>, Usuario Dos <user2@mail.com>"
ENDWITH

Mensajes con formato HTML

Con CDO muy fácilmente se puede enviar mensajes de correo electrónico con formato HTML, solamente configurando la propiedad HTMLBody con código HTML válido, en lugar de configurar la propiedad TextBody que es válida para los mensajes con texto sin formato.
WITH loMsg
  .HTMLBody = "<p>Este es un texto HTML con <b>negritas</b> o con <i>cursivas</i>.</p>"
ENDWITH
Esta propiedad la podemos configurar con el contenido de un archivo HTML con la función FILETOSTR() como se muestra a continuación.
WITH loMsg
  .HTMLBody = FILETOSTR("C:\Archivo.htm")
ENDWITH
Mas adelante veremos otra manera de dar formato HTML a un mensaje de correo electrónico, a partir de un archivo HTML ubicado en el disco local o la Web.


12 de junio de 2014

Nueva Actualización del archivo de Ayuda para VFP 9 SP2 en VFPX

Se ha actualizado el proyecto oficial "VFP 9 SP2 Help File" (Archivo de ayuda para VFP 9 SP2) en VFPX.

Este proyecto se creó cuando el Equipo Visual FoxPro de Microsoft cedió los derechos del código fuente del Archivo de Ayuda para VFP 9 SP2 y permitió su modificación por parte de la Comunidad de Visual FoxPro.

La última actualización del Archivo de ayuda para VFP 9 SP2 está disponible en http://vfpx.codeplex.com/releases/view/23319

VFPX

¿Cuáles son los objetivos de este proyecto?
  1. Proporcionar a la comunidad VFP el Archivo de Ayuda para VFP 9 SP2 corregido. Las correcciones incluidas en el archivo son: el índice, los hipervínculos, y los estilos.
  2. Permitir a la Comunidad VFP mejorar aún más el Archivo de Ayuda, añadiendo el contenido faltante para VFP 9 SP2 y Sedna, y haciendo las correcciones necesarias a los ejemplos.
La página del proyecto es:

-- VFP9 SP2 Help File --
http://vfpx.codeplex.com/wikipage?title=VFP%209%20SP2%20Help%20File

1 de junio de 2014

Como saber si MySQL esta instalado en la PC

Autor: Roberto Reátegui Kanashiro

Rutinas para saber si MySQL esta instalado y corriendo, también para saber en que directorio esta instalado
#DEFINE SC_MANAGER_CONNECT          0x0001
#DEFINE GENERIC_READ                0x80000000
#DEFINE SERVICE_RUNNING             0x00000004
#DEFINE SERVICE_QUERY_CONFIG        0x0001
#DEFINE ERROR_INSUFFICIENT_BUFFER   0x7A

Clear
? "MySQL está Instaldo:", IIf(isServiceInstalled("MySQL"), "SI", "NO")
? "MySQL está Corriendo:", IIf(isServiceRunning("MySQL"), "SI", "NO")
? "Donde se encuentra:", GetServicePath("MySQL")

Function isServiceInstalled(tcServiceName)
Local llResult, lnManager, lnService
  llResult = .F.
  Declare Integer OpenSCManager In Advapi32.dll Integer, Integer, Integer
  Declare Integer OpenService In Advapi32.dll Integer, String, Integer
  Declare Integer CloseServiceHandle In Advapi32.dll Integer
  lnManager = OpenSCManager(0, 0, SC_MANAGER_CONNECT)
  If lnManager <> 0
     lnService = OpenService(lnManager, tcServiceName, GENERIC_READ)
     If lnService <> 0
        CloseServiceHandle(lnService)
        llResult = .T.
     EndIf
     CloseServiceHandle(lnManager)
  EndIf
  Clear Dlls "OpenSCManager", "OpenService", "CloseServiceHandle"
  Return llResult
EndFunc

Function isServiceRunning(tcServiceName)
Local llResult, lnManager, lnService, lcBuffer
   Declare Integer OpenSCManager In Advapi32.dll Integer, Integer, Integer
   Declare Integer OpenService In Advapi32.dll Integer, String, Integer
   Declare Integer CloseServiceHandle In Advapi32.dll Integer
   Declare Integer QueryServiceStatus In Advapi32.dll Integer, String
   llResult = .F.
   lnManager = OpenSCManager(0, 0, SC_MANAGER_CONNECT)
   If lnManager <> 0
      lnService = OpenService(lnManager, tcServiceName, GENERIC_READ)
      If lnService <> 0 then
         lcBuffer = Space(1024)
         If QueryServiceStatus(lnService, @lcBuffer) <> 0
            llResult = CToBin(Substr(lcBuffer, 5, 4), "4RS") = SERVICE_RUNNING
         EndIf
         CloseServiceHandle(lnService)
     EndIf
     CloseServiceHandle(lnManager)
   EndIf
   Clear Dlls "OpenSCManager", "OpenService", "QueryServiceStatus", "CloseServiceHandle"
   Return llResult
EndFunc

Function GetServicePath(tcServiceName)
Local lcResult, lnManager, lnService, lcBuffer, lnSize
   Declare Integer OpenSCManager In Advapi32.dll Integer, Integer, Integer
   Declare Integer OpenService In Advapi32.dll Integer, String, Integer
   Declare Integer CloseServiceHandle In Advapi32.dll Integer
   Declare Integer QueryServiceConfig In Advapi32.dll Integer, String@, Integer, Integer@
   Declare Integer GetLastError In Kernel32.dll
   lcResult = ""
   lnManager = OpenSCManager(0, 0, SC_MANAGER_CONNECT)
   If lnManager <> 0
      lnService = OpenService(lnManager, tcServiceName, SERVICE_QUERY_CONFIG)
      If lnService <> 0 then
         lcBuffer = "" 
         lnSize = 0
         If QueryServiceConfig(lnService, @lcBuffer, 0, @lnSize) = 0 And GetLastError() = ERROR_INSUFFICIENT_BUFFER
            lcBuffer = Space(lnSize)
            If QueryServiceConfig(lnService, @lcBuffer, lnSize, 2) = 1
               lcResult = JustPath(Substr(lcBuffer, 37, AtC("--defaults", lcBuffer) - 37))
            EndIf
         EndIf
         CloseServiceHandle(lnService)
     EndIf
     CloseServiceHandle(lnManager)
   EndIf
   Clear Dlls "OpenSCManager", "OpenService", "QueryServiceConfig", "CloseServiceHandle"
   Return lcResult
EndFunc

2 de mayo de 2014

Búsquedas mas claras

Código publicado en el Blog del filipino Jun Tangunan con el cual podemos realizar búsquedas mas claras en nuestras grillas.

A medida que escribimos un texto para buscar, se filtra y se colorea el texto coincidente en los cada registro de la grilla. Este efecto se logra con las propiedades DynamicForeColor y DynamicFontBold de cada columna



LOCAL oForm
oForm = NEWOBJECT("Form1")
oForm.SHOW
READ EVENTS
RETURN

DEFINE CLASS Form1 AS FORM
  HEIGHT = 390
  WIDTH = 770
  AUTOCENTER = .T.
  CAPTION = "Resultados de búsquedas claros"
  SHOWTIPS = .T.
  _cSearch = ""

  ADD OBJECT grid1 AS GRID WITH ;
    GRIDLINES = 0, HEIGHT = 328, LEFT = 10, TOP = 50, WIDTH = 750, ;
    GRIDLINES = 3, DELETEMARK = .F., GRIDLINECOLOR = RGB(192,192,192) ,;
    FONTNAME = "Tahoma", FONTSIZE = 8, READONLY = .T., ANCHOR = 15

  ADD OBJECT label1 AS LABEL WITH ;
    TOP = 15, LEFT = 10, ;
    CAPTION = "Buscar", BACKSTYLE = 0

  ADD OBJECT Text1 AS TEXTBOX WITH ;
    TOP = 10,LEFT = 55, HEIGHT = 23,WIDTH = 300, VALUE = "", ;
    TOOLTIPTEXT = "Comience a escribir para buscar los registros coincidentes. " + ;
    "Doble clic para borrar la búsqueda"

  PROCEDURE LOAD
    SET TALK OFF
    SET SAFETY OFF
    CLOSE DATABASES ALL
    SELECT company, contact, TITLE ;
      FROM (HOME(2)+"data\customer") ;
      WHERE .F. ;
      INTO CURSOR junk1 READWRITE
  ENDPROC

  PROCEDURE grid1.INIT
    WITH THIS
      .RECORDSOURCETYPE = 6
      .RECORDSOURCE = "junk1"
      .Column1.DYNAMICFORECOLOR = "IIF(thisform._cSearch $ UPPER(company),RGB(220,0,0),RGB(0,0,0))"
      .Column2.DYNAMICFORECOLOR = "IIF(thisform._cSearch $ UPPER(contact),RGB(220,0,0),RGB(0,0,0))"
      .Column3.DYNAMICFORECOLOR = "IIF(thisform._cSearch $ UPPER(title),RGB(220,0,0),RGB(0,0,0))"
      .Column1.DYNAMICFONTBOLD = "IIF(thisform._cSearch $ UPPER(company),.T.,.F.)"
      .Column2.DYNAMICFONTBOLD = "IIF(thisform._cSearch $ UPPER(contact),.T.,.F.)"
      .Column3.DYNAMICFONTBOLD = "IIF(thisform._cSearch $ UPPER(title),.T.,.F.)"
    ENDWITH
  ENDPROC

  PROCEDURE Text1.DBLCLICK
    THIS.VALUE = ""
    ZAP IN junk1
    THISFORM.grid1.REFRESH
  ENDPROC

  PROCEDURE Text1.INTERACTIVECHANGE
    LOCAL lcSearch
    lcSearch = UPPER(ALLTRIM(THIS.VALUE))
    THISFORM._cSearch = m.lcSearch

    SELECT company, contact, TITLE ;
      FROM HOME(2)+"data\customer" ;
      WHERE UPPER(cust_id+company+contact+TITLE) LIKE "%"+m.lcSearch+"%" ;
      INTO CURSOR junk2 NOFILTER

    SELECT junk1
    ZAP IN junk1
    APPEND FROM DBF("Junk2")
    GO TOP
    THISFORM.grid1.REFRESH
  ENDPROC

  PROCEDURE DESTROY
    CLEAR EVENTS
  ENDPROC
ENDDEFINE

Fuente: Sandstorm's Blog (Jun Tangunan, Cabanatuan, Philippines) 20 jun 2012

13 de abril de 2014

Controlar múltiples monitores

Artículo original: Handling Multiple Monitors
http://doughennig.blogspot.com/2007/04/handling-multiple-monitors.html
Autor: Doug Hennig
Traducido por: Ana María Bisbé York

Una de las cosas que hice con mi nuevo portátil fue configurarle múltiples monitores. Algunos desarrolladores que conozco y respeto lo han hecho desde hace años, así que supongo que ya sea hora de que yo también lo intente. No hay nada que decir, me gusta. Tengo generalmente el Examinador y explorador de Windows abiertos en el segundo monitor y mantengo el monitor primario para aquellas cosas que hago a lo largo del día (principalmente VFP y Outlook). Soy más productivo ahora que no tengo que moverme entre la pila de ventanas ni moverme o redimensionar constantemente una ventana.

Sin embargo, una de las cosas que he descubierto hoy, es que algunas de mis aplicaciones no respetan el segundo monitor. Por ejemplo, tengo una clase llamada SFPersistentForm que arrastro a la mayoría de mis formularios. Esta clase guarda el tamaño y posición del formulario cuando se cierra y lo restaura cuando se abre nuevamente, dando al usuario la experiencia que el espera al trabajar con este formulario. Sin embargo, he descubierto que si abro el formulario y lo muevo al segundo monitor, luego llo cierro, y cuando lo reabro el formulario se muestra sin embargo, en el primer monitor (es un formulario con Desktop = .T., por tanto puede existir fuera de la aplicación).

Rápidamente encontré la razón: el código intentaba evitar la situación en la que el formulario pudiera abrirse fuera de los límites de la pantalla, haciéndolo invisible. El siguiente código controla esto:
Thisform.Width = min(max(Thisform.Width, 0, Thisform.MinWidth), ;
  _screen.Width)
Thisform.Height = min(max(Thisform.Height, 0, Thisform.MinHeight), ;
  _screen.Height)
Thisform.Left = min(max(Thisform.Left, 0), _screen.Width - 50)
Thisform.Top = min(max(Thisform.Top, 0), _screen.Height - 50)
("-50"  se utiliza para garantizar que el formulario no comience exactamente  la derecha del borde del monitor, haciéndolo esencialmente invisible.)

Existen dos problemas con este código. Primero, la confiabilidad en _SCREEN asume que el formulario existe con _SCREEN, que es un formulario de nivel superior o uno con Desktop = .T., que no es necesariamente el caso. Segundo, si se maximiza _SCREEN, solo cabe en el monitor actual. Si el formulario está en el otro monitor, las dimensiones de _SCREEN son irrelevantes.

Inicialmente cambié el código:
if Thisform.Desktop or Thisform.ShowWindow = 2
  lnWidth = sysmetric(1)
  lnHeight = sysmetric(2)
else
  lnWidth = _screen.Width
  lnHeight = _screen.Height
endif Thisform.Desktop ...
Thisform.Width = min(max(Thisform.Width, 0, Thisform.MinWidth), ;
  lnWidth)
Thisform.Height = min(max(Thisform.Height, 0, Thisform.MinHeight), ;
  lnHeight)
Thisform.Left = min(max(Thisform.Left, 0), lnWidth - 50)
Thisform.Top = min(max(Thisform.Top, 0), lnHeight - 50)
Sin embargo, SYSMETRIC() sólo devuelve valores para el monitor primario. Entonces, cambié las sentencias a:
declare integer GetSystemMetrics in Win32API integer
#define SM_CXVIRTUALSCREEN 78 && Ancho Virtual 
#define SM_CYVIRTUALSCREEN 79 && Altura Virtual 
lnWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN)
lnHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN)
Ahora el formulario se reabre en la misma posición exacta, incluyendo el monitor donde estaba antes.

8 de abril de 2014

Ubicacion de la carpeta System32, Font, y Programs File

Este codigo permite saber la ubicacion fisica de las carpetas System32, Font, y Programs File.

**** LLevado De VISUAL Basic a lenguaje FOXPRO por In_21
#DEFINE CSIDL_FONTS 20 && Carpeta de Fuentes
#DEFINE CSIDL_PROGRAM_FILES 0x0026 && Carpeta de Programas
#DEFINE CSIDL_SYSTEM 37&& Carpeta de Sistema, System32
* La Api
DECLARE INTEGER SHGetSpecialFolderPath IN shell32.DLL ;
   LONG HWND , STRING @sPath ,LONG Folder
#DEFINE MAX_PATH 255 && Maximo de caracteres del Buffer

LOCAL strLocation,La_Ruta
* Inicializacion
strLocation = ""
La_Ruta = ""
* Aqui determinas cual de las carpetas quieres buscar, 
*   en este caso buscamos la carpeta para programas
lngCSIDL = CSIDL_PROGRAM_FILES 
* Rellenamos la variable con el maximo de espacios (255)
strLocation = SPACE(MAX_PATH) 
* Llamada a la API de Windows, pasando la varibale strLocation por valor
La_Ruta = ObtenerCarpetaEspecial(@strLocation, lngCSIDL) 

* A pantalla para saber la ruta
MESSAGEBOX(La_Ruta) 

FUNCTION ObtenerCarpetaEspecial()
  PARAMETERS strParamLocation, lngParamCSIDL
  LOCAL La_Carpeta
  =SHGetSpecialFolderPath(0, @strParamLocation, lngParamCSIDL)
  *  Quitarle el caracter de Nulo al final
  La_Carpeta = SUBSTR(RTRIM(strParamLocation),1,LEN(RTRIM(strParamLocation))-1)
  RETURN La_Carpeta
ENDFUNC

28 de febrero de 2014

NotePad++ (NotePad plus) y Visual FoxPro

"VISUAL FOXPRO" EN NOTEPAD++

NotePad++ permite que el usuario defina las características de coloreado y envoltura para un lenguaje no definido de forma nativa, en este articulo proporciono una configuración "sugerida" para el leguaje de "Visual FoxPro".

Notepad++ es un editor de texto GPL con soporte para varios lenguajes de programación que incluye opciones avanzadas para desarrolladores.

Este editor incluye características como coloreado y envoltura de sintaxis, pestañas, resaltado de paréntesis e indentación, grabación y reproducción de macros y soporte de extensiones.

Entre los lenguajes de programación que soporta están Ada, ASP, ASM Ensamblador (x86), ASM Ensamblador (Z80), AutoIt, Batch, C, C#, C++, Caml, CMake, COBOL, CSS, D, Diff, Flash ActionScript, Fortran, Gui4Cli, Haskell, HTML, INNO, Java, JavaScript, JSP, KiXtart, Lisp, Lua, Makefile, MATLAB, MS INI (archivo), NSIS, Objective-C, Pascal, Perl, PHP, PostScript, PowerShell, Properties, Python, R, RC (fichero de recurso), Ruby, Shell, Scheme, Smalltalk , SQL, Tcl, TeX, VB, VHDL, Verilog, XML, YAML.

Lamentablemente de forma nativa no incluye "Visual FoxPro", sin embargo se puede personalizar de tal forma que soporte el lenguaje, a continuación se explica como hacerlo.

INSTALE NOTEPAD++

Ingrese a la pagina del creador http://notepad-plus-plus.org, descargue e instale la ultima versión.

"VISUAL FOXPRO" EN NOTEPAD++

NotePad++ permite que el usuario defina las características de coloreado y envoltura para un lenguaje no definido de forma nativa, en este articulo proporciono una configuración "sugerida" para el leguaje de "Visual FoxPro".

Descargue los archivos

Definicion de Visual FoxPro (UDL)
Opcionales
Funciones/Procedimientos en programa (functionList.xml)
NotePad como editor oredeterminado de programas VFP (defaultnotpadeditvfpprg.reg)
Definir el lenguaje
  1. Ingrese a "NotePadd++"
  2. En el menú principal ingrese a "Lenguaje -> Definir idioma"
  3. En la ventana que desplegada presione el botón "Importar.." y seleccione el archivo "userDefineLang-VisualFoxPro-Jgoh.xml" en la ubicación en donde se descargo
Con estos pasos ya se definió el lenguaje y cada uno de sus atributos. Cierre NotePad++ para asegurar que tome el nuevo lenguaje.

Lista de funciones

NotePad tiene la opción "Lista de funciones" (En el menú principal ingrese a "Vista -> Lista de funciones") la cual es útil cuando se abre un programa que contiene definiciones de Funciones o Procedimientos. Sin embargo como NotePad++ de forma nativa no tiene soporte para VFP es necesario agregar la configuración para dicha característica
  1. Ingrese al directorio %APPDATA%\notepad++\
  2. Edite el archivo functionList.xml con NotePad++
  3. En la sección <associationMap> adicione la siguiente linea al final:
    <association userDefinedLangName="Visual FoxPro" id="vfp_syntax"/>
  4. En la sección <parsers> adicione al final:
  5.    <parser id="vfp_syntax" displayName="VFP Classes & Functions" commentExpr="(t*!*)">
        <classRange
         mainExpr="^[t ]*((define class)[s]+)[w:]+[s]+as[s]+([w:]+)"
         closeSymbole = "^[t ]*enddefine"
         displayMode="node">
         <className>
          <nameExpr expr="[w:]+[s]+as[s]+([w:]+)"/>
         </className>
         <function
          mainExpr="^[t ]*((procedure|proc|function|func)[s]+)[w:]+[s]*[ (,)w]*">
          <functionName>
           <funcNameExpr expr="((procedure|proc|function|func)[s]+)[w:]+[s]*[ (,)w]*"/>
           <funcNameExpr expr="[s]+[w:]+[s]*[ (,)w]*"/>
           <funcNameExpr expr="[w:]+[s]*[ (,)w]*"/>
          </functionName>
         </function>
        </classRange>
    
        <function
         mainExpr="^[t ]*((procedure|proc|function|func)[s]+)[w:]+[s]*[ (,)w]*"
         displayMode="$functionName">
         <functionName>
          <funcNameExpr expr="((procedure|proc|function|func)[s]+)[w:]+[s]*[ (,)w]*"/>
          <funcNameExpr expr="[s]+[w:]+[s]*[ (,)w]*"/>
          <funcNameExpr expr="[w:]+[s]*[ (,)w]*"/>
         </functionName>
        </function>
       </parser>
    
Guarde el archivo.

NOTA: Si lo desea puede remplazar el archivo local functionList.xml por el que descargo

NotePad++ como editor predeterminado de programas desde Visual FoxPro

Usted puede predeterminar como editor de programas (*.prg) a NotePad desde la interfaz de FoxPro, para eso debemos crear el registro de windows la siguiente entrada
[HKEY_CURRENT_USER\Software\Microsoft\VisualFoxPro\9.0\Options]
"TEDIT"="/N C:\Program Files (x86)\Notepad++\notepad++.exe"
NOTA: Si lo desea puede combinar el archivo defaultnotpadeditvfpprg.reg a su registro de windows presionando el botón derecho del ratón sobre el y seleccionando la opción "Combinar". recuerde que debe editar el archivo y cambiar la ruta del programa dependiendo de donde instalo NotePad.

SCREENSHOT


FUENTES
Jose Guillermo Ortiz Hernandez