1 de noviembre de 2008

Soluciones SSH con FoxPro

Hace poco se presentó el desafío de conectarme a través de Hamachi a un servidor, y por esa misma conexión crear una aplicación que pueda correr a través de SSH.

Por supuesto lo primero que pensé es hacerlo en VFP, pero la conexión SSH en Windows no permite levantar interfaces gráficas, así que las solución fue FPD (FoxPro para DOS) y VFP (Visual FoxPro).

Aquí les presento la solución.

Por defecto FPD no provee un medio para hacer conexión a SQL, hay una librería FLL que anda dando vuelta por ahí, pero con SQL Express 2005 es un verdadero problema. La solución, como siempre en FoxPro, es mas sencilla de lo que parece.

Lo primero que hice es crear una App en VFP que haga las consultas a SQL, algo así:
PARAMETERS lcinifile,ltabla
_screen.Visible= .F. 
SET RESOURCE  OFF
PUBLIC lc_con
SET ECHO OFF
SET TALK OFF
SET SAFETY OFF 
SET NOTIFY OFF 
SET default TO (JUSTPATH(SYS(16,0)))
lcpath = JUSTPATH(SYS(16,0))
lcpathini = lcpath + "\" + lcinifile
SQLSETPROP(0,"DispLogin",3)
lc_con=SQLCONNECT("localdb","miuser","mipass")
if m.lc_con < 0
  nopc = MESSAGEBOX("No se pudo conectar via DSN, talvez ODBC no este configurado",0+48,"Error")
ELSE
  lcselect=LeerIni('consulta','lselect',lcpathini) 
  lctipo=leerini('consulta','tipo',lcpathini)
  DO case
    CASE ALLTRIM(UPPER(lctipo)) = "SELECT"
      m.nresult = SQLEXEC(m.lc_con,lcselect,ltabla)
      SELECT (ltabla)
      ltabla = JUSTPATH(SYS(16,0)) + "\" ltabla + ".dbf"
      COPY TO (ltabla) TYPE FOX2X 
    CASE ALLTRIM(UPPER(lctipo)) = "INSERT"
      m.nresult = SQLEXEC(m.lc_con,lcselect)
    CASE ALLTRIM(UPPER(lctipo)) = "UPDATE"
      m.nresult = SQLEXEC(m.lc_con,lcselect)
    CASE ALLTRIM(UPPER(lctipo)) = "DELETE"
      m.nresult = SQLEXEC(m.lc_con,lcselect)
    CASE ALLTRIM(UPPER(LCTIPO)) = "STORE"
      mc_select(lctipo,lcselect,ltabla)
      SELECT (ltabla)
      ltabla = JUSTPATH(SYS(16,0)) + "\" ltabla + ".dbf"
      COPY TO (ltabla) TYPE FOX2X 
  ENDCASE 
ENDIF 
El procedimiento LeerIni lo pueden encontrar en este mismo portal. Es un procedimiento para leer archivos .INI y el mc_select no es mas que un procedimiento donde esta la variable m.nresult simplificado para pasarles parámetros.

Bueno ... esta aplicación recibe dos parámetros lcIniFile y lTabla, donde lcIniFile es el archivo .INI que debe tener las siguientes claves este archivo .INI lo arma FPD) por ej:
[CONSULTA]
LSELECT=select * from Vista_Vehiculo_Grupo ORDER BY codigo_interno
tipo=select
Y lTabla es el nombre de tabla que quieren crear.

En una App en FPD 2.6 hay que armar el siguiente procedimiento en un PRG con nombre mcSelect (usen el que mas les guste)
 parameter lctipo,lcselect,lccursor,lcini
 lcini=lcini+".ini"
 SET TEXTMERGE ON
 SET TEXTMERGE TO (lcini) NOSHOW
 \[CONSULTA]
 \LSELECT=<<LCSELECT>>
 \tipo=<<lctipo>>
 SET TEXTMERGE OFF
 SET TEXTMERGE TO
 lcmacro1="run odbcvfp.exe "+lcini+" "+lcCursor
 &lcmacro1
Esta rutina recibe lctipo,lcselect,lccursor,lcini

Donde:
lctipo es el tipo de Sql Statement que desean hacer SELECT, INSERT,UPDATE,DELETE o EXEC en este ultimo uso STORE.

lcselect es el Sql Statement que le pasan.

lccursor es la tabla que desean obtener

lcini el archivo .INI que desean armar para que odbcvfp ejecute la consulta

Una vez que recibe los parametros arma un archivo .INI ejecuta odbcvfp (visto mas arriba) que arma el cursor que necesitamos o simplemente ejecuta la consulta

Eso es todo ... ahora para que FPD pueda hacer una consulta a un servidor SQL habria que hacer asi
lc_select1="select * from personal order by nombre"   && armo la consulta
do mcselect with "insert",lc_insert2,"tPer","bal5"   && ejecuto la consulta
*esto arma la tabla tPer 
use tPer in 0
select tper
browse
Y listo ... FPD ahora puede hacer consultas a un cualquier servidor a través de ODBC!!! desde acá usen su imaginación !!!


Saludos

Carlos Emilio

23 de octubre de 2008

Función EsMayusculas()

Función para determinar si una cadena pasada está en mayúsculas o no.

Hoy me he visto en la necesidad de saber si las cadenas de una tabla (había 9000) estaban en mayúsculas o no.

Tenemos una tabla para traducción de idiomas, en la columna de la izquierda el TextoATraducir, y en la derecha el TextoTraducido. Hemos empleado google, para hacer una traducción inicial (Bastante decente por cierto), pero al traducir, había frases en mayúsculas en el TextoATraducir, que al traducirlas, las ha dejado en minúsculas.

Esto, al visualizarse en la aplicación no se ve bien, pues por diseño, todas nuestras etiquetas están en mayúscula.

Así que necesitaba una función para saber si la frase entera está en mayúscula o no. Pero Fox no la tiene.

Así que he diseñado una función que me ha sacado del aprieto. No se si es muy perfecta, pero a mi me ha resultado muy útil.

Un Saludo.

Fernando Puyuelo
*---------------------------------------------------------
* Método:
*  FUNCTION EsMayusculas
* Parameters:
*  tcCadena: E : Cadena a verificar si está en Mayúsculas
* Objetivo:
*  Revisar si una cadena pasada por parametro está en Mayúsculas o no.
* Ejemplo:
*  ? EsMayusculas("ESTO ES UNA FRASE EN MAYÚSCULAS") -> .T.
*  ? EsMayusculas("ESTO ES UNA FRASE no todo en MAYÚSCULAS") -> .F.
* Programador:
*  Fernando PUyuelo Ossorio
*  INFORMÁTICA BORSAN, S.L.
* Fecha de Terminación:
*  22/10/2008 11:12:24
* Modificaciones:
*---------------------------------------------------------
FUNCTION EsMayusculas
LPARAMETERS tcCadena

LOCAL i, llEsMayusculas
i = 0
llEsMayusculas = NULL

FOR i = 1 TO LEN(tcCadena)

 llLetra = SUBSTR(tcCadena,i,1)
 IF !EMPTY(llLetra)
  llEsMayusculas = ISUPPER(llLetra)
 ENDIF 

 IF !llEsMayusculas
  EXIT
 ENDIF
ENDFOR

IF ISNULL(llEsMayusculas)
 llEsMayusculas = .F.
ENDIF

RETURN llEsMayusculas
*FIN FUNCTION EsMayusculas --------------------------------------

20 de octubre de 2008

Liberado FoxCharts 1.0 Release Candidate

Ya esta disponible para descarga libre y gratuita la versión 1.0 Release Candidate de esta excelente clase de código abierto.

Gracias Cesar por todo tu esfuerzo para liberar esta versión de esta excelente clase para toda la comunidad mundial de Visual FoxPro.

Pueden descargar FoxCharts 1.0 Release Candidate libremente de:

http://vfpx.codeplex.com/releases/view/18515

Los requisitos para utilizar FoxCharts son:

. Visual FoxPro 9.0
. Librería GdiPlusX de VFPX

Algunos ejemplos de la clase FoxCharts (http://vfpx.codeplex.com/wikipage?title=FoxCharts)







19 de octubre de 2008

ActiveVFP Liberado

Home of ActiveVFP - Foxpro on the World Wide Web

Ya esta para descargar gratuitamente este excelente framework para desarrollar fácilmente aplicaciones Web con Visual FoxPro.

Pueden descargar libremente la última versión de:

http://activevfp.codeplex.com/releases/view/53030

ActiveVFP es un proyecto de código abierto completamente gratuito que permite crear fácilmente aplicaciones Web con Visual FoxPro, ya que provee un framework fácil de usar para crear DLL multihilos con código VFP puro.

La página de este proyecto es:

http://activevfp.codeplex.com

30 de septiembre de 2008

Parches de seguridad para VFP (GDI+)

La semana pasada Microsoft liberó unos parches de seguridad para VFP 8 SP1, VFP 9 SP1 y VFP9 SP2, esto consiste en problemas detectados en librerías de GDI+ (y por lo mismo afecta a muchos otros productos de la compañía de Redmon).

Pueden encontrar más información aquí:

--- Security Updates for GDIPlus for VFP 8.0 and 9.0 ---
http://msdn.microsoft.com/en-us/vfoxpro/bb264582.aspx

Y descargar los parches de los siguientes sitios:

Espero esta información les sea de utilidad.

Espartaco Palma

13 de septiembre de 2008

Mensaje de error cuando instala la actualización de seguridad GDI+ para VFP

Artículo # 957599 de la Base de Conocimiento de Microsoft sobre un problema conocido luego de instalar la actualización de seguridad de GDI+ para Visual FoxPro donde aparece el siguente mensaje de error: "La característica que intenta utilizar está en un recurso de red que no está disponible".

Artículo original: Error message when you try to install an update for Visual FoxPro 8.0 or for Visual FoxPro 9.0: "The feature you are trying to use is on a network resource that is unavailable"
http://support.microsoft.com/kb/957599

Cuando usted instala una actualización para Microsoft Visual FoxPro 8.0 o para Microsoft Visual FoxPro 9.0, puede recibir un mensaje de error similar al siguiente:

La característica que intenta utilizar está en un recurso de red que no está disponible. Haga clic en Aceptar para volver a intentar, o ingrese en el cuadro de texto una ruta alternativa de la carpeta que contiene el paquete de instalación nombre_archivo.msi.

Para mas información sobre "MS08-052: Description of the security update for GDI+ for Visual FoxPro 9.0 Service Pack 2: September 9, 2008" mire el siguente artículo: http://support.microsoft.com/kb/955370

SE APLICA A
  • Microsoft Visual FoxPro 9.0 Professional Edition
  • Microsoft Visual FoxPro 8.0 Professional Edition

4 de agosto de 2008

Informes personalizados por el usuario

Estuve pensando como hacer para agrupar y mostrar los resultados de totales, etc, a pedido del usuario, sin necesidad de crear un informe por cada agrupación.

Entonces la solucion era mas facil de lo que imagine.

EL PROBLEMA:

Tengo una lista de datos en un Grid. Ahora el usuario hace click en los encabezados y este se ordena sobre esa columna.. necesito entonces que cada vez que el usuario hace ordena por una columna el reporte automáticamente agrupe sobre ese orden y totalice esa agrupación.

Este es el ejemplo:



Este es la acción del usuario, ordeno por chofer:



Ahora usando solo un reporte que este interprete la acción del usuario ordene y agrupe y tire los resultados:



LA SOLUCIÓN:

Es simple, crear una variable publica en mi caso frxhabilita... que se le pueda asignar un valor numérico cada vez que el usuario hace click en los encabezados para ordenar.. 1=en el encabezado fecha, 2=producto, 3 = vehículo, etc.

Una vez echo esto pasamos al reporte, en el datagrouping del reporte agrupar pero.. con esta expresión

iif(frxhabilita=4,alltrim(upper(Va_ventaarido.producto)),"")

Si frxhabilita=4, agrupe por producto sino no haga nada.

Les va a quedar algo asi:


Y en cada total indican en printwhen, frxhabilita = 4 ... o sea que solo muestre cuando frxhabilita = 4

Espero les sirva, a mi me solucionó un montón de problemas... sobre todo el estar haciendo un formulario por cada orden que pedía el usuario.

Saludos

Carlos Emilio

8 de julio de 2008

Estirar y mantener una imagen en la pantalla principal (_Screen)

Como vimos en artículos anteriores publicados en este Blog, la manera de poner una imagen de fondo en la pantalla principal de Visual FoxPro es agregar un objeto Image al objeto _Screen.
Esta imagen la podemos iniciar centrada con:

-- Centrar una imagen en la pantalla principal (_Screen) --
http://comunidadvfp.blogspot.com/2002/03/centrar-una-imagen-en-la-pantalla.html

Y mantenerla centrada, aun cuando cambiamos el tamaño de la ventana principal con:

-- Mantener centrada una imagen en la pantalla principal (_Screen) --
http://comunidadvfp.blogspot.com/2006/01/mantener-centrada-una-imagen-en-la.html

Ahora lo que deseamos es que la imagen de fondo, cubra toda la pantalla principal, y se ajuste automáticamente el tamaño cuando redimensionamos la ventana.

Para hacer esto tenemos al menos dos formas distintas obteniendo el mismo resultado buscado.
1. A partir de VFP 8.0, podemos utilizar la función BINDEVENT() para enlazar el método Resize del objeto _Screen, con un método personalizado en nuestro objeto Image.

_SCREEN.ADDOBJECT("oImagen","MiImagen")
WITH _Screen.oImagen
  lcPicture = GETPICT()
  .PICTURE = lcPicture
   .STRETCH = 2 && 1=Mantiene las proporciones, 2=Cubre todo
  *-- Solo si la imagen tiene transparencia
  * .BACKSTYLE = 0 && 0=Transparente, 1=Opaca
  .LEFT = 0
  .TOP = 0
  .WIDTH = _SCREEN.WIDTH
  .HEIGHT = _SCREEN.HEIGHT
  BINDEVENT(_SCREEN,"Resize",_SCREEN.oImagen,"MiResize")
  .VISIBLE = .T.
ENDWITH

DEFINE CLASS MiImagen AS IMAGE
  PROCEDURE MiResize
    WITH THIS
      .LEFT = 0
      .TOP = 0
      .WIDTH = _SCREEN.WIDTH
      .HEIGHT = _SCREEN.HEIGHT
    ENDWITH
  ENDPROC
  PROCEDURE DESTROY
    UNBINDEVENT(THIS)
  ENDPROC
ENDDEFINE

2. A partir de VFP 9.0 podemos utilizar solo la propiedad Anchor del objeto Image, sin utilizar la función BINDEVENT(), y sin la necesidad de tener una clase personalizada Image con un método que maneje su redimensionamiento.

_SCREEN.ADDOBJECT("oImagen","Image")
WITH _Screen.oImagen
  lcPicture = GETPICT()
  .PICTURE = lcPicture
   .STRETCH = 2 && 1=Mantiene las proporciones, 2=Cubre todo
  *-- Solo si la imagen tiene transparencia
  * .BACKSTYLE = 0 && 0=Transparente, 1=Opaca
  .LEFT = 0
  .TOP = 0
  .WIDTH = _SCREEN.WIDTH
  .HEIGHT = _SCREEN.HEIGHT
  .ANCHOR = 15
  .VISIBLE = .T.
ENDWITH

Hay tres cosas (solo dos si no se utiliza la propiedad Anchor) que debemos tener en cuenta para un mejor resultado visual:
  1. Según el tipo y proporciones de ancho/alto de la imagen original, hay que ver si es mejor mantener las poporciones de la imagen; o llenar toda la pantalla con la imagen sin mantener sus proporciones. Para ello debemos configurar la propiedad Stretch con el valor 1 si queremos mantener las proporciones de la imagen, o el valor 2 si queremos llenar toda la pantalla con la imagen.
  2. Si la imagen no posee transparencias, la propiedad BackStyle debe tener el valor 1, que es su valor por omisión. Solo si la imagen posee transparencias se debe configurar la propiedad BackStyle con el valor 0. De otra manera se obtienen resultados no deseados cuando se redimensiona la imagen
  3. En el caso de utilizar la propiedad Anchor, esta debe configurarse al final, luego de que se configuren las propiedades Left, Top, Height y Width de la imagen.
Hasta la próxima.

Luis María Guayán

23 de junio de 2008

Será bueno seguir programando en VFP ?

Recientemente en "Mundo Visual Foxpro" acabo de ver nuevamente la pregunta de si vale la pena seguir desarrollando en VFP. En lo personal pienso que sí y me tomé la libertad de responderle afirmativa al postulante de la cuestión. Se me ha ocurrido publicar aquí, en Portal Fox, también mi opinión, con mucho respeto a los colegas que podrían opinar totalmente lo contrario.

A quien pregunta entonces si aún vale la pena desarrollar en VFP, yo le respondo rotundamente que si porque:

1- Tu piensas permanecer vivo aún mas allá del 2,017 ¿no? Entonces tus aplicaciones en VFP 9 seguirán vivas mientras tu sigas vivo. Por ejemplo, yo aún tengo aplicaciones hechas en Foxpro para DOS 2.6 funcionando "de maravilla" en Windows Vista. Son aplicaciones sólidas, bien hechas, robustas, rápidas y mis clientes no tienen intención de migrarlas. Son aplicaciones que están operando desde 1,992 (16 años...!). Otro punto importante: yo dejo que mi cliente decida cuando migrar, NO LO PRESIONO NI CHANTAJEO para que lo haga; veo en el cliente un amigo y no "una minita de oro" a quien poderle estar exprimiendo a cada rato con cada cambio de sistema operativo que hace Microsoft.

2- Varios clientes "me han cortado" porque han querido moverse a otras plataformas que "supuestamente" son mucho mejores que VFP (Entiéndase Visual Studio con SQL Server). De entre los que me "habían cortado", tres han vuelto de regreso conmigo. La razón de que hayan regresado conmigo no fue porque .NET, Visual Studio o SQL Server sean malos. Sino porque tenían que moverse a SQL Server (un chorro de plata mas en licencias), depender de los cambios de versiones de Windows Server, y lo más importante, los programadores que les hablaron maravillas de .NET eran muy buenos en eso: "hablar de las bondades de .NET", pero como programadores en sí mismos, eran sumamente inexpertos, impacientes, imprecisos, inconstantes e incumplidos. No alcanzaron a llevar a término sus nuevas aplicaciones con sus flamantes nuevos lenguajes de programación.

Mis clientes primero se frustraron y aburrieron de tanta excusa en los retrasos de entrega del proyecto, los gastos cada vez mayores en herramientas de terceros (que para reportar ahora hay que comprar Crystal Reports...), etc. etc.

Luego de tanta frustración, incumplimiento, incomodidad, gastos extras no mencionados al principio y pobres avances con lo que consideraban "la octava maravilla", resolvieron mejor volver con su viejo pero confiable programador.

Mis clientes no volvieron conmigo porque yo les programara en VFP (a estas alturas no les importa en que les programe). Ellos volvieron conmigo porque en mis aplicaciones tenían, experiencia, constancia, presición, velocidad, resolución de problemas a toda prueba y ante todo cumplimiento con los ofrecimientos cotizados.

3- No es tu herramienta la que te trae éxito, es tu actitud frente a los problemas informáticos que se dan en el camino. Cuando hay un problema "x" en la implementación de una aplicación, eres de los que esperan que otros les digan como salir adelante o eres tu de los que encuentran la solución. Eres de los que "se limpian" en Windows y dicen: "Windows es así, no hay nada que podamos hacer al respecto". Eres de los que resuelven problemas o de los que buscan quien los resuelve por ti. Eres de los que descubren el verdadero mecanismo de un problema o de los que bajan de Internet "parches" o cualquier cosa que mediocremente disimule un problema. No digo con esto que no sea bueno "documentarse" e "investigar" en la Internet o Portal Fox en busca de soluciones (es también absurdo "re-inventar" innecesariamente la rueda), pero lo haces como última instancia o es lo primero que haces frente a un problema.

4- No importa que "tan poderoso" sea el lenguaje de programación que elijas, éste jamás podrá "Pensar por Ti", "Solucionar problemas por ti", "Encontrar mejores alternativas de diseño por tí", "Buscar clientes por ti", "Administrar mejor tu tiempo por tí", "Auto-Diseñarse por tí". Cualquier lenguaje de programación, por poderoso que sea, solo es una herramienta en manos de un hábil o un torpe ingeniero, será solo como un pincel en manos de un prodigioso o un mediocre artista.

Te recuerdo la célebre anécdota del pordiosero que tocaba desafinadamente su maltrecho violín en la salida de un teatro donde justamente en ese momento el famoso Paganini daba un concierto. A la salida, luego de haber escuchado al maestro, la multitud se alejaba del desafinado pordiosero como quien le huye a la peste, hasta que el mismo Paganini, le pidió al pordiosero "su mismo violín", lo afinó y tocó allí afuera con ese mugroso violín, un fragmento de la obra que acababa de tocar dentro del teatro. La multitud lo rodeó y escuchó con admiración y asombro al genial maestro Paganini mientras el pordiosero gritaba: "Ese es mi violín..."

A lo largo de tu profesión como programador verás verdaderas "obras maestras" hechas en VFP, Delphi, XBase++, Xailer, Clippper, Visual Flagship, MySQL con PHP, .NET, Visual Studio, SQL Server, Visual C, etc. etc. Y también verás "grandísimas porquerías" hechas con los mismos lenguajes de programación ya citados. Es cosa de la actitud mental del programador, no de la herramienta que este utiliza. Recuerda: "El hábito no hace al monje..." o dicho en nuestro lenguaje: "La herramienta de programación no hace al buen programador..."

Para cuando ya no sea posible usar mas VFP 9 porque ya no pueda funcionar en ningún sistema operativo, yo ya seré un anciano que de todos modos tendré que estar jubilado de la programación y dedicarme a alguna otra cosa. Si te pones a pensar que tengo corriendo programas luego de 16 años como el caso de Foxpro para DOS, haciendo una analogía, se podría decir que 16 años luego del fin del soporte para VFP 9 (2,016), lo cual sería allá por el 2,032 yo ya tendré 64 años, por lo que esperaría estarme dedicando a alguna otra cosa menos desgastante y estresante que la programación de aplicaciones informáticas (algo así como desactivar bombas terroristas, re-hacer como doble de riesgo los documentales del "cazador de cocodrilos", servir como sujeto de prueba para desarrollo de armas bacteriológicas...)

Si eres del tipo de programador que resuelve, que se responsabiliza (en vez de culpar al hardware, a Windows, a los otros, al usuario), que investiga, que escudriña, que encuentra soluciones; tu futuro con VFP está garantizado, independientemente de cuando acaba el soporte de Microsoft para VFP. Los lenguajes de programación tipo XBASE vinieron para quedarse y quien no siga con VFP lo hará con Xailer, Visual Xbase++, Visual Flagship, Visual DBase o algún otro que surja en el camino. Y finalmente ¿Piensas vivir por siempre?, ¿Cuantos años mas piensas seguir viviendo de la programación? ¿50 años más talvez...?

Tip "existencial": Cuando un programador "habla maravillas" de su lenguaje de programación para obtener un contrato, puedes estar seguro de que no es un buen programador y que el producto terminado no será muy bueno, sencillamente porque se apoya en el trabajo de otros para valorar su trabajo propio y endosa la responsabilidad del éxito de su proyecto a su herramienta de trabajo.

Cuando un programador se apoya en las características de su programa terminado para obtener un contrato, es muy probable que sea un buen programador porque se se basa en el mérito de su propio trabajo y no el de otros. Asume la responsabilidad del éxito de su proyecto y no basa el éxito de su trabajo en lo "poderoso" de su herramienta. Como decimos en mi tierra: "El perico donde sea es verde."

Por ejemplo, tu no ves que Microsoft ande alardeando de que Windows Server 2003 esta hecho en "C", para que la gente lo compre. Mas bien, habla de las cualidades propias del software, haciendo totalmente de lado en que lenguaje de programación esta hecho. Es decir, se apoya en los méritos propios del software de Server 2003 y no en las ventajas de haberlo "programado" en lenguaje "C" en vez de JAVA, por ejemplo.

Edgar Acevedo

22 de junio de 2008

Usando la Orientación a Objetos para Reducir el Tiempo de Desarrollo

Artículo original: Using Object Orientation to Reduce Development Time
http://www.jamesbooth.com/reducedevtime.htm
Autor: J. Booth
Traductor: 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.

11 de junio de 2008

Interfaz Visual FoxPro con (SQLServer, MySQL, PostgreSQL y Oracle)

Lanzo al mercado Clase para poder trabar con cualquier motor de Base de Datos, sin tener que cambiar el código nativo de Visual FoxPro a MySQL, SQLServer, PostgreSQL o Oracle.

Antes de comenzar a explicar de cómo funciona la clase quiero darles algunos alcances y no se dejen sorprender por otras personas por que lo único que quieren es lucrar con personas que no conocen mucho de este tema.

Somos muchos los programadores que hemos venido desarrollando aplicación con la base nativa de Visual FoxPro; particularmente nunca me gusto Visual FoxPro como motor de base de datos ya que es limitado, si uno quiere orientarse a realizar aplicación remotas o de entorno web es allí donde surgen los inconvenientes de limitación, no se si este equivocado pero eso es mi punto de vista.

La gran interrogante a todo esto es, si migro a un motor de gestor de base de datos, tendré que cambiar la forma de programar, bueno en teoría si. Aquí unos pequeños comandos y la forma como seria para programar si se cambia la base de datos, pero con la clase que desarrolle no se tiene que cambiar casi nada en absoluto.

Ahora déjenme explicarles como funciona

Se ha desarrollado dos clases, que es para mantenimientos y otra que es para transacciones.

Primero todos los programadores en Visual FoxPro, sabemos que nuestros formularios utilizamos diferentes herramientas como TextBox, LisTbox, Combobox, etc... y en su propiedad controlsource hacemos referencia a la tabla y el campo que esta enlazado a este objeto. Bien si tenemos un formulario ya desarrollado lo único que vamos a tener que hacer es copiar todos los objetos utilizados en nuestro mantenimiento a nuestro nuevo formulario que hereda de la clase (mantenimiento) y configurar las siguientes propiedades. Primero comenzamos viendo las propiedades que utilizamos para un Textbox se fijan en el controlsource, bueno esa es la tabla que esta en el motor de MySQL, SQLServer o PostgreSQL



Bueno como eso va ser copiado del formulario anterior no hay problema, ahora veremos las propiedades del formulario.



La propiedades ha utilizar son:
  • SQLTabla = la tabla con lo que queremos trabajar
  • nCeros = cuando el código será autogenerador, (00001,00002, etc.)
Con solo estas dos propiedades, tienen un mantenimiento de tablas, ahora pasemos a la pestaña Listado General.



Bien aquí utilizamos las propiedades del grilla, muchas veces queremos una búsqueda interactiva, ejemplo tengo 1000 clientes no voy a bajar uno por uno, aquí explico como crear la búsqueda.
  • GenBusqueda = “S” genera un buscador, “N” sin buscador
  • cBusCampo1 = aquí va el primer campo para la búsqueda
  • cBusCampo2 = aquí va el segundo campo para la búsqueda
  • cBusCampo3 = aquí va el tercer campo para la búsqueda
  • cTituloBus = es la presentación que aparecerá en el combobox.
Ya todo esto definido tendremos un mantenimiento diferente a lo acostumbrado.



Bueno yo lo tengo ya acoplado a mis sistemas.



La ventana de búsqueda que configuramos.



Sin programar ninguna línea de código solo haciendo la copia del formulario que ya teníamos desarrollado y configurando algunas propiedades.

Ahora vayamos a lo supuestamente más difícil, como trabajar con las transacciones que tenemos. Las conocidas tablas (cabeceas de documento y detalle de documento). Bueno se ha creado 4 propiedades fundamentales y un método que hay que tener en cuenta

Propiedades:

  • Clave primaria = llave primara para la actualización de registros
  • Validacampotablasql = Valor de .T. o .F. Compara la tabla del SQL con el cursor que se ha utilizado.
  • Validaimg = Valor de .T. o .F. Si la tabla tiene imágenes
  • Validar fecha = Valor de .T. o .F. Cuando queremos guardar fechas vacía en mysql no permite enviar error como si estuviéramos mandando valores .null. a un campo de tipo fecha, en SQLServer no hay problema.
  • sqlupdateinsert = Sus parámetros (cAccion, cTablaSqlCur, cTablaSql, CondUpdate)
  • cAccion = “N” nuevo; “A” Actualiza.
Forma de uso, no pongo la opción para eliminar porque es sumamente sencillo. El comando que se utiliza para recuperar una tabla de cualquier motor de base de datos es.

SQLExecute (Conexión,”Select * From Tabla ”,”TablaCursor”)
Yo tengo un método que es similar con la diferencia que sale el error específico si sale mal la consulta o hay algún campo mal escrito.

Thisform.Execute(”Select * from Tabla ”,”TablaCursor”)
Forma de uso: Si queremos utilizar un cursor que hemos agregado campos diferentes que no pertenecen a la tabla que vamos hacer referencia.


6 de junio de 2008

Expandiendo expresiones con TextMerge()

La consulta

Hace unas semanas me consultaron de como poder combinar un archivo de plantilla con los datos de una tabla de VFP, para armar el texto personalizado de un correo electrónico. Mi respuesta rápida fue: "... con la función TextMerge() ...", y brindé el enlace a un artículo escrito por el amigo Esparta Palma sobre el tema:

-- Evolución del tratamiento de cadenas con TEXTMERGE --
http://comunidadvfp.blogspot.com/2014/10/evolucion-del-tratamiento-de-cadenas.html

La función TextMerge(), incorporada a partir de Visual FoxPro 7.0, y como bien lo indica Esparta en su artículo, tiene similares funcionalidades que los comandos SET TEXTMERGE y TEXT ... TEXTMERGE ... ENDTEXT, pero ... ¡¡¡ con anabólicos !!!, ya que TextMerge() permite la expansión de expresiones en forma anidada, y también permite recursividad si se le indica en su último parámetro.

La necesidad

La verdadera necesidad de quien me consultó, aparte de combinar simplemente los campos de la tabla donde estaban los datos de los destinatarios de los correos electrónicos, era incluir una tabla con datos de otra tabla, que obviamente era diferente por cada destinatario. Gracias a los anabólicos de la función TextMerge(), esto no sería inconveniente, ya que dentro de la expresión que se permite para la expansión, tranquilamente se puede incluir una función definida por el usuario. Como veremos en el ejemplo completo, se incluyen tres funciones definidad por el usuario que nos retornan la lista en cuestión, y resultados de cuentas y sumatorias.

La cosmética

Una vez logrado esto, también se deseaba que el correo electrónico enviado tenga formato HTML para una mejor apariencia. Entonces partiendo de un archivo de plantilla que contiene código HTML con las expresiones a expandir encerradas entre los delimitadores (por defecto "<<" para el inicio y ">>" para el final). Para el caso de la función que debia generar una lista, esta lo obtendiamos con una "tabla formateada" con código HTML. Para generar esta tabla utlizaríamos el código del artículo: "Convertir tablas a formato HTML".

El correo electrónico

Una vez obtenido el código HTML para cada correo electrónico, configuramos todas las propiedades del objeto que utilizamos para el envío de correos con sus respectivos valores. Generalmente la propiedad que se configura para darle formato HTML al cuerpo de un correo electónico es HtmlBody. Para mis pruebas de envío de correo electrónico utilicé CDO (Collaboration Data Objects) y el código del artículo "Mas sobre el envio de mensajes de correo electrónico desde Visual FoxPro" de este Blog.

La siguiente imagen nos muestra la apariencia del correo electrónico.



El código de ejemplo

A continuación se muestra un programa de ejemplo, en donde bien podemos generar el código HTML en tiempo de ejecución, o mejor aun, tomar ya un archivo HTML generado con algún editor que nos permitirá un mayor manejo en el formato de texto.

*-- Creo una Plantilla con código HTML
SET TEXTMERGE OFF
TEXT TO lcFile NOSHOW
<p><i><<ALLTRIM(Employees.TitleOfCourtesy)>><br>
<b><<ALLTRIM(Employees.FirstName)>> <<ALLTRIM(Employees.LastName)>></b><br>
<<ALLTRIM(Employees.Address)>><br>
<<ALLTRIM(Employees.City)>><br>
<<ALLTRIM(Employees.Country)>></i></p>
<hr>
<p>Tengo el agrado de hacerle llegar el listado del total de sus ordenes del año 1997 agrupadas por mes:</p>
<<AgregarListaResumen(Employees.EmployeeId)>>
<p>La cantidad total de sus ordenes son <b><<TotalOrdenesLista(Employees.EmployeeId)>></b> y 
suman un total de <b><<TotalFletesLista(Employees.EmployeeId)>></b> en concepto de fletes.</p>
<p>Lo saluda atentamente</p>
<p><u><i>El Gerente</i></u></p>
ENDTEXT
STRTOFILE(lcFile, FULLPATH("Template.htm"))

*-- Tomo la plantilla recien creada o bien una plantilla
*-- creada con alguna herramienta HTML 
lcFile = FILETOSTR(FULLPATH("Template.htm"))
OPEN DATABASE (HOME(2) + "Northwind\Northwind.dbc")
SELECT 0
USE Employees
SCAN ALL
 *-- Por cada Empleado genero el Mail con sus respectivas propiedades
 *-- ...
 *-- ...
 *-- Y genero el codigo HTML que ira en la propiedad BodyHtml del Mail
 lcBodyHtml = TEXTMERGE(lcFile)
 *-- Solo para este ejemplo guardo el archivo HTML para visualizarlo
 STRTOFILE(lcBodyHtml, FULLPATH(FORCEEXT("BodyHtml" + TRANSFORM(Employees.EmployeeId), "htm")))
ENDSCAN
USE IN SELECT("Employees")

PROCEDURE AgregarListaResumen(tnId)
 LOCAL lcHtml, loHtml
 *-- Listado de ordenes por Empleado
 SELECT MONTH(OrderDate) AS mes, COUNT(OrderId) AS Cant_Ordenes, SUM(Freight) AS Importe_Flete ;
  FROM Orders ;
  WHERE EmployeeId = tnId AND YEAR(OrderDate) = 1997 ;
  GROUP BY Mes ;
  ORDER BY Mes ;
  INTO CURSOR Lista
 loHtml = CREATEOBJECT("ClaseHtml")
 lcHtml = loHtml.AgregarTabla("Lista") + CHR(13) + CHR(10)
 RELEASE loHtml
 USE IN SELECT("Lista")
 RETURN lcHtml
ENDPROC

PROCEDURE TotalOrdenesLista(tnId)
 LOCAL lcHtml
 *-- Total de Ordenes por Empleado
 SELECT COUNT(OrderId) AS Tot_Ordenes ;
  FROM Orders ;
  WHERE EmployeeId = tnId AND YEAR(OrderDate) = 1997 ;
  INTO CURSOR TOTAL
 lcHtml = TRANSFORM(TOTAL.Tot_Ordenes)
 USE IN SELECT("Total")
 RETURN lcHtml
ENDPROC

PROCEDURE TotalFletesLista(tnId)
 LOCAL lcHtml
 *-- Total de Fletes por Empleado
 SELECT SUM(Freight) AS Tot_Flete ;
  FROM Orders ;
  WHERE EmployeeId = tnId AND YEAR(OrderDate) = 1997 ;
  INTO CURSOR TOTAL
 lcHtml = TRANSFORM(TOTAL.Tot_Flete)
 USE IN SELECT("Total")
 RETURN lcHtml
ENDPROC


*--------------------------------
DEFINE CLASS ClaseHtml AS CUSTOM
 *--
 *-- Clase para convertir una Tabla a formato HTML
 *-- Código original del artículo:
 *-- "Convertir tablas a formato HTML"
 *-- http://comunidadvfp.blogspot.com/2005/10/convertir-tablas-formato-html.html
 *--
 #DEFINE CR CHR(13)
 #DEFINE LF CHR(10)
 #DEFINE CR_LF CR + LF
 *-- Propiedades
 DIMENSION aCampos(1,1)
 nCampos = 0
 nRegistros = 0
 cNombreAlias = ""
 cArchivoHtml = ""
 cHtml = ""
 cColorBody = "#DDDDDD"
 cColorTitle = "#FFFFDD"
 cColorTable = "#FFFFFF"
 *--
 PROCEDURE AgregarTags(tcTexto, tcTag, tcOpciones)
  RETURN [<] + LOWER(tcTag) + IIF(EMPTY(tcOpciones),[],[ ] + tcOpciones) + [>] + ;
    ALLTRIM(tcTexto) + [</] + LOWER(tcTag) + [>]
 ENDPROC
 *--
 PROCEDURE IniciarTag(tcTag, tcOpciones)
  RETURN [<] + LOWER(tcTag) +  ;
    IIF(EMPTY(tcOpciones),[],[ ] + tcOpciones) + [>]
 ENDPROC
 *--
 PROCEDURE TerminarTag(tcTag)
  RETURN [</] + LOWER(tcTag) + [>]
 ENDPROC
 *--
 PROCEDURE AgregarTabla(tcNombreAlias)

  THIS.cNombreAlias = tcNombreAlias
  THIS.nCampos = AFIELDS(THIS.aCampos,THIS.cNombreAlias)

  LOCAL lcTexto, ln
  lcTexto = THIS.IniciarTag([table],[border="1" cellpadding="2" cellspacing="0"] + ;
    IIF(EMPTY(THIS.cColorTable),[],[ bgcolor="] + THIS.cColorTable + ["])) + CR_LF
  *-- Fila cabecera
  lcTexto = lcTexto + THIS.IniciarTag([tr], ;
    IIF(EMPTY(THIS.cColorTitle),[],[bgcolor="] + THIS.cColorTitle + ["])) + CR_LF
  *-- Recorro campos
  FOR ln = 1 TO THIS.nCampos
    lcTexto = lcTexto + THIS.AgregarTags(THIS.aCampos(ln,1),[th]) + CR_LF
  ENDFOR
  lcTexto = lcTexto + THIS.TerminarTag([tr]) + CR_LF
  *-- Filas de registros
  SELECT (THIS.cNombreAlias)
  SCAN ALL
    THIS.nRegistros = THIS.nRegistros + 1
    *-- Inicio fila
    lcTexto = lcTexto + THIS.IniciarTag([tr]) + CR_LF
    *-- Recorro los campos
    FOR ln = 1 TO THIS.nCampos
      lcTexto = lcTexto + THIS.AgregarTags(THIS.TomarValorCampo(ln),[td], ;
        THIS.AlinearSoloNumeros(ln)) + CR_LF
    ENDFOR
    *-- Fin fila
    lcTexto = lcTexto + THIS.TerminarTag([tr]) + CR_LF
  ENDSCAN
  lcTexto = lcTexto + THIS.TerminarTag([table])
  RETURN lcTexto
 ENDPROC
 *--
 PROCEDURE TomarValorCampo(tn)
  LOCAL lcTexto, lcTipo
  *-- Tipo de campo
  lcTipo = THIS.aCampos(tn,2)
  DO CASE
    CASE INLIST(lcTipo,"C","M") && Caracter y Memo
      lcTexto = ALLTRIM(EVALUATE(THIS.aCampos(tn,1)))
    CASE INLIST(lcTipo,"D","T","I","L")
      lcTexto = TRANSFORM(EVALUATE(THIS.aCampos(tn,1)))
    CASE INLIST(lcTipo,"Y") && Monetario
      *-- Quitar esta opción si no se desar el símbolo monetario
      *-- Este tipo también está contemplado en la opcion siguiente
      lcTexto = TRANSFORM(EVALUATE(THIS.aCampos(tn,1)))
    CASE INLIST(lcTipo,"N","F","B","Y")
      IF THIS.aCampos(tn,4) = 0
        lcTexto = ALLTRIM(STR(EVALUATE(THIS.aCampos(tn,1))))
      ELSE
        lcTexto = ALLTRIM(STR(EVALUATE(THIS.aCampos(tn,1)), ;
          THIS.aCampos(tn,3) + THIS.aCampos(tn,4) + 1, THIS.aCampos(tn,4)))
      ENDIF
    CASE INLIST(lcTipo,"G") && General
      lcTexto = [gen]
    CASE INLIST(lcTipo,"Q","V","W") && Nuevos tipos de VFP 9.0
      lcTexto = TRANSFORM(EVALUATE(THIS.aCampos(tn,1)))
    OTHERWISE
      lcTexto = ""
  ENDCASE
  lcTexto = THIS.TransformarTexto(lcTexto)
  IF lcTipo = "M" && Si es campo Memo
    *-- Transformo retornos de carro y saltos de lineas
    lcTexto = STRTRAN(lcTexto, CR_LF, [<br>])
    lcTexto = STRTRAN(lcTexto, CR, [<br>])
    lcTexto = STRTRAN(lcTexto, LF, [<br>])
  ELSE && Si no es campo Memo
    *-- Transformo espacios [ ] por [&nbsp;]
    lcTexto = STRTRAN(lcTexto, [ ], [&nbsp;])
  ENDIF
  RETURN lcTexto
 ENDPROC
 *--
 PROCEDURE TransformarTexto(tcTexto)
  *-- Transformo algunos caracteres "\<>&
  tcTexto = STRTRAN(tcTexto, ["], [&quot;])
  tcTexto = STRTRAN(tcTexto, [\], [&#092;])
  tcTexto = STRTRAN(tcTexto, [<], [&lt;])
  tcTexto = STRTRAN(tcTexto, [>], [&gt;])
  tcTexto = STRTRAN(tcTexto, [&], [&amp;])
  *-- Si es vacio o nulo
  IF EMPTY(tcTexto) OR ISNULL(tcTexto)
    tcTexto = [&nbsp;]
  ENDIF
  RETURN tcTexto
 ENDPROC
 *--
 PROCEDURE AlinearSoloNumeros(tn)
  LOCAL lcTexto, lcTipo
  lcTipo = THIS.aCampos(tn,2)
  DO CASE
    CASE INLIST(lcTipo,"N","F","B","Y","I")
      *-- Alinea a la derecha solo campos numéricos
      lcTexto = [align="right"]
    OTHERWISE
      lcTexto = []
  ENDCASE
  RETURN lcTexto
 ENDPROC

ENDDEFINE
*--------------------------------

El final

Espero que esto los ayude a encontrar diversos usos que se le puede dar a la función TextMerge(). Lo positivo de este artículo y esta solución, es que para toda ella utilizamos código publicado en este Blog.

A raíz de otras consultas recibidas, próximamente ampliaré sobre el tema del envio de correos electrónicos con CDO, con la inclusión de formato HTML, archivos adjuntos, y de imágenes y sonidos embebidos.

Hasta la próxima.

Luis María Guayán

4 de junio de 2008

Buscar el nombre de un Tag en un archivo de índices de una tabla

Función que busca el nombre de un Tag en un archivo de índices de una tabla seleccionada, utilizando la información de la función ATAGINFO() de VFP7 y superior.
Ej:
USE HOME(2) + "NORTHWIND\CUSTOMERS.DBF"

? BuscarTag("Customers", "City")

*---------------------------------------------------
* FUNCTION BuscarTag(tcAlias, tcTag)
*---------------------------------------------------
* Busca el nombre de un tag en el alias de 
* una tabla abierta en un area de trabajo
* RETORNO: Lógico
*   .T. = Si el tag existe en la tabla
*   .F. = Si el tag no existe o el alias no existe 
*---------------------------------------------------
FUNCTION BuscarTag(tcAlias, tcTag)
  RETURN SELECT(tcAlias) > 0 AND ;
    ATAGINFO(laTag,"",SELECT(tcAlias)) > 0 AND ;
    ASCAN(laTag,tcTag,-1,-1,1,1) > 0
ENDFUNC
*---------------------------------------------------
Luis María Guayán

26 de mayo de 2008

Redondear a X centavos hacia arriba o hacia abajo

Estas funciones redondean a X centavos para arriba o para abajo según se necesite.
lnNro = 12345.67

? RedondearMasAx(lnNro, 5)
? RedondearMenosAx(lnNro, 5)
?
? RedondearMasAx(lnNro, 25)
? RedondearMenosAx(lnNro, 25)
?
? RedondearMasAx(lnNro, 50)
? RedondearMenosAx(lnNro, 50)

FUNCTION RedondearMenosAx(tnVal, tnDec)
  RETURN Floor(tnVal * 100/tnDec) / (100/tnDec)
ENDFUNC

FUNCTION RedondearMasAx(tnVal, tnDec)
  RETURN CEILING(tnVal * 100/tnDec) / (100/tnDec)
ENDFUNC
Cada aplicación tiene sus propias necesidades de redondeo, diferentes a la función nativa ROUND() de Visual FoxPro. Es por ello que en la comunidad siempre se consulta sobre este tema, y en PortalFox existen las siguientes rutinas que cubren la mayoría de las necesidades:

-- Redondear a 5 centavos por arriba --
http://comunidadvfp.blogspot.com/2004/07/redondear-5-centavos-por-arriba.html

-- Redondear hacia arriba o hacia abajo --
http://comunidadvfp.blogspot.com/2007/06/redondear-hacia-arriba-o-hacia-abajo.html

-- Truncar un número --
http://comunidadvfp.blogspot.com/2004/08/truncar-un-numero.html

Luis María Guayán

23 de mayo de 2008

Haciendo aplicaciones portables en VFP - Parte 3 de 3

Autor: Sergio Hugo Sanchez

Y por último, llegamos a Windows Vista y sus consecuencias de automatizar el proceso de instalación de los controles ActiveX. Despues de probarlo en un sistema Vista, el programa seguía sin funcionar; asi que me dedique a buscar en Internet todo lo que pude relacionado al tema de instalación. Para resumir, he aquí 3 puntos:

1) Instalacion de los controles desactivado la UAC de Windows Vista mediante el panel de control. (Difícil).

2) Instalación de los controles usando archivos Manifest (como indica su blog Calvin Hsia (Medio)

3) Instalación de los controles con derechos de administrador (Facilísmo tan a la vista que no me lo creo!).

Bien. La cuestión es que Windows Vista y su seguridad no permiten el registro automático. El primer punto, seria necesario que la PC donde trabajemos tuviera desactivado esto, pero lo menciono como difícil porque yo daré mi aplicación a un usuario y no creo que el quiera esta apagando el UAC si se encuentra con una maquina Vista.

El segundo método, me llevo a leer en el blog de Calvin su método para crear archivos Manifest que se pegan al EXE para que se autoregistren los controles. Ademas, en la propia ayuda de DBITech, informan sobre lo que se tiene que hacer para registrar sus controles. En este explica como se debe ejecutar el programa REGSVR32 como administrador y en otro tema como hacer los archivos MANIFIEST para los controles DBI.

Utilizando las herramientas de Markus Winhard quien creo Manifest Tools, pude crear mi archivo Manifest y anexarlo a mi .EXE.

La idea es crear un archivo aplicacion.exe.manifest (xml) con las instrucciones de registro. Luego, "agregar" este archivo al ejecutable.

En la aplicacion ejemplo que utilice uso dos controles DBI: ctDropDate que es un control para desplegar un calendario y poner fechas y el ctToolBar que es una barra de herramientas con iconos.
<?xml version="1.0" encoding="utf-8"?>
<assembly xsi:schemaLocation="urn:schemas-microsoft-com:asm.v1 assembly.adaptive.xsd" manifestVersion="1.0"
 xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2"
 xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" xmlns="urn:schemas-microsoft-com:asm.v1"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance“>
<assemblyIdentity 
        name="monitortc2.exe" 
        version="1.0.0.0" 
        type="win32" 
/>
<file name="ctDropDate.ocx" asmv2:size="139264">
    <typelib tlbid="{29F50288-212F-11D5-A85D-0080C8DFC881}" 
        version="2.0" 
        helpdir="" 
        resourceid="0" 
        flags="HASDISKIMAGE"  
     />
    <comClass clsid="{29F50288-212F-11D5-A85D-0080C8DFC881}" 
        threadingModel="Apartment" 
        tlbid="{29F50288-212F-11D5-A85D-0080C8DFC881}" 
        progid="ctDropDate.ctDropDateCtrl.2" 
        description="ctDropDate Control" 
     />
</file>

<file name="ctToolbar.ocx" asmv2:size="167936">
    <typelib tlbid="{83A59732-3096-4711-A41B-8CF8FF98326E}" 
        version="3.0" 
        helpdir="" 
        resourceid="0" 
        flags="HASDISKIMAGE"  
     />
    <comClass clsid="{43A5A355-2366-438A-AA33-1C30DA9E3686}" 
        threadingModel="Apartment" 
        tlbid="{83A59732-3096-4711-A41B-8CF8FF98326E}" 
        progid="ctTOOLBAR.ctToolBarCtrl.3" 
        description="ctToolBar Control 3" 
     />
</file>

</assembly>
Como se puede apreciar, tengo 2 controles que registrar para este ejecutable. Los datos de typelib y Class ID fueron tomados usando la herramienta OLEVIEW (que esta en Microsoft pero tambien en Download.com).

Luego, usando la herramienta de Importar Manifest al EXE como mencione antes, queda listo mi ejecutable. Lo probamos y … wala! Registrado.

Ahora bien, como tercer punto, creo que es mucho mas sencillo el ultimo. Porque sin crear ningún Manifest, cuando ponemos nuestro USB y le damos a ejecutar nuestro programa, en lugar de usar el doble click con el botón izquierdo damos click al botón derecho. En el menú, le damos click en EJECUTAR COMO ADMINISTRADOR. Y listo! El programa registra los controles igual que en XP sin crear ningun archivo ni paso extra (porque, oiga usted, esta medio engorroso andar buscando los archivos OCX con sus clases y demás del registro para hacer el dichoso archivo XML).

Si le digo a mi usuario, dale doble click para usar el programa desde el usb portable y si ves que usas Vista, dale un click derecho y ejecútalo como administrador, pues hasta aquí llegamos.

Estoy buscando crear una rutina para identificar el OS que se trate y lanzar un mensaje de advertencia. O simplemente dejar el manifest y listo. Dependerá del tiempo que tenga en preparar mi aplicación para ser portable.

RESUMEN

Hasta aquí, creo que hacer portable una aplicación VFP no lleva mas que copiar todos los archivos necesarios a una carpeta junto con su RUNTIME. En el caso de controles extra, implica el problema de registrarlos debidamente. No probé esto con versiones anteriores de Windows y por supuesto en otros ambientes.

Otra desventaja es que la aplicación portable siempre estara del orden de 5 a 10MB. Por que? Porque los runtimes casi ocupan unos 5MB (o un poquito mas) mas el EXE y los archivos necesarios (ico, bmp, log, mem, ini, etc, etc).

La ultima cosa es trabajar con los datos. Tener nuestras rutinas claras para la conexión con los datos, ya sea de manera local (sistemas sencillos) o de manera remota (para LAN, WAN o internet).

Seguiré probando este método para ver que tan bien trabaja. Deseeme suerte.

21 de mayo de 2008

Haciendo aplicaciones portables en VFP - Parte 2 de 3

Autor: Sergio Hugo Sanchez

Continuando con lo de la creacion de sistemas portables, como llevo tiempo que utilizo algunos de los controles DBI-Tech, pues si que es un problema. (Malo malo, al no usar VFP puro verdad?). Bueno, ni modo.

Y aqui viene la parte dificil. Determinar si el control esta registrado, registrarlo en su caso y al finalizar desregistrarlo. Asi que he creado una pequena rutina:
*
* CHECKOCX.PRG
* Programa para checar si estan registrados los controles OCX necesarios
* Junio, 22, 2004.
*
*
* Necesitamos tener una variable en alguna parte para saber si ya esta registrado o no…
* MOD: 14,Mayo,2008… Agregamos una funcion para detectar primero si el OCX esta registrado.
*       En caso de no estarlo debemos hacerlo.
*
* Checando si los controles ActiveX necesarios para el sistema estan registrados
*
* Listado con todos los con todos los controles OCX necesarios. 
* Poner en el array tantos OCX como necesite el sistema. Este numero debera ser pasado
* mas adelante para el proceso.
*
* El array necesita 2 dimensiones o columnas, ya que la segunda correspondera al 
* archivo fisico OCX y en donde se encuentra si es que esta en una subcarpeta.
* Ejs.
*  lstOCX(1,1) = "ctdropdate.ctdropdatectrl.2"
*  lstOCX(1,2) = "ctdropdate.ocx"
*   o
*  lstOCX(1,2) = "OCX\ctdropdate.ocx"
*
* Tambien usaremos una variable de parametro para hacer 1 de 3 cosas:
* 1) Check = Checar si el control esta registrado o no y registrarlo en su caso
* 2) RegALL = Registrar el control aunque ya estuviera registrado
* 3) UnRegALL = Desregistrar el control.
* 
* La razon de estas opciones es que para aplicaciones portables necesitaremos desregistrar el control
* para que no quede huella en el sistema Host.
*
PARAMETERS cOpcion
IF PARAMETERS() = 0
 RETURN
ENDIF
DIMENSION lstOCX(2,2)
lstOCX(1,1) = "ctdropdate.ctdropdatectrl.2"
lstOCX(1,2) = "ctdropdate.ocx"
lstOCX(2,1) = "cttoolbar.cttoolbarctrl.3"
lstOCX(2,2) = "cttoolbar.ocx"

* Hacer un ciclo con el ultimo numero que corresponde a la cantidad de OCX necesarios
* No calculamos porque nosotros le damos la cantidad
FOR i = 1 TO 2
 DO CASE
  CASE cOpcion = "Check" 
   WAIT WINDOW "Checando controles"
   lCheck = OcxRegistrado( lstOCX( i, 1 ) ) && Llamamos la funcion que checa si esta registrado o no: (.T./.F.)
   IF lCheck = .F.
    * Si el control necesario no esta registrado en la PC, registremoslo
    * Debemos pasarle a esta funcion el archivo OCX a registrar
    lRegistrado = OCXCmdReg( lstOCX( i, 2) )
    *  ? "Archivo " + lstOCX( i,2) +" Ya NO ESTABA registrado. Pero ahora si lo esta"
   ELSE
    *  ? "Archivo " + lstOCX( i,2)+ " Ya estaba registrado"
   ENDIF 
  CASE cOpcion = "RegALL"
   lRegistrado = OCXCmdReg( lstOCX( i, 2) )
  CASE cOpcion = "UnRegALL" 
   WAIT WINDOW "Desregistrando controles" 
   lRegistrado = OCXCmdUnReg( lstOCX( i, 2) )

 ENDCASE
NEXT
RETURN

* Funcion Como saber si un activex ya fue registrado
* JCVisual. 19/Dic/2007
*
* http://www.portalfox.com/article.php?sid=2543
*
FUNCTION OcxRegistrado(cClase)
    Declare Integer RegOpenKey In Win32API ;
        Integer nHKey, String @cSubKey, Integer @nResult
    Declare Integer RegCloseKey In Win32API ;
        Integer nHKey
    nPos = 0
    lEsta = RegOpenKey(-2147483648, cClase, @nPos) = 0
 
    If lEsta
        RegCloseKey(nPos)
    Endif

    Return lEsta
Endfunc

* Funcion: Registrar y Desregistrar un OCX
* Jorge Mota. 16/Agosto/2002
*
* http://www.portalfox.com/article.php?sid=506
*
FUNCTION OCXRegistrar(cActiveX)
DECLARE INTEGER DLLSelfRegister IN "vb6stkit.DLL” STRING lpDllName
* Debemos de poner una lista de los controles
lcFileOCX = SYS(5) + CURDIR() + cActiveX
*lcFileOCX = "C:\DBITECH\Toolbox6\DBITech\Component Toolbox 6.0\Components\ctlist.ocx”
liRet = DLLSelfRegister( lcFileOCX )
IF liRet = 0
 SelfRegisterDLL = .T.
* MESSAGEBOX( "Registrado OCX” )
ELSE
 SelfRegisterDLL = .F.
 MESSAGEBOX( "Error - No registrado OCX” )
ENDIF
ENDFUNC


* Funcion: Registrar OCX con comando
* 
* JCVisual. 19/Dic/2007
*
* http://www.portalfox.com/article.php?sid=2543
*
FUNCTION OCXCmdReg(cActiveX)
 cRun=”REGSVR32 /s ” + cActiveX
 RUN /N &cRun
ENDFUNC

* Funcion: Des-registrar OCX con comando cuando termine
FUNCTION OCXCmdUnReg(cActiveX)
 cRun=”REGSVR32 /u /s ” + cActiveX
 RUN /N &cRun
ENDFUNC
Como pueden observar he usado una función publicada en PortalFox para checar si el control esta registrado en el sistema. Si no, registrarlo.

Cabe notar que la funcion de Jorge Mota no funciona. Busque en Internet otra sin exito por lo que segui el metodo tradicional de REGSVR32. En la pagina http://support.microsoft.com/kb/146219/es de Microsoft, incluso indican que se puede distribuir este archivo con nuestra aplicacion, asi que lo pondre en mi directorio.

Como nota, aclaro que el programa tiene un listado de mis controles que utilizo en un arreglo bidimensional. En la primer columna esta la referencia al registro que se obtiene de la forma y la propiedad OleClass. La segunda columna corresponde al archivo fisico OCX.

Ahora, desde mi programa principal, antes de iniciar el programa mando llamar la rutina:
*
* Checar si los controles OCX necesarios para el sistema se encuentran
* registrados, en caso contrario deben registrarse. Es util para sistemas portables
* o PCs donde no estan instaladas todas las librerias necesarias.
*
WAIT WINDOW "Checando OCX"
DO CheckOCX WITH "Check"
*_Screen.Visible = .f.
DO FORM tc_consulta
READ EVENTS
* Al terminar no olvidar desregistrar si es portable
WAIT WINDOW "Si es portable todos fuera"
DO CheckOCX WITH "UnRegALL"
Y listo. Ahora a ejecutar el sistema desde un USB Disk. Funcionara? Vamos a ver…
Pues si. Funciona. Con WindowsXP. Compile mi programa, lo pase a mi USB y lo ejecute en una maquina que no tiene nada de activeX y VFP. El programa detecta que los controles no estan registrados y procede a hacerlo. De momento como demo, he dejado unas cuantas lineas visibles y wait windows para ver que estaba haciendo. Despues, el programa se ejecuto sin problemas. Al cerrarlo, desregistro los controles y aqui no paso nada.

Ahora viene otro problema. El registro en Windows Vista. Eso lo veremos en el siguiente tema.

Se aceptan comentarios.

19 de mayo de 2008

Haciendo aplicaciones portables en VFP - Parte 1 de 3

Autor: Sergio Hugo Sanchez

Hoy en dia parece que vuelve una fiebre por las aplicaciones portables. Como si fuera la gran novedad de estos tiempos. Aun recuerdo como si fuera ayer cuando TODAS las aplicaciones eran portables.

Asi es, antes de Windows, en el mundo de MSDOS todas las aplicaciones eran portables. Creabas el directorio (entonces no se conocia el concepto de "carpeta"). copiabas los archivos y voila!. Ya esta. Nada de instalar en la carpeta de archivos de programas, o de windows o de system32, o del registro. En fin, eran otros tiempos.

Ahora me ha surgido un proyecto en el cual tengo que hacer una de tres cosas:

1) Instalar la aplicacion en un servidor y ejecutarla desde ahi a ciertos usuarios para que esten disponibles atraves de una WAN.

2) Instalar la aplicacion en cada computadora de la WAN con acceso a los datos del servidor (cliente/servidor), crear unas cuentas de usuario para que solo los autorizados utilicen dicha aplicacion.

3) Hacer una aplicacion portable que se ejecute en un disco USB de tal forma que solo los usuarios autorizados lleven su llave y la ejecuten en cualquier computadora de la WAN. Ojo, los datos no estaran en el USB, sino que quiza residan en el servidor de la empresa.

Pues bien. Se podra?

La cuestion es ... seguridad. Las personas autorizadas podran desplazarse a cualquier punto de la WAN y usar alguna de las PC para conectarse a la base de datos central. Sin embargo, dicha aplicacion no deberia residir en la PC local. Aunque se creen cuentas de usuario y se apliquen restricciones de uso, o se escondan las carpetas, o se use un programa con contrasena de carpetas o aplicaciones, la idea es que simplemente no aparezca.

Que tal si hay un robo, o un accidente, o desastre y la PC de alguna forma se ve afectada? Los datos no deben residir en ella, y de preferencia tampoco la aplicacion.

COMO CREAR UNA APLICACION PORTABLE EN VFP.

Se que los gurus y viejos programadores de VFP saben esto desde hace años, ya que Fox siempre ha sido portable desde DOS. Bastaba con poner los runtimes en la misma carpeta y ya.

Paso 1.

Para comenzar, he copiado todos los archivos del sistema/programa a una carpeta del USB. DBFs locales, archivos ini, iconos, ejecutables, etc.

Paso 2.

En segundo lugar, he copiado los archivos runtime que indica la ayuda de Visual Fox. A su saber:
vfp9r.dll
vfp9renu.dll
msvcr71.dll
Hasta aqui va funcionando. Ahora, como mi aplicacion utiliza un par de archivos OCX, tambien debemos incluirlos. Estos se copian a la carpeta raiz del mismo sistema. Todo junto.

Listo. Si su aplicacion es VFP puro sin librerias extras. Creo que hasta aqui esta resuelto el problema.

El problema vendra si tiene archivos OCX o DLL que registrar, asi como si va a trabajar con Windows Vista y su sistema tiene que usar estos registros.

No estoy hablando de la parte de los datos. Los datos pueden residir incluso en el servidor o en la misma carpeta donde esta el sistema. Eso es otro tema.

Cuando tiene controles ActiveX a registrar necesitamos hacer una pequena rutina que podamos ejecutar para que se autoregistren. Eso lo veremos en el proximo tema.

12 de mayo de 2008

Buscar el nombre de un campo en todas las tablas de una DBC

Estas dos maneras de buscar el nombre de un campo en todas las tablas de una base de datos, son respuestas a una pregunta de un usuario del foro de UT, enviadas por por Sergey Berezniker y Luis María Guayán respectivamente.

*** 1 ***
lcDBC = HOME(2) + "NORTHWIND\NORTHWIND.DBC"
USE (lcDbc) ALIAS MyDbc
*-- Campo a buscar
lcFieldName = "City"
SELECT LEFT(db1.ObjectName,50) AS TableName, LEFT(db2.ObjectName,50) AS FieldName ;
  FROM mydbc db1 JOIN mydbc db2 ON db1.ObjectId = db2.ParentId ;
  WHERE db1.ObjectType = "Table" ;
  AND db2.ObjectType = "Field" AND db2.ObjectName = LOWER(lcFieldName) 
USE IN SELECT("MyDbc")

*** 2 ***
lcDBC = HOME(2) + "NORTHWIND\NORTHWIND.DBC"
OPEN DATABASE (lcDbc)
lnTables = ADBOBJECTS(laTables, "TABLE")
*-- Campo a buscar
lcFieldName = "City"
FOR lnI = 1 TO lnTables
  USE (ADDBS(JUSTPATH(lcDBC)) + laTables(lnI)) ALIAS "Tmp"
  lnFields = AFIELDS(laFields, "Tmp")
  IF ASCAN(laFields,lcFieldName,1,ALEN(laFields,1),1,9) > 0
    ? DBF("Tmp")
  ENDIF
  USE IN SELECT("Tmp")
ENDFOR
CLOSE DATABASES

2 de mayo de 2008

Comprobar, Iniciar y Parar Servicios desde VFP

Con estas funciones podemos comprobar si un Servicio se está ejecutando en Windows, y podemos Iniciarlo o Pararlo si disponemos de los permisos adecuados.

Tomamos como ejemplo el servicio Themes de Windows XP.

1. Consultar si el servicio Themes se esta ejecutando:
oShell = CREATEOBJECT("Shell.Application") 
? oShell.IsServiceRunning("Themes") 
oShell = Null
2. Consultar si se puede iniciar o detener el servicio Themes:
oShell = CREATEOBJECT("Shell.Application") 
? oShell.CanStartStopService("Themes")
oShell = Null
3. Iniciar el servicio Themes:
oShell = CREATEOBJECT("Shell.Application") 
? oShell.ServiceStart("Themes", .F.) 
oShell = Null
4. Detener el servicio Themes:
oShell = CREATEOBJECT("Shell.Application") 
? oShell.ServiceStop("Themes", .T.) 
oShell = Null

Luis María Guayán

28 de abril de 2008

Bar codes on Visual FoxPro reports

Traducción al inglés de Ricardo Aráoz, del artículo "Códigos de barras en informes de Visual FoxPro" publicado en español en PortalFox, para la comunidad de habla inglesa.


Bar codes on Visual FoxPro reports

NEW: Download freely the new FoxBarcode class to generate barcodes from Visual FoxPro, by clicking the following link: http://sites.google.com/site/foxbarcode

CONTENTS

This document describes the way to add bar codes to VFP reports using True Type fonts. The bar codes with which we'll deal here are :
  • Code 39
  • Code 128
  • EAN-13
  • EAN-8
  • Interleaved 2 of 5
In order to accomplish this we'll need, besides the True Type fonts, user defined VFP functions which will codify the input text into the proper format for each code and font. The True Type fonts and the functions can be downloaded from further down in this page.

At the bottom of this page you'll find examples on how to use these functions, a results sample from VFP reports, and notes for it's best usage. A VFP example with two reports may also be downloaded.

BAR CODES

We will briefly describe each possible code in order to help you choose the best fit for the app we will be deploying.

CODE 39

This is a variable length code, whose top length is given by the available printing space and the bar code reader we'll be using.

The character set of code 39 includes 43 characters : digits 0-9, letters A-Z (only upper case), space, and the following symbols: - + . / *. The "*" must be at the beginning and end of the code, so it shouldn't be used in an input text.

Each character is compose of 5 bars and 4 spaces, 3 of these 9 elements are wide (from there the name "3 of 9") and 6 thin elements.

Code 39 is composed of :
Start + InputText + End 

CODE 128

This is also a variable length code, and shorter than code 39.

Code 128 includes digits 0-9, letters A-Z (upper and lower case), and all standard ASCII characters (for a total of 128 characters, that's where the name comes from).

Code 128 is divided in three sub-sets A, B and C.
  • A includes: digits, upper case letters, and control codes.
  • B includes: digits, upper and lower case letters, and control codes.
  • C includes: only digits and compresses two digits into each character giving an excellent density.
This code has a control digit that offers more safety. This digit must be as follows:
* initial digit for subset A = 203
* initial digit for subset B = 204
* initial digit for subset C = 205
* final digit for all subsets = 206
sum = initial digit
for each inputCharacter
  sum = sum + (valueOfInputCharacter * position)
  CheckDigit = sum % 103
Code 128 is composed of :
Start + InputText + CheckDigit + End 

EAN-13 AND EAN-8 (European Article Numbering)

EAN-13 is used world wide for retail sales. It is of fixed length (13 characters). EAN-8 is a shortened version of EAN-13 which includes only 8 characers.

These codes are assigned and controlled by EAN International (http://www.ean.be) and authorized entities in every country.

In EAN-13, the first 6 digits stand for the country and company, the next 6 digits stand for the product, and the last digit is a control digit. Besides the country, the first two or three digits may stand for: books (ISBN), newspapers (ISSN), internal use, etc.

In EAN-8, the first 4 digits stand for the country and company, the next 3 digits stand for the product, and the last one is a control digit.

The control digit's algorithm is :
Sum = 0
for each InputDigit (12 or 7 digits)
  CorrectionValue = 1 if the digit's position is odd
                    3 if the digit's position is even
  Sum = Sum + ValueOfInputDigit * CorrectionValue
  CheckDigit = 10 - (Sum % 10)
  If CheckDigit = 10
    CheckDigit = 0
  endif 
EAN codes are composed of :
CountryCode + CompanyCode + ProductCode + CheckDigit

INTERLEAVED 2 OF 5

This is a variable length code, just like Code 39 and 128.

Interleaved 2 of 5 only includes digits 0-9 and fits two digits per character, giving a very good density. All Interleaved 2 of 5 codes have an even number of digits.

Every couple of digits is codified in a character composed by 5 bars and 5 spaces, 2 out of 5 bars are wide, hence the name "2 of 5".

This code has check digit by the following algorithm:
Sum = 0
for each InputDigit
  CorrectionValue = 1 if the digit's position is odd
                    3 if the digit's position is even
  Sum = Sum + ValueOfInputDigit * CorrectionValue
  CheckDigit = 10 - (Sum % 10)
  If CheckDigit = 10
    CheckDigit = 0
  endif 
Interleaved 2 of 5 is composed of:
Start + InputText + CheckDigit + End

TRUE TYPE FONTS

The true type fonts used in these examples are freeware files and work perfectly with the VFP functions included below.

The fonts used here may be downloaded from: fuentes.zip (30.9 Kb)

For better printing and readability of the codes our advise is to use the following font sizes:

True Type FontFileSize
PF Barcode 39PF_C39.ttf20 ó 22
PF Barcode 128PF_C128.ttf22, 24, 26 ó 28
PF EAN P36PF_EAN_P36.ttf36
PF EAN P72PF_EAN_P72.ttf72
PF Interleaved 2 of 5PF_I2OF5.ttf36 ó 48
PF Interleaved 2 of 5 WidePF_I2OF5_W.ttf28 ó 36
PF Interleavev 2 of 5 TextPF_I2OF5_Text.ttf28 ó 36

VFP FUNCTIONS

See below the VFP functions that transform an input text to the selected bar code.

These functions may be downloaded from: funcion.zip (2.2 Kb)
*------------------------------------------------------
* FUNCTION _StrTo39(tcString) * CODIGO 39
*------------------------------------------------------
* Converts a string to be printed with
* True Type Font "PF Barcode 39"
* USE: _StrTo39('Codigo 39')
* RETURNS: Character
*------------------------------------------------------
FUNCTION _StrTo39(tcString)
  LOCAL lcRet
  lcRet = '*' + tcString + '*'
  RETURN lcRet
ENDFUNC
*------------------------------------------------------
* FUNCTION _StrTo128A(tcString) * CODIGO 128A
*------------------------------------------------------
* Converts a string for printing with
* True Type Font "PF Barcode 128"
* Numerics and alphabetic (only upper case)
* If a character is not valid it is replaced by a space
* USE: _StrTo128A('CODIGO 128 A')
* RETURNS: Character
*------------------------------------------------------
FUNCTION _StrTo128A(tcString)
  LOCAL lcStart, lcStop, lcRet, lcCheck, ;
    lnLong, lnI, lnCheckSum, lnAsc
  lcStart = CHR(103 + 32)
  lcStop = CHR(106 + 32)
  lnCheckSum = ASC(lcStart) - 32
  lcRet = tcString
  lnLong = LEN(lcRet)
  FOR lnI = 1 TO lnLong
    lnAsc = ASC(SUBS(lcRet,lnI,1)) - 32
    IF NOT BETWEEN(lnAsc,0,64)
      lcRet = STUFF(lcRet,lnI,1,CHR(32))
      lnAsc = ASC(SUBS(lcRet,lnI,1)) - 32
    ENDIF
    lnCheckSum = lnCheckSum + (lnAsc * lnI)
  ENDFOR
  lcCheck = CHR(MOD(lnCheckSum,103) + 32)
  lcRet = lcStart + lcRet + lcCheck + lcStop
  *--- This changes spaces and invalid characters
  lcRet = STRTRAN(lcRet,CHR(32),CHR(232))
  lcRet = STRTRAN(lcRet,CHR(127),CHR(192))
  lcRet = STRTRAN(lcRet,CHR(128),CHR(193))
  RETURN lcRet
ENDFUNC
*------------------------------------------------------
* FUNCTION _StrTo128B(tcString) * CODIGO 128B
*------------------------------------------------------
* Converts a string to be printed with
* True Type Font "PF Barcode 128"
* Numerics and alphabetic (upper and lower case)
* If a character is not valid it is replaced by a space
* USE: _StrTo128B('Codigo 128 B')
* RETURNS: Character
*------------------------------------------------------
FUNCTION _StrTo128B(tcString)
  LOCAL lcStart, lcStop, lcRet, lcCheck, ;
    lnLong, lnI, lnCheckSum, lnAsc
  lcStart = CHR(104 + 32)
  lcStop = CHR(106 + 32)
  lnCheckSum = ASC(lcStart) - 32
  lcRet = tcString
  lnLong = LEN(lcRet)
  FOR lnI = 1 TO lnLong
    lnAsc = ASC(SUBS(lcRet,lnI,1)) - 32
    IF NOT BETWEEN(lnAsc,0,99)
      lcRet = STUFF(lcRet,lnI,1,CHR(32))
      lnAsc = ASC(SUBS(lcRet,lnI,1)) - 32
    ENDIF
    lnCheckSum = lnCheckSum + (lnAsc * lnI)
  ENDFOR
  lcCheck = CHR(MOD(lnCheckSum,103) + 32)
  lcRet = lcStart + lcRet + lcCheck + lcStop
  *--- This changes spaces and invalid characters
  lcRet = STRTRAN(lcRet,CHR(32),CHR(232))
  lcRet = STRTRAN(lcRet,CHR(127),CHR(192))
  lcRet = STRTRAN(lcRet,CHR(128),CHR(193))
  RETURN lcRet
ENDFUNC
*------------------------------------------------------
* FUNCTION _StrTo128C(tcString) * CODIGO 128C
*------------------------------------------------------
* Converts a string to be printed with
* True Type Font "PF Barcode 128"
* Only numeric characters
* USE: _StrTo128C('1234567890')
* RETURNS: Character
*------------------------------------------------------
FUNCTION _StrTo128C(tcString)
  LOCAL lcStart, lcStop, lcRet, lcCheck, lcCar, ;
    lnLong, lnI, lnCheckSum, lnAsc  
  lcStart = CHR(105 + 32)
  lcStop = CHR(106 + 32)
  lnCheckSum = ASC(lcStart) - 32
  lcRet = ALLTRIM(tcString)
  lnLong = LEN(lcRet)
  *--- La longitud debe ser par
  IF MOD(lnLong,2) # 0
    lcRet = '0' + lcRet
    lnLong = LEN(lcRet)
  ENDIF
  *--- Convert couple of digits to character
  lcCar = ''
  FOR lnI = 1 TO lnLong STEP 2
    lcCar = lcCar + CHR(VAL(SUBS(lcRet,lnI,2)) + 32)
  ENDFOR
  lcRet = lcCar
  lnLong = LEN(lcRet)
  FOR lnI = 1 TO lnLong
    lnAsc = ASC(SUBS(lcRet,lnI,1)) - 32
    lnCheckSum = lnCheckSum + (lnAsc * lnI)
  ENDFOR
  lcCheck = CHR(MOD(lnCheckSum,103) + 32)
  lcRet = lcStart + lcRet + lcCheck + lcStop
  *--- This changes spaces and invalid chara
  FOR lnI = 1 TO lnLong STEP 2
    lcCar = lcCar + CHR(VAL(SUBS(lcRet,lnI,2)) + 32)
  ENDFOR
  lcRet = lcCar
  lnLong = LEN(lcRet)
  FOR lnI = 1 TO lnLong
    lnAsc = ASC(SUBS(lcRet,lnI,1)) - 32
    lnCheckSum = lnCheckSum + (lnAsc * lnI)
  ENDFOR
  lcCheck = CHR(MOD(lnCheckSum,103) + 32)
  lcRet = lcStart + lcRet + lcCheck + lcStop
  *--- This changes spaces and invalid characters
  lcRet = STRTRAN(lcRet,CHR(32),CHR(232))
  lcRet = STRTRAN(lcRet,CHR(127),CHR(192))
  lcRet = STRTRAN(lcRet,CHR(128),CHR(193))
  RETURN lcRet
ENDFUNC
*------------------------------------------------------
* FUNCTION _StrToEan13(tcString,.T.) * CODIGO EAN-13
*------------------------------------------------------
* Converts a string to be printed with
* True Type Font "PF EAN P36" or "PF EAN P72"
* PARAMETERS:
*   tcString: 12 dígit string (0..9)
*   tlCheckD: .T. Only generates check digit
*             .F. Generates check digit and characters to be printed
* USE: _StrToEan13('123456789012')
* RETURNS: Character
*------------------------------------------------------
FUNCTION _StrToEan13(tcString,tlCheckD)
  LOCAL lcLat, lcMed, lcRet, lcJuego, ;
    lcIni, lcResto, lcCod, lnI, ;
    lnCheckSum, lnAux, laJuego(10), lnPri
  lcRet = ALLTRIM(tcString)
  IF LEN(lcRet) # 12
    *--- Error en parámetro
    *--- debe tener un largo = 12
    RETURN ''
  ENDIF
  *--- Generate Check digit
  lnCheckSum = 0
  FOR lnI = 1 TO 12
    IF MOD(lnI,2) = 0
      lnCheckSum = lnCheckSum + VAL(SUBS(lcRet,lnI,1)) * 3
    ELSE
      lnCheckSum = lnCheckSum + VAL(SUBS(lcRet,lnI,1)) * 1
    ENDIF
  ENDFOR
  lnAux = MOD(lnCheckSum,10)
  lcRet = lcRet + ALLTRIM(STR(IIF(lnAux = 0,0,10-lnAux)))
  IF tlCheckD
    *--- If I only generate a check digit
    RETURN lcRet
  ENDIF
  *--- To print with True Type Font PF_EAN_PXX
  *--- 1st. dígit (lnPri)
  lnPri = VAL(LEFT(lcRet,1))
  *--- Character code table
  *--- according to 'lnPri' (¡DO NOT TOUCH!)
  laJuego(1) = 'AAAAAACCCCCC' && 0
  laJuego(2) = 'AABABBCCCCCC' && 1
  laJuego(3) = 'AABBABCCCCCC' && 2
  laJuego(4) = 'AABBBACCCCCC' && 3
  laJuego(5) = 'ABAABBCCCCCC' && 4
  laJuego(6) = 'ABBAABCCCCCC' && 5
  laJuego(7) = 'ABBBAACCCCCC' && 6
  laJuego(8) = 'ABABABCCCCCC' && 7
  laJuego(9) = 'ABABBACCCCCC' && 8
  laJuego(10) = 'ABBABACCCCCC' && 9
  *--- Initial character (outside the code)
  lcIni = CHR(lnPri + 35)
  *--- Lateral and central characters
  lcLat = CHR(33)
  lcMed = CHR(45)
  *--- Rest of the characters
  lcResto = SUBS(lcRet,2,12)
  FOR lnI = 1 TO 12
    lcJuego = SUBS(laJuego(lnPri + 1),lnI,1)
    DO CASE
      CASE lcJuego = 'A'
        lcResto = STUFF(lcResto,lnI,1,CHR(VAL(SUBS(lcResto,lnI,1)) + 48))
      CASE lcJuego = 'B'
        lcResto = STUFF(lcResto,lnI,1,CHR(VAL(SUBS(lcResto,lnI,1)) + 65))
      CASE lcJuego = 'C'
        lcResto = STUFF(lcResto,lnI,1,CHR(VAL(SUBS(lcResto,lnI,1)) + 97))
    ENDCASE
  ENDFOR
  *--- Compose code
  lcCod = lcIni + lcLat + SUBS(lcResto,1,6) + lcMed + ;
    SUBS(lcResto,7,6) + lcLat
  RETURN lcCod
ENDFUNC
*------------------------------------------------------
* FUNCTION _StrToEan8(tcString,.T.) * CODIGO EAN-8
*------------------------------------------------------
* Converts a string to be printed with
* True Type Font "PF EAN P36" ó "PF EAN P72"
* PARAMETERS:
*   tcString: 7 digit string (0..9)
*   tlCheckD: .T. Only generates check digit
*             .F. Generates check digit and characters to be printed
* USE: _StrToEan8('1234567')
* RETURNS: Character
*------------------------------------------------------
FUNCTION _StrToEan8(tcString,tlCheckD)
  LOCAL lcLat, lcMed, lcRet, ;
    lcIni, lcCod, lnI, ;
    lnCheckSum, lnAux
  lcRet = ALLTRIM(tcString)
  IF LEN(lcRet) # 7
    *--- Parameter error
    *--- must have length = 7
    RETURN ''
  ENDIF
  *--- Generate check digit
  lnCheckSum = 0
  FOR lnI = 1 TO 7
    IF MOD(lnI,2) = 0
      lnCheckSum = lnCheckSum + VAL(SUBS(lcRet,lnI,1)) * 1
    ELSE
      lnCheckSum = lnCheckSum + VAL(SUBS(lcRet,lnI,1)) * 3
    ENDIF
  ENDFOR
  lnAux = MOD(lnCheckSum,10)
  lcRet = lcRet + ALLTRIM(STR(IIF(lnAux = 0,0,10-lnAux)))
  IF tlCheckD
    *--- If I only generate check digit
    RETURN lcRet
  ENDIF
  *--- To print with True Type Font PF_EAN_PXX
  *--- Lateral and central characters
  lcLat = CHR(33)
  lcMed = CHR(45)
  *--- Characters
  FOR lnI = 1 TO 8
    IF lnI <= 4
      lcRet = STUFF(lcRet,lnI,1,CHR(VAL(SUBS(lcRet,lnI,1)) + 48))
    ELSE
      lcRet = STUFF(lcRet,lnI,1,CHR(VAL(SUBS(lcRet,lnI,1)) + 97))
    ENDIF
  ENDFOR
  *--- Compose code
  lcCod = lcLat + SUBS(lcRet,1,4) + lcMed + SUBS(lcRet,5,4) + lcLat
  RETURN lcCod
ENDFUNC
*------------------------------------------------------
* FUNCTION _StrToI2of5(tcString) * INTERLEAVED 2 OF 5
*------------------------------------------------------
* Converts a string to be printed with
*   True Type Font "PF Interleaved 2 of 5"
*   or "PF Interleaved 2 of 5 Wide"
*   or "PF Interleaved 2 of 5 Text"
*   Only numeric characters
* USE: _StrToI2of5('1234567890')
* RETURNS: Character
*------------------------------------------------------
FUNCTION _StrToI2of5(tcString)
  LOCAL lcStart, lcStop, lcRet, lcCheck, ;
    lcCar, lnLong, lnI, lnSum, lnAux
  lcStart = CHR(40)
  lcStop = CHR(41)
  lcRet = ALLTRIM(tcString)
  *--- Generate check digit
  lnLong = LEN(lcRet)
  lnSum = 0
  lnCount = 1
  FOR lnI = lnLong TO 1 STEP -1
    lnSum = lnSum + VAL(SUBSTR(lcRet,lnI,1)) * ;
      IIF(MOD(lnCount,2) = 0,1,3)
    lnCount = lnCount + 1
  ENDFOR
  lnAux = MOD(lnSum,10)
  lcRet = lcRet + ALLTRIM(STR(IIF(lnAux = 0,0,10 - lnAux)))
  lnLong = LEN(lcRet)
  *--- Length must be even
  IF MOD(lnLong,2) # 0
    lcRet = '0' + lcRet
    lnLong = LEN(lcRet)
  ENDIF
  *--- Convert a couple of digits to character
  lcCar = ''
  FOR lnI = 1 TO lnLong STEP 2
    IF VAL(SUBS(lcRet,lnI,2)) < 50
      lcCar = lcCar + CHR(VAL(SUBS(lcRet,lnI,2)) + 48)
    ELSE
      lcCar = lcCar + CHR(VAL(SUBS(lcRet,lnI,2)) + 142)
    ENDIF
  ENDFOR
  *--- Compose code
  lcRet = lcStart + lcCar + lcStop
  RETURN lcRet
ENDFUNC
*------------------------------------------------------

USE EXAMPLES

These are some examples on how to use the conversion functions. All functions have the text to be codified as a CHARACTER parameter.

Example 1: To convert text "This is an example" to Code 128 B:
lcTexto = "This is an example"
lcCodBar = _StrTo128B(lcTexto)
? lcCodBar FONT "PF Barcode 128",36 
Example 2: To convert number 12345678 to Code 128 C (this code only takes even length numbers, if length is odd then it adds a "0" to the left of the number):
lcTexto = "12345678"
lcCodBar = _StrTo128C(lcTexto)
? lcCodBar FONT "PF Barcode 128",36
Example 3: To convert country Argentina ("779"), the company "1234" and the product "01234" to EAN-13 (I can only use 12 characters, the thirteenth character is the check code added by the function):
lcTexto = "779123401234"
lcCodBar = _StrToEAN13(lcTexto)
? lcCodBar FONT "PF EAN P72",72
Example 4: To code number 123456789 to Interleaved 2 of 5 (this code only takes even length numbers, if length is odd then it adds a "0" to the left of the number) with the three true type fonts:
lcTexto = "123456789"
lcCodBar = _StrToI2of5(lcTexto)
*-- I 2of5 Normal
? lcCodBar FONT "PF Interleaved 2 of 5",36
*-- I 2of5 Wide
? lcCodBar FONT "PF Interleaved 2 of 5 Wide",36
*-- I 2of5 Human Readable
? lcCodBar FONT "PF Interleavev 2 of 5 Text",36 
To use these codes in VFP reports, you must format the field with the chosen True Type font, and in the expression you must call the corresponding function passing as parameter the text to be codified.

To codify and show field MiCodigo from table MiTabla in a report field, in the field dialog window, we must input in expression:
_StrTo128B(MiTabla.MiCampo).
Another way is to create a cursor through a SELECT with a field with the data already formatted and generate the report from the cursor:
SELECT *, ;
  _StrTo39(MiTabla.MiCampo) as CodBar ;
  FROM MiTabla ;
  INTO CURSOR MiCursor 

RESULTS SAMPLE

These graphics where generated using the functions and fonts in this document, in VFP reports:

Código 39


Codigo 128 A


Código 128 B


Código 128 C


Código EAN 13


Código EAN 8


Código I 2de5 (ancho)


Código Interleaved 2de5


Código I 2de5 (lectura humana)


DOWNLOADS
FINAL NOTES

Once generated the bar code reports it is convenient to print them in laser printers due to their excellent definition, given this there'll be no problems with the reading of the bar codes.
With bubble jet printers, results are not optimum and maybe some bar code readers wont be able to read the code in the first scan.

These codes may also be placed in VFP forms, but only for visualizing, as no bar code reader will be able to read them from the monitor.

In order to use these functions in FoxPro for Windows, you will have to modify some commands that are not supported by FPW.

Up to the next time!

Luis María Guayán

25 de abril de 2008

FoxCharts (Traducción)

Artículo original: FoxCharts
http://weblogs.foxite.com/vfpimaging/archive/2008/04/04/5919.aspx
Autor: VFPIMAGING
Traducido por: Ana María Bisbé York


¿Desea crear algunos gráficos geniales en VFP?

¿Sin controles ActiveX, dlls o productos de terceros? ¿Qué piensa de estos gráficos?









Recientemente, tuve muchas discusiones con gente que pedía componentes para gráficos. Una vez que GdiPlusX nos brinda todas esas posibilidades, pensé que valdría la pena comenzar un proyecto sobre esto.

FoxCharts será una subclase de la clase ImageCanvas de GdiPlusX, que nos permite dibujar directamente sobre un objeto imagen, entre muchos otras características útiles y estupendas, que están fuera del alcance de este escrito.

Objetivos de FoxCharts:
  • Crear gráficos bonitos y modernos en puro VFP
  • No hay componentes ActiveX
  • Fácil de configurar
  • Fácil de personalizar.
  • Fácil de guardar en disco o imprimir
  • Código abierto (open source)
  • Aprovechar las capacidades de dibujos de GdiPlusX, que permiten a los usuarios modificar los gráficos de la forma que quieran.
  • Guardar como EMF, resultando gráficos perfectos al imprimir en informes VFP.

21 de abril de 2008

La función AERROR() retorna información ampliada para los errores 1104 y 1105

Artículo de la Base de Conocimientos de Microsft que indica que Visual FoxPro 9.0 SP2 mejoró la información de AERROR() para los errores 1104 y 1105.

El enlace al artículo original es el siguiente: AError() function returns extended information for errors 1104 and 1105 in Visual FoxPro 9.0 SP2

Estos errores son: Error al leer el archivo (1104) y Error al escribir el archivo (1105). El array producido por AERROR() contiene más información del error, que consiste en el número de error y mensaje de error del sistema operativo. El número de error y mensaje de error se almacenan en los elementos 6 y 7 del array. La información adicional de este error corresponde a los valores devueltos por la función GetLastError() de la API de Windows .

El siguiente ejemplo muestra una rutina ON ERROR que utiliza la función AERROR() para obtener mas información acerca del error 1104:

ON ERROR DO ErrHand      

Error 1104 && Esto causa el mensaje de error de lectura de archivo

ON ERROR

PROCEDURE ErrHand
   = AERROR(aErrorArray)
   DISPLAY MEMORY LIKE aErrorArray 
ENDPROC 

Si se tratara de un error 1104 real, el sexto elemento del array seria el número de error del sistema operativo, y el séptimo elemento es el mensaje de error del sistema operativo.

16 de abril de 2008

Búsquedas de archivos y texto con Filer.dll

El componente Filer.dll que se incluye desde las primeras versiones de Visual FoxPro nos brinda un motor de búsqueda de archivos y de texto.

Este componente DLL no tiene una interfaz de usuario. Se puede crear una instancia del objeto Filer en un programa, y buscar archivos y textos sin la intervención del usuario.

Una vez instanciado, se especifican las condiciones de búsqueda de archivos y/o texto, y se ejecuta el método Find(). Este método retorna un objeto colección con sus propiedades, que permiten obtener información de los archivos que cumplen las condiciones de la búsqueda; y métodos que permiten editar o eliminar estos archivos.

Ejemplos

1. Búsqueda del archivo "Customer.dbf":
LOCAL loFiler AS 'Filer.FileUtil'

*-- Creo el objeto
loFiler = CREATEOBJECT('Filer.FileUtil')
*-- Indico la ruta
loFiler.SearchPath = HOME(1)
*-- Indico el archivo o mascara (Ej: *.dbf)
loFiler.FileExpression = 'Customer.dbf'
*-- Indico que busque en subcarpetas
loFiler.SubFolder = 1
*-- Busco...
loFiler.Find(0)

IF loFiler.Files.Count > 0
  ? TRANSFORM(loFiler.Files.Count) + ' archivo/s encontrado/s'
  ?
  FOR lnCant = 1 TO loFiler.Files.Count
    WITH loFiler.Files.Item(lnCant)
      ? 'Archivo ' + TRANSFORM(lnCant)
      ? 'Ruta: ' + .Path
      ? 'Nombre: ' + .Name
      ? 'Tamaño: ' + TRANSFORM(.Size)
      ? 'Creado: ' + TRANSFORM(DATETIME(1899,12,30) + .DateTime * 86400)
      ? 'Modificado: ' + TRANSFORM(DATETIME(1899,12,30) + .LastWriteTime * 86400)
      ? 'Ultimo acceso: ' + TRANSFORM(DATETIME(1899,12,30) + .LastAccessTime * 86400)
      ?
    ENDWITH
  ENDFOR
ELSE
  ? 'El archivo no se encontró.'
ENDIF
loFiler = NULL
2. Búsqueda del texto "Microsoft" en archivos "*.TXT":
LOCAL loFiler AS 'Filer.FileUtil'

*-- Creo el objeto
loFiler = CREATEOBJECT('Filer.FileUtil')
*-- Indico la ruta
loFiler.SearchPath = HOME(1)
*-- Indico el archivo o mascara (Ej: *.dbf)
loFiler.FileExpression = '*.TXT'
*-- Indico que NO busque en subcarpetas
loFiler.SubFolder = 0
*-- Indico el texto a buscar
loFiler.SearchText1 = "Microsoft"
*-- Busco...
loFiler.Find(0)

IF loFiler.Files.Count > 0
  FOR lnCant = 1 TO loFiler.Files.Count
   *-- Edito el archivo
   loFiler.Files.Item(lnCant).Edit
  ENDFOR
ELSE
  MESSAGEBOX('No se encontró el texto en ningún archivo.',64,'Aviso')
ENDIF
loFiler = NULL

Notas adicionales

1. Visual FoxPro incluye un formulario de ejemplo, donde se muestra una interfaz de usuario para Filer.dll. Para ver este formulario, ejecute:
DO FORM (HOME(1) + 'Tools\Filer\Filer.scx')
Al ejecutar el formulario Filer.scx se agrega el elemento Filer al menú de Herramientas (Tools) de Visual FoxPro, que permanece por toda la sesión de VFP.
2. Las propiedades de fechas y horas (DateTime, LastWriteTime y LastAccessTime), retornan un valor numérico, donde la parte entera es la cantidad de días transcurridos desde el 30 de diciembre de 1899; y la parte fraccionaria es la fracción del día que determina la hora.

3. Todos los archivos que se encuentran en la carpeta \Tools\Filer del directorio de instalación de Visual FoxPro pueden ser distribuidos con nuestras aplicaciones.

4. Puede ver información mas detallada de todas las propiedades y métodos del objeto Filer, en la ayuda de VFP, bajo el tema Filer.dll.

Luis María Guayán

14 de abril de 2008

Objetos con fondos de colores degradados con GDI+ Revisado

Artículo original: GRADIENT OBJECTS WITH GDI+ REVISITED 
http://weblogs.foxite.com/vfpimaging/archive/2006/07/26/2076.aspx
Autor: Cesar Ch.
Traducido por: Ana María Bisbé York


He aquí la última versión de la clase GradientObjects.

Muchas gracias a: Craig Boyd, Bo Durban, Emerson S. Reed, Malcolm Greene, Keith Gordijn, Luis Navas, Ana Bisbe, Ailsom Heringerlos y todos los otros que han probado la clase, brindando valiosas opiniones y sugerencias.

La principal modificación de la versión anterior es la creación de una clase nueva, como sugirió Craig Boyd:

"... Crea una clase de usuario que ofrezca esta funcionalidad en lugar de codificar directamente en la clase botón.

Una clase que utilice bindevents para enlazar con los botones (o cualquier elemento de interfaz de usuario que se utilice) va a brindar un nivel adicional de abstracción. Esto permitiría a los desarrolladores utilizar la clase con cualquier otro elemento (por ejemplo, contenedores) y también brinda una vía rápida y fácil para añadir esta funcionalidad a aplicaciones existentes donde tienen sus propias subclases botones (o están utilizando las clases base de VFP).

Añade la clase al formulario y configura algunas propiedades."

Otra modificación importante fue "... cambiar cualquier control optionbutton o checkbox y establecer Graphical en su propiedad Style para el degradado del botón." como sugirió Bo Durban.

Emerson S Red brindó muchas imágenes para mostrar el comportamiento deseado para la clase.

Características:
  • Soltar una instancia de la clase en el formulario o contenedor, y todos los colores de los botones se convertirán en degradados. Se aplica a controles CommandButton, y CheckBox y ListBox gráficos.
  • Ofrece 9 tipos de degradado
  • Selecciona diferentes colores de fondo (BackColor) cuando el objeto tiene el foco y cuando no.
  • Crea imágenes con escalas de grises (GreyScale) para botones inhabilitados.
  • Define la posición de la imagen (Picture Position)
  • Convierte los bordes de la imagen en trasparente (independientemente del color que utilice)
  • Las teclas de acceso directo (\<) especificadas en la propiedad Caption se muestran normalmente y mantienen su funcionamiento.




Cómo se crea:
  • La clase crea un archivo de imagen nuevo que imita la apariencia original del botón, utilizando todas las propiedades de texto, como Caption, Fontbold, FontItalic y la imagen original, todo redibujado con un fondo con gradiente especificado.
  • El uso extensivo de GDI+ para crear imágenes, 100 % con llamadas directas a API, para obtener la máxima velocidad en la creación de imágenes que serán sustituidas en los botones.
  • En el primer momento, se crean solamente las imágenes principales de los botones. Solamente cuando el ratón o el foco está sobre un objeto específico que crea esa clase en la "Imagen seleccionada". Lo mismo ocurre con "Escalas de grises de imágenes inhabilitadas".
  • Muchos eventos se controlan utilizando BindEvents, como: MouseMove, MouseEnter, GotFocus, LostFocus.
  • Se crearon tres tipos de gradientes diferentes utilizando funciones diferentes.
  • Para los que tengan GradientModes de 1 a 4, utilizados GdipCreateLineBrushFromRectI para crear un gradiente sencillo con brocha lineal con 2 colores.
  • Para los que tengan GradientModes de 5 a 8, GdipCreateLineBrushFromRectI se emplea nuevamente en combinación con GdipSetLinePresetBlend, que de acuerdo con MSDN "define matrices de colores y posiciones utilizadas para interpolar mezclas de colores en un degradado multicolor." Fue utilizado para crear un color 3 de degradado comenzando por la propiedad BackColor1 con BackColor2 en el centro y nuevamente BackColor1 en la otra punta.
  • Para los que tengan GradientModes igual a 9, he creado un PathGradientBrush utilizando GdipCreatePath, GdipAddPathRectangle, GdipCreatePathGradientfromPath, GdipSetPathGradientCenterColor y GdipSetPathGradientSurroundColor. Hay un ejemplo muy bueno utilizado en el último artículo de Craig Boyd publicado en FoxTalk 2.0. El color que rodea está definido por la propiedad BackColor1. El color del centro está definido por BackColor2.
  • La clase GDI+ ImageAttributes es utilizada para aplicar una matriz de color para crear imágenes GreyScale (con escalas de grises) para botones inhabilitados. Se utiliza también para crear un fondo transparente de las imágenes utilizando la función RemapTable.
  • Las teclas de acceso directo están habilitadas utilizando el StringFormatHotKeyPrefix
Propiedades a configurar en la clase

Vea la ficha Favoritos en la ventana Propiedades.


  • BackColor1 - Numérico, el valor RGB del color inicial del fondo degradado cuando el objeto NO tiene el foco y el ratón no se está moviendo sobre el.
  • BackColor2 - Numérico, el valor RGB del color de destino del fondo degradado cuando el objeto NO tiene el foco y el ratón no se está moviendo sobre el.
  • SelBackColor1 - Numérico, el valor RGB del color inicial del fondo degradado cuando el objeto tiene el foco o el ratón se está moviendo sobre el.
  • SelBackColor2 - Numérico, el valor RGB del color de destino del fondo degradado cuando el objeto tiene el foco o el ratón se está moviendo sobre el.
  • CaptionForeColor - Numérico, si se configura, sobreescribe el color original establecido para los controles por la propiedad ForeColor.
  • CaptionBold - Lógico, si se configura, sobreescribe el aspecto original establecido para los controles por la propiedad CaptionBold.
  • CreateBorder - Lógico, establece el borde con el color de BackGround1.
CreateBorder = .T.

CreateBorder = .F.
  • GradientMode - Numérico, de 1 a 9, determina el tipo de degradado a aplicar en los botones.
1 - Horizontal 2 - Vertical 3 - Diagonal1 4 - Diagonal2 utilizando una brocha lineal con dos colores
5 - Horizontal 6 - Vertical 7 - Diagonal1  8 - Diagonal2 utilizando una brocha lineal con tres colores, donde el color que rodea es BackColor1 y el color de centro es BackColor2
9 Tipo PathGradient rectangular, donde el color que rodea es BackColor1 y el color de centro es BackColor2


  • ReduceColorLevel - Numérico, configura automáticamente el color de destino del degradado (backcolor2 y selbackcolor2) desde 0 (no cambia) a 100 (blanco). Si es .F., no se aplica el cambio y se utilizará el valor original de backcolor2 y selbackcolor2.
  • CreateDisabledPicture - Lógico, .T. = crear una imagen con escala de grises de los botones para ser usadas cuando se desactivan uno o más controles.
  • MouseDownEffect - Lógico, determina si el botón se oscurecerá o se aclarará cuando baja el ratón
  • MouseDownBrightness - Numérico, porcentaje de luminosidad, -100 = Negro,0 = no cambia; +100 = Blanco
  • TranspImgBack - Lógico, determina si la pintura tendrá su fondo convertido a transparente (alpha = 0). De forma predeterminada, la clase admitirá el color del primer pixel, coordenadas (0,0) tiene fondo transparente, y sustituye todos los colores que coincidan con este color por transparente.
Redibujar un objeto específico

En muchos casos, necesitamos cambiar por código algunas propiedades de algunos botones, como su título (Caption). Cuando utilice esta clase, usted necesita llamar al método UpdateControl de la clase, como en el ejemplo siguiente:
Thisform.CmdButton1.Caption = "New Caption"
Thisform.CmdButton1.FontItalic = .T.
Thisform.Gradobjects1.UpdateControl(Thisform.CmdButton1) 

Asignar diferentes efectos a algunos botones específicos

Es muy fácil de conseguir. Ponga el botón de comando en un contenedor aparte y suelte una instancia de la clase, configurando sus propiedades según sea necesario.

Debajo se muestra un formulario en modo desarrollo y el resultado, cuando se ejecuta.





Esta es la primera versión de esta clase, seguramente necesitará más pruebas. Si encuentra algún error, dígamelo, por favor! Todas las sugerencias serán muy bien recibidas.

Puede descargar la clase de este enlace: http://www.geocities.com/macmarbr/gradobjects.zip

Algunos enlaces relacionados:

Alguna información extra sobre este tema podrá encontrar en:
31-07-06

Reparado: Ahora trabajan normalmente las teclas de acceso directo en títulos (Caption) "\<"

Reparado: Cuando pasa el ratón sobre un botón que tiene el foco se mantiene el efecto "seleccionado".

Reparado: Si un usuario elimina el valor de la propiedad Backcolor y lo cambia a (Ninguno) el control fuerza al predeterminado. Es una medida de seguridad, gracias a Bernard Bout.

Actualización: Nuevo formulario que muestra cómo agregar gradientes diferentes a un formulario. Ver "twoGradients.scx" en la carpeta principal.

03-08-06

Reparado: Error en LOSTFOCUS / GOTFOCUS

Actualización: El método "UpdateControl" permite cambiar el color de los objetos

Actualización: El método "ChangeColors" admite valores desde -100 a 100. -100 = Negro; 0 = No cambia;  +100 = Blanco

Actualización: Formulario "TestGradAnimation.scx" que muestra como hacer un botón "Parpadeo" con los efectos

Actualización: Nuevas propiedades "MouseDownEffect" y "MouseDownBrightness"

MouseDownEffect - Lógica, determina si el botón se oscurecerá o aclarará cuando el ratón está presionado

MouseDownBrightness - Numérico, Luminosidad -100 = Negro; 0 = No cambia;  +100 = Blanco

Nota

Esta clase es totalmente gratis. La información brindada en esta página y el código descrito en este artículo no tiene garantía absoluta. úselo bajo su propio riesgo.