30 de octubre de 2006

Numerar los registros de un Select

Un truco para numerar los registros retornados por un comando SELECT-SQL.

Este truco fue tomado del artículo "Una consulta interesante escrita para SQL Server 2000 y SQL Server 2005" del amigo Miguel Egea (SQL Server MVP y Webmaster de PortalSQL).

Muchas veces he leído en diversos mensajes en el Foro de VFP, que se necesita numerar los registros retornados por un SELECT para pintar los registros alternados de un control Grid, o también para enumerar los documentos por clientes, o los ítems por factura. Con este truco, esto se puede lograr en una sola sentencia SELECT.

La idea es hacer un JOIN contra la misma tabla y contar los registros que tengan el identificador mayor o igual que la segunda tabla del JOIN.

Para hacer mas gráfica la idea, tomemos el ejemplo de enumerar los Clientes de la tabla Customers de la base de datos Northwind:
OPEN DATABASE (HOME(2) + "Northwind\Northwind")
SELECT COUNT(*) AS Registro, c1.CustomerId, c1.CompanyName ;
  FROM Customers C1 ;
  INNER JOIN Customers C2 ON C1.CustomerId >= C2.CustomerId ;
  GROUP BY C1.CustomerId, C1.CompanyName ;
  ORDER BY Registro

Con esta mismo concepto, también podemos hacer "cortes" en la numeración y reinicializarla según una condición.

En este otro ejemplo numeramos las Ordenes de la tabla Orders y hacemos un corte por Cliente:
OPEN DATABASE (HOME(2) + "Northwind\Northwind")
SELECT COUNT(*) AS Registro, O1.OrderId, O1.CustomerId, O1.ShipName ;
  FROM Orders O1 ;
  INNER JOIN Orders O2 ON O1.CustomerId = O2.CustomerId ;
  AND O1.OrderId >= O2.OrderId ;
  GROUP BY O1.OrderId, O1.CustomerId, O1.ShipName ;
  ORDER BY O1.CustomerId, Registro

A partir de Visual FoxPro 9.0, las mejoras incorporadas en el comando SELECT-SQL nos permite retornar el mismo conjunto de datos utilizando subconsultas.

Los mismos 2 ejemplos anteriores con sintaxis para VFP9:
OPEN DATABASE (HOME(2) + "Northwind\Northwind")
SELECT (SELECT COUNT(*) ;
  FROM Customers C2 ;
  WHERE C1.CustomerId >= C2.CustomerId) AS Registro, C1.* ;
  FROM Customers C1 ;
  ORDER BY Registro
OPEN DATABASE (HOME(2) + "Northwind\Northwind")
SELECT (SELECT COUNT(*) ;
  FROM Orders O2 ;
  WHERE O1.OrderId >= O2.OrderId AND ;
  O1.CustomerId = O2.CustomerId) AS Registro, O1.* ;
  FROM Orders O1 ;
  ORDER BY O1.CustomerId, Registro

Nota: No utilizar este truco con tablas que contengan muchos registros, ya que la performance del JOIN contra la misma tabla puede ser muy pobre. Antes de implementar esta solución, es conveniente comprobar que la velocidad de respuesta no afecte considerablemente los tiempos de nuestra aplicación.

Hasta la próxima,

Luis María Guayán

28 de octubre de 2006

West Wind Internet & Client Tools 5.0 liberado



Ya está disponible la versión 5.0 de West Wind Internet & Client Tools, un paquete integrado que proporcionan funcionalidad de Internet y mucho mas, a aplicaciones VFP.

West Wind Internet & Client Tools es un paquete integrado de clases de VFP muy útiles que proporcionan funcionalidad de Internet, HTTP, Mail, FTP, acceso TCP/IP y mucho mas. También incluye un juego de utilidades que proporcionan acceso orientado a objetos a datos de SQL Server y Web, gráficas básicas para aplicaciones de escritorio y Web, y una rica librería de funciones útiles en el desarrollo diario con VFP.

West Wind Internet & Client Tools es una herramienta shareware. La versión registrada tiene un costo de U$S 199 y es royalty free.
Para mas información y descargar una copia de West Wind Internet & Client Tools haga clic aquí.

26 de octubre de 2006

Integrando VFP con Microsoft Virtual Earth

Microsoft Virtual Earth es la herramienta de Microsoft, que compite con Google Maps.

En este artículo de Dave Crozier (Replacement-Software), se muestra un excelente ejemplo de como integrarlo con Visual FoxPro.

Hace aproximadamente un mes se ha publicado en PortalFox el artículo "Google Maps en un formulario de VFP", donde se incorpora una página HTML a un formulario de VFP que nos permitía navegar en Google Maps.

Con Microsoft Virtual Earth también se puede obtener un resultado similar, como se muestra en el Blog de Dave Crozier, donde se ha publicado un artículo, sobre como incorporar esta herramienta de Microsoft en un formulario de Visual FoxPro, con varias e interesantes opciones, y con su código fuente completo para descarga.

Para leer el artículo "VFP integration with Microsoft Virtual Earth" de Dave Crozier (en Inglés), y descargar el ejemplo haga clic aquí.


(*) Imagen tomada del Blog de Dave Crozier

Dave promete mejorar este código y publicarlo cuando este terminado. ¡A estar atentos!

17 de octubre de 2006

Detectar el estado de un informe

Hace algunas semanas en un mensaje en el Grupo de Noticias en Español de Visual FoxPro se consultaba sobre como detectar si un informe estaba en modo de vista previa o imprimiéndose, para así agregar una condición de impresión a ciertos controles (campos, etiquetas, imágenes, líneas, etc.) que deben por ejemplo visualizarse en la vista previa, pero no imprimirse.

A partir de Visual FoxPro 8, esto es muy fácil con la función SYS(2040) que nos indica si hay un informe activo, si está en vista previa o se está imprimiendo.

SYS(2040) retorna un caracter "0" si no hay ningún informe activo; "1" si el informe esta en vista previa; ó "2" si el informe se está enviando a la impresora o a un archivo.

Por ejemplo si solo necesitamos visualizar un control en modo de vista previa, se debe agregar como condición de impresión la expresión SYS(2040)="1"

Otra opción, para campos y etiquetas, es utilizar una expresión tipo:

IIF(SYS(2040)="1", "Vista Previa", "Impresora")

Para mas detalles vea la ayuda de la Función SYS(2040) en la documentación de Visual FoxPro.

Luis María Guayán

13 de octubre de 2006

Formularios con fondos degradados con GDI+ Parte 2

Artículo original: Gradient Backgrounds in your forms with GDI+ Part 2
http://weblogs.foxite.com/vfpimaging/archive/2006/06/22/1906.aspx
Autor: Cesar Ch.
Traducido por: Ana María Bisbé York


En mi entrada anterior con el mismo título, mostré cómo crear fondos con degradado VERTICAL en formularios. (Nota de la traductora: El autor se refiere a "Gradient Backgrounds in your forms with GDI+" que está traducido al español bajo el título "Formularios con fondos de colores degradados con GDI+")

Como explicó Malcolm Greene, (http://weblogs.foxite.com/vfpimaging/archive/2006/06/13/1825.aspx#comments) la misma técnica puede ser utilizada para contenedores VFP.

De acuerdo con MSDN, "La clase LinearGradientBrush define una brocha que pinta un color degradado en el que el color cambie uniformemente desde la línea de límite que comienza el degradado hasta la línea final del límite de la brocha. Las líneas de borde de un degradado lineal son dos líneas rectas paralelas. El degradado de color es perpendicular a las líneas de borde, cambiando gradualmente a través del movimiento desde la línea de borde inicial a la línea de borde final. El color degradado tiene un color en la línea de borde inicial y otro en la línea de borde final."

11 de octubre de 2006

Creando una aplicación VFP como un servicio

Normalmente, la creación de un servicio es bastante compleja, pero la creación de una aplicación VFP como un servicio es bastante simple gracias al par de herramientas INSTSRV.EXE y SRVANY.EXE

Artículo en inglés en el Blog de Calvin Hsia.

-- Creating a VFP application as a service --
http://blogs.msdn.com/calvin_hsia/archive/2004/12/13/282351.aspx

10 de octubre de 2006

Formularios con fondos de colores degradados con GDI+

Artículo original: Gradient Backgrounds in your forms with GDI
http://weblogs.foxite.com/vfpimaging/archive/2006/06/13/1825.aspx 
Autor: Cesar Ch.
Traducido por: Ana María Bisbé York 

Este escrito va para Bernard Bout.

Después de leer este post, vea también Gradient Backgrounds in your forms with GDI+ Part 2 (traducido al español en PortalFox como "Formularios con fondos de colores degradados con GDI+ Parte 2").





GDI+ permite crear muchos efectos tales como degradado de colores. Esta característica no se incluyó en _gdiplus.vcx; pero se puede acceder fácilmente con una única llamada a la función Flat de API. La mayor parte del código que se muestra a continuación relacionado con la brocha con Degradado es de Bob Durban.

Aplicarlo en formularios es realmente sencillo. Agregue el código que está debajo a los eventos del formulario: LOAD, RESIZE y DESTROY

Load Event
LOCAL lcGradFile
lcGradFile = ADDBS(SYS(2023))+SYS(2015)+".bmp"
This.AddProperty("cTempGradFile",lcGradFile)
IF FILE(lcGradFile)
  CLEAR RESOURCES (lcGradFile)
ENDIF
LOCAL lnRGBColor1
lnRGBColor1 = RGB(60,30,180) && Blue

* Crear la imagen para el degradado con GdipCreateLineBrushI 
SET CLASSLIB TO HOME() + "ffc/_gdiplus.vcx" ADDITIVE

* Declarar API
DECLARE Long GdipCreateLineBrushI IN GDIPLUS.dll ;
  String point1, String point2, ;
  Long color1, Long color2, ;
  Long wrapMode, Long @lineGradient

* Crear un objeto color y guarda los valores de color ARGB en variables
LOCAL loClr AS GpColor OF HOME() + "ffc/_gdiplus.vcx"
LOCAL lnColor1, lnColor2
loClr = CREATEOBJECT("gpColor")
loClr.FoxRGB = lnRGBColor1
lnColor1 = loClr.ARGB
loClr.FoxRGB = RGB(255,255,255) && White
lnColor2 = loClr.ARGB

* Crear un bitmap
LOCAL loBmp AS GpBitmap OF HOME() + "ffc/_gdiplus.vcx"
loBmp = CREATEOBJECT("gpBitmap")
loBmp.Create(1,Thisform.Height)

* Obtener un objeto gráfico bitmap 
LOCAL loGfx AS GpGraphics OF HOME() + "ffc/_gdiplus.vcx"
loGfx = CREATEOBJECT("gpGraphics")
loGfx.CreateFromImage(loBmp)

* Obtener una brocha con degradado
LOCAL loBrush as GpBrush OF HOME() + "ffc/_gdiplus.vcx"
LOCAL hBrush && Brush Handle
hBrush = 0
GdipCreateLineBrushI(BINTOC(0,"4rs")+BINTOC(0,"4rs"), ;
  BINTOC(0,"4rs")+BINTOC(Thisform.Height,"4rs"), ;
  lnColor1, lnColor2, 0, @hBrush)
loBrush = CREATEOBJECT("gpBrush")
loBrush.SetHandle(hBrush, .T.)

* Llenar nuestro mapa de bits con el degradado
loGfx.FillRectangle(loBrush,0,0,1,Thisform.Height)
loBmp.SaveToFile(lcGradFile,"image/bmp")
Thisform.AddObject("ImgBackGround","Image")
WITH Thisform.ImgBackGround
  .Stretch = 2
  .Width = Thisform.Width
  .Height = Thisform.Height
  .Picture = lcGradFile 
  .Visible = .T.
ENDWITH
RETURN

Resize Event
Thisform.ImgBackGround.Width = Thisform.Width
Thisform.ImgBackGround.Height = Thisform.Height

Destroy Event
WITH Thisform
  IF FILE(.cTempGradFile)
    CLEAR RESOURCES (.cTempGradFile)
    ERASE (.cTempGradFile)
  ENDIF
ENDWITH

Las nuevas clases GDI+

El código que he presentado en el evento LOAD funciona muy bien; pero tiene una apariencia bastante fea, al compararlo con lo que seremos capaces de hacer cuando sean liberadas las nuevas clases Sedna-X GDI+

El código que aparece a continuación va a sustituir todo el código relacionado con la creación del color degradado en el evento LOAD.

¡Es que no puedo esperar a que estén finalizadas estas clases!

Su trabajo es realmente brillante, y va a añadir mucho más que una clase envoltorio (wrapper)
* Crear una imagen con degradado lineal GDI+ utilizando 
* las clases xfc GDI+ de Bo Durban y Craig Boyd
System = NEWOBJECT("xfcSystem","system.vcx")
LOCAL loBmp AS xfcBitmap
LOCAL loGfx AS xfcGraphics
LOCAL loBrush AS xfcBrush
WITH System.Drawing 
  loBmp = .Bitmap.New(1, Thisform.Height) 
  loGfx = .Graphics.FromImage(loBmp) 
  loBrush = .Drawing2D.LinearGradientBrush.New( ; 
    .Rectangle.New(0, 0, 1, Thisform.Height), ; 
    .Color.FromRGB(lnRGBColor1), .Color.White, 1) 
  loGfx.FillRectangle(loBrush, loBrush.Rectangle) 
  loBmp.Save(lcGradFile, .Imaging.ImageFormat.Bmp) 
ENDWITH





Haga clic para descargar el código fuente de este formulario de ejemplo

9 de octubre de 2006

Añadir un "sizing grip" a un formulario de nivel superior

Este código demuestra como añadir un "sizing grip" a un formulario de nivel superior.

El sizing grip se muestra en el estilo "clásico" de Windows, es decir, sin temas, y solamente tiene funcionalidad en formularios de nivel superior. Pero el código es tan simple, que me parece vale la pena verlo.
PUBLIC oForm1
oForm1 = NEWOBJECT("Form1")
oForm1.SHOW
RETURN

DEFINE CLASS Form1 AS FORM
  HEIGHT = 269
  WIDTH = 348
  SHOWWINDOW = 2
  CAPTION = "Demo de sizing grip"
  AUTOCENTER = .T.
  NAME = "Form1"

  ADD OBJECT label1 AS LABEL WITH ;
    NAME = "Label1", CAPTION = "Label1", ;
    HEIGHT = 17, LEFT = 204, TOP = 204, WIDTH = 40

  PROCEDURE Label1.MOUSEDOWN
    LPARAMETERS nButton, nShift, nXCoord, nYCoord
    #DEFINE WM_NCLBUTTONDOWN 0xA1
    #DEFINE HTBOTTOMRIGHT 17
    SendMessage(THISFORM.HWND, WM_NCLBUTTONDOWN, HTBOTTOMRIGHT, 0)
  ENDPROC

  PROCEDURE Label1.INIT
    THIS.HEIGHT = SYSMETRIC(15)
    THIS.WIDTH = SYSMETRIC(14)
    THIS.FONTNAME = [Marlett]
    THIS.FONTSIZE = 12
    THIS.FORECOLOR = RGB(128, 128, 128)
    THIS.ANCHOR = 0
    THIS.LEFT = THISFORM.WIDTH - THIS.WIDTH - 1
    THIS.TOP = THISFORM.HEIGHT - THIS.HEIGHT - 1
    THIS.ANCHOR = 12
    THIS.MOUSEPOINTER = 8
    THIS.CAPTION = [o]
    DECLARE INTEGER SendMessage IN user32 ;
      INTEGER HWND,INTEGER wMsg,INTEGER wParam,INTEGER LPARAM
  ENDPROC
ENDDEFINE
Carlos Alloatti

8 de octubre de 2006

Librería VFPCompression (Zip/Unzip para Visual FoxPro)

El MVP Craig Boyd (SweetPotato Software, Inc.) pone a disposición de toda la comunidad una librería para compactar y descompactar archivos con Visual FoxPro.

La librería VFPCompression y su documentación están disponibles para libre descarga y uso en el Blog de Craig.

Existen tres entradas en su Blog con las versiones previas y definitiva, la documentación y funciones agregadas, y algunos comentarios sobre su desarrollo, y de la cooperación recibida de Bo Durban (Moxie Data, Inc.)

Los enlaces a las entradas en su Blog son los siguientes:
¡Gracias Craig por tu excelente trabajo y tu continua dedicación hacia la comunidad de VFP!

Ana María Bisbé York

7 de octubre de 2006

Clase ctl32_scontainer para VFP9 (Contenedor con barras de desplazamiento)

Ya está disponible la clase ctl32_scontainer. Esta clase consiste en un objeto container, con barras de desplazamiento.

Descripción, imágenes y documentación en el siguiente enlace:

ctl32_scontainer_class

Descargas:
ctl32_20080903.zip



Carlos Alloatti

6 de octubre de 2006

Dibujar o indexar formatos de píxel a imágenes con GDI+

Artículo original: DRAWING ON GIFS OR INDEXED PIXEL FORMAT IMAGES WITH GDI+
http://weblogs.foxite.com/vfpimaging/2006/03/18/drawing-on-gifs-or-indexed-pixel-format-images-with-gdi-/
Autor: Cesar Ch.
Traducido por: Ana María Bisbé York


Es muy común que necesitemos dibujar algunas formas, textos o aplicar algunos efectos a las imágenes.

Pero GDI+ tiene limitaciones al trabajar con imágenes creadas en formatos de índices pixelados, tales como 1 bppIndexed (0x00030101), 4bppIndexed (0x00030402) and 8bppIndexed (0x00030803). Estos son formatos de pixel utilizados también por GIF (Graphics Interchange Format). Si intenta dibujar en este tipo de imágenes puede recibir un error de este tipo:



Una solución muy sencilla es cargar el GIF utilizando GpImage, y obtener algunas propiedades, tales como Width y Height. Luego, crea un objeto imagen nuevo y vacío con el mismo tamaño que el GIF original; pero utilizando formato indizado pixel. _gdiplus.vcx utiliza PIXELFORMAT_32bppPARGB como predeterminado.

El siguiente paso es "dibujar" el GIF original en el bitmap creado en un formato pixel no indizado utilizando el procedimiento DrawImage. Ahora puede dibujar libremente sobre esta nueva imagen.
Finalmente, puede guardar la imagen nueva como GIF o cualquier otro tipo.

Un detalle importante que debe conocer es que cuando le pide a GDI+ que lo almacene como GIF, lo convertirá automáticamente la imagen nuevamente 8 a un formato 8bppIndexed, que es el predeterminado para GDI+.

He aquí un ejemplo de código que realiza esto, dibujando dos elipses y un rectángulo sobre una imagen.
#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
LOCAL lcSource, lcDest
lcSource = GETPICT("gif")
IF EMPTY(lcSource)
  RETURN
ENDIF
lcDest = ADDBS(JUSTPATH(lcSource))+ "_" + JUSTSTEM(lcSource)

*** Carga la imagen y verifica si está indizada :
LOCAL loImage AS GPIMAGE OF ffc/_gdiplus.vcx
loImage = NEWOBJECT('gpImage',HOME() + 'ffc/_gdiplus.vcx')
loImage.CreateFromFile(lcSource)
wImg = loImage.Imagewidth
hImg = loImage.ImageHeight
lcPixFormat = GetPixFormatName(loImage.PixelFormat)
IF NOT "INDEXED" $ UPPER(lcPixFormat)
  MESSAGEBOX("Dibuje directamente sobre la imagen, ya que no tiene formato " + ;
    "de pixel indizado !",64, "UTILICE LA TÉCNICA HABITUAL")
  RETURN
ENDIF

*** Crea un nuevo bitmap con las mismas dimensiones :
LOCAL loBitmap AS GpBitmap OF ffc/_gdiplus.vcx
loBitmap = NEWOBJECT("GpBitmap", HOME() + "ffc/_gdiplus.vcx")
LOCAL loGraph AS GpGraphics OF HOME() + ffc/_gdiplus.vcx
loGraph = NEWOBJECT('GpGraphics', HOME() + "ffc/_gdiplus.vcx")
loBitmap.CREATE(wImg, hImg, GDIPLUS_PIXELFORMAT_16bppRGB555 )

*** Pega la imagen original sobre la nueva imagen recién creada :
loGraph.CreateFromImage(loBitmap)
loGraph.DrawImageScaled(loImage, 0, 0, wImg, hImg)

*** Ahora podemos dibujar lo que deseemos sobre la imagen :
LOCAL loBlue, loRed, loGreen AS GpColor OF ffc/_gdiplus.vcx
LOCAL loPen AS GpPen OF HOME() + ffc/_gdiplus.vcx
loBlue = NEWOBJECT("GpColor", HOME() + "ffc/_gdiplus.vcx","",40,40,240 )
loRed = NEWOBJECT("GpColor", HOME() + "ffc/_gdiplus.vcx","",240,40,40 )
loGreen = NEWOBJECT("GpColor", HOME() + "ffc/_gdiplus.vcx","",40,230,40 )
loPen = NEWOBJECT("GpPen", HOME() + "ffc/_gdiplus.vcx")
loPen.CREATE(loBlue, 12)
loGraph.DrawEllipse( loPen, 0, 0, wImg, hImg)
loPen.PenColor = loRed
loGraph.DrawEllipse( loPen, 0+12, 0+12 , wImg-24 , hImg-24)
loPen.PenColor = loGreen
loGraph.DrawRectangle( loPen, 0+30, 0+30 , wImg-60 , hImg-60)

*** Guarda la imagen en GIF y JPG :
loBitmap.SaveToFile(lcDest+".jpg","image/jpeg","quality=100")
loBitmap.SaveToFile(lcDest+".gif","image/gif")
RETURN
PROCEDURE GetPixFormatName(nPix)
  DO CASE
    CASE nPix = 0x00030101
      RETURN "1bppIndexed"
    CASE nPix = 0x00030402
      RETURN "4bppIndexed"
    CASE nPix = 0x00030803
      RETURN "8bppIndexed"
    CASE nPix = 0x00101004
      RETURN "16bppGrayScale"
    CASE nPix = 0x00021005
      RETURN "16bppRGB555"
    CASE nPix = 0x00021006
      RETURN "16bppRGB565"
    CASE nPix = 0x00061007
      RETURN "16bppARGB1555"
    CASE nPix = 0x00021808
      RETURN "24bppRGB"
    CASE nPix = 0x00022009
      RETURN "32bppRGB"
    CASE nPix = 0x0026200A
      RETURN "32bppARGB"
    CASE nPix = 0x000E200B
      RETURN "32bppPARGB"
    CASE nPix = 0x0010300C
      RETURN "48bppRGB"
    CASE nPix = 0x001C400E
      RETURN "64bppPARGB"
    OTHERWISE
      RETURN "No identificado"
  ENDCASE
ENDPROC
Este procedimiento tiene un gran inconveniente, ya que en esta conversión automática, GDI+ escribe el archivo utilizando una paleta de medio tono por lo que el objeto imagen tendrá el color reducido. GDI+ hace la conversión a color desde 32 bits-por-pixel (32 BPP) al escribir la imagen al archivo.

De acuerdo con Szaak Priester: "Cuando GDI+ guarda un bitmap en formato GIF, realiza una forma cruda de determinación de los valores (cuantización) de los colores. Siempre emplea la misma paleta de colores, se rellena fundamentalmente con los 216 "colores Web-safe" Al inicio de Internet, estos colores eran los únicos mostrados consistentemente por la mayoría de los examinadores, de ahí su nombre... los 216 colores web-safe fueron escogidos básicamente por sus méritos técnicos (dividen uniformemente el espacio d color RGB) y no debido a sus cualidades visuales. Como consecuencia, la paleta web-safe (llamada también "paleta de medio tono") contiene fundamentalmente púrpuras casi perceptibles, muchos verdosos y marrones turbios, por tato, mucha parte utilizable del espectrum es seriamente poco poplado."

Debajo, puede ver el resultado que se obtiene utilizando la técnica que se ha comentado aquí.

GIF - Imagen original GIF - 8bppIndexed Image JPEG - 32bppARGB

Preste atención a la diferencia entre la calidad de estas tres imágenes.

Esta técnica puede ser utilizada también para redimensional cualquier imagen, incluyendo GIFs. Con el objeto de redimensionar, todo lo que necesitamos es cambiar en el código anterior las instrucciones que tratan del tamaño de imagen, loBitmap.Create(NewWidth, NewHeight, PixelFormat) y al pegar la imagen original en la creada nueva, loGraph.DrawImagePortionat(loOriginalImage, 0, 0, NewWidth, newHeight).

Necesitas solamente conocer las limitaciones de GIFs y decidir si guardará la imagen como GIF o como otro formato de imagen.

He aquí algunas diferencias entre formatos GIFs y JPEGs, de acuerdo con MSDN:
GIF vs. JPEG

¿Debe guardar las imágenes en formato GIF o JPEG? Aunque esta pregunta no está relacionada directamente con la administración de la paleta, confunde a mucha gente, y es de alguna manera relevante para nuestro tema.

Existen dos diferencias fundamentales entre imágenes GIF y JPEG:
Las imágenes GIF están comprimidas de forma que conservan todos sus datos, mientras las imágenes JPEG están comprimidas de forma que pierden muchos de sus datos.
Las imágenes GIF están limitadas como máximo a 256 colores, mientras las imágenes JPEG no están limitadas por la cantidad de colores que utilizan.
Pero ... ¿existe una forma de convertir una imagen en un formato GIF sin emplear una paleta de colores mejor distribuida en lugar de una paleta de medio tono que estropea las imágenes? Por supuesto; pero esto ya es tema para otro artículo ...

5 de octubre de 2006

Exportar Cursor VFP a EXCEL

Hace un tiempo atrás encontré en este portal una rutina que me fue muy útil, pues exportaba tablas o cursores de VFP a un Libro Excel.

El inconveniente que se me presentó es que cuando utilizaba exportaciones de tablas con demasiados registros, la espera era eterna. Producto que la rutina en cuestión escribía celda a celda los datos de cada registro de la tabla.

Pues bien, buscando siempre mejorar los métodos, les presento esta rutina de exportación de tablas y/o cursores que se apoya en la importación de datos tipo texto de Microsoft Excel:

********************************************************************
********************************************************************
*!* FUNCTION Exp2Excel( [cCursor, [cFileSave, [cTitulo]]] )
*!*
*!* Exporta un Cursor de Visual FoxPro a Excel, utilizando la
*!* técnica de importación de datos externos en modo texto.
*!*
*!* PARAMETROS OPCIONALES:
*!* - cCursor  Alias del cursor que se va a exportar.
*!*            Si no se informa, utiliza el alias
*!*            en que se encuentra.
*!*
*!* - cFileName  Nombre del archivo que se va a grabar.
*!*              Si no se informa, muestra el libro generado
*!*              una vez concluída la exportación.
*!*
*!* - cTitulo  Titulo del informe. Si se informa, este
*!*            ocuparía la primera file de cada hoja del libro.
********************************************************************
********************************************************************
FUNCTION Exp2Excel( cCursor, cFileSave, cTitulo )
  LOCAL cWarning
  cWarning = "Exportar a EXCEL"
  IF EMPTY(cCursor)
    cCursor = ALIAS()
  ENDIF
  IF TYPE('cCursor') # 'C' OR !USED(cCursor)
    MESSAGEBOX("Parámetros Inválidos",16,cWarning)
    RETURN .F.
  ENDIF
  *********************************
  *** Creación del Objeto Excel ***
  *********************************
  WAIT WINDOW 'Abriendo aplicación Excel.' NOWAIT NOCLEAR
  oExcel = CREATEOBJECT("Excel.Application")
  WAIT CLEAR

  IF TYPE('oExcel') # 'O'
    MESSAGEBOX("No se puede procesar el archivo porque no tiene la aplicación" ;
      + CHR(13) + "Microsoft Excel instalada en su computador.",16,cWarning)
    RETURN .F.
  ENDIF

  oExcel.workbooks.ADD

  LOCAL lnRecno, lnPos, lnPag, lnCuantos, lnRowTit, lnRowPos, i, lnHojas, cDefault

  cDefault = ADDBS(SYS(5)  + SYS(2003))

  SELECT (cCursor)
  lnRecno = RECNO(cCursor)
  GO TOP

  *************************************************
  *** Verifica la cantidad de hojas necesarias  ***
  *** en el libro para la cantidad de datos     ***
  *************************************************
  lnHojas = ROUND(RECCOUNT(cCursor)/65000,0)
  DO WHILE oExcel.Sheets.COUNT < lnHojas
    oExcel.Sheets.ADD
  ENDDO

  lnPos = 0
  lnPag = 0

  DO WHILE lnPos < RECCOUNT(cCursor)

    lnPag = lnPag + 1 && Hoja que se está procesando

    WAIT WINDOWS 'Exportando cursor '  + UPPER(cCursor)  + ' a Microsoft Excel...' ;
      + CHR(13) + '(Hoja '  + ALLTRIM(STR(lnPag))  + ' de '  + ALLTRIM(STR(lnHojas)) ;
      + ')' NOCLEAR NOWAIT

    IF FILE(cDefault  + cCursor  + ".txt")
      DELETE FILE (cDefault  + cCursor  + ".txt")
    ENDIF

    COPY  NEXT 65000 TO (cDefault  + cCursor  + ".txt") DELIMITED WITH CHARACTER ";"
    lnPos = RECNO(cCursor)

    oExcel.Sheets(lnPag).SELECT

    XLSheet = oExcel.ActiveSheet
    XLSheet.NAME = cCursor + '_' + ALLTRIM(STR(lnPag))

    lnCuantos = AFIELDS(aCampos,cCursor)

    ********************************************************
    *** Coloca título del informe (si este es informado) ***
    ********************************************************
    IF !EMPTY(cTitulo)
      XLSheet.Cells(1,1).FONT.NAME = "Arial"
      XLSheet.Cells(1,1).FONT.SIZE = 12
      XLSheet.Cells(1,1).FONT.BOLD = .T.
      XLSheet.Cells(1,1).VALUE = cTitulo
      XLSheet.RANGE(XLSheet.Cells(1,1),XLSheet.Cells(1,lnCuantos)).MergeCells = .T.
      XLSheet.RANGE(XLSheet.Cells(1,1),XLSheet.Cells(1,lnCuantos)).Merge
      XLSheet.RANGE(XLSheet.Cells(1,1),XLSheet.Cells(1,lnCuantos)).HorizontalAlignment = 3
      lnRowPos = 3
    ELSE
      lnRowPos = 2
    ENDIF

    lnRowTit = lnRowPos - 1
    **********************************
    *** Coloca títulos de Columnas ***
    **********************************
    FOR i = 1 TO lnCuantos
      lcName  = aCampos(i,1)
      lcCampo = ALLTRIM(cCursor) + '.' + aCampos(i,1)
      XLSheet.Cells(lnRowTit,i).VALUE=lcname
      XLSheet.Cells(lnRowTit,i).FONT.bold = .T.
      XLSheet.Cells(lnRowTit,i).Interior.ColorIndex = 15
      XLSheet.Cells(lnRowTit,i).Interior.PATTERN = 1
      XLSheet.RANGE(XLSheet.Cells(lnRowTit,i),XLSheet.Cells(lnRowTit,i)).BorderAround(7)
    NEXT

    XLSheet.RANGE(XLSheet.Cells(lnRowTit,1),XLSheet.Cells(lnRowTit,lnCuantos)).HorizontalAlignment = 3

    *************************
    *** Cuerpo de la hoja ***
    *************************
    oConnection = XLSheet.QueryTables.ADD("TEXT;"  + cDefault  + cCursor  + ".txt", ;
      XLSheet.RANGE("A"  + ALLTRIM(STR(lnRowPos))))

    WITH oConnection
      .NAME = cCursor
      .FieldNames = .T.
      .RowNumbers = .F.
      .FillAdjacentFormulas = .F.
      .PreserveFormatting = .T.
      .RefreshOnFileOpen = .F.
      .RefreshStyle = 1 && xlInsertDeleteCells
      .SavePassword = .F.
      .SaveData = .T.
      .AdjustColumnWidth = .T.
      .RefreshPeriod = 0
      .TextFilePromptOnRefresh = .F.
      .TextFilePlatform = 850
      .TextFileStartRow = 1
      .TextFileParseType = 1 && xlDelimited
      .TextFileTextQualifier = 1 && xlTextQualifierDoubleQuote
      .TextFileConsecutiveDelimiter = .F.
      .TextFileTabDelimiter = .F.
      .TextFileSemicolonDelimiter = .T.
      .TextFileCommaDelimiter = .F.
      .TextFileSpaceDelimiter = .F.
      .TextFileTrailingMinusNumbers = .T.
      .REFRESH
    ENDWITH

    XLSheet.RANGE(XLSheet.Cells(lnRowTit,1),XLSheet.Cells(XLSheet.ROWS.COUNT,lnCuantos)).FONT.NAME = "Arial"
    XLSheet.RANGE(XLSheet.Cells(lnRowTit,1),XLSheet.Cells(XLSheet.ROWS.COUNT,lnCuantos)).FONT.SIZE = 8

    XLSheet.COLUMNS.AUTOFIT
    XLSheet.Cells(lnRowPos,1).SELECT
    oExcel.ActiveWindow.FreezePanes = .T.

    WAIT CLEAR

  ENDDO

  oExcel.Sheets(1).SELECT
  oExcel.Cells(lnRowPos,1).SELECT

  IF !EMPTY(cFileSave)
    oExcel.DisplayAlerts = .F.
    oExcel.ActiveWorkbook.SAVEAS(cFileSave)
    oExcel.QUIT
  ELSE
    oExcel.VISIBLE = .T.
  ENDIF

  GO lnRecno

  RELEASE oExcel,XLSheet,oConnection

  IF FILE(cDefault + cCursor + ".txt")
    DELETE FILE (cDefault + cCursor + ".txt")
  ENDIF

  RETURN .T.

ENDFUNC

***
***
Saludos desde Chile,

OSCAR CALDERON FUENTES