29 de junio de 2007

Como determinar el tipo verdadero de un archivo de imagen

Artículo original: How to determine the real type of an Image file
http://weblogs.foxite.com/vfpimaging/archive/2007/06/03/3865.aspx
Autor: Cesar Ch.
Traducido por: Ana María Bisbé York

¿Ha notado que puede cambiar la extensión de un archivo de imagen a otros tipos de archivos de imagen, y Windows aun puede mostrar esas imágenes?

En efecto, cuando renombramos por ejemplo la imagen "YourPicture.Bmp" a "YourPicture.Jpg", todo el archivo permanece intacto, sólo la extensión de archivo es cambiada. Cuando Windows abre esa imagen, encuentra la información en la cabecera de la imagen, al principio del archivo, una firma específica que indica el tipo verdadero de imagen con la que está tratando.

Tore Bleken, de Noruega, ha creado una función muy sencilla y útil que busca cierta información específica en la cabecera del archivo. Él simplemente usó un editor hexadecimal, y examinó varios archivos. ¡Muchas gracias a Tore por el código y permitir publicarlo aquí!

FUNCTION FileType
  LPARAMETERS lcData

  LOCAL lcReturn,lcContents

  IF PCOUNT()=0 OR VARTYPE(lcData)#'C'
    lcReturn=''
  ELSE
    IF ADIR(laDummy,lcData)>0 && Archivo
       lcContents=FILETOSTR(lcData)
    ELSE && Variable de memoria
       lcContents=lcData
    ENDIF

    DO CASE
      CASE LEN(lcContents)<4
         lcReturn=''
      CASE LEFT(lcContents,3)=CHR(0xFF)+CHR(0xD8)+CHR(0xFF)
         lcReturn='JPG'
      CASE LEFT(lcContents,3)='GIF'
         lcReturn='GIF'
      CASE SUBSTR(lcContents,42,3)='EMF'
         lcReturn='EMF'
      CASE LEFT(lcContents,4)=CHR(0xD7)+CHR(0xCD)+CHR(0xC6)+CHR(0x9A)
         lcReturn='WMF'
      CASE LEFT(lcContents,4)=CHR(0x4D)+CHR(0x4D)+CHR(0x00)+CHR(0x2A)
         lcReturn='TIF'
      CASE LEFT(lcContents,4)=CHR(0x89)+'PNG'
         lcReturn='PNG'
      CASE LEFT(lcContents,2)='BM'
         lcReturn='BMP'
      CASE LEFT(lcContents,3)='CWS' AND ASC(SUBSTR(lcContents,4,1))<16
         lcReturn='SWF'
      CASE LEFT(lcContents,3)='FWS' AND ASC(SUBSTR(lcContents,4,1))<16
         lcReturn='SWF'
      OTHERWISE
         lcReturn=''
    ENDCASE

  ENDIF
  RETURN lcReturn
ENDFUNC

27 de junio de 2007

Imágenes con aspecto borroso (blur) con GdiPlusX

Artículo original: Blur Images with GdiPlusX
http://weblogs.foxite.com/vfpimaging/archive/2007/06/22/4153.aspx
Autor: Cesar Ch.
Traducido por: Luis María Guayán 


Lograr un aspecto borroso en imágenes es también muy fácil.

La técnica más simple es redimensionar la imagen original a un tamaño MUCHO MÁS PEQUEÑO, y después volver a redimensionarla al tamaño original.

La razón es muy obvia, cuando aumentamos las dimensiones de cualquier imagen, tenemos una pérdida de calidad, causando el efecto de aspecto borroso.

IMPORTANTE
Requiere VFP9 y GdiPlusX para funcionar. 

¡Por favor asegúrese de tener la última versión!
http://www.codeplex.com/VFPX/Wiki/View.aspx?title=GDIPlusX&referringTitle=Home

* Iniciamos GdiPlusX
_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx")))

WITH _SCREEN.System.Drawing

  * Cargo la imagen original
  LOCAL loBmp as xfcBitmap
  loBmp = .Bitmap.New(GETPICT())

  * Tomo un rectangulo con las dimensiones del bitmap 
  LOCAL loRect as xfcRectangle 
  loRect = loBmp.GetBounds()

  * Inicializo el objeto gráfico para poder dibujar en la imagen 
  LOCAL loGfx AS xfcGraphics
  loGfx = .Graphics.FromImage(loBmp)
  loGfx.Clear(.Color.White)

  LOCAL lnReduceFactor as Integer 
  lnReduceFactor = 10 && la imagen será reducida en 10 veces

  * Tomo una miniatura con el tamaño deseado 
  LOCAL loDestBmp as xfcImage
  loDestBmp = loBmp.GetThumbnailImage(loBmp.Width / lnReduceFactor, loBmp.Height / lnReduceFactor)
  
  * Dibujo la imagen, mostrando la intensidad del canal CYAN. 
  loGfx.DrawImage(loDestBmp, loRect)
  loBmp.Save("c:\blurred.jpg", .Imaging.ImageFormat.Jpeg) 

  RUN /N Explorer.exe c:\Blurred.Jpg

ENDWITH
RETURN

Imagen Original


Factor = 4


Factor = 8


Factor = 12


Factor = 20


Imagen Original


Factor = 4


Factor = 8


Factor = 12


Factor = 20

25 de junio de 2007

VFPPaint - Utilitario flexible de Pintura y Dibujo con GdiPlusX

Artículo original: VFPPaint - Flexible Drawing and Paint utility with GdiPlusX
http://weblogs.foxite.com/vfpimaging/archive/2007/06/14/4033.aspx
Autor: Cesar Ch.
Traducido por: Luis María Guayán


A veces los usuarios tienen que hacer algunas modificaciones básicas en imágenes que son parte de una aplicación, y no es cómodo para ellos tener que buscar archivos, seleccionar sitios para guardar, etc.... En la gran mayoría de los casos, hacen el trabajo con MS-Paint, pero siempre lo necesitan mas personalizado ... y no pueden.

Siendo desafiado por Frank Cazabon en un hilo de Foxite, finalmente tomé el coraje para crear mi propia aplicación "Paint", usando GDI+.

VFPPaint trabaja como MSPaint, y crea un "lienzo" que permite dibujar libremente lo que se desee con el ratón.

Es apenas un solo archivo .SCX de 50 KB que hace todo el trabajo, en conjunto con la librería GdiPlusX.





IMPORTANTE
Requiere VFP9 y GdiPlusX para funcionar. 

La última versión estable de GdiPlusX es la del 11 de Mayo de 2007 (Alfa 0.8a). Por favor asegúrese que tiene la última versión, porque VFPPaint usa algunas funciones que fueron añadidas recientemente.

http://www.codeplex.com/VFPX/Wiki/View.aspx?title=GDIPlusX&referringTitle=Home

Cuando ejecute VFPPaint.SCX, le pedirá que seleccione la carpeta donde está ubicada la librería GDIPlusX.

Características:
  • Dibujar puntos, líneas, rectángulos y elipses con el ratón.
  • Escribir texto, elegir cualquier fuente, tamaño y estilo, y aparecerá en el lugar que usted haga clic en la imagen. (arrastre y suelte para elegir el mejor lugar).
  • Seleccionar cualquier color, desde la paleta o directamente de la imagen cargada o lienzo y seleccionar el ancho de pluma que quiere dibujar.
  • Seleccionar la forma o el tipo del dibujo con los botones de opciones gráficas, y luego ir al lienzo (objeto Imagen) y cliquear con el botón izquierdo del ratón.
  • Arrastrar y soltar está disponible para rectángulos, líneas y elipses.
  • Característica básica de DESHACER.
  • Rotar y voltear imágenes.
  • Cambiar el tamaño de las imágenes.
  • Imprimir y guardar imágenes
Comandos Básicos:

Cargar imagen al lienzo
Guardar imagen en el formato de imagen deseado
Imprimir imagen
Deshacer el últimos cambio
Seleccionar un color de la paleta
Seleccionar un color del lienzo
Limpiar todo el lienzo con el color seleccionado
Rotar a la izquierda
Rotar a la derecha
Voltear imagen horizontalmente
Voltear imagen verticalmente
Dibujar puntos
Dibujar rectángulo
Dibujar elipse
Dibujar línea
Rectángulo lleno
Elipse llena
Dibujar texto
Cambiar el tamaño a la imagen
Seleccionar Fuente, Tamaño y Tipo

Todas las imágenes son generadas sin acceso a disco, usando la nueva propiedad PictureVal del control Imagen. Para este caso específico, no usé el ImageCanvas que viene con GdiPlusX porque necesité un poco de personalización extra. Después de la última liberación de GdiPlusX, se consiguió que el uso de la propiedad PictureVal que tiene el objeto Imagen se hizo muy simpe, con los nuevos métodos agregados a la clase Imagen (GetPictureVal y GetPictureValfromHBitmap).

Como a veces tenemos que trabajar con una imagen que es más grande que nuestra pantalla, el lienzo del dibujo fue puesto en un formulario de nivel superior que tiene barras de desplazamiento.

Otra buena característica es la posibilidad de "DESHACER". VFPPaint almacena el mapa de bit anterior en un cache. Esto puede ser mejorado, tal vez se tiene que "deshacer" en el nivel más alto. Para esto, muy poco código será necesario.

Usando el lienzo, trate de arrastrar y soltar los objetos. Para esto, la característica "deshacer" fue muy usada, porque la forma es dibujada, y cuando el usuario mueve al ratón, se restaura la imagen original desde el buffer y se dibuja otra vez en la nueva posición.

Cuando una imagen muy grande es cargada, primero se pregunta si el lienzo tiene que ser redimensionado.



Las barras de desplazamiento aparecen en la imagen, para ayudar a recorrerla. En un primer momento, pensé en usar el muy buen control contenedor con barras de desplazamiento CTL32 de Carlos Alloatti y Malcolm Greene, pero me rendí porque necesitaba la propiedad PictureVal, que está presente sólo en el control Imagen. De este modo, hay actualmente 2 formularios en el "screenshot" de abajo. Uno es "en un formulario de nivel superior" que contiene sólo un objeto Imagen, y reside por lo general el formulario de VFPPaint. El otro formulario, es "un formulario de nivel superior". El objeto imagen "del formulario hijo" cambia su tamaño automáticamente, según las dimensiones de la imagen. Cuando este objeto es redimensionado, el formulario hijo automáticamente cambia el tamaño; y muestra y ajusta las barras de desplazamiento de ser necesario.



Ejemplo de redimensionado:



VFPPaint también permite que se pase un archivo de imagen como parámetro. De esta manera, puede abrir el formulario cargando automáticamente la imagen deseada para editar. Ejecute el formulario con:

DO FORM vfppaint WITH "eyes.gif"

Si pasa una imagen GIF como un parámetro, será automáticamente convertida a 24bpp. Esto es porque las imágenes GIFs son indexadas. Un ejemplo clásico de imágenes indexadas son las monocromáticas. Las imágenes monocromáticas puras usan sólo 1 bit por pixel. ¡De este modo, en sólo un byte usted puede almacenar la información de 8 pixeles! En el caso de GIFs, estas son por lo general 8 bits por pixel, entonces en un byte podemos tener un valor que se extiende de 0 a 255, esto es exactamente la cantidad de colores soportados por los GIFs. Lamentablemente, la versión redistribuible de GDI+ tiene una limitación trabajando con imágenes creadas en formatos de pixeles indexados, como 1bppIndexed (0x00030101), 4bppIndexed (0x00030402) y 8bppIndexed (0x00030803). Si trata de usar este tipo de imágenes, recibirá un mensaje de error cuando se crea el objeto Gráfico, que le permite usar la imagen. Mas información detallada sobre esto, la puede encontrar en el artículo: DRAWING ON GIFS OR INDEXED PIXEL FORMAT IMAGES WITH GDI+ (Nota del traductor: Este artículo se encuentra traducido en PortalFox en: Dibujar o indexar formatos de píxel a imágenes con GDI+). Por esto los GIFs son convertidos a un formato de 24bpp antes de inicializar las posibilidades de edición.

Esto aun tiene mucho que puede ser mejorado, como la adición de efectos especiales, como brillo, contraste, ajustes de tonalidad, cortar y pegar partes de la imagen, guardar y recuperar imágenes o trozos de la imagen en el portapapeles, etc.

Pero lo principal es que es totalmente configurable. Lo más importante en este formulario, es la técnica que fue usada, que verá que no hay demasiado código allí.

Esto todavía tiene que ser probado. Siéntase libre de contactarme si encuentra algún problema o tiene algunas sugerencias. Quizás algunas de sus necesidades pueden ser de interés para ser agregado a VFPPaint, y será un placer mejorarlo.

¡Cualquier comentario será bienvenido!

Descarga de VFPPaint:
http://weblogs.foxite.com/files/vfpimaging/vfppaint/vfppaint.zip

15 de junio de 2007

Redondear hacia arriba o hacia abajo

A veces necesitamos redondear un número de una manera diferente que la función ROUND() nativa de VFP. Las siguientes funciones redondean un número "hacia arriba" o "hacia abajo" en los decimales, unidades, decenas, etc. como lo muestran los siguientes ejemplos, según el segundo parámetro pasado.
? RedondearMas(123.123,-2) 
? RedondearMenos(123.123,-2)

? RedondearMas(123.123,0) 
? RedondearMenos(123.123,0)
 
? RedondearMas(123.123,1) 
? RedondearMenos(123.123,1)
FUNCTION RedondearMas(tnNro, tnPos) 
  RETURN CEILING(tnNro/10^tnPos)*10^tnPos 
ENDFUNC 

FUNCTION RedondearMenos(tnNro, tnPos) 
  RETURN FLOOR(tnNro/10^tnPos)*10^tnPos 
ENDFUNC 
Luis María Guayán

13 de junio de 2007

Validar cuenta de correo electrónico

Función enviada al Grupo de Noticias por Anders Altberg que valida una cuenta de e-mail.
FUNCTION ValidarCuentaEmail
  LPARAMETERS email && la cuenta
  loRegExp = CreateObject("VBScript.RegExp")
  loRegExp.IgnoreCase = .T.
  loRegExp.Pattern =  '^[A-Za-z0-9](([_\.\-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([\.\-]?[a-zA-Z0-9]+)­*)\.([A-Za-z]{2,})$'
  m.valid = loRegExp.Test(m.email)
  RELEASE loRegExp
  RETURN M.valid
ENDFUNC

11 de junio de 2007

Cliente-Servidor: Manejo de Fechas

Los servidores de bases de datos tienen, al igual que Visual FoxPro, la capacidad de manejar fechas. VFP tiene la capacidad de conectarse a cualquier DBMS (DataBase Management System), ya sea por medio de ODBC o de ADO, el manejo de fechas no es nada complicado como veremos a continuación.

Primero que nada, debemos tener en cuenta dos puntos importantísimos al respecto.

No existe el tipo de datos Date (Fecha)

En efecto, los DBMS no tienen un tipo de datos Date, en su lugar, se tiene un tipo de datos DateTime (Fecha-Hora), el cuál, al igual que el tipo de dato de DateTime de VFP tiene una precisión de segundos, es decir, un format Dia, Mes, Año, Hora, y Segundos. En VFP las variables DateTime se pueden definir con la función DateTime() o mapearse directamente sobre la asignación de un campo tipo DateTime:
ltFechaInicio = DateTime(lnAnnio, lnMes, lnDia, lnHora, lnSegundos)
  ltFechaFin = Cursor.tFechaFin
Los tipos de datos DateTime, a diferencia de los Date, se basan en segundos, y no en días, por lo que su aritmética varía en consecuencia, nuestro compañero Luis María Guayán ya he escrito un extenso documento sobre esto aquí:

--- Trabajar con fechas y horas en Visual FoxPro ---
http://comunidadvfp.blogspot.com/2014/09/trabajar-con-fechas-y-horas-en-visual.html

Por lo tanto, debemos tener en cuenta este detalle y acostumbrarse a tal hecho, claro está, dependiendo de tu DBMS podrías retornar otro tipo de datos, o en su defecto, sobre el cursor resultante, aplicar la conversión de datos si así fuere necesario.

No existen las fechas vacias

Los DBMS no entienden el concepto de las fechas vacias que maneja VFP, de hecho, dependiendo del manejador de base de datos, obtendrás resultados inesperados, o un error.

Entonces, ¿ Cómo debemos manejar las fechas vacias (ldFecha = {}) ?, las fechas vacias, deberían convertirse todas a valores nulos (ldFecha = .null.), y en las consultas SQL, manejar las funciones pertinentes.

--- ¿Qué es un valor null entre amigos? ---
http://comunidadvfp.blogspot.com/2014/09/que-es-un-valor-null-entre-amigos.html

--- NULL (SQL) ---
http://en.wikipedia.org/wiki/Null_(SQL)

--- Working With Columns That Contain Null Values ---
http://www.databasejournal.com/features/mssql/article.php/3399931

De este modo, si estás contemplando hacer un upsizing desde tablas DBF a un servidor de base datos, entonces, antes deberás cambiarlas a nulos todos aquellos valores que fueran vacios.
UPDATE MiTabla SET dFecha = null WHERE EMPTY(dFecha)

Trabajando con Fechas en los Servidores de Base de Datos

Una vez contemplado la anterior, podemos introducirnos al tema, y como comentaba líneas arriba, no es nada difícil, sólo debemos usar la Parametrización.

En el caso de que manejemos SQL Pass Trough (SPT), sabemos que deberíamos envíar una cadena de texto, es decir, sólo la consulta SQL, y claro, podremos olvidarnos de formatearlo para que concuerde con la manera en que tu servidor maneje los datos.

Durante mucho tiempo se ha debatido en los foros de discusión, que las fechas deberían ser mandadas con formato estándar ANSI: AAAAMMDD.HHSS (20070506.1320), la verdad que eso no es tan cierto, por lo menos en VFP, lo que debemos hacer es hacer uso de la parametrización.

Como muestra, un pequeño código que podríamos utilizar para mandar una consulta a un servidor de Base de Datos SQL Server (cualquier versión):

WITH Thisform
   ltFechaInicio = .txtFechaInicio.Value
   ltFechaFin = .txtFechaFin.Value
ENDWITH

TEXT TO lcConsulta NOSHOW
  SELECT iID, cIDFactura, tFecha, yImporte, nCantidad
     FROM DetalleFactura 
     WHERE Fecha BETWEEN ?ltFechaInicio AND ?ltFechaFin
ENDTEXT

IF SQLExec(lnHandle, lcConsulta, lcCursor) > 0
   *** Hacer algo
ELSE
   IF AERROR(laError) > 0
      Messagebox("Error en la consulta:"+laError[2])
   ENDIF
ENDIF

Sencillo? Si, la magia se realiza justo en el driver ODBC, VFP se encarga de comunicarle que es un tipo de datos DateTime y hará la conversión pertinente. Tan fácil como eso.

Retomando el caso de los valores nulos

Como mencionabamos anteriormente, los servidores de Bases de Datos no manejan el concepto de fechas vacias, por lo que a la hora de mandar ese dato, debemos "convertir" las variables a null, y así mandarlas al servidor.

WITH Thisform
   iProductoID = .txtProductoID.Value
   nCantidad = .txtCantidad.Value
   yPrecioVenta = .txtPrecio.Value   
   IF EMPTY(.txtFecha.value)
      tFechaVenta = .null.
   ENDIF
ENDWITH
TEXT TO lcSQL NOSHOW
   INSERT INTO DetalledeVenta (iProductoID,nCantidad, yPreciodeVenta, tFechaVenta)
     VALUES (?iProductoID, ?nCantidad, ?yPrecioVenta, ?tFechaVenta)
ENDTEXT

Notas adicionales

Como nota al margen, debemos también tomar en cuenta que al basarse en tipo de datos DateTime, se debe hacer especial énfasis en que los inicios y fines del día.

Como un ejemplo, supongamos que quisieramos obtener los movimientos realizados únicamente en la fecha 11 de Junio de 2007, entonces, al mandar la consulta:

lcTabla = "DetalledeVenta"
ldFechaInicial = DATE(2007,06,11)
ldFechaFinal  = ldFechaInicial+1
TEXT TO lcSQL NOSHOW TEXTMERGE
   SELECT idMovimiento, iProductoID, nCantidad, yPrecioVenta 
      FROM <<lcTabla>>
      WHERE tFechaVenta BETWEEN ?ldFechaInicial AND ?ldFechaFinal
ENDTEXT

IF SQLExec(lnHandle,lcSQL,lcCursor) > 0
  ** Hacer el trabajo con el cursor resultante
   SELECT (lcCursor)
   ***SCAN
   ***ENDSCAN
ELSE
   IF AERROR(laError) > 0
      Messagebox("Error en la consulta:"+laError[2])
   ELSE
      Messagebox("Error inesperado en la aplicacion")
  ENDIF
ENDIF

Cuando usamos la parametrización debemos revisar que los tipos de datos de las variables de VFP correspondan a los tipos de datos de la base de datos SQL, es decir, si tu tipo de datos en tu formulario es numérico y en la base de datos es caracter, entonces antes deberías cambiar la variable a enviar a tipo caracter (¿STR(lnNum, 5)?), como caso especial los tipos de datos Date en VFP son compatibles con los tipos DateTime del controlador ODBC, con la única salvedad de que estas fechas serán contemplados con la hora inicial del día.

Espero que les sea de utilidad.

Espartaco Palma Martínez

8 de junio de 2007

Registrar OCX / DLL en Windows Vista

Antes de Windows Vista cada vez que necesitabamos registrar una libreria ActiveX (OCX, DLL) o un EXE lo podiamos hacer de diversas maneras, una de las comunes era simplemente utilizando el comando REGSVR32 de la suiguiente manera...
regsvr32 [/u] [/s] [/n] [/i[:líneaDeComandos]] nombrelibreria.DLL o OCX
Parámetros:
  • /u: Elimina el registro del servidor.
  • /s: Especifica que regsvr32 se ejecute sin interfaz y que no presente ningún cuadro de mensaje.
  • /n: Especifica que no se invoca DllRegisterServer. Esta opción se tiene que utilizar con /i.
  • /i: líneaComandos Invoca DllInstall y le pasa una [líneaComandos] opcional. Cuando se utiliza con /u, activa la desinstalación de .dll.
  • nombrelibreria.DLL o OCX: Especifica el nombre del archivo .dll que se va a registrar.
  • /?: Muestra Ayuda en el símbolo del sistema.
Al tratar de instalar librerias OCX y/o DLL's en Windows Vista (Asi tambien como arhivos .EXE que necesitan ser registrados) nos damos con la desagradable sorpresa que no se puede, el SO reporta un mensaje de error que dice mas o menos :

Se cargo el modulo Libreria.ocx pero se produjo un error en la llamada a dllRegisterserver (codigo de error: 0x80004005)

Otro error encontrado es:

"Unexpected error; quitting"

Al empezar a indagar con ese problema me di con que los usuarios de VB6 y anteriores tienen el mismo problema asi que e aqui la solucion:

El problema radica fundamentalmente en que Windows Vista hace mucho mas incapie en la seguridad del sistema y ya que cualquiera de estos tipos de archivos son potencialmente peligrosos, a menos que el ususario actual sea el Admnistrador no permitira la registración de estos archivos.

Por lo tanto una solucion es loguearse en el sistema como administrador (Primero debemos activar este usuario ya que por defecto viene deshabilitado y al mismo tiempo desactivar el UAC (User Account Control), todo esto se hace dentro del Panel de control/Control de Usuarios (Debemos aclarar que no alcanza que el usuario actual tenga perfil de administrador debemos logueaes especificamente con la cuenta Administrador o Administrator en su version inglesa)

Una vez hecho esto ya podremos instalar las librerias tal como lo haciamos antes.

Una solucion alternativa y mas rapida es:
  1. Clicear en Inicio
  2. En "Iniciar busqueda" o "Start Search" tipear cmd
  3. Una vez encontrado el icono de cmd en el menu
  4. click derecho en el icono del cmd (command)
  5. elegir la opcion "Run as Administrator" ("Ejecutar como Administrador")
  6. Ir a la carpeta en donde se encuentran las librerias
  7. Tipear nomlibreria.ext /regserver o REGSVR32 nomlibreria.ext (En donde .ext seria OCX/DLL o EXE según el caso)
Esto solucionara el problema y nos permitira probar nuestros sistemas/librerias en el nuevo SO de M$
Para mas información sobre seguridad en windows Vista:

http://technet2.microsoft.com/WindowsVista/en/library/0d75f774-8514-4c9e-ac08-4c21f5c6c2d91033.mspx?mfr=true

Daniel Salazar
www.ZondaSoftware.com.ar
Salta - Argentina

7 de junio de 2007

FontFace de Reportes

Me paso que todos mi reports estaban armados con la fuente 'Tahoma' y queria pasarlos a 'Arial'. Cuando termine el primero me di cuenta que necesitaba casi todo un día para terminar todos los .FRX, asi que, a facilitar la cosa.

En 7 lineas en la ventana de comando pude cambiar todas la fuentes de todos los títulos y textboxes de los informes de mi proyecto. Pueden ver en el codigo que pueden hacer varias otras cosas (ej. Cambiar el tamaño de las letras que reunan alguna(s) condicion(es), etc etc).

EL CODIGO:

SELECT NAME FROM project.pjx WHERE TYPE='R' INTO CURSOR tmp
SCAN ALL
 SELECT 0
 USE (tmp.name)
 REPLACE fontface WITH 'Arial' FOR !EMPTY(FONTFACE)
 USE IN (tmp.name)
ENDSCAN

Guillermo Gastón Giménez

4 de junio de 2007

Cliente-Servidor: Parametrización de Variables

Cuando manejamos la conexión a un DBMS (DataBase Management System) podemos mandar cualquier tipo de datos, no solo caracteres de texto. Aplicable a cualquier servidor de Base de Datos (Microsoft SQL Server, MySQL, PostgreSQL, Firebird, etc).

Es muy común que cuando empezamos a manejar datos de una fuente externa a VFP, es decir, a servidores de base de datos, nos encontremos con la limitación de que únicamente podemos mandar cadenas de texto en las consultas SQL.

Para apoyarnos un poco, tomaremos una típica consulta SQL...
  SELECT iID, cClave, cCliente, cRegistro
     WHERE (iID > <NumeroInferior > ) 
        AND (iID < < NumeroSuperior > )
     FROM Cliente

Donde, obviamente, el "Numero Inferior" y "Numero Superior" serían datos variables a consultar, tomados de algún formulario de VFP.

Nuestro primer intento puede ser intentar formatear la cadena, de manera a que lo que se envía sea igual que la consulta deseada:
   lcConsulta = "  SELECT iID, cClave, cCliente, cRegistro "+
                      "      WHERE (iID > " + STR(thisform.txtNumerInferior.Value) + ")"+
                      "           AND (iID < " + STR(Thisform.txtNumeroSuperior.Value) + ")"+
                      "    FROM Cliente "

Lo anterior funcionará, ya que igual, tenemos una cadena de texto y los valores, que ya han sido convertidos a cadena serán enviados tal cual a nuestro servidor.

Pero eso, al parecer, no es eficiente... Quizás lo podríamos hacer mejor, no? Pues si, como vimos en un artículo pasado, podríamos usar bloques TEXT ... ENDTEXT, para mejorar nuestra presentación:
WITH ThisForm
   lnNumeroInferior = .txtNumeroSuperior.Value
   lnNumeroSuperior = .txtNumerSuperior.Value
ENDWITH

TEXT TO lcConsulta NOSHOW TEXTMERGE
  SELECT iID, cClave, cCliente, cRegistro
     WHERE (iID > <<NumeroInferior>> ) 
        AND (iID < <<NumeroSuperior >> )
     FROM Cliente
ENDTEXT

Funciona, si, también hace lo que deseamos, enviará los datos con los valores que queremos sin ningún problema. Pero, hagamos esto un poco más complicado: ¿Qué pasará cuando querramos hacer una búsqueda en base a un dato de tipo caracter? Cambiando un poco el ejemplo, buscaremos el conjunto de datos que cumpla con la condición de encontrar los datos de un cliente con base a la clave (string) del mismo:
   SELECT iID, cCliente, cDireccion, dFechaAlta, yAdeudo
        FROM Cliente
        WHERE cClave = 'ALFKI'

Como vemos en la consulta, y en el caso específico de SQL Server, cuando se hace una query en base a datos de caracteres debemos encerrar dicho valor entre comillas sencillas, ¿sencillo? , ¿o no?
   lcCliente = Thisform.txtCliente.Value
   TEXT TO lcConsulta NOSHOW TEXTMERGE
      SELECT iID, cClave, cCliente, cDireccion
           FROM Cliente
           WHERE cClave = '<<lcCliente>>'      
   ENDTEXT

Una vez más, vemos que el ejemplo, también funciona, pero tiene un detalle: debemos formatear cada consulta de acuerdo a los requerimentos de nuestro servidor, y según sea el caso, podría que fuera diferente de un servidor a otro, además de que por lo que a mí respecta, pienso que es bastante engorroso y podría evitarse.

La Parametrización al rescate

La forma sencilla de no tener complicaciones con los distintos tipos de datos de los servidores, y los distintos tipos de datos de VFP, resulta en usar la Parametrización, ¿y que es esto?, consiste en anteponer un símbolo de interrogación justo antes de una variable:
   lcCliente = Thisform.txtCliente.Value
   TEXT TO lcConsulta NOSHOW 
      SELECT iID, cClave, cCliente, cDireccion
           FROM Cliente
           WHERE cClave = ?lcCliente
   ENDTEXT
   IF SQLExec(lnHandle, lcConsulta, lcCursor) > 0
     ***  ¿Manipular, mostrar los datos?
     *** SELECT (lcCursor)
   ELSE
      IF AERROR(laError) > 0
         Messagebox("Error en la consulta: "+laError[2])
      ELSE
         Messagebox("Error inesperado en la aplicación")
      ENDIF
   ENDIF   

Como podrás notar, en el bloque TEXT...ENDTEXT hemos prescindido de la función de TEXTMERGE, además de las comillas sencillas, y en su lugar, sólo hemos puesto un símbolo de cierre de interrogación.

¿Qué estamos haciendo?, cuando usamos la Parametrización, delegamos la función de formateo de variables al driver ODBC, VFP se encargará de comunicarle qué tipo de datos es, y casi por arte de magia, hará la conversión pertinente. ¿Sencillo?, asi es, y funcionará con todos los tipo de datos de VFP (no será el caso de objetos, no confundir). Haz el intento ;-).

Nota al margen

En los ejemplos arriba escritos hemos hecho solo consultas de datos, pero aplicará a cualquiera de los comandos de tipo DML (Data Manipulation Language) de SQL, tales como INSERT, UPDATE, DELETE, etc.:
  ** Insertar datos 
  TEXT TO lcInsert NOSHOW
    INSERT INTO Detalles (cClave, iProductoID, nCantidad, yPrecioVenta, dFechaVenta)
       VALUES (?lcClave, ?lnProductoID, ?lnCantidad, ?lyPreciodeVenta, ?ldFechaVenta)
   ENDTEXT
   IF SQLExec(lnHandle,lcInsert) > 0
      *** Datos insertados con éxito
      *** Messagebox("Datos insertados con éxito")
   ELSE
       IF AERROR(laError) > 0
          Messagebox("Error al insertar datos:"+laError[2])
       ELSE
          Messagebox("Error inesperado!!")
       ENDIF
   ENDIF

   ** Actualizar un registro 
  TEXT TO lcUpdate NOSHOW
     UPDATE Detalles SET nCantidad = ?lnCantidad 
       WHERE ( iMovimientoID = ?lnMovimiento )
  ENDTEXT
  IF SQLExec(lnHandle, lcUpdate) > 0
     *** Actualizados con exito
     *** Messagebox("Datos actualizados con éxito")
  ELSE
     IF AERROR(laError) > 0
        Messagebox("Error al actualizar el registro:"+laError[2])
     ELSE
        Messagebox("Error inesperado!!")
     ENDIF
  ENDIF

   ** Borrar registro(s) 
   TEXT TO lcDelete NOSHOW
     DELETE FROM Detalles WHERE (iFacturaID = ?lnFactura)
   ENDTEXT
   llSucess = .F.
   IF VERSION(2) >= 9
      *** Usar las nuevas características de VFP9 para conocer los registros afectados
      llSucess = SQLExec(lnHandle,lcDelete,NULL,aRowsAffected) > 0
      IF (llSucess)
         lcMessage = "Se han borrado "+STR(aRowsAffected[1,2] ) + ;
                           " artículos de la Factura "+STR(lnFactura)
      ENDIF
   ELSE
     llSucess = SQLExec(lnHandle,lcDelete) > 0
     lcMessage = "Se han borrado todos los articulos de la Factura "+str(lnFactura)
   ENDIF
   IF llSucess
      *** Registros borrados con exito
      *** Messagebox(lcMessage)
   ELSE
      IF AERROR(laError) > 0
        Messagebox("Error al intentar borrar los datos:"+laError[2])
      ELSE
        Messagebox("Error inesperado!!")
      ENDIF
   ENDIF

Espero que este artículo les sea de utilidad.

Espartaco Palma Martínez