30 de julio de 2006

Obtener la información de los metadatos para sus imágenes con GDI+

Artículo original: GETTING METADATA INFORMATION FROM YOUR PICTURES WITH GDI+
http://weblogs.foxite.com/vfpimaging/archive/2006/03/02/1252.aspx
Autor: Cesar Ch.

Traducido por: Ana María Bisbé York

¿Sabía que todos los JPEGs de su cámara digital contienen mucha información extra?

Puede obtener mucha información interesante del tipo: Título, Equipamiento, modelo de cámara, velocidad del obturador, apertura del lente, fecha de la imagen ¡ y mucho más ! Estas etiquetas de metadato se guardan en un archivo JPEG que indica varios parámetros y la imagen con las condiciones que ocurren mientras se va creando la imagen. algunos formatos de imagen le permiten almacenar el metadato junto con una imagen como JPEG, TIFF y PNG.



Nuevamente GDI+ hace nuestras vidas más sencillas, proporcionándonos una función para obtener esa información: GetPropertyItem guardado en la clase GpImage de _gdiplus.vcx.

Baje y ejecute este archivo (http://weblogs.foxite.com/vfpimaging/attachment/1252.ashx) y seleccione una imagen de cualquier cámara digital y verá toda la información de los metadatos almacenada en ella.

En la primera parte de este código, obtengo las propiedades más comunes de la clase GpImage, tales como ImageWidth, ImageHeight, HorizontalResolution, VerticalResolution y PixelFormat. En el resto del código obtengo los metadatos desde el archivo imagen, utilizando GetPropertyIdList y GetPropertyItem. Es importante observar que GetPropertyIdList recibe una matriz como parámetro, y devuelve una matriz llenado con los metadatos.

loImage.CreateFromFile(lcSource)
DIMENSION raPropIDList(1)
LOCAL nCount, n, lcTagName, lnProp, luProp
nCount = loImage.GetPropertyIdList(@raPropIDList)
FOR n = 1 TO nCount
  lnProp = raPropIDList(n)
  luProp = loImage.GetPropertyItem(lnProp)
  ? TRANSFORM(lnProp), TRANSFORM(luProp)
ENDFOR

Es posible obtener otro tipo de información.

Mire los dos elementos finales den la imagen, ExifLightSource y ExifFlash. En ambos casos, tenemos un valor cero. Verifique esta tabla, para que vea lo que pueden significar estos valores:

TagID : 0x9208 (37384) - LightSource int16u ExifIFD
1 = Luz natural
2 = Luz Fluorescente
3 = Luz de Tungsteno
4 = Flash
9 = Fine Weather
10 = Nublado
11 = Penumbra, sombra
12 = Fluorescente diurno
13 = Fluorescente diurno blanco
14 = Fluorescente blanco frío
15 = Fluorescente blanco
17 = Luz Standard A
18 = Luz Standard B
19 = Luz Standard C
20 = D55
21 = D65
22 = D75
23 = D50
24 = ISO Tungsteno de estudio
255 = Otro

TagID : 0x9209 (37385) - Flash interno16u ExifIFD
0x0 = No Flash
0x1 = Disparó
0x5 = Disparó, No detectó Retorno
0x7 = Disparó, Detectó Retorno
0x9 = Encendido
0xd = Encendido, No detectó Retorno
0xf = Encendido, Detectó Retorno
0x10 = Apagado
0x18 = Automático, no disparó
0x19 = Automático, Disparó
0x1d = Automático, Disparó, No detectó Retorno
0x1f = Automático, Disparó, Detectó Retorno
0x20 = No hay función flash
0x41 = Disparó, Reducción de ojos rojos
0x45 = Disparó, Reducción de ojos rojos, No detectó Retorno
0x47 = Disparó, Reducción de ojos rojos, Detectó Retorno
0x49 = Encendido, Reducción de ojos rojos
0x4d = Encendido, Reducción de ojos rojos, No detectó Retorno
0x4f = Encendido, Reducción de ojos rojos, Detectó Retorno
0x59 = Automático, Disparó, Reducción de ojos rojos
0x5d = Automático, Disparó, Reducción de ojos rojos, No detectó Retorno
0x5f = Automático, Disparó, Reducción de ojos rojos, Detectó Retorno

En este enlace, podrá encontrar otra mucha información sobre las etiquetas de metadatos: http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html

Comentario agregado el 04/06/06

Escribí un artículo mucho más detallado sobre este tema que fue publicado en el número de abril de 2006 de la Revista UniversalThread Magazine.

Merece mucho la pena echarle un vistazo, allí encontrará una subclase para GpImage que le permite leer, escribir o eliminar etiquetas de imagen.

Puede crear un procedimiento sencillo para agregar información a las imágenes que distribuye o genera con sus proyectos, etc.

Para ver el artículo, todo lo que necesita es introducir su login y password. Si no tiene uno, apresúrese porque Michel Fournier acaba de abrir todo el contenido de la revista a toda la comunidad. Regístrese ahora mismo gratuitamente.

http://www.utmag.com/wconnect/wc.dll?9,7,10,,2090


21 de julio de 2006

Escalar y cortar con GDI+

Artículo original: SCALE AND SHEAR WITH GDI+
http://weblogs.foxite.com/vfpimaging/2006/02/07/rotate-flip-images-with-vfp9-and-gdi-
Autor: Cesar Ch.
Traducido por: Ana María Bisbé York


Ejecute el código que aparece debajo para cambiar el corte y la escala de una imagen.

Muchas gracias a Anatolyi Mogylevets, de www.news2news.com.

Una gran parte de este código es suyo, aunque como está incompleta _gdiplus.vcx, nos obliga a llamar directamente al API http://www.news2news.com/vfp/?example=479.

Para entender mejor como trabaja con GDI+, mire también estos enlaces:
http://www.vbaccelerator.com/home/VB/Code/vbMedia/Using_GDI_Plus/ Scale__Rotate__Skew_and_Transform_Images/article.asp
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdicpp/GDIPlus/GDIPlusreference/classes/matrixclass/matrixmethods/shear.asp
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdicpp/GDIPlus/GDIPlusreference/classes/matrixclass/matrixmethods/shear.asp




* SCALE AND SHEAR WITH GDI+
* --------------------------
* Muchas gracias a Anatolyi Mogylevets, de www.news2news.com
* Gran parte de este código es suyo, aunque como _gdiplus.vcx 
* está incompleta,nos obliga a llamar directamente a través de API
* http://www.news2news.com/vfp/?example=479
DECLARE INTEGER GdipCreateMatrix IN gdiplus INTEGER @matrix 
DECLARE INTEGER GdipDeleteMatrix IN gdiplus INTEGER matrix 
DECLARE INTEGER GdipShearMatrix IN gdiplus; 
INTEGER matrix, SINGLE shearX, SINGLE shearY, INTEGER ord 
DECLARE INTEGER GdipScaleMatrix IN gdiplus; 
INTEGER matrix, SINGLE scaleX, SINGLE scaleY, INTEGER ord 
DECLARE INTEGER GdipSetWorldTransform IN gdiplus; 
INTEGER graphics, INTEGER matrix
lcSource = GETPICT("jpg;gif;bmp")
lcDestination = ADDBS(JUSTPATH(lcSource))+ "Sheared_" +;
JUSTSTEM(lcSource)+".jpg"
LOCAL loImage AS GpImage OF _gdiplus.vcx
loImage = NEWOBJECT("GpImage", HOME() + "FFC\_gdiplus.vcx")
loImage.CreateFromFile(lcSource)

LOCAL loBitmap AS GpBitmap OF _gdiplus.vcx
loBitmap = NEWOBJECT("GpBitmap", HOME() + "FFC\_gdiplus.vcx")
LOCAL loGraphics AS GpGraphics OF _gdiplus.vcx
loGraphics = NEWOBJECT('GpGraphics',HOME() + "FFC\_gdiplus.vcx")

*** Ahora vamos a crear una imagen nueva con 
*** Create Method - Crea un objeto bitmap. 
*** Sintaxis: ? THIS.Create(tnWidth, tnHeight[, tnPixelFormat])
*** tnPixelFormat, opcional, una de las constantes GDIPLUS_PIXELFORMAT_*, 
*** predeterminadas para GDIPLUS_PIXELFORMAT_32bppARGB.

LOCAL lnWidth, lnHeight, lnPixelFormat
lnWidth = loImage.ImageWidth
lnHeight = loImage.ImageWidth
lnPixelFormat = loImage.PixelFormat
LOCAL matrix1, xScaleFactor, yScaleFactor, xShearfactor, yShearFactor
xScaleFactor = 1.30
yScaleFactor = 0.75
xShearFactor = 0.20
yShearFactor = 0.10
STORE 0 TO matrix1 

lnNewWidth = lnWidth * xScaleFactor * (1 + xShearFactor)
lnNewHeight = lnHeight * yScaleFactor * (1 + yShearFactor)
loBitmap.Create(lnNewWidth, lnNewHeight, lnPixelFormat) 
loGraphics.CreateFromImage(loBitmap)
* crea un objeto Matrix 
* y aplica las transformaciones de la escala y corte 
= GdipCreateMatrix(@matrix1) 
= GdipScaleMatrix(matrix1, xScaleFactor, yScaleFactor, 0) 
= GdipShearMatrix(matrix1, xShearFactor, yShearFactor, 0)
= GdipSetWorldTransform(loGraphics.GetHandle(), matrix1) 
loGraphics.DrawImageAt(loImage, 0, 0) 
loGraphics.ResetTransform()
= GdipDeleteMatrix(matrix1) 
loBitmap.SaveToFile(lcDestination, "image/jpeg")

20 de julio de 2006

Obtener el número de página en el que fué impreso cada registro en un reporte

Usando esta función podés obtener el número de página en el que fue impreso cada registro en un reporte.

Para que el reporte actualice los datos hay que colocar en el "On Exit" de la banda Detail del reporte una llamada a la función SavePageNo()

Una vez finalizado el reporte queda en la tabla PageCnt.dbf del directorio temporal cada registro impreso con el número de página correspondiente.

FUNCTION SavePageNo
  LOCAL nSel, nRec
  nRec = RECNO()
  nSel = SELECT()
  IF EMPTY(SELECT("PageCnt"))
    CREATE TABLE (ADDBS(SYS(2023)) + "PageCnt") FREE ;
      (RECNO I UNIQUE, PageNo I)
    INDEX ON BINTOC(RECNO) TAG RECNO
  ENDIF
  IF SEEK(BINTOC(nRec), "PageCnt")
    REPLACE PageNo WITH _PAGENO IN PageCnt
  ELSE
    INSERT INTO PageCnt VALUES (nRec, _PAGENO)
  ENDIF
  SELECT(nSel)
  RETURN
ENDFUNC

Mario Lopez

18 de julio de 2006

Abrir tablas

Artículo original: Opening tables
http://weblogs.foxite.com/andykramek/2006/06/10/opening-tables
Autor: Andy Kramek
Traducido por: Ana María Bisbé York


Uno de los códigos, que se escribe con más frecuencia, en la mayoría de las aplicaciones, tiene el siguiente aspecto:
*** Guardar el área de trabajo actual
lnSelect = SELECT()
*** Seleccionar el área requerida
IF ! USED( <Alias> )
  USE <table_name> IN 0 AGAIN ALIAS <Alias>
ENDIF
SELECT <New Work Area>
*** Hacer algo aquí
...
<commands>
...
*** Devolver el control al área original
SELECT (lnSelect)
Bien, admitamos que no es tan difícil; pero de esta forma o con alguna variación se repite muchas veces en una aplicación. Debemos ser capaces de hacerlo mejor, y de hecho podemos, ya que tenemos detrás nuestro el poder de la Orientación a objetos. La clase SelAlias define un objeto que acepta el nombre del alias de una tabla como un parámetro y establece el área de la tabla. Si la tabla no está abierta, abrirá la tabla por nosotros y si no puede encontrar la tabla, nos preguntará su localización para poder encontrarla. Más importante aun, la clase "recordará" que fue abierta la tabla y cuando el objeto sea liberado cerrará la tabla y se restaurará el área de trabajo que estaba activa cuando fue instanciado el objeto. La clase, brinda además la posibilidad para un parámetro adicional que puede ser utilizado para especificar un nombre físico de tabla cuando es necesario abrir la tabla con un alias diferente del nombre de la tabla.

Vea que esta clase no expone propiedades o métodos y hace todo su trabajo en los métodos Init y Destroy. Al crear un objeto basado en esta clase, y definirla como LOCAL, nosotros nunca más necesitaremos escribir el código que aparece antes. Se pretende que la clase pueda ser utilizada en cualquier momento que se necesite abrir una tabla al vuelo, por tanto típicamente la utilizo al crear una variable local para utilizarla como referencia al objeto. La consecuencia es que tan pronto como el método que crea la referencia termine, el objeto es liberado y la tabla se cierra (si no estaba abierta antes) y el alias original se vuelve a activar.

La clase está basada en la clase base relation - es pequeña y ligera, es rápida de instanciar; pero a diferencia de la clase base empty tiene los métodos nativos Init() y Destroy().

He utilizado este código de forma extensa "tal cual"; pero puede extenderse fácilmente para controlar otras condiciones y escenarios - por ejemplo, no va a funcionar con vistas, debido a que asume que existe un archivo físico - pero es fácilmente reparable si necesita que controle también este escenario. En cualquier caso, he aquí el código, es usted libre de modificarlo y mejorarlo
********************************************************************
*** Nombre.....: SELALIAS.PRG
*** Autor...: Andy Kramek & Marcia Akins
*** Fecha.....: 06/10/2006
*** Nota...: Copyright (c) 2006 Tightline Computers, Inc
*** Compilador.: Visual FoxPro 09.00.0000.3504 for Windows
*** Función.: Clase para seleccionar un área de trabajo específica y restablecerla
*** .........: Utilice como un objeto local para cambiar el área de trabajo.
*** .........: Al liberarlo va a restablecer el área de trabajo anterior
*** .........: Acepta dos parámetros, el primero es el Alias y es obligatorio
*** .........: El segundo es el nombre de la tabla. Si no se pasa, asume que será
*** .........: el mismo alias que está utilizando.
*** .........: loSel = CREATEOBJECT( 'xSelAlias', <Alias> [, |<File Name> ] )
********************************************************************
********************************************************************
DEFINE CLASS xSelAlias AS RELATION
******************************************************************
  PROTECTED nOldArea
  nOldArea = 0
  PROTECTED lWasOpen
  lWasOpen = .T.
  PROTECTED cAlias
  cAlias = ''
  ****************************************************************
  *** Init: Método nativo de inicialización
  ****************************************************************
  PROCEDURE INIT( tcAlias, tcTable )
    LOCAL llRetVal
    *** No se pasa ningún Alias - Nos vamos
    IF ! VARTYPE( tcAlias ) = "C" OR EMPTY( tcAlias )
      ASSERT .F. MESSAGE "Debe pasar un nombre de ALIAS PARA seleccionar el área
      de trabajo"
        RETURN .F.
    ENDIF
    tcAlias = UPPER( ALLTRIM( tcAlias ) )
    IF VARTYPE( tcTable ) # "C" OR EMPTY( tcTable )
      tcTable = tcAlias
    ELSE
      tcTable = UPPER( ALLTRIM( tcTable ) )
    ENDIF
    *** Si ya está en esa área de trabajo, no hace nada
    IF UPPER( ALLTRIM( ALIAS() ) ) == tcAlias
      RETURN .F.
    ELSE
      *** ¿Existe la tabla?
      tcTable = FORCEEXT( tcTable, 'dbf' )
      IF NOT FILE( tcTable )
        *** Intenta encontrarla....
        tcTable = LOCFILE( JUSTFNAME( tcTable ), 'dbf', "Cannot FIND" )
        IF EMPTY( tcTable )
          *** No podemos hacer nada más
          RETURN .F.
        ELSE
          *** Quizás sea que recibimos mal el nombre - establece el alias de la tabla
          tcAlias = JUSTSTEM( tcTable )
        ENDIF
      ENDIF
    ENDIF
    *** Si el alias especificada no está abierta - la abre
    IF ! USED( tcAlias )
      USE ( tcTable ) AGAIN IN 0 ALIAS ( tcAlias ) SHARED
      *** ¡ Y verifica !
      llRetVal = USED( tcAlias )
      *** If Forced Open, Note the fact
      IF llRetVal
        THIS.lWasOpen = .F.
      ENDIF
    ELSE
      llRetVal = .T.
    ENDIF
    *** Si todo OK, guardamos el área de trabajo actual
    *** Ahora se mueve al área de trabajo especificada
    IF llRetVal
      THIS.nOldArea = SELECT()
      SELECT ( tcAlias )
      THIS.cAlias = tcAlias
    ENDIF
    *** Devuelve el estado
    RETURN llRetVal
  ENDPROC
  ****************************************************************
  *** Destroy: Método nativo de destrucción de objetos
  ****************************************************************
  PROCEDURE DESTROY
    WITH THIS
      *** Si la tabla fue abierta por el objeto, se cierra
      IF NOT .lWasOpen
        USE IN ( THIS.cAlias )
      ENDIF
      *** Restablece el área de trabajo anterior
      IF NOT EMPTY( .nOldArea )
        SELECT ( .nOldArea )
      ENDIF
    ENDWITH
  ENDPROC
ENDDEFINE

17 de julio de 2006

Zorro Volador: Aplicando informes de VFP a cualquier dato, en cualquier entorno

Nuevo libro de Lisa Slater-Nicholls que abarca el uso de informes de VFP como front-end para cualquier tipo de datos como back-end.



Título original: Flying Fox: Applying Visual FoxPro Reporting to Any Data, in Any Environment
Autora: Lisa Slater Nicholls
Lenguaje: Inglés
ISBN: 3-937133-09-7
Páginas: 152
Formatos disponibles: Impreso (incluye Ebook) o solamente Ebook (PDF 4,5 MB)
Precio: Impreso + Ebook U$S 39,95; Solo Ebook: U$S 29,95
Fecha de publicación: Julio de 2006
Código fuente: Completo y listo para descargar (1,1 MB)
Con Visual FoxPro 9.0 puede añadir informes flexibles y económicos a cualquier base de datos accesible por ODBC u OLEDB. Este libro brinda los instrumentos y técnicas para usar con VFP 9.
Mas información: http://www.hentzenwerke.com/catalog/flyingfox.htm

14 de julio de 2006

Rotar / invertir imágenes con VFP 9 y GDI+

Artículo original: ROTATE / FLIP IMAGES WITH VFP9 AND GDI+
http://weblogs.foxite.com/vfpimaging/archive/2006/02/07/1125.aspx
Autor: Cesar Ch.
Traducido por: Ana María Bisbé York


Rotar y/o invertir imágenes son tareas simples para GDI+. Para ver los diferentes resultados, cambie el valor constante en el parámetro"RotateFlip" abajo en el código.
*-- Constantes para RotateFlipType
#define RotateNoneFlipNone 0
#define Rotate90FlipNone   1
#define Rotate180FlipNone  2
#define Rotate270FlipNone  3
#define RotateNoneFlipX    4
#define Rotate90FlipX      5
#define Rotate180FlipX     6
#define Rotate270FlipX     7

LOCAL lcSource, lcDestination
lcSource = GETPICT("jpg;gif;bmp")
lcDestination = "Flipped_" + lcSource

LOCAL loImage AS GpImage OF ffc/_gdiplus.vcx
loImage = NEWOBJECT("GpImage", "ffc/_gdiplus.vcx")
loImage.CreateFromFile(lcSource)
** Pruebe cambiando este valor empleando las constantes mostradas antes
loImage.RotateFlip(Rotate180FlipNone)
loImage.SaveToFile(lcDestination,"image/jpeg")

13 de julio de 2006

Menús con múltiples columnas

Con el diseñador de menús, o haciendo nuestros menús por código, podemos crear menús con múltiples columnas o submenús horizontales. Solo debemos anteponer la barra invertida "\" seguida del caracter "|" (CHR(124)) al inicio del ítem del menú que deseamos iniciar como una nueva columna. Los ítems sucesivos se colocaran en la misma columna hasta encontrar otro ítem que comience con "\|".

Por ejemplo si deseamos iniciar una nueva columna con el ítem "Imprimir", debemos definir nuestra barra con:
DEFINE BAR 3 OF MiPopup PROMPT "\|Imprimir"
Ejecutando el siguiente código de ejemplo, veremos en la primera opción, un menú con múltiples columnas como lo muestra la Figura 1


Figura 1

La segunda opción del menú nos muestra el submenú horizontal de la Figura 2.


Figura 2

SET SYSMENU TO
SET SYSMENU AUTOMATIC
DO MenuHorizontal
READ EVENTS
SET SYSMENU TO DEFAULT
*---
PROCEDURE MenuHorizontal
  DEFINE PAD PadArchivo OF _MSYSMENU ;
    PROMPT "\<Archivo" KEY ALT+A
  DEFINE PAD PadEdicion OF _MSYSMENU ;
    PROMPT "\<Edicion" KEY ALT+E
  ON PAD PadArchivo OF _MSYSMENU ;
    ACTIVATE POPUP PopArchivo
  ON PAD PadEdicion OF _MSYSMENU ;
    ACTIVATE POPUP PopEdicion
  *---
  DEFINE POPUP PopArchivo MARGIN RELATIVE SHADOW
  DEFINE BAR 1 OF PopArchivo ;
    PROMPT "\<Nuevo" PICTRES _MFI_NEW
  DEFINE BAR 2 OF PopArchivo ;
    PROMPT "\<Abrir" PICTRES _MFI_OPEN
  DEFINE BAR 3 OF PopArchivo ;
    PROMPT "\<Salir" PICTRES _MFI_QUIT
  DEFINE BAR 4 OF PopArchivo ;
    PROMPT "\|\<Guardar" PICTRES _MFI_SAVE
  DEFINE BAR 5 OF PopArchivo ;
    PROMPT "Guardar \<como" PICTRES _MFI_SAVAS
  DEFINE BAR 6 OF PopArchivo ;
    PROMPT "Guardar como \<HTML" PICTRES _mfi_saveashtml
  DEFINE BAR 7 OF PopArchivo ;
    PROMPT "\|\<Vista preliminar" PICTRES _MFI_PREVU
  DEFINE BAR 8 OF PopArchivo ;
    PROMPT "\<Imprimir" PICTRES _mfi_sysprint
  DEFINE BAR 9 OF PopArchivo ;
    PROMPT "\<Enviar" PICTRES _MFI_SEND
  ON SELECTION BAR 3 OF PopArchivo CLEAR EVENTS
  *---
  DEFINE POPUP PopEdicion MARGIN RELATIVE SHADOW
  DEFINE BAR 1 OF PopEdicion ;
    PROMPT "\|\<Copiar" PICTRES _MED_COPY
  DEFINE BAR 2 OF PopEdicion ;
    PROMPT "\|Cor\<tar" PICTRES _MED_CUT
  DEFINE BAR 3 OF PopEdicion ;
    PROMPT "\|\<Pegar" PICTRES _MED_PASTE
  DEFINE BAR 4 OF PopEdicion ;
    PROMPT "\|\<Deshacer" PICTRES _MED_UNDO
  DEFINE BAR 5 OF PopEdicion ;
    PROMPT "\|\<Rehacer" PICTRES _MED_REDO
ENDPROC
*---

Luis María Guayán

7 de julio de 2006

Convertir imágenes a formatos diferentes con VFP9 y GDI+

Artículo original: CONVERT IMAGE TYPES WITH VFP9 AND GDI+
http://weblogs.foxite.com/vfpimaging/archive/2006/02/07/1126.aspx
Autor: Cesar Ch.
Traducido por: Ana María Bisbé York


Este fragmento de código carga una imagen y la guarda en la misma carpeta original en un formato soportado por GDI+
LOCAL lcSource, lcDestination
lcSource = GETPICT()
lcDestination = JUSTPATH(lcSource) + "\_" + JUSTSTEM(lcSource)

LOCAL loImage AS GpImage OF HOME() + ffc/_gdiplus.vcx
loImage = NEWOBJECT("GpImage", HOME() + "ffc/_gdiplus.vcx")
loImage.CreateFromFile(lcSource)
loImage.SaveToFile(lcDestination + ".jpg","image/jpeg")
loImage.SaveToFile(lcDestination + ".bmp","image/bmp")
loImage.SaveToFile(lcDestination + ".tif","image/tiff")
loImage.SaveToFile(lcDestination + ".gif","image/gif")
loImage.SaveToFile(lcDestination + ".png","image/png")

*!* JPEGs permite seleccionar la calidad de la imagen

loImage.SaveToFile(lcDestination + ".jpg","image/jpeg", "quality=70")

5 de julio de 2006

Actualizar nuestra aplicación VFP "on the fly"

Autor: Ricardo Fynn Reissig

Planteamos el siguiente escenario. Tenemos nuestra aplicación VFP que es un ejecutable llamado main.exe.

Este reside en los 40 puestos de trabajo de la red de nuestro cliente y accede a una base de datos (no importa cual) en su servidor central.

Ok, esta es un arquitectura tipo, pero que pasa cuando liberamos una nueva versión de nuestro software y hay que distribuirla a los 40 puestos de trabajo ?

Soluciones hay varias, en esta entrega les mostramos como actualizar nuestra aplicación directamente de Internet sin intervención del usuario, algo similar a como trabaja (salvando las distancias) un antivirus.

El primer truco

Lo primero que debemos hacer es partir nuestra aplicación en dos partes, una que lance a main.exe y antes controle la versión y si es necesario la actualice automáticamente. Por que esto ? simplemente por que cuando nuestro ejecutable este cargado en memoria no lo podremos sobrescribir con la nueva versión.

Una idea es renombrar a nuestro main.exe a main.bin, esta continuará siendo nuestra aplicación "camuflada" y usaremos un programa para lanzar a main.bin

También podríamos hacer de nuestra aplicación una DLL, pero no veremos este tema aquí.

4 de julio de 2006

Utilizando la función ANETRESOURCES()

Podemos utilizar la función ANETRESOURCES() (disponible desde Visual FoxPro 6.0) para obtener en un vector, pasado como primer parámetro, todos los nombres de computadoras de un dominio o grupo de trabajo; o también los recursos compartidos e impresoras de red de una computadora específica.

En el segundo parámetro se debe especificar el nombre de dominio o grupo de trabajo (esto a partir de Visual FoxPro 7.0); o el nombre de la computadora con el formato \\NombreComputadora.

La función ANETRESOURCES() nos retornará cualquier recurso de red, solo los recursos compartidos, o solo las impresoras de red, según el valor del tercer parámetro, que puede ser 0, 1 ó 2 respectivamente.

El siguiente código de ejemplo nos mostrará un formulario similar al de la Figura 1. Este formulario carga en una lista los nombres de las computadoras del dominio o grupo de trabajo actual (o el que nosotros especifiquemos en el cuadro de texto), y a medida que recorremos esta lista, nos mostrará los recursos compartidos e impresoras de red de cada computadora.


Figura 1
PUBLIC goMiForm
goMiForm = NEWOBJECT("MiForm")
goMiForm.SHOW(1)
RETURN

DEFINE CLASS MiForm AS FORM
  HEIGHT = 260
  WIDTH = 530
  AUTOCENTER = .T.
  SHOWWINDOW = 2
  CAPTION = "Ejemplo de ANETRESOURCES()"
  ICON = HOME(1) + "\Graphics\Icons\Win95\NetHood.ico"
  NAME = "frmANet"
  ADD OBJECT lstcomputadoras AS LISTBOX WITH ;
    HEIGHT = 168, LEFT = 16, TOP = 72, WIDTH = 184, ;
    NAME = "lstComputadoras"
  ADD OBJECT lstrecursos AS LISTBOX WITH ;
    HEIGHT = 72, LEFT = 216, TOP = 72, WIDTH = 296, ;
    NAME = "lstRecursos"
  ADD OBJECT lstimpresoras AS LISTBOX WITH ;
    HEIGHT = 72, LEFT = 216, TOP = 168, WIDTH = 296, ;
    NAME = "lstImpresoras"
  ADD OBJECT txtdominio AS TEXTBOX WITH ;
    HEIGHT = 24, LEFT = 16, TOP = 24, WIDTH = 184, ;
    NAME = "txtDominio"
  ADD OBJECT lblTit1 AS LABEL WITH ;
    NAME = "lblTit1", AUTOSIZE = .T., FONTBOLD = .T., ;
    BACKSTYLE = 0,    CAPTION = "Dominio", ;
    HEIGHT = 17, LEFT = 16, TOP = 8, WIDTH = 48
  ADD OBJECT lblTit2 AS LABEL WITH ;
    NAME = "lblTit2", AUTOSIZE = .T., FONTBOLD = .T., ;
    BACKSTYLE = 0,    CAPTION = "Computadoras", ;
    HEIGHT = 17, LEFT = 16, TOP = 56, WIDTH = 86
  ADD OBJECT lblTit3 AS LABEL WITH ;
    NAME = "lblTit3", AUTOSIZE = .T., FONTBOLD = .T., ;
    BACKSTYLE = 0, CAPTION = "Recursos compartidos", ;
    HEIGHT = 17, LEFT = 216, TOP = 56, WIDTH = 132
  ADD OBJECT lblTit4 AS LABEL WITH ;
    NAME = "lblTit4", AUTOSIZE = .T.,  FONTBOLD = .T., ;
    BACKSTYLE = 0, CAPTION = "Impresoras compartidas", ;
    HEIGHT = 17, LEFT = 216, TOP = 152, WIDTH = 143
  ADD OBJECT cmdCargarDominio AS COMMANDBUTTON WITH ;
    TOP = 24, LEFT = 216, HEIGHT = 24, WIDTH = 136, ;
    CAPTION = "Cargar dominio", NAME = "cmdCargarDominio"
  *--
  PROCEDURE CargarRecursos
    LPARAMETERS tcComputadora
    LOCAL ln
    *-- Lista de recursos compartidos
    WITH THISFORM.lstRecursos
      .CLEAR
      FOR ln = 1 TO ANETRESOURCES(la,tcComputadora,1)
        .ADDITEM("\" + la(ln),ln,1)
      ENDFOR
      .PICTURE = HOME(1) + ;
        "\Graphics\Bitmaps\Outline\Nomask\OpenFold.bmp"
    ENDWITH
    *-- Lista de impresoras de red
    WITH THISFORM.lstImpresoras
      .CLEAR
      FOR ln = 1 TO ANETRESOURCES(la,tcComputadora,2)
        .ADDITEM("\" + la(ln),ln,1)
      ENDFOR
      .PICTURE = HOME(1) + ;
        "\Graphics\Bitmaps\Outline\Nomask\PrintFld.bmp"
    ENDWITH
  ENDPROC
  *--
  PROCEDURE CargarComputadoras
    LPARAMETERS tcDominio
    LOCAL ln
    *-- Lista de computadoras del dominio o grupo de trabajo
    WITH THISFORM.lstComputadoras
      .CLEAR
      FOR ln = 1 TO ANETRESOURCES(la,tcDominio,0)
        .ADDITEM("\" + la(ln),ln,1)
      ENDFOR
      .PICTURE = HOME(1) + ;
        "\Graphics\Bitmaps\Outline\Nomask\MyComp.bmp"
      .LISTINDEX = 1
    ENDWITH
  ENDPROC
  *--
  PROCEDURE INIT
    LOCAL lcDominio, lcComputadora
    *-- Toma el dominio actual
    lcDominio = GETENV("USERDOMAIN")
    THISFORM.txtDominio.VALUE = lcDominio
    THISFORM.CargarComputadoras(lcDominio)
    lcComputadora = THISFORM.lstComputadoras.VALUE
    THISFORM.CargarRecursos(lcComputadora)
  ENDPROC
  *--
  PROCEDURE lstComputadoras.INTERACTIVECHANGE
    LOCAL lcComputadora
    WAIT WINDOW NOWAIT "Trabajando ..."
    lcComputadora = THIS.VALUE
    THISFORM.CargarRecursos(lcComputadora)
    WAIT CLEAR
  ENDPROC
  *--
  PROCEDURE cmdCargarDominio.CLICK
    LOCAL lcDominio
    WAIT WINDOW NOWAIT "Trabajando ..."
    lcDominio = ALLTRIM(THISFORM.txtDominio.VALUE)
    THISFORM.CargarComputadoras(lcDominio)
    lcComputadora = THISFORM.lstComputadoras.VALUE
    THISFORM.CargarRecursos(lcComputadora)
    WAIT CLEAR
  ENDPROC
ENDDEFINE

Para mas detalles de la función ANETRESOURCES() mire el archivo de ayuda de Visual FoxPro.

3 de julio de 2006

Trucos sobre Depuración

Artículo original: Debugging Tips
http://doughennig.blogspot.com/2006/05/debugging-tips.html
Autor: Doug Hennig
Traducido por: Ana María Bisbé York

Como dije en mi post http://doughennig.blogspot.com/2006/04/back-from-glgdw.html sobre GLGDW http://www.hentzenwerke.com/conferences/glgdw2006.htm, me perdí la sesión sobre las Mejores prácticas de Depuración. He aquí un par de trucos que iba a mencionar. Siento si alguien los mencionó, yo estaba tan ocupado configurando el PC de Rick para hacer mi presentación, que no escuché lo que dijeron.

1.- Escriba código que sea cómodo de depurar

Yo escribía código de este tipo:
llReturn = UnaFuncion() and OtraFuncion() and OtraFuncionMas()
Ahora no hago esto, por dos razones: es más difícil de entender que:
llReturn = UnaFuncion()
llReturn = llReturn and OtraFuncion()
llReturn = llReturn and OtraFuncionMas()
Pero, lo más importante, es más difícil de depurar. Si se ejecuta paso a paso, la ejecución va a UnaFuncion. Si decide que no necesita hacer el seguimiento dentro de esa función, y quiere saltar a la siguiente, puede pensar en Paso a paso por procedimientos para lograrlo. Desafortunadamente esto provoca que se ejecute la línea siguiente al llReturn, por tanto la única forma en que podría hacer seguimiento a OtraFuncionMas sería recorriendo UnaFuncion y OtraFuncion o agregando específicamente un SET STEP ON (o estableciendo un punto de ruptura) para OtraFuncionMas.

Vea que yo no suelo escribir este tipo de código:
llReturn = UnaFuncion()
if llReturn
  llReturn = OtraFuncion()
  if llReturn
    llReturn = OtraFuncionMas()
  endif
endif
Para mi, esto es aun más difícil de leer que el primer ejemplo.

He aquí otro ejemplo de código que yo escribía y que no se depura cómodamente:
SomeVariable = left(UnaFuncion(), 5) + substr(OtraFuncion(), 2, 1)
La razón de que no sea cómodo es que no se puede ver lo que devuelven UnaFuncion y OtraFuncion a no ser que haga seguimiento específico de su código. En su lugar, utilizo este código:
Variable1 = UnaFuncion()
Variable2 = OtraFuncion()
OtraVariable = left(Variable1, 5) + substr(Variable2, 2, 1)
De esta forma, puedo recorrer el código y ver qué devuelven estas funciones sin tener que recorrerlas. Si algo parece estar mal, podré colocarme de nuevo en la llamada y Configurar siguiente instrucción recorriendo paso a paso la función para ver qué es lo que está mal.

2.- Instrumente su aplicación

Rod Paddock, Lisa Slater Nicholls, yo y otros, hemos escrito artículos sobre instrumentar las aplicaciones. La idea es esparcir llamadas a un objeto que realice un log a lo largo de nuestra aplicación. Si el log está desactivado, no ocurre nada, si está activado, por ejemplo,  oLogger.lPerformLogging es .T.), se escribe un mensaje en una ubicación (un archivo de texto, una tabla, un log de Eventos de Windows, etc.) indicando lo que hace la aplicación. Encuentro que esto tiene un gran valor a la hora de dar seguimiento a los problemas. Aunque la generación de logs es genial, sólo brindará un vistazo de la situación en la que ocurrió el error. A veces es necesario saber cómo se llegó hasta allí.

Además, a veces el problema que experimenta el  usuario no ha generado un error, sino un problema de comportamiento. Al examinar el log de diagnóstico, podemos ver todos los pasos (en cualquier caso, son los que usted ha instrumentado) que han causado ese comportamiento. Además, puede utilizar SET COVERAGE en su aplicación; pero esto genera toneladas de información, mucho más de lo que necesita y no le dará la clave de la causa del problema. El artículo de Lisa es lo más reciente que he leído, así que es un buen punto de partida. El artículo de Rod está publicado en la revista FoxTalk en Septiembre de 1997 y el mío en octubre de 2003 de la revista FoxTalk (ambos artículos requieren estar suscrito a la revista).

Redimensionar imágenes con VFP 9 y GDI+

Artículo original: RESIZE IMAGES WITH VFP9 and GDI+
http://weblogs.foxite.com/vfpimaging/archive/2006/02/06/1124.aspx
Autor: Cesar Ch.
Traducido por: Ana María Bisbé York


La función GetThumbnailImage puede ser utilizada para redimensionar imágenes; pero el resultado nos deja una imagen de menor calidad.
#DEFINE GDIPLUS_PIXELFORMAT_1bppIndexed    0x00030101
#DEFINE GDIPLUS_PIXELFORMAT_4bppIndexed    0x00030402
#DEFINE GDIPLUS_PIXELFORMAT_8bppIndexed    0x00030803
#DEFINE GDIPLUS_PIXELFORMAT_16bppGrayScale 0x00101004
#DEFINE GDIPLUS_PIXELFORMAT_16bppRGB555    0x00021005
#DEFINE GDIPLUS_PIXELFORMAT_16bppRGB565    0x00021006
#DEFINE GDIPLUS_PIXELFORMAT_16bppARGB1555  0x00061007
#DEFINE GDIPLUS_PIXELFORMAT_24bppRGB       0x00021808
#DEFINE GDIPLUS_PIXELFORMAT_32bppRGB       0x00022009
#DEFINE GDIPLUS_PIXELFORMAT_32bppARGB      0x0026200A
#DEFINE GDIPLUS_PIXELFORMAT_32bppPARGB     0x000E200B
#DEFINE GDIPLUS_PIXELFORMAT_48bppRGB       0x0010300C
#DEFINE GDIPLUS_PIXELFORMAT_64bppPARGB     0x001C400E

lcSource = GETPICT("jpg;gif;bmp")
lcDestination = ADDBS(JUSTPATH(lcSource))+ "Resized_" +;
JUSTSTEM(lcSource)+".bmp"
LOCAL loImage AS GpImage OF ffc/_gdiplus.vcx
loImage = NEWOBJECT("GpImage", HOME() + "ffc/_gdiplus.vcx")
loImage.CreateFromFile(lcSource)

LOCAL loBitmap AS GpBitmap OF ffc/_gdiplus.vcx
loBitmap = NEWOBJECT("GpBitmap", HOME() + "ffc/_gdiplus.vcx")
LOCAL loGraphics as GpGraphics OF HOME() + ffc/_gdiplus.vcx
loGraphics = NEWOBJECT("GpGraphics",HOME() + "ffc/_gdiplus.vcx")

*** Ahora creamos una imagen con
*** Create Method - Crea un objeto bitmap .
*** ¿La sintaxis?: THIS.Create(tnWidth, tnHeight[, tnPixelFormat])
***
*** tnPixelFormat, es opcional, una de las constantes GDIPLUS_PIXELFORMAT_*,
*** su valor predeterminado es GDIPLUS_PIXELFORMAT_32bppARGB.

LOCAL lnNewWidth, lnNewHeight
lnNewWidth = 640 && Coloque aquí el ancho (Width) deseado
lnNewHeight = 480 && Coloque aquí la altura (Height) deseada

loBitmap.Create(lnNewWidth, lnNewHeight, GDIPLUS_PIXELFORMAT_32bppPARGB)
*** Las otras constantes están al inicio de este código

loGraphics.CreateFromImage(loBitmap)
loGraphics.DrawImageScaled(loImage, 0, 0, lnNewWidth, lnNewHeight)
loBitmap.SaveToFile(lcDestination, "image/bmp")
RETURN