19 de diciembre de 2007

Cómo saber si un ActiveX ya fué registrado

A veces distribuimos ActiveX (archivos OCX), los cuales es necesario registrar en Windows para poder utilizarlos, pero cómo averiguar si ya lo está para evitar su registro cada vez que se ejecute el sistema o registrarlo si es necesario.

Determinar si un ActiveX esta registrado llamando la siguiente función:
? OcxRegistrado("mscomctl2.monthview.2") && MontView
? OcxRegistrado("mscomctl2.dtpicker.2") && Date Time Picker
? OcxRegistrado("mscomctllib.treectrl.2") && Treeview
? OcxRegistrado("mschart20lib.mschart.2") && Ms Chart
? OcxRegistrado("mscommlib.mscomm.1") && MsComm

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
Los archivos OCX tienen una referencia o nombre interno, para averiguar cual es agregamos este a un formulario de VFP, lo seleccionamos y revisamos la propiedad OleClass y ese será el nombre que utilizaremos.

Para registrar el OCX puede ser:

1) Directamente desde la opción Run/Ejecutar del botón inicio de Windows:
REGSVR32 <ArchivoOCX>
2) Desde Fox con macro de sustitución:
cRun="REGSVR32 <ArchivoOCX>"
!&cRun
3) Con la rutina de Jorge Mota:
DECLARE INTEGER DLLSelfRegister IN "Vb6stkit.DLL" ;
   STRING lpDllName
=DLLSelfRegister(<ArchivoOCX>)
-- REGISTRAR Y DESREGISTRAR UN ARCHIVO OCX O DLL --
http://comunidadvfp.blogspot.com/2002/08/registrar-y-desregistrar-un-archivo-ocx.html

Saludos.

Jesus Caro V

10 de diciembre de 2007

Semana del mes

Otra opción para los compañeros que necesitan saber la semana del mes ....

ldate = DATE()
? WEEK(ldate) - (WEEK(ldate - DAY(ldate) + 1) - 1)
Ernesto Hernandez

30 de noviembre de 2007

Semanas del mes

Un compañero de trabajo hoy me estaba solicitando una rutina que le mostrara las semanas del mes así que basado en un ejemplo que existe en fox.wikis.com del ultimo día del mes lo adapte e hice la siguiente función a la cual podría ayudar a alguien como este compañero si existe una mejor forma agradecería sus comentarios.
? SEMANASMES(DATE())

FUNCTION SEMANASMES(ldFecha)
  IF TYPE("ldFecha") # "D"
    MESSAGEBOX('Parametro invalido',16,"Alto")
    RETURN 0
  ENDIF
  RETURN CEILING(DAY(GOMONTH(ldFecha, 1) - DAY(ldFecha)) / 7)
ENDFUNC
Emanuel Omar Villicaña Villegas

28 de noviembre de 2007

Agregar campo autoincremental a tabla con registros

Tuve que agregar un campo incremental a varias tablas que tenían cantidades variables de registros y me encontré con el problema que, por un lado, a todos los registros les puso el valor de cero; y por otro lado, el campo era de sólo lectura.

Imaginé que había una función que actualizaba ese campo en todos los registros, pero busqué información al respecto en la documentación y web pero al no encontrarla (y con la presión de terminar el trabajo lo más pronto posible) hice la función siguiente:

FUNCTION NewIncrem
   LPARAMETERS lcTabla, lcNombreCampo, lnError, lnUltInc
   * abre la tabla
   TRY
      USE (lcTabla) IN 0 EXCLUSIVE
      lnError = 0
   CATCH
      MESSAGEBOX("Error al abrir tabla", 48, "Error")
      lnError = 1
   ENDTRY
   IF lnError = 1
      RETURN 0
   ENDIF
   * crea el campo, si ya existe el nombre o es inválido cancela la operación
   * crea el campo como numérico para poder actualizar los valores
   TRY
      SELECT (lcTabla)
      ALTER TABLE (lcTabla) ADD COLUMN (lcNombreCampo) N(10)
   CATCH
      MESSAGEBOX("Error al crear el campo", 48, "Error")
      lnError = 1
   ENDTRY
   IF lnError = 1
      RETURN 0
   ENDIF
   * actualiza los valores del campo
   REPLACE (lcNombreCampo) WITH RECNO() ALL
   * actualiza el tipo de campo a incremental y el siguiente valor del autoincremental
   ALTER TABLE (lcTabla) ALTER COLUMN (lcNombreCampo) I
   GO BOTTOM
   lnUltInc = &lcNombreCampo
   ALTER TABLE (lcTabla) ALTER COLUMN (lcNombreCampo) INT AUTOINC NEXTVALUE iUltReg+1 STEP 1
   RETURN 1
ENDFUNC
Lo primero que hace es crear el campo como numérico, para poder reescribirlo, le pone a cada fila el número de registro que le corresponde. Luego, modifica el tipo de campo a entero autoincremental y actualiza el valor siguiente en la base de datos.

Devuelve numérico: 0 si falló, 1 si se realizó la operación.

Para llamarlo:
=NewIncrem("miTabla", "miCampo")


Arturo Panana

7 de noviembre de 2007

Rutina para una fecha en letras

Hice una variaciones para utilizar la rutina de Hector Urrutia en una función definida por el usuario (UDF) el programa puede tener cualquier nombre yo le puse datestr.prg y asi se llama la función, es como sigue:
PARAMETERS tddate
LOCAL lcStr as String
lcStr = UPPER(CDOW(tdDate)) + [, ] + ALLT(STR(DAY(tdDate))) + [ DE ] + ;
  UPPER(SUBSTR(SUBSTR(DMY(tdDate),AT(" ",DMY(tdDate),1) + 1,LEN(DMY(tdDate))),1,RAT(" ",SUBSTR(DMY(tdDate),;
  AT(" ",DMY(tdDate),1) + 1,LEN(DMY(tdDate))),1))) + [ DE ]+ ALLT(STR(YEAR(tdDate)))
RETURN lcStr 
De esta forma agrega mucha funcionalidad ya que si tenemos una tabla donde haya un campo fecha se puede ver directamente usando el browse tanto el campo fecha como su descripción en letra utilizando la función como un campo calculado por ejemplo:
BROWSE FIELD FECHA,A=DATESTR(FECHA)
también
DISP ALL FECHA,DATESTR(FECHA)
también si tienes la fecha en una variable
? DATESTR(variable)
Gracias a todos en especial a Hector mucha salud y suerte.

Nelson Maranjes
Cuba

30 de octubre de 2007

Retorna una fecha en palabras

Rutina que retorna una fecha en palabras.
*!* Retorna una fecha en palabras 
*!* USO: ? DateToStr(DATE())
FUNCTION DateToStr(tdDate as Date)
 LOCAL lcStr as String
 lcStr = UPPER(CDOW(tdDate)) + [, ] + ALLT(STR(DAY(tdDate))) + [ DE ] + ;
   UPPER(SUBSTR(SUBSTR(DMY(tdDate),AT(" ",DMY(tdDate),1) + 1,LEN(DMY(tdDate))),1,RAT(" ",SUBSTR(DMY(tdDate),;
   AT(" ",DMY(tdDate),1) + 1,LEN(DMY(tdDate))),1))) + [ DE ]+ ALLT(STR(YEAR(tdDate)))
  RETURN lcStr 
ENDFUNC 
Saludos desde El Salvador

Hector Urrutia

22 de octubre de 2007

Cómo obtener la palabra bajo el cursor en un control editbox

Ejemplo de cómo obtener la palabra bajo el cursor en un control editbox.

PUBLIC oForm1

oForm1 = NEWOBJECT("Form1")
oForm1.SHOW
RETURN

DEFINE CLASS Form1 AS FORM
  TOP = 0
  LEFT = 0
  HEIGHT = 276
  WIDTH = 419
  DOCREATE = .T.
  CAPTION = "Form1"
  ALLOWOUTPUT = .F.
  NAME = "Form1"

  ADD OBJECT Edit1 AS EDITBOX WITH ;
    FONTNAME = "Tahoma", FONTSIZE = 8, NAME = "Edit1", ;
    ANCHOR = 15, HEIGHT = 217, LEFT = 12, TOP = 12, WIDTH = 397, ;
    VALUE = [In Windows© XP®, {owner/draw} menus show icons, but Windows Vista® uses ] ;
    + [alpha-blended bitmaps instead.] + CHR(13) + CHR(10) ;
    + [Windows Vista "provides" menus are (part of) the 'visual schema'. ] ;
    + CHR(13) + CHR(10) ;
    + [Using a destroyed menu handle] + CHR(13) + CHR(10)

  ADD OBJECT Timer1 AS TIMER WITH ;
    TOP = 216, LEFT = 0, HEIGHT = 23, WIDTH = 23, ;
    INTERVAL = 100, NAME = "Timer1"

  ADD OBJECT Text1 AS TEXTBOX WITH ;
    FONTNAME = "Tahoma", FONTSIZE = 8, ;
    HEIGHT = 23, LEFT = 12, TOP = 240, WIDTH = 396, ;
    ANCHOR = 14, READONLY = .T., NAME = "Text1"

  PROCEDURE Edit1.KEYPRESS
    LPARAMETERS nKeyCode, nShiftAltCtrl
    *!* We have to use a timer to get out of the KeyPress event loop
    *!* because Selstart and SelLength get updated AFTER pressing
    *!* the left/right arrow keys, but does NOT get updated
    *!* after pressing up/down arrow keys
    THISFORM.Timer1.ENABLED = .T.
  ENDPROC

  PROCEDURE Edit1.MOUSEUP
    LPARAMETERS nButton, nShift, nXCoord, nYCoord
    THISFORM.Timer1.ENABLED = .T.
  ENDPROC

  PROCEDURE Timer1.TIMER
    THIS.ENABLED = .F.
    m.loEdit = THISFORM.edit1
    m.charfilter = " ,;.:[]{}()/\'®©<>" + '"' + CHR(13) + CHR(0)
    m.lcWord = ""
    m.lcChar = ""
    *!* 25 arbitrary limit for search, just in case
    FOR m.lnx = 0 TO 25
      m.lcWord = m.lcChar + m.lcWord
      m.lcChar = SUBSTR(m.loEdit.TEXT, m.loEdit.SELSTART + m.loEdit.SELLENGTH - m.lnx, 1)
      IF m.lcChar $ m.charfilter
        EXIT
      ENDIF
    ENDFOR
    m.lcChar = ""
    FOR m.lnx = 1 TO 25
      m.lcWord = m.lcWord + m.lcChar
      m.lcChar = SUBSTR(m.loEdit.TEXT, m.loEdit.SELSTART + m.loEdit.SELLENGTH + m.lnx, 1)
      IF m.lcChar $ m.charfilter
        EXIT
      ENDIF
    ENDFOR
    THISFORM.text1.VALUE = m.lcWord
    m.loEdit = NULL
  ENDPROC

ENDDEFINE

Carlos Alloatti

7 de octubre de 2007

Directorio actual: FULLPATH(CURDIR())

VFP contiene un número de funciones para ayudar a determinar un directorio o carpeta específica. La función a menudo mas necesaria es una que devuelve la ruta completa del directorio actual.

Volviendo a los días de Fox2x, esto es lo que yo utilicé:

? SYS(5) + SYS(2003)

En VFP también he visto esta variación:

? SYS(5) + CURDIR()

He estado utilizando éste por bastante tiempo, sobre todo porque se parece el más fácil recordar:

? FULLPATH(CURDIR())

Otro desarrollador sugiere esto:

? FULLPATH("")

[072] VFP Tips & Tricks - Drew Speedie

2 de octubre de 2007

VFP9 a MySQL5 - Eliminacion de Procedimientos y Funciones

Ha veces puede ser necesario o resultarnos util borrar todas las funciones, y procedimientos que tenemos almacenados en nuestra base de datos. Bien sea por limpieza de la misma o para volver a recrearlos a través de un Script de MySQL. (Archivo de texto con con instrucciones propias de MySQL).

Los procedimientos están almacenados en una tabla de las base de datos principal de MySQL (mysql.proc). Y las funciones están almacenadas en la tabla (mysql.func). Simplemente lo único de debemos hacer es un "ZAP" de dichas tablas.

Utilizaremos la sentencia de MySQL "TRUNCATE" que es equivalente al "ZAP" de Visual FoxPro.

Vamos ha hacerlo como siempre desde nuestro querido FOX.
******************************************************
* ZAP(eliminacion)   de Funciones y Procedimientos de MySQL
******************************************************

LOCAL CSQL, NH, CCADENA
CSQL=””
NH=0
CCADENA=””
CSQL= "DRIVER={MySQL ODBC 3.51 Driver};" + ;
"SERVER=127.0.0.1;" + ;
"PORT=3306;" + ;
"UID=usuario;" + ;
"PWD=pasword;" + ;
"DATABASE=mybasededatos;" + ;
"OPTIONS=2049;"

NH=SQLSTRINGCONNECT(""+CSQL, .T.)
IF NH>0
        SQLSETPROP(NH,'Asynchronous', .T.)
        SQLSETPROP(NH,'BatchMode', .T.)

        ** ZAP de Funciones
        TEXT TO CSQL TEXTMERGE NOSHOW
                TRUNCATE TABLE MYSQL.FUNC
        ENDTEXT
        SQLPREPARE(NH,""+CSQL)
        SQLEXEC(NH)

        ** ZAP de Procedimientos
        TEXT TO CSQL TEXTMERGE NOSHOW
                TRUNCATE TABLE MYSQL.PROC
        ENDTEXT
        SQLPREPARE(NH,""+CSQL)
        SQLEXEC(NH)

        SQLDISCONNECT(NH)
 
ENDIF

RELEASE CSQL,NH,CCADENA

*************************************************************
Una vez eliminados los procedimientos y Las funciones podemos recrearlos a través de un Script de MySQL

Desde la linea de comandos del MySQL podemos ejecutar:
SOURCE C:\SCRIPT.SQL
Donde SCRIPT.SQL es un archivo de texto que contiene instrucciones MySQL específicas que puede ser creado perfectamente con el NOTEPAD de Windows.

Antonio L. Montagut
www.ontarioxb.es


27 de septiembre de 2007

Inconsistencia de SELECT - SQL

Encontré la siguiente inconsistencia de SELECT-SQL que quiero compartir aquí. Prueben el siguiente código:

*-- primer ejemplo --
CREATE CURSOR prueba (texto c(15), numero I)
INSERT INTO prueba VALUES ("algo",9999)
SELECT SUM(numero) FROM prueba ;
  WHERE texto = "noexiste" ;
  INTO CURSOR primero
? sum_numero
BROWSE
*-----------------------------

Verán que el SELECT produce un resultado de un registro con un valor nulo en la columna sum_numero.

Sin embargo, si el SELECT se expresa de la siguiente forma:

*-- segundo ejemplo (con GROUP BY) --
SELECT texto, SUM(numero) FROM prueba ;
  WHERE texto = "noexiste" ;
  GROUP BY 1 ;
  INTO CURSOR segundo
? sum_numero
BROWSE
*-----------------------------

En este segundo ejemplo la consulta produce un resultado con cero registros y "? sum_numero" devuelve Cero (0) en vez de .NULL. como el primer ejemplo. Nótese entonces, que ambas consultas, siendo conceptualmente iguales, devuelven resultados diferentes.

Sin embargo, en VFP8, ambas formas producen el mismo resultado: sum_numero contiene cero, como en el segundo ejemplo.

La primera vez que noté que en VFP9, los casos como el primer ejemplo, me devolvían valores nulos si ningún registro satisface la condición WHERE, pensé que tenía sentido ese comportamiento. Es decir, traté de encontrarle un sentido que lo justifique. Me dije: “que la suma de todos los valores existentes sea igual a cero no es lo mismo a que no haya valores que sumar, en cuyo caso, devolver nulo aporta una información válida”. Lo acepté sin pensar mucho. Sin embargo, luego noté que no en todos los casos la ausencia de valores a sumar para satisfacer una condición WHERE devuelve un valor nulo. Entonces, esto ya debe ser clasificado como una inconsistencia, creo yo.

Además, y eso ya es un comentario aparte, en verdad que el valor nulo devuelto por el primer ejemplo no me sirve en ninguno de los casos prácticos en los que yo los utilizo. Así es que tuve que tomar el cuidado de utilizar SELECT siempre de la forma del segundo ejemplo.

Expongo este caso para que lo tomen en cuenta si todavía no lo han notado.

Saludos

Mario Esquivel Bado

25 de septiembre de 2007

Exportar a OpenOffice.org Calc

Rutina de Hector Urrutia para exportar un cursor a OpenOffice Calc.
*-------------------------------------------------------------*
*!*- FUNCTION ExporToCalc([cCursor], [cDestino], [cFileSave])
*!*- cCursor:  Alias del cursor que se va a exportar.
*!*- cDestino:  Nombre de la carpeta donde se va a grabar.
*!*- cFileName:  Nombre del archivo con el que se va a grabar.
*-------------------------------------------------------------*
FUNCTION ExporToCalc(cCursor, cDestino, cFileSave)
  LOCAL oManager, oDesktop, oDoc, oSheet, oCell, oRow, FileURL
  LOCAL ARRAY laPropertyValue[1]

  cWarning = "Exportar a OpenOffice.org Calc"

  IF EMPTY(cCursor)
    cCursor = ALIAS()
  ENDIF

  IF TYPE('cCursor') # 'C' OR !USED(cCursor)
    MESSAGEBOX("Parametros Invalidos",16,cWarning)
    RETURN .F.
  ENDIF

  lColNum = AFIELDS(lColName,cCursor)

  EXPORT TO (cDestino + cFileSave + [.ods]) TYPE XL5

  oManager = CREATEOBJECT("com.sun.star.ServiceManager.1")

  IF VARTYPE(oManager, .T.) # "O"
    MESSAGEBOX("OpenOffice.org Calc no esta instalado en su computador.",64,cWarning)
    RETURN .F.
  ENDIF

  oDesktop = oManager.createInstance("com.sun.star.frame.Desktop")

  COMARRAY(oDesktop, 10)

  oReflection = oManager.createInstance("com.sun.star.reflection.CoreReflection")

  COMARRAY(oReflection, 10)

  laPropertyValue[1] = createStruct(@oReflection, "com.sun.star.beans.PropertyValue")
  laPropertyValue[1].NAME = "ReadOnly"
  laPropertyValue[1].VALUE= .F.

  FileURL = ConvertToURL(cDestino + cFileSave + [.ods])

  oDoc = oDesktop.loadComponentFromURL(FileURL , "_blank", 0, @laPropertyValue)

  oSheet = oDoc.getSheets.getByIndex(0)

  FOR i = 1 TO lColNum
    oColumn = oSheet.getColumns.getByIndex(i)
    oColumn.setPropertyValue("OptimalWidth", .T.)

    oCell = oSheet.getCellByPosition( i-1, 0 )
    oDoc.CurrentController.SELECT(oCell)

    WITH oDoc.CurrentSelection
      .CellBackColor = RGB(200,200,200)
      .Cell
      .CharColor = RGB(255,0,0)
      .CharHeight = 10
      .CharPosture = 0
      .CharShadowed = .F.
      .FormulaLocal = lColName[i,1]
      .HoriJustify = 2
      .ParaAdjust = 3
      .ParaLastLineAdjust = 3
    ENDWITH
  ENDFOR

  oCell = oSheet.getCellByPosition( 0, 0 )
  oDoc.CurrentController.SELECT(oCell)

  laPropertyValue[1] = createStruct(@oReflection, "com.sun.star.beans.PropertyValue")
  laPropertyValue[1].NAME = "Overwrite"
  laPropertyValue[1].VALUE = .T.

  oDoc.STORE()
ENDFUNC

FUNCTION createStruct(toReflection, tcTypeName)
  LOCAL loPropertyValue, loTemp
  loPropertyValue = CREATEOBJECT("relation")
  toReflection.forName(tcTypeName).CREATEOBJECT(@loPropertyValue)
  RETURN (loPropertyValue)
ENDFUNC

FUNCTION ConvertToURL(tcFile AS STRING)
  IF(TYPE( "tcFile" ) == "C") AND (!EMPTY( tcFile ))
    tcFile = [file:///] + CHRTRAN(tcFile, "\", "/" )
  ELSE
    tcFile = [file:///C:/] + ALIAS() + [.ods]
  ENDIF
  RETURN tcFile
ENDFUNC

AUTOR: Hector Urrutia

Saludos desde EL Salvador

7 de septiembre de 2007

Traducir la función NVL2 de Oracle a Microsoft SQL Server

Artículo original: Translating Oracle’s NVL2 function to Microsoft SQL Server
http://www.foxpert.com/knowlbits_200703_2.htm
Autor: Christof Wollenhaupt
Traducido por: Ana María Bisbé York

En esta dirección de Technet, Microsoft expuso que la traducción adecuada para la función NVL2 de Oracle es:
NVL2 (Salary, Salary*2, 0)
Debería ser:
CASE SALARY
WHEN null THEN 0
ELSE SALARY*2
END
Ellos acertaron en la parte de Oracle, pero fallaron en la sintaxis de su propio servidor. La forma correcta de utilizar WHEN con valores NULL es:
CASE
WHEN SALARY IS NULL THEN 0
ELSE SALARY*2
END
En Microsoft SQL Server usted utiliza IS NULL o IS NOT NULL para verificar si existe NULL. La expresión original debería devolver siempre el valor desde a parte ELSE ya que la condición SALARY = NULL nunca será verdadera.

20 de agosto de 2007

Incrustar Imágenes II - La clase inteligente Treview VFP

Artículo original: Embed II The Smart VFP Treeview Class
http://weblogs.foxite.com/bernardbout/2007/07/14/embed-ii-the-smart-vfp-treeview-class/
Autor: Bernard Bout
Traducido por: Ana María Bisbé York

En el artículo anterior, Embed Your Images describí como se pueden incrustar imágenes en una clase. (Nota de la traductora: Este artículo ha sido publicado bajo el título "Incrustar Imágenes (Bernard Bout) Traducción")

En este escrito, voy a mostrarles la mejor forma de incrustar esas imágenes así como un archivo de configuración dentro de una clase. La clase en cuestión es una clase Treeview mostrada antes en este escrito: VFP is very very cool (Nota de la traductora: Este artículo ha sido publicado bajo el título "Visual FoxPro es muy muy bueno")

El ejemplo de la clase y formularios se pueden descargar desde:
http://weblogs.foxite.com/bernardbout/attachment/4366.ashx

Todo lo que necesita utilizar esta clase es ejecutar el formulario xmldashbuilder.scx y configurar cómo será la apariencia del Treeview. Puede añadir elementos, enlazar imágenes, configurar comandos y se ejecutará cuando se seleccione un nodo del Treeview.

Figura 1 - El generador


Figura 2 - El generador con ayuda


Luego, es simplemente cuestión de arrastrar la clase al formulario, redimensionarla, y ya está.
Una vez que tiene el código fuente, puede abrir fácilmente la clase y ver cómo lo he hecho.
Básicamente, el Treeview está manejado por una tabla que admite tres niveles para cada nodo. Esto suele ser suficiente. Las imágenes se enlazan con cada nodo y son incrustadas a lo largo con la tabla dentro de la propia clase, por lo que no se necesitan más archivos externos, solamente la clase. El generador no es necesario una vez que la clase se haya configurado. Si se necesita una configuración posterior, el generador debe estar en el mismo directorio que la clase.

Puede colocar cualquier comando que sea ejecutado, como se hace en un menú de VFP. Vea, por favor, los comandos de ejemplo que se incluyen en la clase para ejecutar el formulario del generador.
Si necesita hacer cambios en la configuración, llame al generador, hágalos y guárdelos. Estos cambios aparecerán en su formulario la próxima vez que lo ejecute.

Debido a un requerimiento del ActiveX ImageList, las imágenes deben encontrarse externamente, por lo que la clase lo extrae automáticamente a un directorio temporal según sea necesario, en tiempo de ejecución. La clase no va a eliminar los archivos, por lo que serán reutilizados si se utiliza la clase nuevamente. Si es necesario, puede fácilmente agregar esta eliminación de imágenes en el evento Destroy de la clase.

Figura 3. La clase en el formulario

Actualización: 12 de febrero 2009 

He quitado las entradas MemberData de la clase y el formulario. Esta clase trabajará tanto en VFP8 y VFP9. Descargar el archivo adjunto a continuación.
Adjunto: xmltreedash0209.zip

17 de agosto de 2007

MySQL Client-Server Applications with Visual FoxPro

Nuevo libro de Whil Hentzen sobre como desarrollar aplicaciones Cliente-Servidor con VFP y MySql, logrando una combinación poderosa y económica.

Este libro también trata sobre como instalar, configurar y conectar MySql y Visual FoxPro.



Titulo: MySQL Client-Server Applications with Visual FoxPro
Autor: Whil Hentzen
Editado por: Ted Roche
Lenguaje: Inglés
ISBN: 1-930919-70-0
Páginas: 414
Precio: U$S 49.95 (impreso) U$S 29.95 (formato PDF de 8 MB.)
Fecha de publicación: Agosto de 2007

Contenido

Chapter 1: Why Client-Server? Why VFP? Why MySQL?
Chapter 2: Development and Deployment Scenarios
Chapter 3: Installing MySQL on Windows
Chapter 4: Installing MySQL on Linux
Chapter 5: Configuration of Users and Hosts
Chapter 6: Connecting VFP to MySQL
Chapter 7: Configuring MySQL
Chapter 8: The Interactive Use of MySQL
Chapter 9: Under the Hood: Where MySQL Keeps Its Data
Chapter 10: Creating Data Sets from Scratch
Chapter 11: Populating a MySQL Database: LOAD DATA INFILE
Chapter 12: Populating a MySQL Database Programmatically
Chapter 13: Advanced Data Issues
Chapter 14: Constructing SQL to Avoice SQL Injection
Chapter 15: Religious Wars: Remote Views, CursorAdapters, and SQL PassThrough
Chapter 16: A Client-Server State of Mind
Chapter 17: xBase to SQL Conversion Issues
Chapter 18: A Client-Server User Interface for Querying
Chapter 19: A Client-Server User Interface for Add/Edit/Delete
Chapter 20: Relational Integrity
Chapter 21: Getting Started with Stored Procedures
Chapter 22: Deployment

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

13 de agosto de 2007

Incrustar imágenes

Artículo original: Embed Your Images
http://weblogs.foxite.com/bernardbout/2007/02/17/embed-your-images/
Autor: Bernard Bout
Traducido por: Ana María Bisbé York

Siempre ha sido un problema para mi tener que incluir imágenes para cada clase en un archivo externo. No sólo es lento el acceso; es que VFP tiene una forma irritante de mostrar el cursor en forma de Reloj de sol, cada vez que accede desde el disco. Esto es especialmente notable en botones gráficos que tiene un cambio de bitmap para eventos MouseEnter y MouseLeave.

Ya no más. Utilizando un campo en la clase - el campo USER, tengo eliminados, en una sola línea, estos dos irritantes problemas . Utilizando el método descrito aquí, se eliminan las lecturas de disco y no hay necesidad de incluir bitmaps.
Nota: Este método trabaja solamente con VFP9 una vez que la propiedad PictureVal se emplea para cargar imágenes. Esto también trabaja solamente con la clase base IMAGE, porque otros objetos, aunque tienen propiedad Picture, no tienen propiedad PictureVal.
Este método es sencillo. Guarda un bitmap en el campo USER de la clase image y en tiempo de ejecución lo recupera y asigna a PictureVal de la imagen este valor.

Comencemos. Cree una clase nueva - ImageXML basada en un objeto image. Por ahora, solamente guárdela.



He creado un Generador sencillo para ayudar en la inserción de imágenes. Esto está incluido en los archivos para descargar. Usted puede mejorar cuanto quiera el generador.

Ejecute el generador y siga los pasos 1,2,3.


  1. Seleccione la imagen. La imagen seleccionada se muestra junto al botón.
  2. Seleccione la clase que acaba de crear. El cuadro de texto va a mostrar el nombre de la clase seleccionada.
Existe un botón con letras en rojo, por si desea limpiar el contenido del campo USER si existe cualquier otro dato. Seleccione ese botón para limpiar el campo.

3.- Seleccione el botón "Insert image" y la imagen se inserta instantáneamente en el campo USER de la clase. Seleccione otra imagen (1) y agréguela (3). Cuando haya agregado sus imágenes, cierre el generador y examine la clase como tabla (browse). En el campo USER encontrará mucha información nueva. He empleado delimitadores en el generador builder <name> y </name> <picture> y </picture> para separar las imágenes insertadas.

Vea, por favor que las imágenes están tal como las ha insertado por tanto puede saber las que hay cuando necesite asignarlas. Cierre la clase abierta antes como una tabla.

Ahora abra su clase como clase y agréguele un nuevo método - ProcessImages.

Agregue el siguiente código al método ProcessImages
* procesa las imágenes
* abre la clase como una tabla
USE (THIS.CLASSLIBRARY) IN 0 ALIAS xClass SHARED
* toma solamente el archivo que deseamos
SELECT USER FROM xClass WHERE NOT EMPTY(USER) AND baseclass="image" INTO CURSOR 
userStr
* guarda las imágenes
LOCAL cPixStr,nPix,cPixname,cPixContents
nPix = 1
cPixStr = ALLTRIM(USERSTR.USER)
* no hace falta nada más, así que las cerramos
USE IN SELECT("userstr")
USE IN SELECT("xClass")
* hace un lazo en los datos extrayendo las imágenes
DO WHILE .T.
  cPixname = STREXTRACT(cPixStr,[<name>],[</name>],nPix)
  IF EMPTY(cPixname)
    EXIT
  ENDIF
  cPixcontents = STREXTRACT(cPixStr,[<picture>],[</picture>],nPix)
  * agrega una propiedad para guardar ese valor
  This.AddProperty("pix"+TRANSFORM(nPix),cPixContents)
  nPix = nPix + 1 
ENDDO
En el INIT de la clase llame al método
This.ProcessImages()
La clase image está completa.

Para probar la clase image, cree un nuevo formulario y agréguele una instancia de ImageXML.bbImage. Como puede ver en el código, las imágenes son almacenadas en propiedades añadidas en tiempo de ejecución se llaman pix1, pix2, etc para tantas imágenes como haya agregado.

Entonces, abra el evento Init del objeto image y agregue este código:
* ejecuta el código de la clase padre
DODEFAULT()
* asigna valores a la propiedad PictureVal – pix1 o pix2 o pix3 etc.
This.PictureVal = This.Pix1
Si está utilizando el objeto imagen como un botón y quiere que la imagen cambie cuando el ratón entre y salga, entonces escriba este código en el evento MouseEnter:
This.PictureVal = This.pix2
Y cámbielo cuando se vaya el ratón:
This.PictureVal = This.pix1
Esto es todo lo que hay que hacer. No hay imágenes que buscar, ni relojes de arena que ver. Este método para el uso de imágenes incluso trabaja en tiempo de ejecución cuando el VCX es compilado dentro del EXE y puede ser abierto y leído como una tabla.

Como he mencionado, el generador es muy sencillo y puede ser mejorado fácilmente. Hasta ahora, con el ejemplo que se ofrece, solamente se admite una clase imagen por biblioteca de clases; pero cambiando el código en ProcessImages, más de unobjeto imagen puede ser agregado fácilmente a la biblioteca de clases.

Este es el formulario de pruebas con la imagen asignada:



y el cambio de imagen cuando se mueve el ratón sobre el.



31 de julio de 2007

Codigo de barra EAN 128 en FoxPro

Una clase para descargar del sitio de José Guillermo Ortiz Hernández, que permite incluir código EAN 128 en aplicaciones Visual FoxPro.

En el sitio Tortuga Productiva dedicado a Visual FoxPro, está publicado el siguiente artículo con código fuente incluido que permite incluir códigos de barra EAN 128 en aplicaciones Visual FoxPro.

-- El EAN 128, Uso e implementación en FoxPro --
http://www.tortugaproductiva.galeon.com/docs/ean128/index.html



23 de julio de 2007

Nueva versión de GradObjects

Artículo original: New version of GradObjects
http://weblogs.foxite.com/vfpimaging/archive/2007/06/28/4212.aspx
Autor: Cesar Ch.
Traducido por: Ana María Bisbé York


Acabo de subir una nueva versión de la clase GradObjects

Gracias a Nigel por informar de un error que ocurría cuando se utilizaban objetos OptionsGroups gráficos.

He aquí la versión actualizada.

http://www.geocities.com/macmarbr/gradobjects.zip
http://weblogs.foxite.com/files/vfpimaging/gradobjects/gradobjects.zip





Se puede obtener más información en el escrito original:

GRADIENT OBJECTS WITH GDI+
http://weblogs.foxite.com/vfpimaging/archive/2006/07/26/2076.aspx

20 de julio de 2007

Incruste imágenes a sus correos electrónicos con CDOSYS

Artículo original: Embed images to your emails with CDOSYS
http://weblogs.foxite.com/vfpimaging/archive/2007/07/03/4256.aspx
Autor: Cesar Ch.
Traducido por: Luis María Guayán

Basado en algunos excelentes ejemplos que encontré en la Web, especialmente los de Mike Gagnon, he creado mi propia clase para enviar correos electrónicos. Decidí usar CDOSYS (Microsoft Collaboration Data Objects for Windows 2000) porque esto viene con Win2000 / XP / Vista y 2003, y permite enviar mensajes de varios maneras, especialmente incrustando imágenes que son usadas en el contenido HTML del mensaje.

MAPI era mi primera opción, pero lamentablemente esto no permite incrustar imágenes, sólo son permitidos los adjuntos. La automatización de MS Outlook también funcionaría, pero no lo tengo.

Abajo está un simple código que genera una página HTML que contiene algunas imágenes que deben ser enviadas. Las imágenes son incrustadas al mensaje, ¡no como simples adjuntos!. Este permite que mostremos las imágenes en el sitio y con las características que deseamos.

Aparte de las características más comunes, esto también permite que incruste imágenes, agregue adjuntos, configure la prioridad, solicite confirmación de lectura y envíe HTML.

La propiedad "Body" de la clase acepta 3 diferentes tipos de parámetros: 1-un código de HTML, 2-una URL (CDOSYS incrustará la página entera en el mensaje) o 3-un archivo HTML que será incustado. Para esta última opción, mis pruebas fallaron cuándo usé espacios en el nombre de directorio y/o en el nombre del archivo, ¡entonces evite espacios en nombres del archivo!.

Las imágenes de abajo muestran algunas pantallas de ejemplo que generé y recibí, utilizando el código de abajo, abriendo con Outlook Express. Esto también fue probado con algunos correos Web, como por ejemplo Hotmail y los resultados son similares.





Guarde el código de abajo como un PRG, y no olvide cambiar la información en Rojo por la propia, y ejecútelo.


19 de julio de 2007

Informe VFP a PowerPoint

He visto peticiones para pasar un informe de VFP a PowerPoint, aquí tengo un código para hacerlo.
*** Creamos un Objeto de PowerPoint
oPPT=CREATEOBJECT('POWERPOINT.APPLICATION')
PPTPRES=oPPT.PRESENTATIONS.ADD(1)
PPTSLIDE1=PPTPRES.SLIDES.ADD(1,1)

*** Creamos un Cuadro de Texto
oTXT1=PptSlide1.Shapes.AddTextBox(1,5,220,700,25)

WITH oTXT1
 .Line.ForeColor.RGB=RGB(180,180,180)
 .LINE.VISIBLE=.T.
 .TEXTFRAME.TEXTRANGE.TEXT='ING. ERIK REYES LIMA'
 .TEXTFRAME.TEXTRANGE.FONT.SIZE=18
 .TEXTFRAME.TEXTRANGE.FONT.BOLD=.T.
 .FILL.ForeColor.RGB = rgb(255,255,255)
 .FILL.VISIBLE=.T.
 .Shadow.ForeColor.RGB = RGB(180, 0, 119)
 .Shadow.VISIBLE=.T. 
ENDWITH

*** CREAMOS UNA LINEA
oLine1 = pptslide1.Shapes.AddLine(0,43,720,43)
WITH oLine1.Line
 .ForeColor.RGB = rgb(180,0,119)
 .Weight = 2
ENDWITH

*** CREAMOS UNA IMAGEN
PptSlide1.Shapes.Addpicture('C:\FOTOS\MIFOTO.JPG',1,1,0,0,70,40)

*** ACTIVAMOS EL POWERPOINT
OPPT.VISIBLE = .T.
[Sadex]

17 de julio de 2007

VFP9 a MySQL5 - Almacenamiento de Imágenes

Últimante he leído en los foros sobre gente que tiene problemas a la hora de guardar imágenes en una base de datos de MySQL.

Vamos a solucionar esto desglosando tres pequeños ejemplos de almacenamiento de imágenes en MySQL.
  1. Crear una tabla en MySQL para almacenar las imágenes.
  2. Archivar una imágen en un campo de la tabla de nuestra base de datos.
  3. Proceder a su descarga posterior.
1. Crear una tabla de MySQL

Vamos a tener que crear una pequeña tabla en nuestra base de datos MySQL con la siguiente estructura:

CODIGO, DESCRIPCION, FOTO (Atención al campo foto ya que será del tipo BLOB)

Crearemos la tabla desde nuestro querido VFP.
********************************************
* CREACION DE TABLA (albumfotos)
********************************************

LOCAL CSQL, NH, CCADENA
CSQL=""
NH=0
CCADENA=""
CSQL= "DRIVER={MySQL ODBC 3.51 Driver};" + ;
"SERVER=127.0.0.1;" + ;
"PORT=3306;" + ;
"UID=usuario;" + ;
"PWD=pasword;" + ;
"DATABASE=mybasededatos;" + ;
"OPTIONS=2049;"

NH=SQLSTRINGCONNECT(""+CSQL, .T.)
IF NH > 0
        SQLSETPROP(NH,'Asynchronous', .T.)
        SQLSETPROP(NH,'BatchMode', .T.)
        TEXT TO CSQL TEXTMERGE NOSHOW

            CREATE TABLE albumfotos (
                codigo char(03) NOT NULL default '000',
                descripcion varchar(50) default '',
                foto longblob, 
                PRIMARY KEY (codigo)) 
                ENGINE=InnoDB ROW_FORMAT=DYNAMIC

        ENDTEXT
        SQLPREPARE(NH,""+CSQL)
        SQLEXEC(NH)

        WAIT WINDOW 'Tabla Creada'

        SQLDISCONNECT(NH)
 
ENDIF

RELEASE CSQL,NH,CCADENA
*************************************************************

2. Archivar imagen en la tabla de MySQL.

Vamos a archivar una imagen en la tabla (albumfotos). La imagen estará ubicada en el directorio raiz C:\ y será el archivo de imagen FOTO001.JPG

Crearemos una variable en VFP del tipo BLOB que contendrá la imagen antes de su posterior grabación: (En este caso omitiremos las ordenes de conexión a la base de datos)
********************************************
* ARCHIVAR IMAGEN 
********************************************

LOCAL BIMAGEN AS BLOB
BIMAGEN=(0h)         && Inicializamos la Variable
BIMAGEN=FILETOSTR("C:\FOTO001.JPG")

TEXT TO CSQL TEXTMERGE NOSHOW

        REPLACE INTO albumfotos 
                SET   CODIGO='001', 
                        DESCRIPCION='FOTO-001', 
                        FOTO=?BIMAGEN
 
ENDTEXT
SQLPREPARE(NH,""+CSQL)
SQLEXEC(NH)

WAIT WINDOW 'Imagen Grabada'

RELEASE BIMAGEN
*************************************************************

3. Extraer imagen de la tabla de MySQL.

Vamos a Extraer la misma imagen de la tabla de MySQL y ahora la guardaremos en el directorio fotos. (Obviamente en este caso también omitiremos las ordenes de conexión a la base de datos)
********************************************
* EXTRAER IMAGEN 
********************************************

TEXT TO CSQL TEXTMERGE NOSHOW

    SELECT FOTO
        FROM albumfotos WHERE CODIGO='001' 
ENDTEXT
SQLPREPARE(NH,""+CSQL,"TCURSOR")
SQLEXEC(NH)
SELECT TCURSOR
IF RECCOUNT() > 0
    STRTOFILE(TCURSOR.FOTO,"C:\FOTOS\FOTO001.JPG")
ENDIF

CLOSE TABLES ALL

********************************************
Esto no solo sirve para almacenar imágenes solamente, También es posible almacenar documentos PDF, Word, lo que se nos ocurra. Yo por ejemplo lo utilizo para almacenar cada logo de empresa en mi base de datos, para posteriormente imprimirlo en la cabecera de las facturas e informes.


Antonio L. Montagut
www.ontarioxb.es


16 de julio de 2007

SQL Select - Bufferred Queries

Artículo original: SQL Select - Bufferred Queries
http://rickschummer.com/blog/2007/07/sql-select-bufferred-queries.html
Autor: Rick Schummer
Traducido por: Luis María Guayán - 16/07/2007

Utilicé hoy por primera vez, una nueva característica de VFP 9 en un escenario del mundo real: la nueva capacidad de consultar datos de un cursor con buffering, usando la nueva cláusula WITH (BUFFERING = .T.)

Sé que hay desarrolladores de VFP y administradores de bases de datos que dirán que usar esta sintaxis es absolutamente incorrecta porque los datos aun pueden ser revertidos, y así el resultado de la consulta no es reproducible. La información se podría utilizar incorrectamente para tomar decisiones de negocio y esto no es elegante, y no es un enfoque profesional. Estos individuos están absolutamente acertados, cuando necesitas tener control y equilibrio en la base de datos, integridad de los datos en la base de datos, e integridad en informes y análisis.

Por otra parte, yo tenía que usar esta nueva funcionalidad para comprobar la validez de los datos antes de que estos fueran grabados en la base de datos. Aquí está mi escenario:
  1. Tengo datos para importar de una fuente exterior.
  2. Tengo datos para importar de una fuente interna, pero exportados de una aplicación diferente.
  3. Hay una gran posibilidad de datos duplicados en las dos fuentes de datos.
  4. Todos los datos se importan a cursores con buffering.
  5. Tengo que ejecutar la importación de los datos a través de tres tablas distintas, de una vez, o no. Dos de las tablas están relacionadas.
Comienzo la transacción e importo los datos de la fuente interna, después importo los datos de la fuente externa comprobando para saber si hay duplicados. Quiero revisarlos antes de enviar la transacción. En el proceso de la revisión consulto las tablas para asegurarme de que no hay personas duplicadas, ningún vendedor duplicado, y necesito verificar que todas los registraciones son tomadas en el año apropiado.

Aquí está mi consulta para contar el número de la personas con registros duplicados:
SELECT cLastName, cFirstName, COUNT(*) as nCount ;
  FROM people WITH (BUFFERING = .T.) ;
    JOIN registration WITH (BUFFERING = .T.) ;
      ON people.cPeople_PK = registration.cPeople_FK  ;
  GROUP BY cLastName, cFirstName ;
  HAVING nCount > 1 ;
  INTO CURSOR curMultiYearRegistrations
Antes de la importación conté los registros de los datos de ejemplo para hacer mis comparaciones. Esta división y manejo de los datos almacenados en el buffer me ahorró muchos la molestia de quitar los datos importados cuando encontraba errores. Habría podido hacer volar la base de datos y restaurar una backup, pero ésa habría tomado más tiempo. Habría podido importar a otra área, pero con esto habría hecho dos veces el trabajo. Podía suspender el proceso de la importación y hacer algunas consultas específicas para verificar que todo está bien durante cada paso de la importación.

Tengo otra idea donde esta nueva sintaxis ayudará al producto HackCX Professional. Espero agregar en la próxima versión un informe o resumen en pantalla, así se puedan ver todos los cambios almacenados en buffer que se realizaron en la biblioteca de clases que esta cambiando. Seguro que puedo hacer esto sin una sintaxis SQL Select, pero pienso que que escribir un simple SQL Select es más rápido y menos código que recorrer toda la tabla para determinar qué registros han cambiado.
¿Has utilizado esta nueva sintaxis? Si es así ¿Para qué?

6 de julio de 2007

VFP 9 a MySQL5

Voy a Intentar en las próximas semanas escribir una serie de artículos sobre la utilización de VFP atacando bases de datos MySQL. Estos artículos reflejan en cierta manera la experiencia de mi trabajo diario con MySQL.

Función UUID()

El propósito de esta función es devolver un identificador único universal. Un UUID es un número de 128 bits representado por una cadena de cinco números hexadecimales en el formato:
aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee. 
Un UUID está diseñado para ser un único número globalmente en el espacio y el tiempo.

Es de suponer pués que dos llamadas a UUID() generen dos valores diferentes, aunque esas llamadas se realizen en dos ordenadores separados físicamente y que no están conectados en red.

La utilidad de esta función está mas que clara. Nos permite obtener un identificador único por cada registro de una tabla, podemos utilizar esta cadena como clave primaria de la tabla, esto nos permitiria que en el caso de replicación de la base de datos, registros de la misma tabla pero en diferentes bases de datos no se sobreescribieran entre si. Cosa que si prodría pasar si utilizáramos como clave primaria campos autoincrementales.

Ya que al estar en bases de datos diferentes podría darse el caso de tener registros duplicados con el mismo identificador.

Vamos ha aplicar esta función con nuestro querido FOX:
********************************************
* Ejemplo de uso función UUID 
********************************************

LOCAL CSQL, NH, CCADENA
CSQL = ""
NH = 0
CCADENA = ""
CSQL = "DRIVER={MySQL ODBC 3.51 Driver};" + ;
  "SERVER=127.0.0.1;" + ;
  "PORT=3306;" + ;
  "UID=usuario;" + ;
  "PWD=pasword;" + ;
  "DATABASE=mybasededatos;" + ;
  "OPTIONS=2049;"

NH = SQLSTRINGCONNECT("" + CSQL, .T.)
IF NH > 0
  SQLSETPROP(NH,'Asynchronous', .F.)
  SQLSETPROP(NH,'BatchMode', .T.)
  CCADENA = PADR("",32,'0')
  SQLPREPARE(NH, "SELECT UUID()","CURSOR")
  SQLEXEC(NH)
  SELECT CURSOR
  IF RECCOUNT()>0
    CCADENA = LEFT(FIELD(1),08)+ ; 
      SUBSTR(FIELD(1),10,04)+ ;
      SUBSTR(FIELD(1),15,04)+ ;
      SUBSTR(FIELD(1),20,04)+ ;
      RIGHT(FIELD(1),12)
  ENDIF

  WAIT WINDOW CCADENA && Muestra la Cadena
  SELECT CURSOR
  USE
ENDIF
*************************************************************
Antonio L. Montagut
www.ontarioxb.es

4 de julio de 2007

Diferentes formas de obtener las dimensiones de las imágenes (ancho y alto)

Artículo original: Different ways to get Image Dimensions (width and height)
http://weblogs.foxite.com/vfpimaging/archive/2007/05/28/3862.aspx
Autor: Cesar Ch.
Traducido por: Ana María Bisbé York

Una de las preguntas más comunes que he visto en los foros de VFP relacionadas con imágenes es "¿Cómo puedo obtener las dimensiones de un archivo de imagen?"

Afortunadamente, esto es muy fácil de obtener.

A continuación muestro varias técnicas que podemos utilizar, con una breve explicación. cada desarrollador puede escoger cuál de ellas es mejor para sus necesidades. Todas las técnicas son muy sencillas de utilizar. Para usuarios GDI+, lo muestro empleando 3 bibliotecas con las que ya he trabajado. Como bono, la última muestra cómo obtenerlo con GDI+ utilizando llamadas directas a API, para los que no quieren utilizar una clase envoltorio GDI+.

Todos los ejemplos a continuación son libres. Estoy seguro de que hay otras formas de hacerlo. Señalo solamente aquellas que conozco y he probado.

1.- Cargar la imagen en un objeto Image VFP

Esta técnica trabaja con cualquier versión VFP. Cuando asociamos un archivo a la propiedad PicturuFile de un objeto Image, va a guardar las dimensiones en las propiedades Width y Height.
* Técnica 1
* Obtener las dimensiones del objeto Image 
LOCAL lcPictureFile
lcPictureFile = GETPICT()
LOCAL lnWidth, lnHeight
LOCAL loVFPImg as Image
loVFPImg = CREATEOBJECT("Image")
loVFPImg.Picture = lcPictureFile
lnWidth = loVFPImg.Width
lnHeight = loVFPImg.Height
loVFPImg = NULL
MESSAGEBOX("Objeto Image VFP " + CHR(13) + ;
  "Dimensiones: " + TRANSFORM(lnWIdth) + " x " + TRANSFORM(lnHeight))

2.- Utilizar la función LOADPICTURE()

El objeto devuelto por LOADPICTURE() es una instancia de la clase StdPicture. Los valores de altura y ancho son unidades HiMetric (1 HiMetric = 0.01 milímetro).

No estoy realmente seguro de cuál es la forma correcta para utilizar esta función para obtener las dimensiones de una imagen.

Lisa Slater Nicholls publicó hace mucho tiempo una función para obtener las dimensiones; pero a mí no me ha funcionado: http://www.spacefold.com/lisa/lisa_fx4.htm#loadpicture

En una discusión en Foxite, entre Eric den Doop y Boudewijn Lutgerik: http://www.foxite.com/archives/what-mysterious-calculation-is-behind-this-0000067503.htm, Boudewijn obtuvo las dimensiones utilizando un factor diferente.

En mi caso, escogí la técnica que encontré en http://www.xtremevbtalk.com/archive/index.php/t-13097.html

¡ Pero no confío en este proceder porque parece que depende de la resolución de la pantalla !


* Técnica 2
* Obtener las dimensiones del objeto Imagen utilizando la función LOADPICT()
* Código de Lisa Slater Nicholls
* El objeto devuelto por LOADPIC() es una instancia de la clase StdPicture. 
* Los valores de altura y ancho son unidades HiMetric (1 HiMetric = 0.01 milímetro). 
* http://www.foxite.com/archives/what-mysterious-calculation-is-behind-this-0000067503.htm  
* http://www.xtremevbtalk.com/archive/index.php/t-13097.html 
LOCAL lcPictureFile
lcPictureFile = GETPICT()
LOCAL lnWidth, lnHeight
LOCAL loLoadedPict
loLoadedPict = LOADPICTURE(lcPictureFile)
lnHeight = ROUND((loLoadedPict.Height / 26.45454545455),0)
lnWidth = ROUND((loLoadedPict.Width / 26.45454545455),0)
loLoadedPict = NULL
MESSAGEBOX("Función VFP LOADPICTURE()" + CHR(13) + ;
  "Dimensiones: " + TRANSFORM(lnWidth) + " x " + TRANSFORM(lnHeight))

3.- Obtener la información de la cabecera del archivo imagen

Las dimensiones de la imagen se almacenan en sus binarios. El siguiente código utiliza FOPEN y FREAD para extraer esa información. Puede obtener las dimensiones para las imágenes tipo BMP, GIF y JPG.
* Técnica 3
* Obtener la información de la cabecera del archivo imagen
* Los ejemplos se han adaptado a partir de las funciones de Thomas Gehrke en West-Wind Wiki:
* http://www.west-wind.com/wiki/wc.dll?wc~JpgSizeFunction  
* http://www.west-wind.com/wiki/wc.dll?wc~GifSizeFunction 
LOCAL lcPictureFile
lcPictureFile = GETPICT()
LOCAL lnWidth, lnHeight, lcExt
lcExt = LOWER(JUSTEXT(lcPictureFile))
DO CASE
  CASE lcExt = "bmp"
    LOCAL lnHandle, lcBytes
    lnHandle = FOPEN(lcPictureFile)
    IF m.lnHandle > -1
      * Leer los primeros 27 bytes:
      lcBytes = FREAD( m.lnHandle, 27)
      = FCLOSE( m.lnHandle)
      lnWidth = CTOBIN(SUBSTR(lcBytes,19,4),"4RS")
      lnHeight = CTOBIN(SUBSTR(lcBytes,23,4),"4RS")
    ENDIF
  CASE lcExt = "gif"
    LOCAL lnHandle, lcBytes
    lnHandle = FOPEN(lcPictureFile)
    IF m.lnHandle > -1
      * Leer los primeros 10 bytes:
      lcBytes = FREAD( m.lnHandle, 10)
      = FCLOSE( m.lnHandle)
      lnWidth = CTOBIN(SUBSTR( m.lcBytes, 7, 2),"2RS")
      lnHeight = CTOBIN(SUBSTR( m.lcBytes, 9, 2),"2RS")
    ENDIF
  CASE lcExt = "jpg"
    LOCAL lnHandle, lcBytes
    lnHandle = FOPEN(lcPictureFile)
    IF m.lnHandle > -1
      LOCAL lnFileSize, lnCounter, lcBytes
      lnFileSize = FSEEK( m.lnHandle, 0, 2)
      lnCounter = 0
      = FSEEK( m.lnHandle, 0, 0)
      FOR lnCounter = 1 TO m.lnFileSize - 2
        lcBytes = FREAD( m.lnHandle, 3)  
        IF m.lcBytes = CHR(0) + CHR(17) + CHR(8) OR ;
          m.lcBytes = CHR(0) + CHR(11) + CHR(8)
          * ¡Encontrar las marcas de bloque para las dimensiones!
          lcBytes = FREAD( m.lnHandle, 4)
          EXIT
        ELSE
          = FSEEK( m.lnHandle, -2, 1)
        ENDIF
      ENDFOR
      = FCLOSE( m.lnHandle)

      lnWidth = CTOBIN(SUBSTR( m.lcBytes, 3, 2),"2S")
      lnHeight = CTOBIN(SUBSTR( m.lcBytes, 1, 2),"2S")
    ENDIF
ENDCASE
MESSAGEBOX("Información desde la cabecera de imagen" + CHR(13) + ;
  "Dimensiones: " + TRANSFORM(lnWIdth) + " x " + TRANSFORM(lnHeight))

4.- Utilizar GDIPlusX

GDI+ puede brindar esta información y muchas otras, fácilmente.

El ejemplo que muestro a continuación utiliza la biblioteca GdiPlusX de VFPX.
http://www.codeplex.com/VFPX/Wiki/View.aspx?title=GDIPlusX&referringTitle=Home
* Técnica 4
* Obtener la información de la imagen utilizando GDI+, con GdiPlusX
* http://www.codeplex.com/VFPX/Wiki/View.aspx?title=GDIPlusX&referringTitle=Home  
LOCAL lcPictureFile
lcPictureFile = GETPICT()
LOCAL lnWidth, lnHeight
_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx")))
WITH _SCREEN.System.Drawing
  * Crear un Bitmap nuevo
  LOCAL loBmp as xfcBitmap
  loBmp = .Bitmap.FromFile(lcPictureFile)
  lnWidth = loBmp.Width
  lnHeight = loBmp.Height
  loBmp = NULL
  MESSAGEBOX("Biblioteca GDI+ - GdiPlusX" + CHR(13) + ;
    "Dimensiones: " + TRANSFORM(lnWIdth) + " x " + TRANSFORM(lnHeight))
ENDWITH

5.- Utilizar GPIMAGE2

El ejemplo que se muestra a continuación utiliza la biblioteca GpImage de Alexander Golovlev. En el siguiente enlace puede encontrar la versión más reciente para esta clase que contiene algunas funciones Graphics, que he agregado: http://cchalom.sites.uol.com.br/GPIMAGE
* Técnica 5
* Obtener la información de la imagen utilizando GDI+, con GpImage2
* http://cchalom.sites.uol.com.br/GPIMAGE 
LOCAL lcPictureFile
lcPictureFile = GETPICT()
LOCAL lnWidth, lnHeight
IF Not "gpImage" $ SET("Procedure")
  SET PROCEDURE TO gpImage ADDITIVE
ENDIF
loGdip = CREATEOBJECT("gpInit")
loBmp = CREATEOBJECT("gpImage")
loBmp.Load(lcPictureFile)
lnWidth = loBmp.ImageWidth
lnHeight = loBmp.ImageHeight
loBmp = NULL
loGdip = NULL
MESSAGEBOX("GDI+ library - GpImage2" + CHR(13) + ;
  "Dimensiones: " + TRANSFORM(lnWIdth) + " x " + TRANSFORM(lnHeight))

6.- Utilizar _GDIPLUS.VCX

El siguiente ejemplo utiliza la biblioteca de clases Foundations (FCC) _GdiPlus.vcx de Walter Nicholls, liberada con VFP9.
* Técnica 6
* Obtener la información de la imagen utilizando GDI+, con _GdiPlus.vcx, de Walter Nicholls
LOCAL lcPictureFile
lcPictureFile = GETPICT()
LOCAL lnWidth, lnHeight
LOCAL loBitmap as GpBitmap OF HOME() + "/FFC/_GdiPlus.vcx"
loBitmap = NEWOBJECT("GpBitmap", HOME() + "/FFC/_GdiPlus.vcx")
loBitmap.CreateFromFile(lcPictureFile)
lnWidth = loBitmap.ImageWidth
lnHeight = loBitmap.ImageHeight
loBitmap = NULL
MESSAGEBOX("Biblioteca GDI+ - FFC _GdiPlus.vcx" + CHR(13) + ;
  "Dimensiones: " + TRANSFORM(lnWIdth) + " x " + TRANSFORM(lnHeight))

7.- Utilizar GDIPLUS con llamadas directas a API

Si no desea utilizar ninguna clase envoltorio GDI+, he aquí un código que obtiene las dimensiones de la imagen utilizando llamadas directas de API a gdiplus.dll. Asegúrese de tener gdiplus.dll en su sistema.

Gdiplus.dll está disponible gratis y se puede instalar con Win98 y superior. Si no tiene instalado WinXP or .NET runtime entonces debe bajar esta DLL desde http://www.microsoft.com/downloads/details.aspx?FamilyID=6a63ab9c-df12-4d41-933c-be590feaa05a&DisplayLang=en o directamente desde http://download.microsoft.com/download/platformsdk/redist/3097/W98NT42KMeXP/EN-US/gdiplus_dnld.exe

Puede obtener información valiosa sobre GDIPlus en el sitio oficial de Microsoft
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdicpp/GDIPlus/GDIPlusreference/flatgraphics.asp
* Técnica 7
* Obtener información de la imagen utilizando GDI+, con lamadas directas de API
LOCAL lcPictureFile
lcPictureFile = GETPICT()

* API Declarations for GDI+
DECLARE LONG GdiplusStartup IN GDIPLUS.DLL ;
  LONG @ token, STRING @ INPUT, LONG @ OUTPUT
DECLARE LONG GdiplusShutdown IN GDIPLUS.DLL LONG token
DECLARE INTEGER GdipLoadImageFromFile IN GDIPLUS.DLL ;
  STRING wFilename, INTEGER @ nImage
DECLARE INTEGER GdipDisposeImage IN GDIPLUS.DLL INTEGER nImage
DECLARE INTEGER GdipGetImageWidth IN GDIPLUS.DLL ;
  INTEGER nImage, INTEGER @ nWidth
DECLARE INTEGER GdipGetImageHeight IN GDIPLUS.DLL ;
  INTEGER nImage, INTEGER @ nHeight

* Inicializar GDI+.
LOCAL gdiplusStartupInput, lhGdiPlusToken
gdiplusStartupInput = CHR(1) + REPLICATE(CHR(0), 15)
lhGdiPlusToken = 0
IF GdiplusStartup(@lhGdiPlusToken, @gdiplusStartupInput, 0) != 0
  RETURN .F.
ENDIF
* Cargar el objeto Picture
LOCAL lnPictHandle
lnPictHandle = 0
= GdipLoadImageFromFile( STRCONV(lcPictureFile + CHR(0),5), @lnPictHandle)
* Obtener las dimensiones de la imagen
LOCAL lnWidth, lnHeight
STORE 0 TO lnWidth, lnHeight
= GdipGetImageWidth(lnPictHandle, @lnWidth)
= GdipGetImageHeight(lnPictHandle, @lnHeight)
* Limpiar el controlador principal GDI+ 
= GdiplusShutdown(lhGdiPlusToken)
MESSAGEBOX("GDI+ con llamadas API" + CHR(13) + ;
  "Dimensiones: " + TRANSFORM(lnWidth) + " x " + TRANSFORM(lnHeight))

Espero que esto ayude

¡ Que lo disfruten !

2 de julio de 2007

Convertir imágenes a color monocromático con GdiPlusX

Artículo original: Convert Images to Monochrome with GdiPlus X
http://weblogs.foxite.com/vfpimaging/archive/2007/05/26/3857.aspx
Autor: Cesar Ch.
Traducido por: Ana María Bisbé York


Cada cierto tiempo, alguien pregunta sobre la creación de imágenes monocromáticas, que en la mayoría de los casos se utilizarán para enviar por Fax.

Desafortunadamente, GDI+ no brinda soporte para imágenes indexadas a 1 bpp (bipmaps monocromos)

Pero con la ayuda de Anatoliy Mogylevets y Mike Gagnon pude encontrar que la buena y vieja GDI (no GdiPlus) ofrece algunas funciones (CopyImage y LoadImage) para hacerlo en un flash. Entonces, estuvimos de acuerdo en añadir esta importante funcionalidad a la biblioteca como un método nuevo agregado a las clases Image y Bitmap: GetMonochrome().

Pasos para obtener la versión 1bpp monocromática de cualquier imagen utilizando las clases GDI+X
  1. Cargar la imagen con GdiPlus
  2. Llamar a Image.GetMonochrome para obtener un objeto nuevo que contenga la imagen 1bpp.
  3. Guardar normalmente como Bmp.
IMPORTANTE:
Todos los ejemplos siguientes utilizan la nueva biblioteca GdiPlus-X, que está en versión ALPHA; pero que es muy estable y fiable para hacer la mayoría de las tareas GDI+. Descargue la versión más actualizada desde Codeplex:

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

Este es el código de ejemplo:
_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx")))
WITH _SCREEN.System.Drawing
  * Crear un objeto Bitmap basado en un archivo BMP.
  LOCAL loOriginalBmp AS xfcBitmap
  loOriginalBmp = .Bitmap.New(GETPICT())
  LOCAL loMonoChrBmp as xfcBitmap
  loMonoChrBmp = loOriginalBmp.GetMonochrome()
  * Guardar el Bmp monocromático que ha sido creado
  loMonoChrBmp.Save("c:\Monocromatico.bmp", .Imaging.ImageFormat.Bmp)
ENDWITH
RETURN
Y este es el resultado para dos imágenes:





La primera imagen tiene buen aspecto, a que si?

Pero el logo VFPX no aparece en color monocromático. Esto ocurre porque el fondo naranja del texto es oscuro y GDI lo convierte en negro.

Pero utilizando un truco podemos arreglarlo, al convertir la imagen en escala de grises antes de convertirlo en monocromático. No está dentro del alcance de este corto artículo explicar cómo hacer esto, porque ya me dediqué a esto profundamente en dos artículos publicados en UTMAG Special effects on images with new GDIPlus-X classes - Part 1 y Special effects on images with new GDIPlus-X classes - Part 2.

Para convertir a escala de grises, podemos utilizar una matriz de color que va a convertir cada píxel en un promedio entre sus componentes rojo, azul y verde. Para escala de grises normalmente multiplicamos cada componente de color con 0.33 (1/3). Para obtener una imagen más brillante tenemos que multiplicar cada componente por un factor más alto.

Por tanto, he aquí los pasos para convertir a color monocromático:
  1. Crear la imagen originar con GDI+
  2. Crear un Bitmap nuevo temporal del mismo tamaño que el original
  3. Crear un objeto ImageAttributes
  4. Aplicar una matriz de color con escala de grises a la imagen Attributes
  5. Dibujar la imagen original utilizando ImageAttributes
  6. Guardar la imagen
Para este ejemplo he creado un lazo utilizando un factor con rango .10 a 1, para obtener imágenes diferentes.
_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx")))
WITH _SCREEN.System.Drawing
  LOCAL lcImgFile
  lcImgFile = GETPICT()
  * Crea un objeto Bitmap basado en un archivo BMP.
  LOCAL loOriginalBmp AS xfcBitmap
  loOriginalBmp = .Bitmap.FromFile(lcImgFile)
  LOCAL loTempBmp as xfcBitmap
  loTempBmp = .Bitmap.New(loOriginalBmp.Width, loOriginalBmp.Height)
  LOCAL loGfx as xfcGraphics
  loGfx = .Graphics.FromImage(loTempBmp)
  LOCAL loAttr as xfcImageAttributes
  LOCAL loGreyScaleMatrix as xfcColorMatrix
  LOCAL loMonoChrBmp as xfcBitmap
  LOCAL loRectBounds as xfcRectangle
  loRectBounds = loOriginalBmp.GetBounds()
  FOR lnFactor = .10 TO 1 STEP .10
    loGreyScaleMatrix = _Screen.System.Drawing.Imaging.ColorMatrix.New( ;
      lnFactor, lnFactor, lnFactor, 0.0, 0.0, ;
      lnFactor, lnFactor, lnFactor, 0.0, 0.0, ;
      lnFactor, lnFactor, lnFactor, 0.0, 0.0, ;
      0.0 , 0.0 , 0.0 , 1.0, 0.0, ;
      0.0 , 0.0 , 0.0 , 0.0, 1.0)
    loAttr = NULL
    loAttr = .Imaging.ImageAttributes.New()
    loAttr.SetColorMatrix(loGreyScaleMatrix)
    * Dibujar una imagen con las transformaciones en la matriz de colores
    loGfx.DrawImage(loOriginalBmp, loRectBounds, loRectBounds, 2, loAttr)
    loMonoChrBmp = loTempBmp.GetMonochrome()
    * Guarda el Bmp monocromático que ha sido creado
    loMonoChrBmp.Save("c:\" + JUSTSTEM(lcImgFile) + ;
      TRANSFORM(lnFactor * 100) + ".bmp", .Imaging.ImageFormat.Bmp)
  ENDFOR
ENDWITH
RETURN
Y esta vez podemos obtener mejores resultados, acorde con el factor deseado.

Imagen Original
Factor = 10 Factor = 20 Factor = 30 Factor = 40 Factor = 50
Factor = 60 Factor = 70 Factor = 80 Factor = 90 Factor = 100


Imagen Original
Factor = 10 Factor = 20 Factor = 30 Factor = 40 Factor = 50
Factor = 60 Factor = 70 Factor = 80 Factor = 90 Factor = 100

Enlaces relacionados:

Artículos en UTMAG

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