30 de diciembre de 2005

Convertir una expresión de caracteres en una expresión de fecha

Considerando que la función CTOD( ) no es segura, creé esta función para convertir una expresión de caracteres - fecha en formato corto - en una expresión de fecha.

Según la propia ayuda de VFP: "Nota: CTOD( ) puede crear valores ambiguos de fecha y generar un error de compilación...", entonces, como estoy importando datos que están en EXCEL necesité de esta función que, tal vez, pueda serle útil a otros.

Sintaxis:
StrToDate(cExpresion, cFormatoDeFecha)
Parámetros:
  • cExpresion: Especifica una expresión de caracteres.
  • cFormatoDeFecha: especifica el formato en el cual se encuetra cExpresion.
Ejemplo de uso:
? StrToDate("2004/5/30";"a/m/d")
? StrToDate("2004-5-30";"a-m-d")
? StrToDate("04.5.30","a.m.d";)
? StrToDate("30/5/4";"d/m/a")
Function StrToDate
    Lparameters lcFecha,lcFormatOrig
    Local ARRAY laFec(3)
    Local nn, lcEstr
    lcDelim = Iif(GetWordCount(lcFormatOrig,"/") = 3, "/", ;
      Iif(GetWordCount(lcFormatOrig,"-") = 3, "-", ;
      Iif(GetWordCount(lcFormatOrig,".") = 3, ".","")))
    If Empty(lcDelim)
       Return {}
       *-- puede ser mejor devolver .f. para provocar un error
    EndIf
    lcFormatOrig = Lower(lcFormatOrig) && por si acaso
    For nn = 1 to 3
        lcEstr = GetWordNum(lcFormatOrig,nn,lcDelim)
        laFec(Iif(lcEstr="a",1,Iif(lcEstr="m",2,3)))=Val(GetWordNum(lcFecha,nn,lcDelim))
    EndFor
    *-- Por si el año se haya escrito abreviado (se interpreta del siglo XXI)
    *-- se puede mejorar esta parte comprobando SET CENTURY TO ROLLOVER
    If Len(Alltrim(Str(laFec(1)))) > 4
       laFec(1)=2000 + laFec(1)
    EndIf
    Return Date(laFec(1),laFec(2),laFec(3))
EndFunc
Mario Esquivel Bado

25 de diciembre de 2005

WMI y Visual FoxPro 9.0 SP1

WMI nos ofrece una serie de funciones sobre el equipo que nos permiten extraer información como números de series de dispositivos USB, CD-Room, Mainboard y tipo de chasis, sobre estos dos últimos trataremos en este artículo.

WMI y Visual FoxPro 9.0 SP1

WMI nos ofrece una serie de funciones sobre el equipo que nos permiten extraer información como números de series de dispositivos USB, CD-Room, Mainboard y tipo de chasis, sobre estos dos últimos trataremos en este artículo.

El dinamismo que nos permite VFP9 y la íntima relación con el sistema operativo describiremos el código que nos permite extraer el número de serie del mainboard y el tipo de chasis en que nuestro sistema se encuentra operando.

* activamos el programa para crear una clase que contiene la descripción del chasis
* SET PROCEDURE TO serial

CLEAR
* nombre equipo o servidor
strComputer = "."
* creamos el objeto con la referencia . que nos indica que es el equipo local
objWMIService = GETOBJECT("winmgmts:{impersonationLevel=impersonate}!\\" + strComputer + "\root\cimv2")
* extraemos la consulta de la raiz que nos permite ver las propiedades a un objeto
colSMBIOS = objWMIService.ExecQuery ("Select * from Win32_SystemEnclosure")

* recorremos el objeto para extraer el número de serie y el número de parte
FOR EACH objSMBIOS IN colSMBIOS
  ? "Número de parte: " + objSMBIOS.PartNumber
  ? "Número de serie: " + objSMBIOS.SerialNumber
NEXT
* recorremos el objeto para extraer el número de tipo 
* de chasis y llamamos a la clase que los contiene
FOR EACH objChassis IN colSMBIOS
  FOR EACH objItem IN objChassis.ChassisTypes
    o_chasis=CREATEOBJECT("c_chasis")
    ? "El chasis es: "+o_chasis.chasis(objitem)
  NEXT
NEXT

* definición de la clase que nos permite validar el # del tipo de chases
DEFINE CLASS c_chasis AS CUSTOM
  PROCEDURE chasis(n_chasis)
    DO CASE
      CASE n_chasis=1
        RETURN "Other"
      CASE n_chasis=2
        RETURN "Unknown "
      CASE n_chasis=3
        RETURN " Desktop "
      CASE n_chasis=4
        RETURN " Low Profile Desktop "
      CASE n_chasis=5
        RETURN " Pizza Box  "
      CASE n_chasis=6
        RETURN " Mini Tower "
      CASE n_chasis=7
        RETURN " Tower "
      CASE n_chasis=8
        RETURN " Portable"
      CASE n_chasis=9
        RETURN " Laptop "
      CASE n_chasis=10
        RETURN " Notebook "
      CASE n_chasis=11
        RETURN " Hand Held "
      CASE n_chasis=12
        RETURN " Docking Station "
      CASE n_chasis=13
        RETURN " All in One "
      CASE n_chasis=14
        RETURN " Sub Notebook "
      CASE n_chasis=15
        RETURN " Space-Saving"
      CASE n_chasis=16
        RETURN " Lunch Box  "
      CASE n_chasis=17
        RETURN " Main System Chassis"
      CASE n_chasis=18
        RETURN " Expansion Chassis "
      CASE n_chasis=19
        RETURN " SubChassis "
      CASE n_chasis=20
        RETURN " Bus Expansion Chassis "
      CASE n_chasis=21
        RETURN " Peripheral Chassis "
      CASE n_chasis=22
        RETURN " Storage Chassis "
      CASE n_chasis=23
        RETURN " Rack Mount Chassis "
      CASE n_chasis=24
        RETURN " Sealed-Case PC "
    ENDCASE
ENDDEFINE

Franklin S. Garzón A. (Ecuador)

12 de diciembre de 2005

Deshabilitar RegEdit y RegEdit32

Con esta opción, se puede evitar deshabilitar la ejecución de las herramientas estándar, para la edición del Registro de Windows, tanto para RegEdit y RegEdit32.

Nota: Para que los cambios tengan efectos, puede ser necesario re-Iniciar el Sistema, dependerá de la versión de Windows.

La información para poder realizar esta opción, consiste es establecer los datos correspondientes en el Registro de Windows.

IMAGEN 1


Notas sobre el código de ejemplo:
En el código de ejemplo se utiliza las funciones API para realizar las acciones en el Registro de Windows.

Esta, no es más, que una de las opciones que puedes utilizar para manipular el Registro

Puedes usar o aplicar las que utilizas actualmente, o las que se encuentran en FFC, etc...

Aunque, en el ejemplo se muestra "donde hay que escribir en el registro" y "cómo"

Este segundo aspecto "cómo" no tiene mayor relevancia, ya que el truco esta en saber "donde", que es la idea base de este mini-artículo o referencia.

Descargar código fuente -> proyecto de ejemplo

Ficheros del proyecto:

form: Formulario -> código ejemplo funcional
declaraciones.prg: Declaraciones -> API.
funciones.prg: Funciones -> para manipular el Registro.
cs_ejemplo.h: #Define -> utilizados

Nota: Para ejecutar el formulario de forma correcta, establece el directorio por defecto, en la ubicación donde lo tengas, el código de ejemplo no realiza comprobación, ni asignación alguna.

Por ejemplo: SET DEFAULT TO "C: \PRUEBAS"



Antonio Muñoz de Burgos y Caravaca

7 de diciembre de 2005

Evite la ruta explícita almacenada en la propiedad Picture

VFP insiste en añadir explicitamente la ruta completa y el nombre de un archivo gráfico en la hoja de propiedades. Ese comportamiento es absolutamente indeseable porque la ruta raramente, es la misma en producción que en desarrollo.

Si usted está seguro de que la carpeta que contiene la imagen gráfica está en el PATH, usted puede configurar por código la propiedad Picture solo con el nombre del archivo. No se olvide de agregar la línea EXTERNAL FILE para asegurarse de que el archivo sea incluido en el proyecto (.PJX):
THIS.Picture = "MiGrafico.JPG"
EXTERNAL FILE MiGrafico.JPG
Pero si usted desea configurar la propiedad Picture en la hoja de propiedades, especifique el nombre del archivo como una expresión, como en el ejemplo siguiente (ingreselo como se muestra, incluyendo las comillas):
="MiGrafico.JPG"
VFP Tips & Tricks - Drew Speedie

5 de diciembre de 2005

Solucionar Error: OLE error code 0x80040112: Appropriate license for this class

Este es un error común al momento de trabajar con algunos ActiveX, veremos la forma de solucionar (o por lo menos darle la vuela)...

Cuando se trabaja con los ActiveX que están incluidos dentro de la distribución de VFP, suele pasar un error justo cuando se ejecuta una línea como la siguiente:
Local loWSock, lcIp
loWSock = CreateObject("MSWinsock.Winsock")
lcIp = loWSock.LocalIP
MessageBox(lcIp)

El código anterior funciona correctamente dentro del IDE de VFP, pero cuando se crea un .EXE y éste tiene algún código donde se crea un objeto por medio de las funciones CREATEOBJECT() , NEWOBJECT(), o por medio del método ADDObject marca el citado error.

Por qué pasa eso?

Este error sucede debido a una restricción de los mismos, que para que funcionen en VFP es necesario que los ActiveX estén embebidos ya sea en un formulario o en una clase heredada de OLEControl.

Cómo solucionarlo

Como comentaba anteriormente, es buena práctica crear clases en donde se tenga embebido dicho control, como un ejemplo aquí tiene un código que hace uso del control MSCommonDialog:
frmMyForm = CREATEOBJECT("Form")

FrmMyForm.AddObject("oleObject1","oleComDialObject")
   WITH FrmMyForm.OleObject1
      .SetOptions()
      .showopen()
      ?.FileName
   ENDWITH

DEFINE CLASS oleComDialObject as OLEControl
    OleClass ="MSComDlg.CommonDialog.1"
    PROCEDURE SetOptions
      #define COMMDLOG_DEFAULT_FLAG 0x00080000
      #define COMMDLOG_RO 4
      #define COMMDLOG_MULTFILES 512

      This.Flags = COMMDLOG_DEFAULT_FLAG + COMMDLOG_RO + COMMDLOG_MULTFILES
      This.FileName = "*.dbf"
      This.filter = "DBF Files|*.dbf"
    ENDPROC
ENDDEFINE

Si deseas mayor documentacion Doug Hennig tiene un documento que explica a mayor detalle el manejo de ActiveX con VFP:

--- Using Visual FoxPro ActiveX Controls (118K) ---
http://downloads.stonefield.com/pub/axsamp.zip

Y también está documentado en el MSDN de VFP como un Bug:

--- BUG: License Error with ActiveX Control Added at Run-Time ---
http://support.microsoft.com/?scid=192693

Espero les sea de utilidad.

Un agradecimiento a Alex Feldstein por el código de MSCommonDialog

Espartaco Palma Martínez

2 de diciembre de 2005

Determinar si un campo es AutoIncremental

Determinar si es un campo específico es un campo AutoIncremental, es un poco molesto. Esa información está solamente disponible comprobando la 17° o 18° columna de la matriz creada por AFIELDS() en la fila del campo.

La rutina X8IsAutoInc.PRG de este artículo hace que esa información sea fácil de consultar.

* 
*  X8IsAutoInc.PRG
*
*  RETURNs a logical value indicating whether the
*  passed field is an AutoInc field.
*
*  Author:  Drew Speedie  
*
*  lParameters
*  tcFieldName (R)  FieldName or Alias.FieldName
*                     to be checked to see if it
*                     is an AutoInc field.
*      tcAlias (O)  If tcFieldName is passed as
*                     Alias.FieldName, this parameter
*                     is ignored.
*                   If tcFieldName is passed as 
*                     just a FieldName, this parameter
*                     is REQUIRED, specifying the ALIAS()
*                     whose tcFieldName is to be
*                     checked to see if it is an AutoInc
*                     field.
*
*  If tcFieldName is in a REMOTE VIEW, this routine
*    RETURNS .F.
*  If tcFieldName is in a LOCAL VIEW, this routine 
*    RETURNS a logical value indicating whether tcFieldName
*    in its base table is an AutoInc field.
*
*  The Alias specified in tcFieldName/tcAlias must be USED(),
*  if the Alias is a local view, its base table must
*  also be USED().
*
LPARAMETERS tcFieldName, tcAlias

IF VERSION(5) < 800
  RETURN .f.
ENDIF

IF NOT VARTYPE(tcFieldName) = "C" ;
     OR EMPTY(tcFieldName) 
  ASSERT .f. MESSAGE "tcFieldName is required"
  RETURN .NULL.
ENDIF

LOCAL lcAlias, lcFieldName
lcFieldName = UPPER(ALLTRIM(tcFieldName))
IF OCCURS(".",lcFieldName) = 1
  lcAlias = JUSTSTEM(lcFieldName)
  lcFieldName = JUSTEXT(lcFieldName)
 ELSE
  IF VARTYPE(tcAlias) = "C" AND NOT EMPTY(tcAlias) 
    lcAlias = UPPER(ALLTRIM(tcAlias))
   ELSE
    ASSERT .f. MESSAGE "tcAlias is required when po does not include the Alias"
    RETURN .NULL.
  ENDIF
ENDIF       

IF CURSORGETPROP("SourceType",lcAlias) = 2
  *
  *  remote view
  *
  RETURN .f.
ENDIF

LOCAL laFields[1], xx, llAutoInc, llView, lnSelect
llAutoInc = .f.

lnSelect = SELECT(0)
SELECT (lcAlias)

llView = CURSORGETPROP("SourceType",lcAlias) = 1
IF llView
  lcAlias = X8BTABLE(lcAlias+"."+lcFieldName,CURSORGETPROP("Database",lcAlias))
  SELECT (lcAlias)
ENDIF

AFIELDS(laFields)
xx = ASCAN(laFields,lcFieldName,1,-1,1,15)
IF xx > 0
  llAutoInc = laFields[xx,17] > 0
ENDIF

SELECT (lnSelect)

RETURN llAutoInc

VFP Tips & Tricks - Drew Speedie

28 de noviembre de 2005

Comparar dos registros

El siguiente código muestra tres formas de comparar los valores de dos registros, en el mismo cursor o en dos cursores con la misma estructura.

1) Creando un objeto con SCATTER NAME por cada registro a ser comparado, hacemos la comparación mediante la función COMPOBJ(). El comando SCATTER incluye una completa flexibilidad para detallar un grupo específico de campos, incluyendo o excluyendo campos Memo o General.

2) Con la función CURSORTOXML() generando una cadena XML por cada registro a ser comparado, hacemos luego una simple comparación de cadenas entre ambas. CURSORTOXML() no soporta personalizar la lista de campos.

3) Generando un "checksum" con la función SYS(2017) por cada registro a ser comparado, SYS(2017) incluye una completa flexibilidad para detallar un grupo específico de campos, incluyendo o excluyendo campos Memo o General. De las tres técnicas, esta es mucho mas rápida y fácil de codificar, pero es probablemente la menos conocida de las tres. Note que SYS(2017) fue agregado a VFP antes de VFP8, pero fue mejorado en VFP8 para manejar cadenas grandes.

TRY
  USE (_Samples+"Northwind\Customers") IN 0
CATCH
ENDTRY
IF NOT USED("Customers")
  MESSAGEBOX("No se pudo abrir la tabla Customers en _Samples",16,"Aviso")
  RETURN
ENDIF

******************************************
*  Comparar objetos
******************************************
LOCAL loFirstRecord, loSecondRecord
SELECT Customers
LOCATE 
SCATTER MEMO NAME loFirstRecord
SKIP
SCATTER MEMO NAME loSecondRecord
IF COMPOBJ(loFirstRecord,loSecondRecord)
  MESSAGEBOX("Los registros son iguales",48,"Aviso")
 ELSE
  MESSAGEBOX("Los registros son distintos",48,"Aviso")
ENDIF

******************************************
*  Comparar XML
******************************************
ERASE FirstRecord.XML
ERASE SecondRecord.XML
SELECT Customers
LOCATE 
CURSORTOXML("Customers","FirstRecord.XML",1,512,1)
SKIP
CURSORTOXML("Customers","SecondRecord.XML",1,512,1)
IF FILETOSTR("FirstRecord.XML") == FILETOSTR("SecondRecord.XML")
  MESSAGEBOX("Los registros son iguales",48,"Aviso")
 ELSE
  MESSAGEBOX("Los registros son distintos",48,"Aviso")
ENDIF
ERASE FirstRecord.XML
ERASE SecondRecord.XML

******************************************
*  Comparar SYS(2017)
******************************************
LOCAL lnFirstRecord, lnSecondRecord
SELECT Customers
LOCATE 
lnFirstRecord = SYS(2017,"",0,3)
SKIP 
lnSecondRecord = SYS(2017,"",0,3)
IF lnFirstRecord = lnSecondRecord
  MESSAGEBOX("Los registros son iguales",48,"Aviso")
 ELSE
  MESSAGEBOX("Los registros son distintos",48,"Aviso")
ENDIF

USE IN Customers
RETURN

VFP Tips & Tricks - Drew Speedie

3 de noviembre de 2005

Email y VFP: Parte 1i (EsSmtp)

Artículo original: Email and VFP: Part 1i (EsSmtp)
http://www.sweetpotatosoftware.com/SPSBlog/PermaLink,guid,def9a19f-04ab-46ff-8421-f700822f1773.aspx 
Autor: Craig Boyd
Traducido por: Ana María Bisbé York


Como hemos visto en las entradas previas de esta serie, el envío de correo electrónico con VFP puede realizarse de varias maneras. En esta entrada, voy a mostrar el empleo de un control ActiveX gratuito, que está disponible y es conocido simplemente como EsSmtp. Este control se vendió por Eurosource (http://www.eurosource.se); pero ahora se encuentra en SourceForge (http://sourceforge.net/projects/activex). No sólo es gratis, sino que es de código abierto. Hay algunos controles disponibles; pero con el único que estoy familiarizado es con EsSmtp. Necesitará descargar y registrar este control para que el ejemplo trabaje. Puede descargarlo aquí: http://sourceforge.net/project/showfiles.php?group_id=47048. Y encontrar información adicional aquí: http://activex.sourceforge.net/essmtp.html.

Bien, sin más dilación, he aquí el código.
*******************************
*!* Ejemplo de utilización de SendViaEsSmtp
*******************************
DIMENSION aryAttach(2)
aryAttach(1) = "C:\attachment1.txt" && cambie a un archivo real que existe en su PC
aryAttach(2) = "C:\attachment2.zip" && cambie a un archivo real que existe en su PC
LOCAL lcFromName, lcFromAddress, lcTo, lcSubject, lcBody, lcCC, lcBCC, lcSMTPServer, lcErrReturn
lcFromName = "Mi Nombre"
lcFromAddress = "yo@midominio.com"
lcTo = "alguien@sudominio.com"
lcSubject = "Hey ¿Ha intentado enviar un email con VFP?"
lcBody = ""Quiero hacerle saber que VFP es muy versátil y hay muchas formas de enviar un email."
lcCC = "otro@sudominio.com"
lcBCC = "mijefe@dominiodeljefe.com"
lcSMTPServer = "mail.myhost.com"
SendViaEsSmtp(@lcErrReturn, lcFromName, lcFromAddress, lcTo, lcSubject, lcBody, @aryAttach, lcCC, lcBCC, lcSMTPServer)
IF EMPTY(lcErrReturn)
  MESSAGEBOX("'" + lcSubject + "' se envió satisfactoriamente.", 64, "Envía email via EsSmtp")
ELSE
  MESSAGEBOX("'" + lcSubject + "' falló al enviar. Causa:" + CHR(13) + lcErrReturn, 64, "Envía email via EsSmtp")
ENDIF

*******************************************
PROCEDURE SendViaEsSmtp(tcReturn, tcFromName, tcFromAddress, tcTo, tcSubject, tcBody, taFiles, tcCC, tcBCC, tcSMTPSever)
*******************************************
  LOCAL loEsSmtp, lnCountAttachments, lnErrorNo
  TRY
    loEsSmtp = CREATEOBJECT("ESSMTP.EsSmtpCtrl.1")
    WITH loEsSmtp
    IF TYPE("tcSMTPSever") = "C"
      .SMTPServer = tcSMTPSever
    ENDIF 
    IF TYPE("tcFromName") = "C"
      .SourceName = tcFromName
    ENDIF
    IF TYPE("tcFromAddress") = "C"
      .SourceAddress = tcFromAddress
    ENDIF
    .DestinationAddress = tcTo
    IF TYPE("tcCC") = "C"
      .CCDestinationAddress = tcCC
    ENDIF
    IF TYPE("tcBCC") = "C"
      .BCCDestinationAddress = tcBCC
    ENDIF
    .Subject = tcSubject
    .MailData = tcBody
    IF TYPE("taFiles", 1) = "A"
      FOR lnCountAttachments = 1 TO ALEN(taFiles)
        .AddAttachment(taFiles(lnCountAttachments), 0) && 0 significa codificar a base64 si es necesario
      ENDFOR
    ENDIF
    IF .SendMail() != 1 && hubo un problema
      lnErrorNo = .ErrorNo
      DO CASE
        CASE lnErrorNo = 421
          THROW "El servicio no está disponible, cerrando el canal de transmisión"
        CASE lnErrorNo = 450
          THROW "No se realizó la acción requerida: buzón no disponible [Por ejemplo, buzón lleno]"
        CASE lnErrorNo = 451
          THROW "Ha abortado la acción requerida: hubo error local al procesar"
        CASE lnErrorNo = 452
          THROW "No se realizó la acción requerida: hay insuficiente capacidad de almacenado del sistema"
        CASE lnErrorNo = 500
          THROW "Error de sintaxis, comando irreconocible [Puede incluir errores del tipo: línea demasiado larga]"
        CASE lnErrorNo = 501
          THROW "Error de sintaxis en los parámetros o argumentos"
        CASE lnErrorNo = 502
          THROW "No se implementó el comando"
        CASE lnErrorNo = 503
          THROW "Está mal la secuencia de comandos"
        CASE lnErrorNo = 504
          THROW "No implementado el parámetro del comando"
        CASE lnErrorNo = 550
          THROW "No se realizó la acción requerida: buzón de correo no disponible [Por ejemplo: no existe, no tiene acceso]"
        CASE lnErrorNo = 552
          THROW "Ha abortado la acción requerida: excede la capacidad de almacenamiento"
        CASE lnErrorNo = 553
          THROW "No se realizó la acción requerida: el nombre del buzón no está admitido [Por ejemplo, es incorrecta la sintaxis del buzón]"
        CASE lnErrorNo = 554
          THROW "Falló la transacción"
        OTHERWISE
          THROW "Error desconocido - Podría estar relacionado con el WinSock"
      ENDCASE
    ENDIF
    ENDWITH
  CATCH TO loError
    tcReturn = [Error: ] + STR(loError.ERRORNO) + CHR(13) + ;
      [LineNo: ] + STR(loError.LINENO) + CHR(13) + ;
      [Message: ] + loError.MESSAGE + CHR(13) + ;
      [Procedure: ] + loError.PROCEDURE + CHR(13) + ;
      [Details: ] + loError.DETAILS + CHR(13) + ;
      [StackLevel: ] + STR(loError.STACKLEVEL) + CHR(13) + ;
      [LineContents: ] + loError.LINECONTENTS
  FINALLY
    RELEASE loEsSmtp
    loEsSmtp = .NULL.
  ENDTRY
ENDPROC

2 de noviembre de 2005

Email y VFP: Parte 1e (Shell)

Artículo original: Email and VFP: Part 1e (Shell)
http://www.sweetpotatosoftware.com/SPSBlog/PermaLink,guid,0041d75b-ce37-4493-aac9-0db82b7317d5.aspx 
Autor: Craig Boyd
Traducido por: Ana María Bisbé York

Puede enviar una URL Mailto como el comando ShellExecute para facilitar el envío de correo electrónico en VFP. Debe observar que la línea de comando (URL) está limitada a 2048 bytes (sin embargo en mi sistema no podría crear una mayor de 2020 bytes) y no hay facilidades para adjuntar ficheros utilizando este método. Tiene por una parte estas limitaciones; pero es una solución que se logra en entorno de desarrollo.

*******************************
*!* Ejemplo de utilización de SendViaShell
*******************************
LOCAL lcTo, lcSubject, lcBody, lcCC, lcBCC, lcErrReturn
lcTo = "alguien@algundominio.com"
lcSubject = "Ha intentado enviar un email con VFP?"
lcBody = "Quiero hacerle saber que VFP es muy versátil" + CHR(13) + "y hay muchas formas de enviar un email."
lcCC = "otro@otrodominio.com"
lcBCC = "mijefe@dominiodeljefe.com"
SendViaShell(@lcErrReturn, lcTo, lcSubject, lcBody, lcCC, lcBCC)
IF EMPTY(lcErrReturn)
  MESSAGEBOX("'" + lcSubject + "' se envió satisfactoriamente.", 64, "Enviar email vía Shell")
ELSE
  MESSAGEBOX("'" + lcSubject + "'falló al enviar. Causa:" + CHR(13) + lcErrReturn, 64, ;
    "Enviar email vía Shell")
ENDIF

*******************************************
PROCEDURE SendViaShell(tcReturn, tcTo, tcSubject, tcBody, tcCC, tcBCC)
*******************************************
DECLARE INTEGER ShellExecute IN shell32.DLL ;
  INTEGER hwndWin, STRING cOperation, STRING cFile, ;
  STRING cParameters, STRING cDirectory, INTEGER nShowWin
  LOCAL lcCommand, lcCRLF

TRY
  lcCRLF = "%0D%0A"
  lcCommand = "mailto:" + tcTo + "?Subject=" + tcSubject + "&Body=" + STRTRAN(tcBody, CHR(13), lcCRLF)
  IF TYPE("tcCC") = "C"
    lcCommand = lcCommand + "&CC=" + tcCC
  ENDIF
  IF TYPE("tcBCC") = "C"
    lcCommand = lcCommand + "&BCC=" + tcBCC
  ENDIF
  IF LEN(lcCommand) > 2020 && debía ser 2048, pero no en mi sistema
    THROW "El comando Mailto está limitado a 2048 bytes"
  ENDIF
  ShellExecute(0, "open", lcCommand, "", "", 1)
CATCH TO loError
  tcReturn = [Error: ] + STR(loError.ERRORNO) + CHR(13) + ;
    [LineNo: ] + STR(loError.LINENO) + CHR(13) + ;
    [Message: ] + loError.MESSAGE + CHR(13) + ;
    [Procedure: ] + loError.PROCEDURE + CHR(13) + ;
    [Details: ] + loError.DETAILS + CHR(13) + ;
    [StackLevel: ] + STR(loError.STACKLEVEL) + CHR(13) + ;
    [LineContents: ] + loError.LINECONTENTS
FINALLY
  CLEAR DLLS "ShellExecute"
ENDTRY
ENDPROC

1 de noviembre de 2005

Email y VFP: Parte 1a (MAPI)

Artículo original: Email and VFP: Part 1a (MAPI)
http://www.sweetpotatosoftware.com/SPSBlog/PermaLink,guid,8f569366-c76a-4873-9029-f31c07cf125e.aspx 
Autor: Craig Boyd
Traducido por: Ana María Bisbé York

Esta serie de entradas de blog serán fundamentalmente códigos de ejemplo, lo que significa que voy a brindar ejemplos que funcionan (corte y pegue en un prg, tenga en cuenta que hay que hacer los cambios necesarios para que su entorno se corresponda y ejecute el código) que puede utilizar para explorar las diferentes facetas de enviar correo electrónico con VFP 9. Voy a cubrir varios temas en esta serie incluyendo varias tecnologías de enviar y recibir y productos de terceros:   POP3, SMTP, MAPI, Outlook, CDO NTS, CDOSYS, JMAIL, ShellExecute, Blat, ESSMTP, OSSMTP, etc.

Comenzaré mostrando un ejemplo del empleo de MAPI para enviar un mensaje desde VFP9 que permita adjuntos, enviar con copias, con copia oculta e incluso un nombre de usuario y contraseña SMTP

*******************************
*!* Ejemplo de utilización de SendViaMAPI
*******************************
DIMENSION aryAttach(2)
aryAttach(1) = "C:\attachment1.txt" && cambie a un archivo real que existe en su PC
aryAttach(2) = "C:\attachment2.zip" && cambie a un archivo real que existe en su PC
LOCAL lcTo, lcSubject, lcBody, lnCount, lcCC, lcBCC, lcUserName, lcPassword, llOpenEmail, lcErrReturn
lcTo = "alguien@algundominio.com"
lcSubject = "¿Ha intentado enviar un email con VFP?"
lcBody = "Quiero hacerle saber que VFP es muy versátil" + CHR(13) + "y hay muchas formas de enviar un email."
lcCC = "otro@otrodominio.com"
lcBCC = "mijefe@dominiodeljefe.com"
lcUserName = "yo@midominio.com" && mi nombre de usuario SMTP 
lcPassword = "Mi_PaSsWoRd" && mi contraseña SMTP 
*!* para enviar correo automáticamente haga llOpenEmail igual a .F.
llOpenEmail = .T. && Si el correo se abrió o no, en el cliente de correo MAPI
SendViaMAPI(@lcErrReturn, lcTo, lcSubject, lcBody, @aryAttach, lcCC, lcBCC, lcUserName, lcPassword, llOpenEmail)
IF EMPTY(lcErrReturn)
  MESSAGEBOX("'" + lcSubject + "'  se envió satisfactoriamente.", 64, "Envía email via MAPI")
ELSE
  MESSAGEBOX("'" + lcSubject + "' falló al enviar. Causa:" + CHR(13) + lcErrReturn, 64, "Envía email via MAPI")
ENDIF

*******************************************
PROCEDURE SendViaMAPI(tcReturn, tcTo, tcSubject, tcBody, taFiles, tcCC, tcBCC, tcUserName, tcPassword, tlOpenEmail)
*******************************************
  #DEFINE PRIMARY 1
  #DEFINE CARBON_COPY 2
  #DEFINE BLIND_CARBON_COPY 3
  LOCAL loSession, loMessages, lnAttachments, loError AS EXCEPTION, loErrorSend AS EXCEPTION
  tcReturn = ""
  TRY
    loSession = CREATEOBJECT( "MSMAPI.MAPISession" )
    IF TYPE("tcUserName") = "C"
      loSession.UserName = tcUserName
    ENDIF
    IF TYPE("tcPassword") = "C"
      loSession.PASSWORD = tcPassword
    ENDIF
    loSession.Signon()
    IF (loSession.SessionID > 0)
      loMessages = CREATEOBJECT( "MSMAPI.MAPIMessages" )
      loMessages.SessionID = loSession.SessionID
    ENDIF
    WITH loMessages
      .Compose()
      .RecipDisplayName = tcTo
      .RecipType = PRIMARY
      .ResolveName()
      IF TYPE("tcCC") = "C"
        .RecipIndex = .RecipCount
        .RecipDisplayName = tcCC
        .RecipType = CARBON_COPY
        .ResolveName()
      ENDIF
      IF TYPE("tcBCC") = "C"
        .RecipIndex = .RecipCount
        .RecipDisplayName = tcBCC
        .RecipType = BLIND_CARBON_COPY
        .ResolveName()
      ENDIF
      .MsgSubject = tcSubject
      .MsgNoteText = tcBody
      IF TYPE("taFiles", 1) = "A"
        lnAttachments = ALEN(taFiles)
        IF LEN(tcBody) < lnAttachments && Se asegura que el cuerpo es suficientemente grande para los adjuntos
          tcBody = PADR(tcBody, lnAttachments, " ")
        ENDIF
        FOR lnCountAttachments = 1 TO lnAttachments
          .AttachmentIndex = .AttachmentCount
          .AttachmentPosition = .AttachmentIndex
          .AttachmentName = JUSTFNAME(taFiles(lnCountAttachments))
          .AttachmentPathName = taFiles(lnCountAttachments)
        ENDFOR
      ENDIF
      TRY
        .SEND(tlOpenEmail)
      CATCH TO loErrorSend
        IF tlOpenEmail && El usuario canceló la operación desde su cliente de correo?
          tcReturn = "El usuario canceló el envío de correo."
        ELSE
          THROW loErrorSend
        ENDIF
      ENDTRY
    ENDWITH
    loSession.Signoff()
  CATCH TO loError
    tcReturn = [Error: ] + STR(loError.ERRORNO) + CHR(13) + ;
      [LineNo: ] + STR(loError.LINENO) + CHR(13) + ;
      [Message: ] + loError.MESSAGE + CHR(13) + ;
      [Procedure: ] + loError.PROCEDURE + CHR(13) + ;
      [Details: ] + loError.DETAILS + CHR(13) + ;
      [StackLevel: ] + STR(loError.STACKLEVEL) + CHR(13) + ;
      [LineContents: ] + loError.LINECONTENTS
  FINALLY
    STORE .NULL. TO loSession, loMessages
    RELEASE loSession, loMessages
  ENDTRY
ENDPROC

26 de octubre de 2005

Convertir tablas a formato HTML


Introducción

En repetidas ocasiones he necesitado pasar mis tablas o cursores resultantes de una consulta a otro formato para enviar información a los usuarios. Obviamente el formato mas conocido es el texto plano y con un simple COPY TO MiArchivo.txt TYPE SDF ya tengo el archivo generado y listo para entregar, pero este formato seguramente no es de nuestro agrado (... y mucho menos del agrado de nuestros usuarios o superiores). En este artículo vamos a ver como podemos pasar nuestras tablas a formato HTML (Hyper Text Mark-up Language) y así poder darle una mejor apariencia, para enviarlo como un correo electrónico o para publicarlo en un sitio de Internet.

¿Qué hay de nuevo viejo?

Aunque muchos lo desconozcan, Visual FoxPro desde hace ya varias versiones anteriores trae una herramienta para poder convertir tablas, formularios, informes, etiquetas y menús a formato HTML. La herramienta se llama GenHtml.prg y se encuentra en la carpeta donde está instalado VFP. La ruta y nombre de este programa está almacenado en la variable del sistema _GENHTML.

Comprobamos que la variable del sistema este correctamente configurada con:
? _GENHTML
Esta variable del sistema la podemos configurar desde el menú de VFP, en Herramientas, Opciones, en la solapa Archivos, y buscamos en la lista "Generador de HTML".

El programa GenHtml.prg

Seguramente muchos utilizaron este programa aun sin saberlo, ya que se llega desde el menú de VFP al seleccionar Archivo, Guardar como HTML ..., cuando nos encontramos en el diseñador de formularios, el diseñador de etiquetas, el diseñador de menús, el diseñador de informes, o en una ventana Examinar (Browse) de una tabla o cursor.

GenHtml.prg utiliza la librería de clases visuales _Html.vcx que son parte de las FoxPro Foundation Classes (FFC) que vienen con Visual FoxPro y se encuentran en la carpeta \ffc\ del directorio de instalación de VFP. ¿Cómo ...? ¿Tampoco conocen las FoxPro Foundation Classes?

En la aplicación de ejemplo Solution que viene con VFP (esta aplicación si que la conocen por el artículo de Esparta Palma "Y tú has explorado el ejemplo Solution.app?"), viene un formulario de ejemplo para generar código HTML a partir de tablas DBFs, bajo la rama Foundations Classes, y Generate HTML. Podemos ejecutar directamente el formulario con la siguiente sentencia:
DO FORM (HOME(2) + "Solution\Ffc\DoHtml.scx")

Usar GenHtml.prg

Desde la ventana de comandos o desde una aplicación podemos usar directamente el programa GenHtml.prg pasándole los parámetros correspondientes. Algunos de estos parámetros son:
  • El archivo HTML de destino.
  • El archivo de origen, que puede ser de extensión DBF (tabla), SCX (formulario), FRX (informe), LBX (etiquetas) o MNX (menús)
  • La opción de visualización, que algunos de sus posibles valores son: 0=Genera el archivo HTML; 1=Genera y muestra el archivo HTML en el editor de VFP; 2=Genera y muestra el archivo HTML en el Explorador de Internet y 3=Genera el archivo HTML y muestra el cuadro de diálogo "Guardar como HTML" con las opciones anteriores.
Para conocer la sintaxis completa y la lista de todos los parámetros, lean la ayuda de VFP en el tema GenHtml.prg.

Para ver un ejemplo de como se genera un archivo HTML a partir de la tabla Products de la base de datos Northwind, ejecutamos la siguiente sentencia:
DO (_GENHTML) WITH "Productos.htm", HOME(2)+"Northwind\Products.dbf", 2
Hasta aquí vimos que podemos generar archivos HTML con las herramientas que nos ofrece Visual FoxPro. El código del programa y las clases de las FFC las podemos incluir sin inconvenientes en nuestras aplicaciones.

Otra clase mas

Personalmente tuve la necesidad de crear una clase mas elemental y sencilla, que solo convierte tablas y cursores a formato HTML. Aquí la expongo para que ustedes la utilicen en sus aplicaciones y quizás necesiten modificarla para agregarle mas datos al archivo HTML o para cambiar la apariencia de visualización.

La definición de la clase y la forma de uso están en en siguiente código:
*-- Ejemplo de uso
USE (HOME(2) + "Northwind\Products")
loHtml = CREATEOBJECT("ClaseHtml")
IF loHtml.Tabla2Html("Products","C:\Listado.htm") > 0
  *-- Se generó el archivo HTML y lo visualizo
  *-- con la aplicación asociada
  loShell = CREATEOBJECT("Shell.Application")
  loShell.ShellExecute("C:\Listado.htm")
  loShel = NULL
  RELEASE loShell
ELSE
  MESSAGEBOX("No se pudo generar el archivo HTML",16,"Error")
ENDIF
loHtml = NULL
RELEASE loHtml

*--------------------------------
DEFINE CLASS ClaseHtml AS CUSTOM
  *--
  *-- Clase para convertir una Tabla a formato HTML
  *--
  #DEFINE CR CHR(13)
  #DEFINE LF CHR(10)
  #DEFINE CR_LF CR + LF
  *-- Propiedades
  DIMENSION aCampos(1,1)
  nCampos = 0
  nRegistros = 0
  cNombreAlias = ""
  cArchivoHtml = ""
  cHtml = ""
  cColorBody = "#DDDDDD"
  cColorTitle = "#FFFFDD"
  cColorTable = "#FFFFFF"
  *--
  PROCEDURE Tabla2Html(tcNombreAlias, tcArchivoHtml)
    LOCAL lnErr
    IF EMPTY(tcNombreAlias) OR NOT USED(tcNombreAlias)
      RETURN -1
    ENDIF
    IF EMPTY(tcArchivoHtml)
      tcArchivoHtml = FORCEEXT(tcNombreAlias,"htm")
    ENDIF
    THIS.cNombreAlias = tcNombreAlias
    THIS.cArchivoHtml = tcArchivoHtml
    THIS.nCampos = AFIELDS(THIS.aCampos,THIS.cNombreAlias)
    THIS.EscribirHtml()
    lnErr = THIS.GuardarArchivo()
    RETURN lnErr
  ENDPROC
  *--
  PROCEDURE EscribirHtml()
    *-- Inicio html
    THIS.cHtml = THIS.IniciarTag([html]) + CR_LF
    *-- Head
    THIS.cHtml = THIS.cHtml + THIS.IniciarTag([head]) + CR_LF
    THIS.cHtml = THIS.cHtml + THIS.AgregarTags(THIS.cNombreAlias,[title]) + CR_LF
    THIS.cHtml = THIS.cHtml + THIS.TerminarTag([head]) + CR_LF
    *-- Inicio Body
    THIS.cHtml = THIS.cHtml + THIS.IniciarTag([body],[bgcolor="] + ;
      THIS.cColorBody + ["]) + CR_LF
    *-- Titulo
    THIS.cHtml = THIS.cHtml + THIS.AgregarTags(PROPER(THIS.cNombreAlias),[h3]) + CR_LF
    *-- Tabla
    THIS.cHtml = THIS.cHtml + THIS.AgregarTabla(THIS.cNombreAlias) + CR_LF
    *-- Cantidad de registros
    THIS.cHtml = THIS.cHtml + THIS.AgregarTags(TRANSFORM(THIS.nRegistros) + ;
      [ registros.], [p]) + CR_LF
    *-- Fin Body
    THIS.cHtml = THIS.cHtml + THIS.TerminarTag([body]) + CR_LF
    *-- Fin html
    THIS.cHtml = THIS.cHtml + THIS.TerminarTag([html]) + CR_LF
  ENDPROC
  *--
  PROCEDURE AgregarTags(tcTexto, tcTag, tcOpciones)
    RETURN [<] + LOWER(tcTag) + IIF(EMPTY(tcOpciones),[],[ ] + tcOpciones) + [>] + ;
      ALLTRIM(tcTexto) + [</] + LOWER(tcTag) + [>]
  ENDPROC
  *--
  PROCEDURE IniciarTag(tcTag, tcOpciones)
    RETURN [<] + LOWER(tcTag) +  ;
      IIF(EMPTY(tcOpciones),[],[ ] + tcOpciones) + [>]
  ENDPROC
  *--
  PROCEDURE TerminarTag(tcTag)
    RETURN [</] + LOWER(tcTag) + [>]
  ENDPROC
  *--
  PROCEDURE AgregarTabla(tcNombreAlias)
    LOCAL lcTexto, ln
    lcTexto = THIS.IniciarTag([table],[border="1" cellpadding="2" cellspacing="0"] + ;
      IIF(EMPTY(THIS.cColorTable),[],[ bgcolor="] + THIS.cColorTable + ["])) + CR_LF
    *-- Fila cabecera
    lcTexto = lcTexto + THIS.IniciarTag([tr], ;
      IIF(EMPTY(THIS.cColorTitle),[],[bgcolor="] + THIS.cColorTitle + ["])) + CR_LF
    *-- Recorro campos
    FOR ln = 1 TO THIS.nCampos
      lcTexto = lcTexto + THIS.AgregarTags(THIS.aCampos(ln,1),[th]) + CR_LF
    ENDFOR
    lcTexto = lcTexto + THIS.TerminarTag([tr]) + CR_LF
    *-- Filas de registros
    SELECT (tcNombreAlias)
    SCAN ALL
      THIS.nRegistros = THIS.nRegistros + 1
      *-- Inicio fila
      lcTexto = lcTexto + THIS.IniciarTag([tr]) + CR_LF
      *-- Recorro los campos
      FOR ln = 1 TO THIS.nCampos
        lcTexto = lcTexto + THIS.AgregarTags(THIS.TomarValorCampo(ln),[td], ;
          THIS.AlinearSoloNumeros(ln)) + CR_LF
      ENDFOR
      *-- Fin fila
      lcTexto = lcTexto + THIS.TerminarTag([tr]) + CR_LF
    ENDSCAN
    lcTexto = lcTexto + THIS.TerminarTag([table])
    RETURN lcTexto
  ENDPROC
  *--
  PROCEDURE TomarValorCampo(tn)
    LOCAL lcTexto, lcTipo
    *-- Tipo de campo
    lcTipo = THIS.aCampos(tn,2)
    DO CASE
      CASE INLIST(lcTipo,"C","M") && Caracter y Memo
        lcTexto = ALLTRIM(EVALUATE(THIS.aCampos(tn,1)))
      CASE INLIST(lcTipo,"D","T","I","L")
        lcTexto = TRANSFORM(EVALUATE(THIS.aCampos(tn,1)))
      CASE INLIST(lcTipo,"Y") && Monetario
        *-- Quitar esta opción si no se desar el símbolo monetario
        *-- Este tipo también está contemplado en la opcion siguiente
        lcTexto = TRANSFORM(EVALUATE(THIS.aCampos(tn,1)))
      CASE INLIST(lcTipo,"N","F","B","Y")
        IF THIS.aCampos(tn,4) = 0
          lcTexto = ALLTRIM(STR(EVALUATE(THIS.aCampos(tn,1))))
        ELSE
          lcTexto = ALLTRIM(STR(EVALUATE(THIS.aCampos(tn,1)), ;
            THIS.aCampos(tn,3) + THIS.aCampos(tn,4) + 1, THIS.aCampos(tn,4)))
        ENDIF
      CASE INLIST(lcTipo,"G") && General
        lcTexto = [gen]
      CASE INLIST(lcTipo,"Q","V","W") && Nuevos tipos de VFP 9.0
        lcTexto = TRANSFORM(EVALUATE(THIS.aCampos(tn,1)))
      OTHERWISE
        lcTexto = ""
    ENDCASE
    lcTexto = THIS.TransformarTexto(lcTexto)
    IF lcTipo = "M" && Si es campo Memo
      *-- Transformo retornos de carro y saltos de lineas
      lcTexto = STRTRAN(lcTexto, CR_LF, [<br>])
      lcTexto = STRTRAN(lcTexto, CR, [<br>])
      lcTexto = STRTRAN(lcTexto, LF, [<br>])
    ELSE && Si no es campo Memo
      *-- Transformo espacios [ ] por [&nbsp;]
      lcTexto = STRTRAN(lcTexto, [ ], [&nbsp;])
    ENDIF
    RETURN lcTexto
  ENDPROC
  *--
  PROCEDURE TransformarTexto(tcTexto)
    *-- Transformo algunos caracteres "\<>&
    tcTexto = STRTRAN(tcTexto, ["], [&quot;])
    tcTexto = STRTRAN(tcTexto, [\], [&#092;])
    tcTexto = STRTRAN(tcTexto, [<], [&lt;])
    tcTexto = STRTRAN(tcTexto, [>], [&gt;])
    tcTexto = STRTRAN(tcTexto, [&], [&amp;])
    *-- Si es vacio o nulo
    IF EMPTY(tcTexto) OR ISNULL(tcTexto)
      tcTexto = [&nbsp;]
    ENDIF
    RETURN tcTexto
  ENDPROC
  *--
  PROCEDURE AlinearSoloNumeros(tn)
    LOCAL lcTexto, lcTipo
    lcTipo = THIS.aCampos(tn,2)
    DO CASE
      CASE INLIST(lcTipo,"N","F","B","Y","I")
        *-- Alinea a la derecha solo campos numéricos
        lcTexto = [align="right"]
      OTHERWISE
        lcTexto = []
    ENDCASE
    RETURN lcTexto
  ENDPROC
  *--
  PROCEDURE GuardarArchivo()
    RETURN STRTOFILE(THIS.cHtml,THIS.cArchivoHtml)
  ENDPROC
  *--
ENDDEFINE
*--------------------------------

Conclusiones

Las conclusiones que podemos sacar de este artículo son muchas y dependen de las respuestas a las siguientes preguntas:
  • ¿Conocían el programa GenHtml.prg?
  • ¿Generaron alguna vez archivos HTML desde el menú Archivo, Guardar como HTML?
  • ¿Utilizaron alguna vez las FoxPro Foundation Classes?
  • ¿Exploraron la aplicación Solution.app o el formulario DoHtml.scx?
  • ¿Desarrollaron algún código para generar archivos HTML?
Si contestaron mas de un "NO", los invito a que exploren los ejemplos y las herramientas que nos brinda Visual FoxPro, que son muchas mas de las que se imaginan, y nos brindan la posibilidad de dar soluciones rápidas y efectivas a los requerimientos de nuestros usuarios.

Hasta la próxima.

Luis María Guayán

20 de octubre de 2005

Agregar columnas en sentencias SELECT SQL

Artículo original: Adding Columns in SQL SELECT statements
http://weblogs.foxite.com/andykramek/archive/2005/09/18/921.aspx
Autor: Andy Kramek
Traducido por: Ana María Bisbé York 

Un requerimiento habitual cuando trabajamos con datos, independientemente de su origen, es poder agregar "al vuelo" columnas adicionales al resultado. Esto es muy sencillo si sólo se necesita una columna en blanco, basta con definirla directamente en la consulta utilizando la función SPACE(), de esta forma:
SELECT SPACE(30) AS newcol FROM nametable
(Observe que la palabra "AS" en realidad no es requerida por la sintaxis de VFP (o SQL Server); pero algunos de los dialectos lo requieren, y en cualquier caso, pienso que mejora la lectura de la consulta.) Ahora, vamos a suponer que tenemos algunos datos en una tabla como esta:

cfirstclastiintcolnnum1nnum2
Andy Kramek 0 123.45 3456.78
Vladimir Andropovitch 31111.65654.32

Si necesitamos concatenar las columnas del nombre para crear un "nombre completo", entonces, podemos hacer algo como esto:
SELECT (ALLTRIM( cfirst ) + " " + ALLTRIM( clast )) AS fullname FROM sample
Aunque, en la práctica esto no es tan intuitivo como se puede ver a simple vista. La cuestión aquí es que cuando creamos una "columna computada" (que es lo que yo hago aquí) VFP crea la definición para esa columna basado en la longitud de las columnas existentes en la concatenación. Entonces, si ambos campos "cfirst" y "clast" se definen como c(20), el resultado se define con un campo que tenga c(41). En otras palabras, los 20 caracteres para el primer campo, uno para el espacio y 20 caracteres para cada campo.

Esto está bien, aunque puede ser una pérdida de espacio; pero no provocará pérdida de datos y si deseamos en realidad acortarlos, podemos simplemente utilizar la función PADR() para forzar el ancho de un valor específico:
SELECT PADR( ALLTRIM( cfirst ) + " " + ALLTRIM( clast ), 30) AS fullname FROM sample
Sin embargo, si además estamos convirtiendo tipos de datos (de números a caracteres, por ejemplo), entonces tendremos un problema potencia debido a que en este caso VFP no conoce de qué largo pudieran ser los datos. Todo lo que puede hacer es basarse en la definición del tamaño del primer elemento encontrado. Entonces, la consulta siguiente:
SELECT TRANSFORM( nnum1 ) AS cvalue FROM sample
Devolverá un conjunto resultante con la columna "cvalue" definida como c(6) - en otras palabras, el número de caracteres en el primer valor de la tabla. Esto, por supuesto, significa que el segundo valor se trunca debido a que en realidad contiene siete caracteres. Entonces, ahora es muy importante asegurarse de que hemos especificado un formato para el campo que sea tan largo como para manipular cualquier posible valor y nosotros podemos utilizar la función PADR() para controlar el formato una vez que hayamos transformado el dato: como esto:
SELECT PADR( TRANSFORM( nnum1 ), 10) AS cvalue FROM sample
Pero, la introducción de la función CAST() en VFP 9.0 brinda una alternativa, debido a que nos permite decir directamente a VFP cómo deseamos obtener la salida de los resultados. Entonces, en VFP 9.0 podemos escribir la primera consulta de esta forma:
SELECT CAST( ALLTRIM( cFirst ) + " " + ALLTRIM( cLast ), AS CHAR(30)) AS fullName FROM sample
y la segunda:
SELECT CAST( nnum1 AS CHAR(10)) AS cvalue FROM sample
Todo esto está muy bien cuando estamos trabajando con columnas existentes; pero qué ocurre si necesitamos crear una columna nueva con un tipo de dato específico? Bueno, es bastante fácil crear columnas de caracteres, empleando la función SPACE() (o incluso PADL()) para crear el ancho requerido:
SELECT *, SPACE(30) AS newstring FROM sample
De forma similar, si necesita una columna para datos tipo moneda, las debería definir como "$U" y un dato nuevo utilizando una cadena fecha vacía "{}", una nueva columna decimal utilizando una cadena de ceros, una columna lógica utilizando .F., etc.
SELECT $0 AS yamount, {} AS dpaid, 00000.00 AS newbal, .F. AS lCleared FROM sample
Teniendo esto, podría estar tentado a pensar, que crear una columna para datos enteros sería tan sencillo como:
SELECT 0 AS newint FROM sample
Después de todo, al crear una columna para enteros en una tabla, se inicializa con "0", entonces, parece razonable decirle a VFP que al crear una columna para valor "0" obtendremos un entero. Desafortunadamente no es el caso! Lo que en realidad ocurre es que VFP crea una columna numérica con ancho = 1 y decimales = 0. El resultado es que solamente puede guardar los valores de 0-9. ¡Y esto no es lo que queremos!

Entonces, cómo obtenemos un valor entero? Bueno, antes de VFP 9.0 existía un pequeño truco y dos vías posibles para hacerlo. La primera (y más sencilla) sería definir la columna con suficiente espacio para guardar el entero.
SELECT *, 0000000000 AS newint FROM sample
El resultado en realidad no es una columna para datos enteros, muy por el contrario, es una columna numérica muy grande (N(10,0)) y siempre encuentro problemático estar contando tantos ceros. Para crear una verdadera columna para enteros en un conjunto resultante tenemos que emplear un truco que implica crear un producto cartesiano.


19 de octubre de 2005

Varios artículos sobre cómo trabajar con correos desde VFP

Craig Boyd, en su blog http://www.sweetpotatosoftware.com/SPSBlog/default.aspx ha escrito una serie de artículos relativos al tema de trabajo con correos electrónicos desde Visual FoxPro.

En esta serie Craig demuestra con ejemplos de código como procesar mensajes desde VFP 9.0 y productos de terceros: POP3, SMTP, MAPI, Outlook, CDO NTS, CDOSYS, JMAIL, ShellExecute, Blat, ESSMTP, OSSMTP, etc.

Los enlaces se pueden encontrar en:

Email and VFP: Part 1a (MAPI)
http://www.sweetpotatosoftware.com/SPSBlog/PermaLink,guid,8f569366-c76a-4873-9029-f31c07cf125e.aspx
Traducido al español en: http://comunidadvfp.blogspot.com/2005/11/email-y-vfp-parte-1a-mapi.html

Email and VFP: Part 1b (Outlook)
http://www.sweetpotatosoftware.com/SPSBlog/PermaLink,guid,fb9e9267-3642-4176-94ea-9239691b61fa.aspx

Email and VFP: Part 1c (CDOSYS)
http://www.sweetpotatosoftware.com/SPSBlog/PermaLink,guid,71acd54c-dcda-4dfa-b4ae-74854dd7947f.aspx

Email and VFP: Part 1d (CDO NTS)
http://www.sweetpotatosoftware.com/SPSBlog/PermaLink,guid,40a6327a-44f7-4e98-9e83-cb50c2ebd4c1.aspx

Email and VFP: Part 1e (Shell)
http://www.sweetpotatosoftware.com/SPSBlog/PermaLink,guid,0041d75b-ce37-4493-aac9-0db82b7317d5.aspx
Traducido al español en: http://comunidadvfp.blogspot.com/2005/11/email-y-vfp-parte-1e-shell.html

Email and VFP: Part 1f (WinExec)
http://www.sweetpotatosoftware.com/SPSBlog/PermaLink,guid,87d1b04e-afbf-4c41-9ba7-5bcef933128b.aspx

Email and VFP: Part 1g (w3JMail)
http://www.sweetpotatosoftware.com/SPSBlog/PermaLink,guid,90d53f97-21a5-49b1-a0d7-0933c6eb62e5.aspx

Email and VFP: Part 1h (BLAT)
http://www.sweetpotatosoftware.com/SPSBlog/PermaLink,guid,6b86dea6-66ec-4f8f-a610-73dd6f896fb7.aspx

Email and VFP: Part 1i (EsSmtp)
http://www.sweetpotatosoftware.com/SPSBlog/PermaLink,guid,def9a19f-04ab-46ff-8421-f700822f1773.aspx
Traducido al español en: http://comunidadvfp.blogspot.com/2005/11/email-y-vfp-parte-1i-essmtp.html

Estas entregas constituyen un gran resumen que abarca posibilidades de uso en muchas variantes.

No te lo pierdas. Gracias Craig.

Saludos,

Ana María Bisbé York

17 de octubre de 2005

Agregar un menú en un formulario SDI

Introducción


Continuando con el tema de formularios SDI (Interfaz de un solo documento - Single Document Interface), esta vez mostraré como agregar un menú en un formulario de nivel superior.

Crear el formulario de nivel superior


Como ya vimos en un artículo anterior, para crear un formulario SDI en Visual FoxPro, debemos crear un Formulario de nivel superior, configurando la propiedad ShowWindow = 2 (Como formulario de nivel superior). Un formulario de nivel superior aparece como una ventana independiente sobre el escritorio de Windows y se muestra en la barra de tareas de Windows.

Crear el menú


La creación de un menú SDI para agregar en un formulario de nivel superior, es igual que la creación de cualquier otro menú, solo debemos marcar la casilla de verificación Formulario de nivel superior en el cuadro de dialogo Opciones generales que se muestra al seleccionar la opción Ver -> Opciones generales desde el diseñador de menú, como lo muestra la figura siguiente:

Cuando generamos el menú con esta opción activada, se deberá llamar al menú desde el método Init del formulario de nivel superior con una sentencia como la siguiente
DO MiMenu.mpr WITH THISFORM,.T.
Editando el archivo MiMenu.mpr podemos ver en las primeras líneas comentadas, una ayuda con las distintas formas de llamar al menú generado.

Código de ejemplo


El siguiente código es un ejemplo para mostrar un formulario de nivel superior con un menú incorporado. Para ejecutarlo con un menú personalizado, se debe habilitar la línea DO MiMenu.mpr WITH Thisform, .T. en el método Init del formulario, y quitar la llamada al procedimiento MiMenuEjemplo.
PUBLIC goMiForm
goMiForm=CREATEOBJECT("MiForm")
goMiForm.SHOW(1)
RETURN
*---
*--- Definición de MiForm
*---
DEFINE CLASS MiForm AS FORM
  SHOWWINDOW = 2
  DOCREATE = .T.
  AUTOCENTER = .T.
  CAPTION = "Ejemplo de menú en un formulario SDI"
  NAME = "MiForm"

  PROCEDURE INIT
    *DO MiMenu.mpr WITH Thisform, .T.
    DO MiMenuEjemplo WITH THISFORM, .T.
  ENDPROC

  PROCEDURE DESTROY
    RELEASE MENU (THIS.NAME) EXTENDED
  ENDPROC
ENDDEFINE
*---
*--- MiMenuEjemplo.spr
*---
PROCEDURE MiMenuEjemplo
  LPARAMETERS oFormRef, getMenuName, lUniquePopups
  LOCAL cMenuName, nTotPops, a_menupops, cTypeParm2, cSaveFormName
  IF TYPE("m.oFormRef") # "O" OR ;
      LOWER(m.oFormRef.BASECLASS) # 'form' OR ;
      m.oFormRef.SHOWWINDOW # 2
    MESSAGEBOX([Este menú solo puede ser llamado en un formulario de nivel superior])
    RETURN
  ENDIF
  m.cTypeParm2 = TYPE("m.getMenuName")
  m.cMenuName = SYS(2015)
  m.cSaveFormName = m.oFormRef.NAME
  IF m.cTypeParm2 = "C" OR (m.cTypeParm2 = "L" AND m.getMenuName)
    m.oFormRef.NAME = m.cMenuName
  ENDIF
  IF m.cTypeParm2 = "C" AND !EMPTY(m.getMenuName)
    m.cMenuName = m.getMenuName
  ENDIF
  DIMENSION a_menupops[3]
  IF TYPE("m.lUniquePopups")="L" AND m.lUniquePopups
    FOR nTotPops = 1 TO ALEN(a_menupops)
      a_menupops[m.nTotPops]= SYS(2015)
    ENDFOR
  ELSE
    a_menupops[1]="archivo"
    a_menupops[2]="edición"
    a_menupops[3]="ayuda"
  ENDIF
  *---
  *--- Definición del menú
  *---
  DEFINE MENU (m.cMenuName) IN (m.oFormRef.NAME) BAR
  DEFINE PAD _1mv0kg6re OF (m.cMenuName) PROMPT "\<Archivo" COLOR SCHEME 3 KEY ALT+A
  DEFINE PAD _1mv0kg6rf OF (m.cMenuName) PROMPT "\<Edición" COLOR SCHEME 3 KEY ALT+E
  DEFINE PAD _1mv0kg6rg OF (m.cMenuName) PROMPT "A\<yuda" COLOR SCHEME 3 KEY ALT+Y
  ON PAD _1mv0kg6re OF (m.cMenuName) ACTIVATE POPUP (a_menupops[1])
  ON PAD _1mv0kg6rf OF (m.cMenuName) ACTIVATE POPUP (a_menupops[2])
  ON PAD _1mv0kg6rg OF (m.cMenuName) ACTIVATE POPUP (a_menupops[3])

  DEFINE POPUP (a_menupops[1]) MARGIN RELATIVE SHADOW COLOR SCHEME 4
  DEFINE BAR 1 OF (a_menupops[1]) PROMPT "\<Nuevo" PICTRES _MFI_NEW
  DEFINE BAR 2 OF (a_menupops[1]) PROMPT "\<Abrir" PICTRES _MFI_OPEN
  DEFINE BAR 3 OF (a_menupops[1]) PROMPT "\-"
  DEFINE BAR 4 OF (a_menupops[1]) PROMPT "\<Guardar" PICTRES _MFI_SAVE
  DEFINE BAR 5 OF (a_menupops[1]) PROMPT "\<Imprimir" PICTRES _MFI_SYSPRINT
  DEFINE BAR 6 OF (a_menupops[1]) PROMPT "\<Enviar" PICTRES _MFI_SEND
  DEFINE BAR 7 OF (a_menupops[1]) PROMPT "\-"
  DEFINE BAR 8 OF (a_menupops[1]) PROMPT "\<Salir" PICTRES _MFI_QUIT
  ON SELECTION BAR 8 OF (a_menupops[1]) DO _Salir

  DEFINE POPUP (a_menupops[2]) MARGIN RELATIVE SHADOW COLOR SCHEME 4
  DEFINE BAR 1 OF (a_menupops[2]) PROMPT "\<Deshacer" PICTRES _MED_UNDO
  DEFINE BAR 2 OF (a_menupops[2]) PROMPT "\-"
  DEFINE BAR 3 OF (a_menupops[2]) PROMPT "Cor\<tar" PICTRES _MED_CUT
  DEFINE BAR 4 OF (a_menupops[2]) PROMPT "\<Copiar" PICTRES _MED_COPY
  DEFINE BAR 5 OF (a_menupops[2]) PROMPT "\<Pegar" PICTRES _MED_PASTE

  DEFINE POPUP (a_menupops[3]) MARGIN RELATIVE SHADOW COLOR SCHEME 4
  DEFINE BAR 1 OF (a_menupops[3]) PROMPT "\<Ayuda" PICTRES _MST_HPSCH
  DEFINE BAR 2 OF (a_menupops[3]) PROMPT "\-"
  DEFINE BAR 3 OF (a_menupops[3]) PROMPT "Acerca \<de..." PICTRES _MST_ABOUT

  ACTIVATE MENU (m.cMenuName) NOWAIT

  IF m.cTypeParm2 = "C"
    m.getMenuName = m.cMenuName
    m.oFormRef.NAME = m.cSaveFormName
  ENDIF
ENDPROC

PROCEDURE _Salir
  _SCREEN.ACTIVEFORM.RELEASE
ENDPROC
Al ejecutar el código anterior, veremos el formulario SDI con el menú como lo muestra la siguiente figura:


Comentarios


Recordar que Visual FoxPro trae en la aplicación "Solution" un ejemplo similar donde se muestra un formulario SDI, en el cual se agrega un menú, una barra de herramientas, y se crean ventanas secundarias. Para ver la aplicación "Solution" ejecute:
DO (HOME(2) + "Solution\Solution")
O directamente corra el ejemplo del formulario SDI ejecutando:
DO FORM (HOME(2) + "\Solution\Forms\SDIForm.scx")

Hasta la próxima.


Luis María Guayán

10 de octubre de 2005

Dominar el control Microsoft ListView

Artículo original: Taming the Microsoft ListView control
http://www.ml-consult.co.uk/foxst-28.htm
Autor: Mike Lewis
Traducido por: Ana María Bisbé York

Deje que el control SimpleList haga el duro trabajo de obtener listas de vistas. (List view)

La versión 1.1 de SimpleList apareció en Marzo 2003. Vea debajo un resumen de las nuevas posibilidades.

Si ha experimentado con los controles ActiveX que vienen con Visual FoxPro, probablemente haya pasado a través del control ListView. Este versátil control proporciona una forma atractiva para mostrar los datos en una lista, y ofrece amplias alternativas para los controles listbox (listas) y grids (cuadrículas), nativos de VFP.

Pero, mientras que ListView ofrece útiles ventajas, puede ser frustrantemente difícil de programar. A diferencia del control grid de VFP, no lo puede enlazar a una tabla simplemente estableciendo una propiedad, incluso una tarea tan mundana como es especificar un encabezado de columna requiere varias líneas de código.

Si le gusta la idea de tener un control ListView en su aplicación; pero verse libre de toda su complejidad, eche un vistazo a nuestro control SimpleList. Como sugiere su nombre, SimpleList toma el trabajo duro de utilizar y controlar el ListView. Es, en esencia una envoltura del control ListView, cuyo objetivo es proporcionar un método de llenar la lista basada en un simple cursor. De hecho, una vez que tenga su cursor preparado sólo necesitará tres líneas de comandos para ver aparecer su lista. (Observe que SimpleList requiere Visual FoxPro 7.0 o superior)

¿Qué es exactamente un ListView?

Para ver un ListView en acción, necesita ver solamente el panel derecho de la ventana del Explorador de Windows. Es el control ListView el que proporciona una lista de archivos y carpetas con sus ya familiares íconos grandes, íconos pequeños y ventana de detalles. El control ListView ActiveX puede proporcionar esta misma funcionalidad a sus aplicaciones.

Con una aplicación típica de bases de datos, probablemente esté interesado solamente en mostrar su lista en la ventana de detalles (también conocida como vista informes), ya que esta vista proporciona la vía más intuitiva de vista tabular de datos. Sin embargo, el ListView admite los otros tres tipos de vista de igual modo. La figura 1 muestra un ejemplo de vista de detalles, mientras la figura 2 muestra la vista con íconos grandes.


Figura 1: Un ListView con vista de detalle.

Figura 2: La misma lista en vista de íconos grandes.

Además de proporcionar las cuatro vistas diferentes, el control ListView ofrece otras muchas ventajas:
  • En la vista de detalle, el usuario, puede mover y redimensionar columnas.
  • También en esta vista, el usuario puede ordenar los datos haciendo clic en un encabezado de columna. La primera vez que haga Clic se ordenará de manera ascendente, la segunda vez de manera descendente.
  • Cada elemento de la lista puede tener asociado un ícono o gráfico. Esto es esencial en las vistas con íconos grandes y pequeños, pero quizás menos útil en las listas con vistas de detalles, (aunque los íconos son completamente opcionales en las cuatro vistas).
  • Puede adicionar una casilla de verificación (checkbox) a cada elemento. Esto proporciona una vía sencilla de permitir al usuario hacer selección múltiple de la lista.
  • En la vista de detalles, puede dar a cada elemento su propio tooltip. Esto es útil para mostrar información adicional que de otra forma sería muy ancha para mostrar en la lista.
  • Puede permitir al usuario editar el texto de un elemento, simplemente haciendo clic sobre el, en la forma que pueda renombrar un archivo en el Explorador de Windows).
  • Las listas pueden mejorar su apariencia. Por ejemplo, puede escoger mostrar o no las líneas de cuadrícula en la vista de detalle, puede optar por “encender” el rastreo caliente.
  • Se admite la característica de arrastrar y soltar. Los usuarios pueden arrastrar un elemento de la lista y soltarlo en otro control o aplicación.
Limitaciones


7 de octubre de 2005

Crear una barra de herramientas en un formulario SDI

Introducción


Muchas veces he leído en los distintos foros referidos a Visual FoxPro, mensajes sobre el tema de crear una barra de herramientas en un formulario. En este artículo expondré un ejemplo de un formulario SDI (Interfaz de un solo documento - Single Document Interface) en el cual crearemos una barra de herramientas en tiempo de ejecución.

Lo necesario


Para crear un formulario SDI, Visual FoxPro cuenta con un tipo de formulario llamado "formulario de nivel superior". Estos formularios aparecen como ventanas independientes sobre el escritorio de Windows y también aparecen en la barra de tareas de Windows. Para lograr un formulario de nivel superior, solo debemos configurar la propiedad ShowWindow = 2 (Como formulario de nivel superior).

Para crear la barra de herramientas contenida en un formulario de nivel superior, debemos indicar esto configurando la propiedad ShowWindow = 1 (En formulario de nivel superior)

La clase ToolBar


Vamos a definir nuestra barra de herramientas programáticamente a partir de la clase ToolBar. Cuando se crea una barra de herramientas, VFP coloca los controles de izquierda a derecha en el orden que aparecen en la definición de la clase.

Escribiremos en los métodos Clicks de los controles añadidos, un código simple como un MESSAGEBOX("Hola !") para mostrar el funcionamiento de cada control dentro de la barra de tareas.

El formulario


En formulario del ejemplo vamos a incorporar varios botones de comandos: uno crear la barra de herramientas, cuatro para acoplar la barra de herramientas en las distintas posiciones permitidas, y otro para desacoplar la barra de herramientas.

Vamos a crear la propiedad personalizada ThisForm.oMiToolBar para crear y mantener la barra de herramientas dentro del alcance del formulario.

Una vez ejecutado el código mostrado mas abajo, obtendremos un formulario como el de la siguiente figura.



El código


En el siguiente código está las definiciones de nuestras clases MiForm y MiToolBar.
PUBLIC goMiForm
goMiForm = CREATEOBJECT("MiForm")
goMiForm.SHOW(1)
RETURN
*--
*-- Clase MiForm
*--
DEFINE CLASS MiForm AS FORM
  *-- Propiedades
  SHOWWINDOW = 2 && Formulario de nivel superior
  AUTOCENTER = .T.
  ALWAYSONTOP = .T.
  CAPTION = "Ejemplo de ToolBar en un formulario SDI"
  NAME = "MiForm"
  *-- Objetos
  ADD OBJECT cmd1 AS COMMANDBUTTON WITH ;
    TOP = 14, LEFT = 120, HEIGHT = 24, WIDTH = 120, ;
    CAPTION = "Crear ToolBar", NAME = "cmd1"
  ADD OBJECT cmd2 AS COMMANDBUTTON WITH ;
    TOP = 50, LEFT = 120, HEIGHT = 24, WIDTH = 120, ;
    CAPTION = "Acoplar arriba", NAME = "cmd2"
  ADD OBJECT cmd3 AS COMMANDBUTTON WITH ;
    TOP = 74, LEFT = 60, HEIGHT = 24, WIDTH = 120, ;
    CAPTION = "Acoplar izquierda", NAME = "cmd3"
  ADD OBJECT cmd4 AS COMMANDBUTTON WITH ;
    TOP = 74, LEFT = 180, HEIGHT = 24, WIDTH = 120, ;
    CAPTION = "Acoplar derecha", NAME = "cmd4"
  ADD OBJECT cmd5 AS COMMANDBUTTON WITH ;
    TOP = 98, LEFT = 120, HEIGHT = 24, WIDTH = 120, ;
    CAPTION = "Acoplar abajo", NAME = "cmd5"
  ADD OBJECT cmd6 AS COMMANDBUTTON WITH ;
    TOP = 134, LEFT = 120, HEIGHT = 24, WIDTH = 120, ;
    CAPTION = "Desacoplar", NAME = "cmd6"
  ADD OBJECT cmdSalir AS COMMANDBUTTON WITH ;
    TOP = 182, LEFT = 120, HEIGHT = 24, WIDTH = 120, ;
    CAPTION = "Salir", NAME = "cmdSalir"
  *-- Metodos
  PROCEDURE cmd1.CLICK
    IF NOT PEMSTATUS(THISFORM,"oMiToolBar",5)
      THISFORM.ADDPROPERTY("oMiToolBar",NULL)
    ENDIF
    IF ISNULL(THISFORM.oMiToolBar)
      THISFORM.oMiToolBar = CREATEOBJECT("MiToolBar")
      THISFORM.oMiToolBar.SHOW
    ENDIF
  ENDPROC
  PROCEDURE cmd2.CLICK
    IF PEMSTATUS(THISFORM,"oMiToolBar",5) ;
        AND NOT ISNULL(THISFORM.oMiToolBar)
      THISFORM.oMiToolBar.DOCK(0)
    ENDIF
  ENDPROC
  PROCEDURE cmd3.CLICK
    IF PEMSTATUS(THISFORM,"oMiToolBar",5) ;
        AND NOT ISNULL(THISFORM.oMiToolBar)
      THISFORM.oMiToolBar.DOCK(1)
    ENDIF
  ENDPROC
  PROCEDURE cmd4.CLICK
    IF PEMSTATUS(THISFORM,"oMiToolBar",5) ;
        AND NOT ISNULL(THISFORM.oMiToolBar)
      THISFORM.oMiToolBar.DOCK(2)
    ENDIF
  ENDPROC
  PROCEDURE cmd5.CLICK
    IF PEMSTATUS(THISFORM,"oMiToolBar",5) ;
        AND NOT ISNULL(THISFORM.oMiToolBar)
      THISFORM.oMiToolBar.DOCK(3)
    ENDIF
  ENDPROC
  PROCEDURE cmd6.CLICK
    IF PEMSTATUS(THISFORM,"oMiToolBar",5) ;
        AND NOT ISNULL(THISFORM.oMiToolBar)
      THISFORM.oMiToolBar.DOCK(-1)
    ENDIF
  ENDPROC
  PROCEDURE cmdSalir.CLICK
    THISFORM.RELEASE
  ENDPROC
ENDDEFINE
*--
*-- Clase MiToolBar
*--
DEFINE CLASS MiToolBar AS TOOLBAR
  *-- Propiedades
  CAPTION = "Saludo"
  SHOWTIPS  = .T.
  SHOWWINDOW = 1 && En formulario de nivel superior
  NAME = "MiToolBar"
  *-- Objetos
  ADD OBJECT cmdEsp AS COMMANDBUTTON WITH ;
    HEIGHT = 24, WIDTH = 32, ;
    PICTURE = HOME(1) + "graphics\icons\flags\flgspain.ico", ;
    CAPTION = "", NAME = "cmdEsp", TOOLTIPTEXT = "Saludo"
  ADD OBJECT sep1 AS SEPARATOR WITH ;
    NAME = "sep1"
  ADD OBJECT cmdBra AS COMMANDBUTTON WITH ;
    HEIGHT = 24, WIDTH = 32, ;
    PICTURE = HOME(1) + "graphics\icons\flags\flgbrazl.ico", ;
    CAPTION = "", NAME = "cmdBra", TOOLTIPTEXT = "Saudação"
  ADD OBJECT cmdUSA AS COMMANDBUTTON WITH ;
    HEIGHT = 24, WIDTH = 32, ;
    PICTURE = HOME(1) + "graphics\icons\flags\flgusa02.ico", ;
    CAPTION = "", NAME = "cmdUSA", TOOLTIPTEXT = "Greet"
  ADD OBJECT cmdGer AS COMMANDBUTTON WITH ;
    HEIGHT = 24, WIDTH = 32, ;
    PICTURE = HOME(1) + "graphics\icons\flags\flggerm.ico", ;
    CAPTION = "", NAME = "cmdGer", TOOLTIPTEXT = "Grüß"
  *-- Metodos
  PROCEDURE cmdEsp.CLICK
    MESSAGEBOX("Hola !", 64, "Saludo")
  ENDPROC
  PROCEDURE cmdBra.CLICK
    MESSAGEBOX("Olá !", 64, "Saudação")
  ENDPROC
  PROCEDURE cmdUSA.CLICK
    MESSAGEBOX("Hello !", 64, "Greet")
  ENDPROC
  PROCEDURE cmdGer.CLICK
    MESSAGEBOX("Hallo !", 64, "Grüß")
  ENDPROC
ENDDEFINE

Comentarios


Espero que este ejemplo les sea de utilidad para lograr una interfaz distinta en las aplicaciones desarrolladas. Visual FoxPro trae en la aplicación "Solution" un ejemplo similar donde se muestra un formulario SDI, en el cual se agrega un menú, una barra de herramientas, y se crean ventanas secundarias. Para ver la aplicación "Solution" ejecute:
DO (HOME(2) + "Solution\Solution") 
Hasta la próxima.

Luis María Guayán

1 de octubre de 2005

Cómo pasar parámetros a un procedimiento almacenado de SQL Server

Artículo de la Base de Conocimientos de Microsoft que muestra dos formas de pasar parámetros a procedimientos almacenados de SQL Server desde Visual FoxPro.

 El enlace al artículo de la MSKB (Microsoft Knowledge Base - Base de Conocimientos de Microsoft) es el siguiente:

Español (Traducción Automática):
-- Cómo pasar parámetros a un procedimiento almacenado de SQL Server --
http://support.microsoft.com/kb/247370/es

Inglés: 
-- How to pass parameters to a SQL Server stored procedure -- 
http://support.microsoft.com/kb/247370/en

La información de este artículo se refiere a:

  • Microsoft Visual FoxPro 3.0
  • Microsoft Visual FoxPro 5.0 
  • Microsoft Visual FoxPro 6.0 Professional Edition 
  • Microsoft Visual FoxPro 7.0 Professional Edition 
  • Microsoft Visual FoxPro 8.0 Professional Edition 
  • Microsoft Visual FoxPro 9.0 Professional Edition 

28 de septiembre de 2005

Y tu has explorado el ejemplo Solution.app?

En los foros de discusión de Microsoft se hacen decenas de preguntas con respecto a cosas que hacer para/con Visual FoxPro, muchas de ellas ya están explicadas en esta pequeña aplicación.

Desde los primeros días que empecé a manejar Visual FoxPro de manera profesional (es decir, ya me pagaban por eso) siempre me he metido mucho en la ayuda del producto, y una de las herramientas que me ha servido en demasía es el ejemplo Solutions, ahí ha estado desde hace años (por lo menos desde VFP5 que fue donde yo lo empecé a ver) y se ha ido actualizando con las distintas versiones que han salido.

Si no lo has visto es muy sencillo invocarlo, en la ventana de Comandos (Command Window) solo hay que ejecutar esta línea de código si quieres verlo en acción:

DO (HOME(2)+"Solution\Solution")

Lo que tendrás será un formulario que tiene decenas de ejemplos muy variados y muy útiles...


En ese formulario podrás ejecutar los ejemplos o ver su código (botones "Run Sample" y "See Code", respectivamente).

Si deseas ver el proyecto completo, puedes revisarlo con el siguiente comando:

MODIFY PROJECT (HOME(2)+"Solution\Solution")

Recomiendo ampliamente el revisarlos todos, ahí encontrará mucha información de trucos y técnicas para llevar a cabo varias tareas con Visual FoxPro.

Espero les sea de utilidad.

Espartaco Palma Martínez

24 de septiembre de 2005

Utilizar Windows Script Host para leer, escribir y eliminar claves del Registro

Artículo de la Base de Conocimientos de Microsoft que indica como utilizar Windows Script Host para leer, escribir y eliminar fácilmente claves del Registro de Windows desde Visual FoxPro.

El enlace al artículo de la MSKB (Microsoft Knowledge Base - Base de Conocimientos de Microsoft) es el siguiente:

Español (Traducción Automática):

-- Cómo utilizar el Windows Script Host para leer, escribir y eliminar claves del Registro --
https://support.microsoft.com/es-es/kb/244675

Inglés:

-- How to use the Windows Script Host to read, write, and delete registry keys --
https://support.microsoft.com/en-us/kb/244675

La información de este artículo se refiere a:

  • Microsoft Visual FoxPro 3.0
  • Microsoft Visual FoxPro 5.0
  • Microsoft Visual FoxPro 6.0 Professional Edition
  • Microsoft Visual FoxPro 7.0 Professional Edition
  • Microsoft Visual FoxPro 8.0 Professional Edition
  • Microsoft Visual FoxPro 9.0 Professional Edition

14 de septiembre de 2005

Nuevos valores para InputMask y Format en VFP 9.0

Ahora la propiedad InputMask permite los siguientes dos nuevos valores:
  • U = Permite solo caracteres alfabéticos y los convierte a mayúsculas [A..Z]
  • W = Permite solo caracteres alfabéticos y los convierte a minúsculas [a..z]
La propiedad Format permite el siguente valor en mas controles:
  • Z = Muestra el valor como "blanco" si este es "0" (cero), excepto cuando el control tiene el foco.
Los tipos de datos Date y DateTime también están soportados cuando la propiedad Format tiene el valor "Z". Cuando la fecha ó fecha y hora son vacias, no se muestran los delimitadores " / / " ó " / / : : " cuando el control no tiene el foco.

Un ejemplo para Visual FoxPro 9.0 de estas propiedades se pueden observar en el siguiente formulario:
PUBLIC goForm
goForm = CREATEOBJECT("MiForm")
goForm.SHOW(1)
RETURN

DEFINE CLASS MiForm AS FORM
AUTOCENTER = .T.
  HEIGHT = 250
  WIDTH = 324
  CAPTION = "VFP 9.0 - Nuevas configuraciones"
  NAME = "MiForm"
  ADD OBJECT Text1 AS TEXTBOX WITH ;
    HEIGHT = 23, ;
    LEFT = 192, ;
    TOP = 36, ;
    WIDTH = 120, ;
    MAXLENGTH = 10, ;
    INPUTMASK = REPLICATE("U",10), ;
    NAME = "Text1"
  ADD OBJECT Text2 AS TEXTBOX WITH ;
    HEIGHT = 23, ;
    LEFT = 192, ;
    TOP = 72, ;
    WIDTH = 120, ;
    MAXLENGTH = 10, ;
    INPUTMASK = REPLICATE("W",10), ;
    NAME = "Text2"
  ADD OBJECT Text3 AS TEXTBOX WITH ;
    HEIGHT = 23, ;
    LEFT = 192, ;
    TOP = 132, ;
    WIDTH = 120, ;
    VALUE = 0, ;
    FORMAT = "Z", ;
    NAME = "Text3"
  ADD OBJECT Text4 AS TEXTBOX WITH ;
    HEIGHT = 23, ;
    LEFT = 192, ;
    TOP = 168, ;
    WIDTH = 120, ;
    VALUE = {}, ;
    FORMAT = "Z", ;
    NAME = "Text4"
  ADD OBJECT Label1 AS LABEL WITH ;
    AUTOSIZE = .T., ;
    BACKSTYLE = 0, ;
    CAPTION = "Mayusculas solamente", ;
    HEIGHT = 17, ;
    LEFT = 12, ;
    TOP = 40, ;
    WIDTH = 129, ;
    NAME = "Label1"
  ADD OBJECT Label2 AS LABEL WITH ;
    AUTOSIZE = .T., ;
    BACKSTYLE = 0, ;
    CAPTION = "Minusculas solamente", ;
    HEIGHT = 17, ;
    LEFT = 12, ;
    TOP = 76, ;
    WIDTH = 127, ;
    NAME = "Label2"
  ADD OBJECT Label3 AS LABEL WITH ;
    AUTOSIZE = .T., ;
    BACKSTYLE = 0, ;
    CAPTION = "Blanco si es '0'", ;
    HEIGHT = 17, ;
    LEFT = 12, ;
    TOP = 136, ;
    WIDTH = 88, ;
    NAME = "Label3"
  ADD OBJECT Label4 AS LABEL WITH ;
    AUTOSIZE = .T., ;
    BACKSTYLE = 0, ;
    CAPTION = "Blanco si es fecha vacia", ;
    HEIGHT = 17, ;
    LEFT = 12, ;
    TOP = 172, ;
    WIDTH = 134, ;
    NAME = "Label4"
  ADD OBJECT Label5 AS LABEL WITH ;
    AUTOSIZE = .T., ;
    FONTBOLD = .T., ;
    BACKSTYLE = 0, ;
    CAPTION = "Propiedad InputMask = 'U' y 'W'", ;
    HEIGHT = 17, ;
    LEFT = 78, ;
    TOP = 12, ;
    WIDTH = 167, ;
    NAME = "Label5"
  ADD OBJECT Label6 AS LABEL WITH ;
    AUTOSIZE = .T., ;
    FONTBOLD = .T., ;
    BACKSTYLE = 0, ;
    CAPTION = "Propiedad Format = 'Z'", ;
    HEIGHT = 17, ;
    LEFT = 100, ;
    TOP = 108, ;
    WIDTH = 123, ;
    NAME = "Label6"
  ADD OBJECT Label7 AS LABEL WITH ;
    AUTOSIZE = .F., ;
    WORDWRAP = .T., ;
    BACKSTYLE = 0, ;
    CAPTION = "En ambos casos se muestra en blanco " + ;
    "cuando el control no tiene el foco", ;
    HEIGHT = 36, ;
    LEFT = 24, ;
    TOP = 204, ;
    WIDTH = 276, ;
    FORECOLOR = RGB(0,0,255), ;
    NAME = "Label7"
ENDDEFINE
Recordar que la diferencia entre las propiedades InputMask y Format es que con la propiedad Format se especifica un comportamiento para todo el campo de entrada, y con la propiedad InputMask se especifica que cada caracter de la máscara corresponde a cada caracter ingresado en el campo de entrada.

Luis María Guayán

12 de septiembre de 2005

Alcance en Visual FoxPro - Parte 2

Artículo original: Scoping in Visual FoxPro - Part 2
http://weblogs.foxite.com/andykramek/archive/2005/05/13/446.aspx
Autor: Andy Kramek
Traducido por: Ana María Bisbé York

... continuación de "Alcance en Visual FoxPro - Parte 1"

Como he prometido, hoy continuaré nuestra revisión de temas relativos al alcance en Visual FoxPro con una mirada a cómo el alcance afecta las propiedades (y métodos) de los objetos. Creo que será más fácil pensar en las propiedades como "variables que están limitadas por los objetos". En otras palabras, mientras existan los objetos, existirán sus propiedades. Tan pronto como sea liberado el objeto, sus propiedades (y sus valores) se librarán también. En este sentido, al menos, cabe la analogía con las variables de memoria y su alcance (público, privado y local). Sin embargo, a pesar de la primera apariencia, no se crean por igual todas las propiedades.

VFP es algo inusual entre los lenguajes orientados a objeto en que todas sus propiedades, eventos y métodos (PEMs) de sus clases se crean de forma predeterminada como PUBLIC. Es importante entender que en el contexto de un objeto, la palabra "public" se refiere a la visibilidad de una propiedad (o método) de otro objeto u otros objetos. De hecho, hablando adecuadamente, el conjunto de PEMs que expone un objeto al mundo exterior es su "Interfaz pública". En muchos lenguajes orientados a objeto las propiedades, métodos y eventos nativos no se exponen en la interfaz pública y es el desarrollador quien se encarga de definir los que desea exponer.

VFP hace justamente lo contrario.  A menos que el desarrollador decida otra cosa, todas las PEMs nativas o de usuario, se exponen como parte de la interfaz pública de un objeto y tienen alcance público.

Esto, por supuesto, es de modo general, algo muy bueno, en una herramienta de desarrollo de aplicaciones como Visual FoxPro. Imagine qué tortura sería si cada vez que desee referirse a una propiedad de un objeto como un textbox (por ejemplo: Value, Background Color, Height, Width, Font) desde cualquier otro objeto, antes, tener que definir esta propiedad como "PUBLIC" o escribir un código en algún método especial del objeto en cuestión. Esto es, sin embargo, como ocurre en muchos otros lenguajes orientados a objeto. Vea las propiedades definidas en C# (o en una interfaz COM) y va a ver que existen dos métodos relacionados con ellas - un método "PUT" para asignar un valor y un método "GET" para devolver el valor. ¡ Contraste esto con la facilidad de crear y trabajar con propiedades en VFP !

Sin embargo, hay momentos en los que no deseamos que una propiedad (o método) específicos sean visibles a los objetos externos. ¿Porqué? Puede ser porque la propiedad es esencial en trabajos internos del objeto y no desea cambiarlos salvo  condiciones específicas y controladas o debido a que ese método debe ser llamado solamente como parte de, o en conjunto, con alguna otra operación. Por esta razón VFP implementa dos palabras reservadas adicionales que se pueden emplear para definir el alcance de PEMs.
  • PROTECTED - Las PEMs con alcance PROTECTED (protegidos) son solamente accesibles por los objetos que son instanciados desde una clase que define la PEM o desde una clase que herede desde la clase que lo define. Debido a que Visual FoxPro no implementa herencia múltiple, podemos definir brevemente: "son visibles solamente por la clase que la origina y sus subclases"
  • HIDDEN - Las PEMs con alcance HIDDEN (ocultos) son accesibles solamente por el objeto que los ha instanciado, directamente de la clase que define la PEMs. En otras palabras ellos son: "visibles solamente dentro de la clase original"
Pudiera preguntarse, en este punto, qué ocurre si trata de acceder a una PEM que esté protegida u oculta. La respuesta es sencilla, obtiene un error. Pegue el siguiente código en un PRG y ejecútelo:
oTest = CREATEOBJECT('xObj')
CLEAR
?
? "La siguiente línea va a causar un error - oprima Ignorar"
? "Accede a la propiedad directamente para ver el valor: " + oTest.cProperty
?
? "Llama al método Get y lee el valor: " + oTest.GetProperty()

DEFINE CLASS xObj AS Session
  cProperty = 'Esta propiedad no es visible'' 
  PROTECTED cProperty
  
  FUNCTION GetProperty()
    RETURN This.cProperty
  ENDFUNC
ENDDEFINE
Como puede ver, al declarar la propiedad protegida obtenemos un error " No existe la propiedad <nombre>" al tratar de acceder directamente desde fuera. Sin embargo, cuando llamamos al método GetProperty() - que es público - no hay problemas. Este método está definido en la clase que define la propiedad, y entonces puede ver la propiedad, y devolver su valor incluso si quien lo llama no puede ver la propiedad directamente. Por supuesto, el método pudo hacer mucho más que sencillamente devolver un valor. Los usos frecuentes para una propiedad protegida, con un método Get() que la exponga, incluyen cálculo secuencial, o valores acumulados (ejecutando totales). Cada vez que se llama al método, genera el siguiente número (o agrega algún valor) a la propiedad y devuelve el valor nuevo. El punto es que la UNICA vía para cambiar el valor desde fuera del objeto es llamar al método y que nos permita verificar que el valor se cambie sólo cuando se suponga que cambie. (contraste esto con hacer ThisForm.Text1.Value = "xxxxx").


9 de septiembre de 2005

Alcance en Visual FoxPro - Parte 1

Artículo original: Scoping in Visual FoxPro - Part 1
http://weblogs.foxite.com/andykramek/archive/2005/05/04/421.aspx
Autor: Andy Kramek
Traducido por: Ana María Bisbé York

Lamento la demora que ha habido en esta publicación. Las cosas han estado un poco agitadas en los últimos 10 días. En cualquier caso, he aquí la siguiente serie. Esta vez voy a referirme al alcance de las variables y la semana que viene voy a continuar con las Propiedades y métodos. Espero que lo disfruten.

Alcance en Visual FoxPro - Parte 1

Uno de los aspectos que causa confusión es el alcance de propiedades, métodos y variables en Visual FoxPro. De hecho, es realmente muy sencillo; pero puede ser confuso debido a las diferencias en la sintaxis y su significado. Comencemos, esta semana, con las variables y veremos las propiedades y métodos la semana que viene.

Alcance de las variables

El alcance de las variables define cuánto tiempo existirán y qué programas, métodos, procedimientos o Funciones Definidas por el usuario (UDFs) tienen acceso a ellas. Para ver el tema del alcance veremos el término "procedimiento" que incluye programas, métodos, procedimientos y UDFs. Teniendo esto en mente, existen tres niveles de alcance.

Variables públicas (Public)

Se conocen también como Globales - de hecho se recomienda emplear g como identificador de alcance para variables públicas (la letra "p" se reserva para variables privadas "private"). Una variable pública está disponible en una aplicación entera independientemente de dónde o cuándo es creada. Existe, hasta que sea liberada con RELEASE o que la aplicación (o el VFP) se cierren. Esto significa que cualquier programa puede leer, o cambiar el valor de la variable en cualquier momento. Mientras existen situaciones muy específicas en las que la variable pública es la única solución, como regla general su uso en código de programas no es recomendado - son demasiado vulnerables a cambios inesperados, como para ser confiables.

Habiendo dicho esto, es importante comprender que todas las variables creadas en la ventana de comandos tienen alcance público - y esto es lo que produce el grito de "trabaja en la ventana de comandos; pero no en el EXE". Aunque hay una excepción. En todas las otras circunstancias una variable pública debe ser declarada explícitamente antes que le sea asignado un valor y si no lo hacemos ocurre un error. Por esta razón vemos frecuentemente PUBLIC precedido por un comando RELEASE (vea el código abajo). Al ejecutar un comando PUBLIC se crea(n) la(s) variable(s) y se inicializa(n) en falso .F.
*** Declara un par de variables públicas
RELEASE gnPubNum, gcPubStr
PUBLIC gnPubNum, gcPubStr
*** Hasta que le sea asignado un valor específico
*** las variables públicas tienen valor .F.
gnPubNum = 100
gcPubStr = "cadena de ejemplo"

Variables privadas (Private)

Una variable privada está disponible para el procedimiento que la creó y para cualquier procedimiento que sea llamado por el que la crea, o llamado en la cadena de procedimientos inicializada por el que la crea. Cuando finaliza este procedimiento, la variable se libera automáticamente. Si no ha sido especificado otro alcance, cuando se asigna un valor a una variable, Visual FoxPro la implementa como variable privada. Esto significa que todo lo que hace falta para crear una variable privada es, asignarle valor:
*** Crea las siguientes variables como privadas
pnPriNum = 100
pcPriStr = "Cadena Private"
Aunque,  hay que tener cuidado en una cosa. Si el nombre de la variable ya existe con un alcance determinado (por ejemplo ha sido declarada como Public o Private en un procedimiento padre, las asignaciones mostradas antes no van a crear variables nuevas, sencillamente van a cambiar el valor de la variable existente con ese nombre. No es siempre lo que buscamos y puede ser muy difícil encontrar el lugar que ocasiona un problema. Considere el código siguiente:
*** Ejecuta el procedimiento hijo 3 veces
FOR lnCnt = 1 TO 3
  CallChild(lnCnt * 4
)NEXT
*** Definición del procedimiento hijo
CallChild(tnCounter)
FOR lnCnt = 1 TO tncounter
  ? "Número del lazo: " + TRANSFORM(lnCnt)
NEXT
Pregúntese ahora, ¿cuántas veces se ejecutará el procedimiento hijo? La respuesta es, por supuesto, una vez ! El mismo nombre de contador de ciclo (lnCnt) se emplea en ambos procedimientos (padre e hijo), con el resultado que después de la primera llamada al procedimiento hijo el valor será 4 (es decir lnCnt = 1* 4) y se rompe el ciclo del programa padre. Este es un ejemplo bien obvio de cuán mal pueden ir las cosas con las variables privadas - suponga que en lugar de un par de líneas, el segundo empleo de lnCnt es un archivo de programa diferente ejecutado tres o cuatro niveles más abajo en la cadena de llamadas. Depurar lo que está mal puede ser un gran dolor de cabeza.

Afortunadamente, el uso del comando PRIVATE controla este tema por nosotros. Sin embargo, a diferencia del comando PUBLIC no crea automáticamente una variable nueva. En su lugar, dice a Visual FoxPro que cuando (y si) un nuevo valor es asignado a un nombre de variable específico, debe ignorar cualquier variable existente y crear una nueva, de la que es propietaria el procedimiento actual. Si se re-utiliza un nombre de variable existente (como hice en el ejemplo anterior); pero declarando PRIVATE antes de asignar un valor y luego mirar en el depurador, verá que VFP conserva la variable original; pero ahora está oculta "hidden" y se ha creado una nueva.
Nota: La sentencia PARAMETERS es equivalente a PRIVATE. Las variables definidas empleando este comando son creadas como si hubieran sido declaradas explícitamente como privadas con el mismo nombre que tienen alcance oculto hidden.
Variables locales (Local)


2 de septiembre de 2005

Autocompletar en Cuadros de Textos de VFP 9.0

Introducción

Una nueva característica de Visual FoxPro 9.0 es que el control TextBox (Cuadro de Texto) tiene la funcionalidad de Autocompletar, que nos sugiere valores de una tabla, a medida que se introducen caracteres en él. Si se selecciona un valor de la lista desplegada, el cuadro de texto se completa y se actualiza la tabla que contiene los datos almacenados.

Esta nueva característica ya fue tratada en un artículo de Jorge Mota (Guatemala) publicado en PortalFox: "Usando Autocompletar en TextBoxs de VFP 9"

En este nuevo artículo veremos como podemos hacer para que Autocompletar nos sugiera valores de una tabla de nuestra aplicación.

Las nuevas propiedades

Visual FoxPro 9.0 incorporó tres nuevas propiedades al control TextBox para esta funcionalidad: AutoComplete, AutoCompSource y AutoCompTable.

Si la propiedad AutoComplete se configura con un valor distinto de cero (el valor cero indica que no está habilitado Autocompletar), a medida que se introducen caracteres, se sugieren valores con la exhibición de una lista desplegada. Esta lista muestra los valores que concuerden con los valores previamente almacenados. Según el valor que se configure esta propiedad, la lista se ordenará "Alfabéticamente", "El mas frecuentemente usado", "El mas recientemente usado" o "Personalizado", que se ordena descendentemente por el "peso" que se le da al valor mediante un algorítmo realizado por el usuario que modifique el campo Weight (Peso) de la tabla AutoComp. Los valores permitidos para esta propiedad son 0, 1, 2, 3 y 4.

La propiedad AutoCompSource almacena el nombre clave que se utilizará para buscar en la tabla AutoComp. Si se omite esta propiedad, el nombre del control será la clave de búsqueda en la tabla. Se puede lograr que varios controles del tipo TextBox, utilicen la misma lista, configurando esta propiedad con un valor igual en todos los controles.

La propiedad AutoCompTable especifica el nombre y la ruta de la tabla que almacena los datos usados por un control TextBox para sugerir los valores. El valor por omisión de esta propiedad es (HOME(7) + "autocomp.dbf"). Si la tabla no existe, VFP la creará automáticamente.
Nuestro caso

Para nuestro caso de que Autocompletar nos sugiera los valores de una tabla de nuestra aplicación, haremos hincapié en la propiedad AutoCompTable y la configuraremos con la ruta y nombre de una tabla que crearemos y poblaremos con los datos de nuestra tabla de acuerdo a la estructura y descripción de cada campo de la tabla AutoComp que es la siguiente:

CampoTipoDescripción
Source Caracter(20) Nombre de la fuente del control TextBox.
Data Caracter(254) Dato a mostrar.
Count Entero Número de veces que el dato fue seleccionado.
Weight Entero Especifica el valor a usar cuando AutoComplete esta configurado en 4.
Created DateTime Fecha y hora de creación del dato.
Updated DateTime Fecha y hora de la última modificación del dato.
User Memo Especifica información de usuario.

La tabla AutoComp esta indexada con la siguiente clave:
INDEX ON ;
  UPPER(SOURCE + LEFT(DATA,30)) + PADL(COUNT,8) ;
  TAG DATA FOR NOT DELETED()

El ejemplo

Para nuestro ejemplo tomaremos la tabla "Customers" de la base de datos "Northwind" que viene junto a los ejemplos de Visual FoxPro 9.0. Los campos que seleccionaremos e insertaremos en la tabla son "CompanyName", "ContactName", "City" y "Country".

Primeramente invocamos al método "CrearTablaAutoComp()" que crea la tabla "MiAutoComp.dbf" en el directorio de archivos temporales de Windows y la indexa.

Luego por cada control TextBox en los cuales queremos habilitar la funcionalidad de autocompletar, invocamos al método "InsertarReg()" que inserta en la tabla "lcDbfAuto", los datos de la tabla "lcDbfOrigen" y del nombre de campo y nombre de clave que pasamos como parámetros, mediante un "INSERT INTO ... FROM SELECT ..."; y configuramos las propiedades "AutoComplete", "AutoCompSource" y "AutoCompTable".

El siguiente código genera un formulario con cuatro cuadros de textos con las propiedades configuradas para que Autocompletar nos sugiera los valores de nuestra tabla "MiAutoComp".

PUBLIC goMiForm
goMiForm = CREATEOBJECT("MiForm")
goMiForm.SHOW(1)
RETURN

DEFINE CLASS MiForm AS FORM
  AUTOCENTER = .T.
  CAPTION = "Ejemplo de Autocompletar en TextBox"
  NAME = "MiForm"
  ADD OBJECT txtCompania AS TEXTBOX WITH ;
    HEIGHT = 24, ;
    LEFT = 84, ;
    TOP = 24, ;
    WIDTH = 264, ;
    NAME = "txtCompania"
  ADD OBJECT txtContacto AS TEXTBOX WITH ;
    HEIGHT = 24, ;
    LEFT = 84, ;
    TOP = 72, ;
    WIDTH = 264, ;
    NAME = "txtContacto"
  ADD OBJECT txtCiudad AS TEXTBOX WITH ;
    HEIGHT = 24, ;
    LEFT = 84, ;
    TOP = 120, ;
    WIDTH = 264, ;
    NAME = "txtCiudad"
  ADD OBJECT txtPais AS TEXTBOX WITH ;
    HEIGHT = 24, ;
    LEFT = 84, ;
    TOP = 168, ;
    WIDTH = 264, ;
    NAME = "txtPais"
  ADD OBJECT lblCompania AS LABEL WITH ;
    AUTOSIZE = .T., ;
    BACKSTYLE = 0, ;
    CAPTION = "Compañia", ;
    LEFT = 12, ;
    TOP = 28, ;
    NAME = "lblCompania"
  ADD OBJECT lblContacto AS LABEL WITH ;
    AUTOSIZE = .T., ;
    BACKSTYLE = 0, ;
    CAPTION = "Contacto", ;
    LEFT = 12, ;
    TOP = 76, ;
    NAME = "lblContacto"
  ADD OBJECT lblCiudad AS LABEL WITH ;
    AUTOSIZE = .T., ;
    BACKSTYLE = 0, ;
    CAPTION = "Ciudad", ;
    LEFT = 12, ;
    TOP = 124, ;
    NAME = "lblCiudad"
  ADD OBJECT lblPais AS LABEL WITH ;
    AUTOSIZE = .T., ;
    BACKSTYLE = 0, ;
    CAPTION = "País", ;
    LEFT = 12, ;
    TOP = 172, ;
    NAME = "lblPais"

  PROCEDURE INIT
    LOCAL lcDbfAuto, lcDbfOrigen
    *-- Ruta y nombre de la tabla AutoCompletar
    lcDbfAuto = ADDBS(SYS(2023)) + "MiAutoComp.dbf"
    *-- Creo la tabla AutoCompompletar
    THISFORM.CrearTablaAutoComp(lcDbfAuto)

    *-- Ruta y nombre de la tabla Origen
    lcDbfOrigen = ADDBS(HOME(2))+ "NorthWind\Customers.dbf"

    *-- Inserto los registros para cada campo
    *-- y configuro las propiedades de cada TextBox
    THISFORM.InsertarReg(lcDbfOrigen,lcDbfAuto,"AC_COMPANIA","CompanyName")
    THISFORM.txtCompania.AUTOCOMPLETE = 1
    THISFORM.txtCompania.AUTOCOMPSOURCE = "AC_COMPANIA"
    THISFORM.txtCompania.AUTOCOMPTABLE = lcDbfAuto

    THISFORM.InsertarReg(lcDbfOrigen,lcDbfAuto,"AC_CONTACTO","ContactName")
    THISFORM.txtContacto.AUTOCOMPLETE = 1
    THISFORM.txtContacto.AUTOCOMPSOURCE = "AC_CONTACTO"
    THISFORM.txtContacto.AUTOCOMPTABLE = lcDbfAuto

    THISFORM.InsertarReg(lcDbfOrigen,lcDbfAuto,"AC_CIUDAD","City")
    THISFORM.txtCiudad.AUTOCOMPLETE = 1
    THISFORM.txtCiudad.AUTOCOMPSOURCE = "AC_CIUDAD"
    THISFORM.txtCiudad.AUTOCOMPTABLE = lcDbfAuto

    THISFORM.InsertarReg(lcDbfOrigen, lcDbfAuto,"AC_PAIS","Country")
    THISFORM.txtPais.AUTOCOMPLETE = 1
    THISFORM.txtPais.AUTOCOMPSOURCE = "AC_PAIS"
    THISFORM.txtPais.AUTOCOMPTABLE = lcDbfAuto
  ENDPROC

  PROCEDURE CrearTablaAutoComp(tcDbfAuto)
    *-- Crea mi tabla Autocompletar
    SET SAFETY OFF
    CREATE TABLE (tcDbfAuto) FREE ;
      (SOURCE C(20), DATA C(254), COUNT I, Weight I, ;
      Created T, UPDATED T, USER M)
    *-- Indexo la tabla
    INDEX ON UPPER(SOURCE + LEFT(DATA,30)) + PADL(COUNT,8) ;
      TAG DATA FOR NOT DELETED()
    *-- Cierro tabla
    USE IN SELECT(JUSTSTEM(tcDbfAuto))
  ENDPROC

  PROCEDURE InsertarReg(tcDbfOrigen, tcDbfAuto, tcSource, tcData)
    *-- Inserto los registros a la tabla Autocompletar
    INSERT INTO (tcDbfAuto) ;
      SELECT DISTINCT UPPER(tcSource) AS SOURCE, ;
      EVALUATE(tcData) AS DATA, 0 AS COUNT, 0 AS Weight, ;
      DATETIME() AS Created, DATETIME() AS UPDATED, .F. AS USER ;
      FROM (tcDbfOrigen)
    *-- Cierro tablas
    USE IN SELECT(JUSTSTEM(tcDbfAuto))
    USE IN SELECT(JUSTSTEM(tcDbfOrigen))
  ENDPROC
ENDDEFINE

Si ejecutamos el código e introducimos valores en los cuadros de textos, se mostraran las listas con los valores sugeridos como se observa en la siguiente imagen:



El número de items mostrados en la lista desplegable por defecto es 15. Se puede configurar este número con la siguiente función:

SYS(2910,nValor) && nValor = [5..200]

Conclusiones

Este ejemplo es solo para mostrar como podemos sugerir valores en un cuadro de texto en Visual FoxPro 9.0. Queda en nosotros saber si conviene o no utilizar esta técnica con el control TextBox o elegir otro tipo de control como un ComboBox o ListBox.

Hasta la próxima.

Luis María Guayán

19 de agosto de 2005

VFP 9.0 y API StarOffice 7

Bueno despues de mucho buscar y tanto o mas de estar adaptando codigo, por fin he podido concretar un par de ejemplos a fin de dejar como referencia para aquellos que novatos como yo :-)

Estos ejemplos estan probados y aunque Fox no me marca ningun error y el programa hace todo lo que quiero, se que pudieran surgir algunos errores que no he notado. Como les comento estos ejemplos corren usando VFP9 version en Ingles y StarOffice 7 en Español para Windows.

Dado que estos son los unicos recursos con los que cuento, no he podido hacer mas pruebas; asi que cualquier correccion y comentario lo agradeceria ampliamente.

Por ultimo, el codigo que se muestra, contiene los comandos MAS BASICOS tanto para el procesador de Textos como para la Hoja de Calculo, excepto el insertado de imagenes, si alguien llegase a desarrollar tal codigo creo que seria bueno que lo anotase aqui a fin de darle una mayor continuidad.

De antemano agradesco la información obtenida de entre otras fuentes a:

PortalFox, FoxPress y OpenOffice.org
*###################################*
*                                   *
**** EJEMPLO 1 STAROFFICE WRITER ****
*                                   *
*###################################*

LOCAL ARRAY laPropertyValue[1]
LOCAL loManager, loDesktop, loDocument, loCursor, PaginaPre, EstilosPagina, FormaObjetoGrafico

loManager = CREATEOBJECT( "com.sun.star.ServiceManager" )
loDesktop = loManager.createInstance( "com.sun.star.frame.Desktop" )
comarray( loDesktop, 10 )

loReflection = loManager.createInstance("com.sun.star.reflection.CoreReflection" )
comarray( loReflection, 10 )
 
laPropertyValue[1] = createStruct( @loReflection,"com.sun.star.beans.PropertyValue" )
laPropertyValue[1].NAME = "ReadOnly"
laPropertyValue[1].VALUE = .F.
loDocument = loDesktop.LoadComponentFromUrl( "STAROFFICE.factory:swriter","_blank", 0, @laPropertyValue )
comarray( loDocument, 10 )

loCursor= loDocument.TEXT.CreateTextCursor()
loDocument.text.insertString(loCursor, "Esta es la primera linea del texto.", .f.)
loDocument.text.insertControlCharacter( loCursor, 0, .f.)
loCursor.text.insertString(loCursor, "Esta es la segunda linea", .f.)
loDocument.text.insertControlCharacter( loCursor, 0, .f.)

Tabla= loDocument.createInstance( "com.sun.star.text.TextTable")
Tabla.initialize(4, 4)
loCursor.text.insertTextContent(loCursor, Tabla, .f.)

objRows= Tabla.getRows
ColumnaT= objRows.getByIndex( 0)
Tabla.setPropertyValue("BackTransparent", .f.)
Tabla.setPropertyValue("BackColor", 13421823)

ColumnaT.setPropertyValue( "BackTransparent", .f.)
ColumnaT.setPropertyValue( "BackColor", 6710932)

insertIntoCell ("A1","FirstColumn", Tabla )
insertIntoCell ("B1","SecondColumn", Tabla )
insertIntoCell ("C1","ThirdColumn", Tabla )
insertIntoCell ("D1","SUM", Tabla )

var1=2
var2=48
var3=4

Tabla.getCellByName("A2").setValue(var1)
Tabla.getCellByName("B2").setValue(var2)
Tabla.getCellByName("C2").setValue(var3)
Tabla.getCellByName("D2").setFormula ((var1*var2)/var3)
Tabla.getCellByName("A3").setValue(21.5)
Tabla.getCellByName("B3").setValue(615.3)
Tabla.getCellByName("C3").setValue(-315.7)
Tabla.getCellByName("D3").setFormula ("sum ")
Tabla.getCellByName("A4").setValue (121.5)
Tabla.getCellByName("B4").setValue (-615.3)
Tabla.getCellByName("C4").setValue (415.7)
Tabla.getCellByName("D4").setPropertyValue( "BackColor", 16700000)
Tabla.getCellByName("D4").setFormula("sum")

loCursor.setPropertyValue ("CharColor", 255)
loCursor.text.insertControlCharacter (loCursor, 0 , .f.)
loDocument.text.insertString (loCursor, " Texto de Color" , .f.)
loCursor.setPropertyValue ("CharShadowed", .t.)
loDocument.text.insertString (loCursor, " - texto con sombra" , .f.)
loCursor.setPropertyValue ("CharShadowed", .f.)
loDocument.text.insertControlCharacter( loCursor, 0, .f.)

loDocumentFrame= loDocument.createInstance("com.sun.star.text.TextFrame")

objSize= createStruct(@loReflection, "com.sun.star.awt.Size")
objSize.Width= 15000
objSize.Height= 400
loDocumentFrame.setSize( objSize)
loDocumentFrame.setPropertyValue("AnchorType", 1)
loDocument.text.insertTextContent(loCursor, loDocumentFrame, .f.)

objFrameText= loDocumentFrame.getText
objFrameTextCursor= objFrameText.createTextCursor
objFrameTextCursor.setPropertyValue ("CharHeight", 10)
objFrameText.insertString(objFrameTextCursor, "Primera linea dentro del marco.", .f.)
objFrameText.insertControlCharacter(objFrameTextCursor, 0, .f.)
objFrameText.insertString(objFrameTextCursor, "Otra linea dentro del marco.", .f.)
loDocument.text.insertControlCharacter( loCursor, 0, .f.)
 
loCursor.setPropertyValue ("CharColor", 65536)
loCursor.setPropertyValue ("CharFontName", 'Arial')
loDocument.text.insertString (loCursor, "Cambio de letra a Arial", .f.)
loDocument.text.insertControlCharacter( loCursor, 0, .f.)
loDocument.text.insertControlCharacter( loCursor, 0, .f.)

loCursor.setPropertyValue ("CharWeight", 150)
loCursor.text.insertString(loCursor, "Negritas ", .f.)
loCursor.setPropertyValue ("CharWeight", 100)
loCursor.setPropertyValue ("CharPosture", 50)
loCursor.text.insertString(loCursor, "Cursiva ", .f.)
loCursor.setPropertyValue ("CharPosture", 0)
loDocument.text.insertControlCharacter( loCursor, 0, .f.)
loDocument.text.insertControlCharacter( loCursor, 0, .f.)

loDocument.text.insertString (loCursor, "Texto Normal" ,.f.)
loCursor.setPropertyValue ("CharEscapement", 65)
loCursor.setPropertyValue ("CharHeight", 5)
loDocument.text.insertString (loCursor, " Superíndice" ,.f.)
loCursor.setPropertyValue ("CharEscapement", 0)
loCursor.setPropertyValue ("CharHeight", 10)
loDocument.text.insertString (loCursor, "  Texto Normal" ,.f.)
loCursor.setPropertyValue ("CharHeight", 5)
loCursor.setPropertyValue ("CharEscapement", -35)
loDocument.text.insertString (loCursor, " Subíndice" ,.f.)
loCursor.setPropertyValue ("CharEscapement", 0)
loCursor.setPropertyValue ("CharHeight", 10)
loDocument.text.insertControlCharacter( loCursor, 0, .f.)
loDocument.text.insertControlCharacter( loCursor, 0, .f.)

loCursor.setPropertyValue ("CharUnderline", 1)
loDocument.text.insertString (loCursor, "Subrayado" ,.f.)
loCursor.setPropertyValue ("CharUnderline", 0)
loCursor.setPropertyValue ("CharFlash", .t.)
loDocument.text.insertString (loCursor, " Parpadeando" ,.f.)
loCursor.setPropertyValue ("CharFlash", .f.)
loDocument.text.insertControlCharacter( loCursor, 0, .f.)
loDocument.text.insertControlCharacter( loCursor, 0, .f.)

loCursor.setPropertyValue ("CharHeight", 8)
loCursor.setPropertyValue ("CharFontName", 'Arial Black')
loCursor.setPropertyValue ("CharColor", 14500)
loDocument.text.insertString (loCursor, "Color 1,4500" ,.f.)
loCursor.setPropertyValue ("CharColor", 18500)
loDocument.text.insertString (loCursor, CHR(9)+"Color 18,500" ,.f.)
loCursor.setPropertyValue ("CharColor", 20500)
loDocument.text.insertString (loCursor, CHR(9)+"Color 20,500" ,.f.)
loDocument.text.insertControlCharacter( loCursor, 0, .f.)

loCursor.setPropertyValue ("ParaAdjust", 3)
loDocument.text.insertString (loCursor, "Color 13,459,998 AAAAAAAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB" ;
+" CCCCCC DDDDDD EEEEEEE F F F FFFF GGGGGGG HHHH" ,.f.)
loDocument.text.insertControlCharacter( loCursor, 0, .f.)

loCursor.setPropertyValue ("ParaAdjust", 0)
loDocument.text.insertString (loCursor, "Color 16,711,680 AAAAAAAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB" ;
+" CCCCCC DDDDDD EEEEEEE F F F FFFF GGGGGGG HHHH" ,.f.)
loDocument.text.insertControlCharacter( loCursor, 0, .f.)

loCursor.setPropertyValue ("ParaAdjust", 2)
loCursor.setPropertyValue ("CharColor", 9603696)
loDocument.text.insertString (loCursor, "Color 9,603,696 AAAAAAAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB" ;
+" CCCCCC DDDDDD EEEEEEE F F F FFFF GGGGGGG HHHH" ,.f.)
loDocument.text.insertControlCharacter( loCursor, 0, .f.)

loCursor.setPropertyValue ("ParaAdjust", 1)
loCursor.setPropertyValue ("CharColor", 8355711)
loDocument.text.insertString (loCursor, "Color 8,355,711 AAAAAAAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB" ;
+" CCCCCC DDDDDD EEEEEEE F F F FFFF GGGGGGG HHHH" ,.f.)
loDocument.text.insertControlCharacter( loCursor, 0, .f.)

loCursor.setPropertyValue ("CharColor", RGB(0,0,255))
loDocument.text.insertString (loCursor, "Color 8,355,711 AAAAAAAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBB" ;
+" CCCCCC DDDDDD EEEEEEE F F F FFFF GGGGGGG HHHH " ,.f.)
loDocument.text.insertControlCharacter( loCursor, 0, .f.)

or_url="file:///c:/test.sxw"
laPropertyValue[1].NAME= "Overwrite"
laPropertyValue[1].VALUE= .T.
loDocument.storeAsURL( or_url, @laPropertyValue )