12 de diciembre de 2006

La forma más fácil y rápida de crear imágenes degradadas

Artículo original: Creating Gradient Images the Fast and Easy Way
http://doughennig.blogspot.com/2007/02/creating-gradient-images-fast-and-easy.html
Autor: Doug Hennig
Traducido por: Ana María Bisbé York

He estado intentando últimamente embellecer algunos formularios. Una vía para hacer un formulario atractivo es utilizar algún elemento gráfico. En particular, me gustan las imágenes degradadas, como se ilustra en esta imagen de la pantalla (el degradado aparece vinculado a la imagen; pero no en el formulario real):



Entonces, ¿cómo crear una imagen con color degradado de los colores y tamaño deseados? Aunque yo podría hacerlo dinámicamente utilizando la biblioteca VFPX GDIPlusX, como ha descrito Cesar Ch. en varias entradas de su excelente Blog, he decidido simplemente crear un archivo imagen y emplearlo como la propiedad Picture de un objeto Image.

Con alguna ayuda del blog de Cesar, he creado un programa sencillo, CreateGradient.PRG, que genera una imagen degradada con el archivo especificado, tamaño y colores. El color se auto-explica. Tenga en cuenta que se espera que la librería de clases GDIPlus estén en la raíz de VFP.

lparameters tcFileName, ;
  tnHeight, ;
  tnWidth, ;
  tnColor1, ;
  tnColor2
local lnColor1, ;
  lnColor2, ;
  lnMode, ;
  lnFormat, ;
  loSystem, ;
  lcExt, ;
  loBitmap, ;
  loRect, ;
  loGfx, ;
  loGradBrush

* Asegúrese de que fueron pasados los parámetros.

if vartype(tcFileName) <> 'C' or empty(tcFileName) or ;
  vartype(tnHeight) <> 'N' or vartype(tnWidth) <> 'N'
  error 11
  return .F.
endif vartype(tcFileName) <> 'C' ...

* Si no se han pasado los colores, pídalos.

if pcount() < 4
  lnColor2 = getcolor(rgb( 0, 128, 255))
  lnColor1 = getcolor(rgb(255, 255, 255))
else
  lnColor1 = tnColor1
  lnColor2 = tnColor2
endif pcount() < 4

* Esto asume un degradado horizontal. 
* Configure lnMode a un valor diferente para los otros tipos.

lnMode = 0

* Crea un objeto GDIPlusX System.

loSystem = newobject('xfcSystem', 'System.vcx')
with loSystem.Drawing

  * Determina el tipo de la imagen de la extensión del archivo.

  lcExt = upper(justext(tcFileName))
  do case
    case lcExt = 'PNG'
      lnFormat = .Imaging.ImageFormat.Png
    case lcExt = 'BMP'
      lnFormat = .Imaging.ImageFormat.Bmp
    case lcExt = 'GIF'
      lnFormat = .Imaging.ImageFormat.Gif
    case inlist(lcExt, 'JPG', 'JPEG')
      lnFormat = .Imaging.ImageFormat.Jpeg
    case lcExt = 'ICO'
      lnFormat = .Imaging.ImageFormat.Icon
    case inlist(lcExt, 'TIF', 'TIFF')
      lnFormat = .Imaging.ImageFormat.Tiff
    case lcExt = 'WMF'
      lnFormat = .Imaging.ImageFormat.Wmf
    otherwise
      error 11
      return .F.
  endcase

  * Crea un objeto bitmap y un objeto rectangle del tamaño deseado.

  loBitmap = .Bitmap.New(tnWidth, tnHeight)
  loRect = .Rectangle.New(0, 0, tnWidth, tnHeight)

  * Crea un objeto graphics.

  loGfx = .Graphics.FromImage(loBitmap)
  loGfx.Clear(.Color.White)

  * Crea una brocha de degradado lineal.

  loGradBrush = .Drawing2D.LinearGradientBrush.New(loRect, ;
    .Color.FromRgb(lnColor1), .Color.FromRgb(lnColor2), lnMode)

  * Llena el rectángulo con la brocha de degradado lineal.

  loGfx.FillRectangle(loGradBrush, loRect)

  * Guarda la imagen con el archivo especificado.

  loBitmap.Save(tcFileName, lnFormat)

endwith
return

Gracias a Craig Boyd, Bo Durban, y Cesar por todo el trabajo que han hecho en esta increíble biblioteca de clases.

11 de diciembre de 2006

Leer y modificar valores de cabecera de tabla para un campo autoincrementado

A partir de VFP 8 podemos usar campos enteros autoincrementados automáticamente, aún en tablas libres. (Siempre está la opción de hacer uso de los triggers en tablas vinculadas a una DB, pero hay que armarlo...)

VFP nos informa en que lugar de la cabecera de la tabla coloca el próximo valor y el del incremento a usar en este tipos de campos. En esta oportunidad acerco una rutina que permite leer / modificar los valores en la cabecera de la tabla para un campo autoincrementado.

El programa de ejemplo, que tomé ex-profeso de una versión que alguien ofreció para indicar como modificar el valor y/o el incremento a través del comando SQL ALTER TABLE, es válido y funciona Ok si la tabla puede abrirse en modo exclusivo.

Si por algún motivo (lo más común en un entorno multiusuario) tenemos la tabla abierta en modo compartido y queremos modificar el próximo valor y/o el incremento, esta rutina lo permite (así como leer los valores actuales).
Nota: Para que el ejemplo funcione correctamente se debe descargar la librería LeeIncPtr.dll (incluida en el archivo comprimido junto al programa autoincrement_altera.prg) haciendo clic en el siguiente enlace: AutoIncrement.zip (8,77 KB).
******************************************
* AutoIncrement_Altera.prg
******************************************
Declare Long LEEINCRE In "LeeIncPtr.DLL" ;
  String @ Nom_tabla, String  @ Paso, ;
  String @ Campo_autoinc
Declare Long PONEINCRE In "LeeIncPtr.DLL" ;
  String @ Nom_tabla, Long @ Valor_prox, ;
  String  @ Paso, String @ Campo_autoinc

* La que sigue es la rutina "casi original",
* modificada para forzar una apertura compartida
#Define CRLF Chr(13)+Chr(10)
Local Proximo As Long, Paso As String
Set Exclusive Off
Local lcStr As String
Local lnSelect As Integer

* Save environment and erase auto increment table.
lnSelect = Select()
Select 0
Erase AI_Table.Dbf

* Create Auto Increment table setting the
* 'iID' column as an auto increment field
* with the starting value set to 1 and the
* increment value set to 1.
Create Table AI_Table Free ;
  ( iID i Autoinc Nextvalue 1 Step 1, ;
  CustName c(30))
Close Database All && Agregado
Use In 0 "AI_Table" Shared  && Agregado
* Insert three records into the table.
* You do not assign any values to
* the auto increment field.
Insert Into AI_Table (CustName) Values ("Jane Smith")
Insert Into AI_Table (CustName) Values ("John Doe")
Insert Into AI_Table (CustName) Values ("Greg Jones")
Go Top

* Browse the table.
Browse Nowait

lcStr = "Auto Increment Table created with the " + ;
  "starting value set to 1 and the increment set to 1"
Messagebox(lcStr)

* Alter the table to set the next auto increment value
* inserted to be 100 and incrementing step to be 10.

* Se agregó rutina de error y uso de la librería
Ok = .T.
Try
  Alter Table AI_Table Alter Column iID i Autoinc Nextvalue 100 Step 10
Catch To oErr
  Messagebox("Catch: " + Transform(oErr.ErrorNo) + ;
    Chr(10) + oErr.Message + Chr(10) + ;
    "No puede usar en este modo la sentencia: " + ;
    Chr(10) + oErr.LineContents + Chr(10) + ;
    "Se intentará modificar con la librería", ;
    48,"Error")
  Ok = .F.
Finally
Endtry
If !Ok
  Tbl = Dbf()
  Proximo = 100
  Paso = Chr(10)
  Campo = "iID"
  If PONEINCRE(@Tbl, @Proximo, @Paso, @Campo) <> 1
    Messagebox("Error procesando con PONEINCRE", 48, "Error")
    Return
  Endif
  * Debemos cerrar y volver a abrir la tabla,
  * para que VFP actualice internamente el Step
  Use In ("AI_Table")
  Use In 0 "AI_Table" Shared
Endif
* Insert three more records into the table.
Insert Into AI_Table (CustName) Values ("Jay Lewis")
Insert Into AI_Table (CustName) Values ("Steve Appleton")
Insert Into AI_Table (CustName) Values ("Ken Garvy")
Go Top

* Browse the table.
Browse Nowait

lcStr = "Auto Increment Table was altered to set " + ;
  "the next auto increment value " + ;
  "to 100 and the increment step to 10"
Messagebox(lcStr)

* Leemos como quedó ahora la tabla
Tbl = Dbf()
Campo = "iID"
Prox = LEEINCRE(@Tbl, @Paso, @Campo)
If Prox > 0
  Messagebox("Próximo valor: " + Transform(Prox) + ;
    Chr(10) + "Step: " + Transform(Asc(Paso)), ;
    64, "Estado autoincremento")
Endif

* Restore environment.
Use
Select (lnSelect)

Return
Espero pueda serles de utilidad.

Gustavo Devincenzi

5 de diciembre de 2006

Buscar direcciones postales en Google Maps desde VFP (II)

Buscar direcciones postales en Google Maps e imprimir el resultado, desde un formulario de Visual FoxPro.

Tras haber publicado el articulo Buscar direcciones postales en Google Maps con VFP que permitía mostrar el plano o imagen de satélite de una dirección postal, y viendo que el resultado permitía incorporarlo fácilmente a una aplicación, me puse a buscar la manera de poder imprimirlo.

De momento, la unica forma que he encontrado ha sido la utilizacion de una libreria llamada dibapi32.dll, que solo necesita ser copiada junto con el '.prg' que generemos con el codigo adjunto.
Para quien no disponga de ella, puede ser descargada desde La Web de Davphantom, donde el registro es gratuito.

Tambien he añadido un segundo parametro opcional, que podemos pasar al '.prg' y que correspondera al nombre o identificacion de la direccion buscada, como puede ser el nombre de un cliente, empresa o lugar conocido. Este segundo parametro no se utiliza en la busqueda, sino que se usa para mostrarlo en el 'globo' de información que se muestra sobre el mapa, una vez localizada la dirección.

Creo que eso es todo, asi que pasemos al codigo:
Do MiPrograma With "Paseo Castellana 142, Madrid", "Santiago Bernabeu"

* Con "do MiPrograma", se muestra la pantalla para introducir la direccion deseada.

*
* MiPrograma.prg
*
Procedure MiPrograma
  Lparameters miaddress, miname_id
  *
  Public oMiForm
  oMiForm = Createobject("MiForm", miaddress, miname_id)
  oMiForm.Show
  Return
Endproc

Define Class MiForm As Form
  Height = 560
  Width = 625
  AutoCenter = .T.
  Name = "MiForm"
  SetPoint = ""
  SetDecimals = 2
  ShowWindow = 1
  WindowType = 1
  TitleBar = 0
  BorderStyle = 2
  miname_id = ""

  Add Object Descrip As TextBox With ;
    HEIGHT = 24, Left = 12, Top = 12, Width = 330, MaxLength = 100, ;
    ForeColor = Rgb(88,99,124), BackColor = Rgb(255,255,255), ;
    SelectedForeColor = Rgb(255,255,255), SelectedBackColor = Rgb(88,99,124), ;
    STYLE = 0, Name = "Descrip", SelectOnEntry = .T., Enabled = .T.

  Add Object cmdMostrar As CommandButton With ;
    TOP = 10, Left = 350, Height = 27, Width = 100, ;
    CAPTION = "Mostrar mapa", Name = "cmdMostrar"

  Add Object cmdPrint As CommandButton With ;
    TOP = 10, Left = 500, Height = 27, Width = 60, ;
    CAPTION = "Imprimir", Name = "cmdPrint"

  Add Object cmdCerrar As CommandButton With ;
    TOP = 10, Left = 573, Height = 27, Width = 40, ;
    CAPTION = "Salir", Name = "cmdCerrar"

  Add Object oleIE As OleControl With ;
    TOP = 48, Left = 12, Height = 500, Width = 600, ;
    NAME = "oleIE", OleClass = "Shell.Explorer.2"

  Procedure Load
    Sys(2333,1)
    This.SetPoint = Set("Point")
    This.SetDecimals = Set("Decimals")
    Set Point To .
    Set Decimals To 8
    Set Safety Off
    Declare Integer ReleaseCapture In WIN32API
    Declare Integer SendMessage In WIN32API Integer, Integer, Integer, Integer
    Declare Integer GetFocus In WIN32API
  Endproc

  Procedure Init
    Lparameters miaddress, miname_id
    If Type('miname_id')<>'C'
      miname_id=''
    Endif
    This.miname_id = miname_id
    If Type('miaddress')<>'C'
      This.Descrip.Value=''
    Else
      This.Descrip.Value=miaddress
      Thisform.cmdMostrar.Click()
    Endif
  Endproc

  Procedure MouseDown
    Lparameters nButton, nShift, nXCoord, nYCoord
    Local lnHandle
    If nButton = 1
      ReleaseCapture()
      SendMessage(This.HWnd, 0x112, 0xF012,0)
    Endif
  Endproc

  Procedure cmdCerrar.Click
    Set Point To (Thisform.SetPoint)
    Set Decimals To (Thisform.SetDecimals)
    Thisform.Release
  Endproc

  Procedure cmdPrint.Click
    Thisform.PrintWindow(GetFocus(), "Imprimiendo ...")
  Endproc

  Procedure PrintWindow
    Lparameters tnHWnd, tcJobName
    *
    Local lcJobName    && Nombre de la tarea de impresion
    Local lnRetVal    && Valor de retorno de las funciones del API

    Declare Integer PrintWindow In DibApi32 ;
      INTEGER HWnd, ;
      INTEGER fPrintArea, ;
      INTEGER fPrintOpt, ;
      INTEGER wxScale, ;
      INTEGER wyScale, ;
      STRING @ szJobName

    *!* PW_WINDOW para imprimir la ventana entera
    *!* PW_CLIENT para imprimir el area cliente
    *!* Como ajustar la imagen
    *!* PW_BESTFIT se ajusta al papel pero se mantienen las proporciones
    *!* PW_STRETCHTOPAGE se ajusta para cubrir totalmente el papel pero distorsiona las proporciones
    *!* PR_SCALE escala el tamaño de impresion

    #Define PW_WINDOW 1
    #Define PW_CLIENT 2
    #Define PW_BESTFIT 1
    #Define PW_STRETCHTOPAGE 2
    #Define PW_SCALE 3

    lcJobName = tcJobName + Chr(0)
    lnRetVal = PrintWindow( tnHWnd, PW_CLIENT, PW_BESTFIT, 0, 0, @lcJobName)
    If lnRetVal != 0
      If lnRetVal != 6  && 6 = El usuario cancelo la impresión
        = Messagebox("Imposible Imprimir la ventana" + CRLF + ;
          "PrintWindow API retorno " + Str(lnRetVal), ;
          MB_ICONEXCLAMATION + MB_OK, ;
          "ERROR")
      Endif
    Endif
  Endproc

  Procedure cmdMostrar.Click
    If Empty(Alltrim(Thisform.Descrip.Value))
      Thisform.Descrip.SetFocus()
      Return
    Else
      *
      lcClave = "http://maps.google.es/maps?file=api&v=2.x&" + ;
        "key=ABQIAAAAtOjLpIVcO8im8KJFR8pcMhQjskl1-YgiA" + ;
        "_BGX2yRrf7htVrbmBTWZt39_v1rJ4xxwZZCEomegYBo1w"
      *
      TEXT TO lcHtml NOSHOW TEXTMERGE
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
    <title>Busqueda en Google Maps</title>
    <script src="<<lcClave>>" type="text/javascript"></script>
    <script type="text/javascript">
    //<![CDATA[
    var map = null
    var geocoder = null
    var address = "<<Strtran(ALLTRIM(ThisForm.Descrip.Value),'ñ','n',1,10,1)>>"

    function load()
    { if (GBrowserIsCompatible())
      { map = new GMap2(document.getElementById("map"),'G_SATELLITE_TYPE');
        map.addControl(new GLargeMapControl());
        map.addControl(new GMapTypeControl());
        map.addControl(new GOverviewMapControl());
        geocoder = new GClientGeocoder();
        if (geocoder) {
          geocoder.getLatLng(address,
        function(point)
        { if (!point)
          { alert("No Encontrado");
            }
          else
          { map.setCenter(point, 17);
            map.setMapType(G_MAP_TYPE);
            var marker = new GMarker(point);
            map.addOverlay(marker);
            marker.openInfoWindowHtml('<<Thisform.miname_id>>' + '<br>' + address);
            }
          }
        );
       }
      }
    }
    //]]>
    </script>
  </head>
  <body  scroll="no" bgcolor="#CCCCCC" topmargin="0" leftmargin="0" onload="load()" onunload="GUnload()">
  <div id="map" style="width: 100%; height: 100%"></div>
  </body>
</html>
      ENDTEXT
      *
      Strtofile(lcHtml,"MiHtml.htm")
      Thisform.oleIE.Navigate2(Fullpath("MiHtml.htm"))
    Endif
  Endproc

Enddefine

Hasta la próxima.

Jose Antonio Blasco, Zaragoza, España

1 de diciembre de 2006

Añadir controles (Widgets) a sus pantallas Visual FoxPro

Artículo original: Add Glass XP Widgets to your Visual Foxpro Screens
http://weblogs.foxite.com/bernardbout/archive/2006/11/04/2798.aspx
Autor: Bernard Bout
Traducido por: Ana María Bisbé York


Google, Yahoo, Vista ya lo tienen. Ahora hagamos que lo tengan Visual FoxPro y usted. Hablo de aplicaciones llamadas Widgets que puede utilizar para combinar con su pantalla.

Utilizar gráficos que sean soportado por VFP es una vía para crear Widgets muy interesantes. He descrito aquí cómo el lograr PNG con máscara alpha, puede ser utilizado para mostrar algunos efectos cristalinos como semi "formularios".

Ahora voy a describir cómo esto se puede extender para crear Widgets cristalinos. Una vez que ya tenemos los Widgets, y los hemos utilizado, la técnica descrita aquí le va a ofrecer un Widget Cristalino que podrá arrastrar, similar a Vista. ¡ Y lo mejor de todo es que se puede lograr en XP !
Para este escrito he creado un widget reloj. Puede descargar el código completo desde el archivo adjunto al final de este escrito.

El reloj es una clase sencilla VFP que va a auto ejecutarse al soltarla sobre un formulario y ejecutar el formulario. El truco detrás del efecto cristalino está en la imagen PNG de fondo de la clase:




Mientras que se ve como un viejo gráfico plano en blanco y negro, contiene una máscara de canal alpha embebido en el. El formato PNG admite guardar esta máscara en una imagen y el objeto imagen de VFP respeta y utiliza esta máscara. Si abre esta imagen en un programa Paint que admite máscara, como Photoshop, (Utilizo Paint Shop Pro) entonces, puede ver la máscara. Cuando esta imagen es cargada en un objeto imagen VFP, la máscara tiene efecto en ciertas áreas transparentes basadas en la información en la máscara.

Todo lo que queda es insertar un objeto imagen en un contenedor transparente y se completa el efecto. La clase reloj solamente agrega código a la fecha, año y movimiento de las manecillas. Está también el código en la clase imagen que permite dibujar el contenedor, de tal forma que el reloj se pueda mover. Verifique el código en los eventos Mouse de la clase objeto imagen.

El reloj se puede instanciar en el _Screen o, si está empleando un formulario de nivel superior como la base para su aplicación, como he descrito aquí y aquí, entonces pueden ser utilizadas esta técnica y clase.

Debajo puede ver el efecto Cristalino (Glass) del área exterior que permite que traspase la imagen de fondo. Esta área se utiliza también para arrastrar y soltar el reloj en una nueva localización. Los colores pueden cambiarse sencillamente ya que yo he incluido todo el código. Ejecute el EXE y arrastre el reloj. Tenga cuidado cuando lo suelte, no va a querer romper el cristal ! :)

Aquí está el reloj corriendo dentro de un formulario de nivel superior.



¡ Aquí estoy logrando sus Widgest propios para VFP !

Adjunto: 2798_clockwidget.zip