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