6 de septiembre de 2009

VFP y la Automatización de Outlook

Introducción

Me motivó a escribir este artículo el haber visto desde hace algún tiempo (ay, desde hace BASTANTE tiempo), centenas (o millares) de mensajes en el Grupo FoxBrasil sobre el tema "¿Cómo envío un email desde VFP?".

Quienes me conocen del Grupo saben que adoro estudiar, investigar, y principalmente usar, OLE Automation. Pero veamos, ¿qué significa esta sigla? OLE son las iniciales de Object Linking and Embedding. Formidable, ¿y esto qué es?, dirá usted. OLE Automation es la posibilidad que determinadas aplicaciones tienen de exponer su funcionamiento (PEMs: propiedades, eventos y métodos) a otra aplicación cualquiera. Es como si pudiésemos, por ejemplo, empaquetar Microsoft Word como un objeto e incluirlo dentro de nuestra aplicación VFP (o VB, o VBA, o JavaScript, etc).

Digamos que en un sistema para controlar la suscripción a una revista, es necesario recordar al responsable del sector de cobranzas que un día determinado debe verificar que las cuotas de los suscriptores han sido pagadas. Ese día no es fijo para todos los suscriptores, sino que es determinado por la fecha de suscripción. Así que deberíamos montar una agenda dentro de nuestro sistema para que esta persona sea avisada, ¿verdad? No necesariamente, ya que esta persona utiliza Outlook 2000, y Outlook 2000 es un servidor OLE; o sea que podemos, dentro de VFP, manipular el Outlook 2000 y ejecutar una determinada tarea, tal como abrir una entrada en el calendario, introducir los datos necesarios y guardarlos. P> ¿Es fácil? No, no lo es hasta que tenga en sus manos la documentación del servidor OLE (el Modelo de Objetos) y (en muchas oportunidades) ésta es la tarea más ardua.

Pretendo, de acuerdo a mi tiempo libre y a la paciencia de mi familia, escribir una serie de artículos sobre el Modelo de Objetos de Microsoft Outlook, Internet Explorer y Lotus Notes, aunque como comencé a estudiar éste último hace poco, no se si será posible. Por motivos didácticos, comenzaremos esta serie de artículos por Outlook, y me gustaría aclarar que esta serie está basada en otra, escrita por Andrew Ross MacNeill para la revista FoxPro Advisor.

Microsoft Outlook

Outlook es una herramienta de productividad que incluye Calendario, Correo electrónico (e-mail), Contactos y Tareas (como ítems principales). Utilizo la versión más reciente, Outlook 2000, así que todas las referencias serán en relación a esta versión. El Modelo de Objetos de Outlook 2000 puede ser consultado en el archivo VBAOUTL9.CHM. Si no lo tiene instalado, envíeme un mensaje y se lo haré llegar. Como se puede ver en la Figura 1, el objeto Application es el primero de la jerarquía. Ese objeto no sirve para mucho, pero es la puerta de entrada. Aunque Outlook sea usado para una serie de cosas, es primordialmente un paquete de e-mail. Por esta razón, utiliza MAPI, Messaging Application Programming Interface.


Figura 1 - Modelo de Objetos de Microsoft Outlook 2000.

Al abrir Outlook, éste crea una referencia a un objeto MAPI NameSpace. Ese objeto almacena referencias a la ubicación de las Carpetas, ítems y configuraciones usadas por Outlook. ¿Saltamos al interior? Vamos, echemos una mirada a este código:

LOCAL loApplication, loNameSpace, loContacts, loInbox, lcMessage

loApplication = CREATEOBJECT("Outlook.Application")
loNameSpace   = loApplication.GetNameSpace("MAPI")

loContacts    = loNameSpace.GetDefaultFolder(10)
loInbox       = loNameSpace.GetDefaultFolder(6)

lcMessage     = "Nombre del Folder 'Contacts': " + CHR(9) + loContacts.Name + CHR(13) + CHR(10)+;
                "Nombre del Folder 'Inbox': "    + CHR(9) + loInbox.Name

MSGSVC(lcMessage)

RELEASE ALL

RETURN

Primero llamamos a Outlook y creamos el objeto NameSpace (siempre comenzamos así), después, usamos el método GetDefaultFolder para obtener una referencia (otro objeto) a algunos de los Folders (carpetas) más usados y, finalmente, mostramos sus nombres. La Tabla 1 muestra la lista de parámetros posibles para el método GetDefaultFolder.

Parámetro Folder
3 Deleted Items (Elementos eliminados)
4 OutBox (Bandeja de salida)
5 Sent Items (Elementos enviados)
6 Inbox (Bandeja de entrada)
9 Calendar (Calendario)
10 Contacts (Contactos)
11 Journal (Diario)
12 Notes (Notas)
13 Tasks (Tareas)
16 Drafts (Borrador)

Esta estrategia funciona bien con los Folders "padre", pero ¿si deseamos saber el contenido de la Bandeja de entrada? Para eso vamos a usar la propiedad Folders que todo objeto Folder posee. Vea el siguiente ejemplo:

LOCAL loApplication, loNameSpace, loInbox, lnContador, loFolder

loApplication = CREATEOBJECT("Outlook.Application")
loNameSpace   = loApplication.GetNameSpace("MAPI")

loInbox       = loNameSpace.GetDefaultFolder(6)

FOR lnContador = 1 TO loInbox.Folders.Count

    loFolder = loInbox.Folders(lnContador)
 
    MSGSVC("Folder: " + loFolder.Name)

ENDFOR

RELEASE ALL

RETURN

Primero seleccionamos la Bandeja de entrada, verificamos el número de Folders contenidos y mostramos sus nombres. Es bueno recordar que podemos hacer referencia a un Folder por su número (Folders(4)) o por su nombre (Folders("Bandeja de entrada")).

Cada Folder tiene una colección de ítems. Use la propiedad Count para saber el número de ítems. Así podemos modificar ligeramente nuestro programa para que también muestre el número de ítems contenidos en cada Folder:

LOCAL loApplication, loNameSpace, loInbox, lnContador, loFolder, lcMensagem

loApplication = CREATEOBJECT("Outlook.Application")
loNameSpace   = loApplication.GetNameSpace("MAPI")

loInbox       = loNameSpace.GetDefaultFolder(6)

FOR lnContador = 1 TO loInbox.Folders.Count

    loFolder   = loInbox.Folders(lnContador)
 
    lcMensagem = "Folder: " + CHR(9) + loFolder.Name + CHR(13) + CHR(10) +;
                 "Items: "  + CHR(9) + STR(loFolder.Items.Count)
 
    MSGSVC(lcMensagem)
 
ENDFOR

RELEASE ALL

RETURN

Trabajando con Contactos

El ítem Contactos (ContactItem en nuestro Modelo de Objetos) almacena diversas informaciones sobre un determinado Contacto. Para cada ítem, es posible almacenar 3 direcciones, 3 direcciones de mail, 19 números de Fax, teléfono, etc, y muchos otros datos. Aún así, si estos campos no fuesen suficientes, podemos crear otros definidos por el usuario (propiedad UserProperties). Los campos definidos por el usuarios son almacenados en cada ítem individualmente, o sea que un determinado registro puede tener un campo que los otros no posean. De esta manera, para recuperar algunos datos sobre nuestros Contactos tendríamos:

LOCAL loApplication, loNameSpace, loContacts, lnContador, loContact, lcMensagem

loApplication = CREATEOBJECT("Outlook.Application")
loNameSpace   = loApplication.GetNameSpace("MAPI")

loContacts    = loNameSpace.GetDefaultFolder(10)

FOR lnContador = 1 TO loContacts.Items.Count

    loContact  = loContacts.Items(lnContador)
 
    lcMensagem = "Contacto: "  + CHR(9) + STR(lnContador)     + CHR(13) + CHR(10) +;
                 "Nombre: "    + CHR(9) + loContact.FirstName + CHR(13) + CHR(10) +;
                 "Apellido: "  + CHR(9) + loContact.LastName  + CHR(13) + CHR(10) +;
                 "Empresa: "   + CHR(9) + loContact.CompanyName

    MSGSVC(lcMensagem)

ENDFOR

RELEASE ALL

RETURN

Agregando y modificando Contactos

Para agregar un nuevo Contacto usamos el método Add y guardamos los cambios con el método Save. Haríamos:

LOCAL loApplication, loNameSpace, loContacts, loNewContact

loApplication = CREATEOBJECT("Outlook.Application")
loNameSpace   = loApplication.GetNameSpace("MAPI")

loContacts    = loNameSpace.GetDefaultFolder(10)

loNewContact  = loContacts.Items.Add()

loNewContact.FirstName   = "Filippo"
loNewContact.LastName    = "Cavalcanti"
loNewContact.FullName    = "Filippo Cavalcanti"
loNewContact.CompanyName = "Global Connection"

loNewContact.Save()   
 
RELEASE ALL

RETURN

Como podrá observar, mi hijo forma parte ahora de su archivo de Contactos. Para eliminarlo, use el método Delete.

Navegando, Ordenando y Buscando Datos

El Modelo de Objetos de Outlook provee métodos para facilitar la navegación dentro de un folder. Con el primer método, contamos el número de ítems dentro de un determinado folder y después, vamos pasando de a uno hasta el último:

LOCAL loApplication, loNameSpace, loContacts, loNewContact

loApplication = CREATEOBJECT("Outlook.Application")
loNameSpace   = loApplication.GetNameSpace("MAPI")

loContacts    = loNameSpace.GetDefaultFolder(10)

loItens       = loContacts.Items

FOR lnContador = 1 TO loItens.Count

    lcMensagem = "Nombre:   " + CHR(9) + loItens.Item(lnContador).FirstName + CHR(13) + CHR(10) +;
                 "Apellido: " + CHR(9) + loItens.Item(lnContador).LastName  + CHR(13) + CHR(10) +;
                 "Empresa:  " + CHR(9) + loItens.Item(lnContador).CompanyName
                 
    MSGSVC(lcMensagem)

ENDFOR

RELEASE ALL

RETURN

Podemos incluir el método Sort para ordenar los ítems por el campo especificado en el primer parámetro. El segundo parámetro indica si deseamos ordenar en orden creciente (.F.) o decreciente (.T.). Así que tendríamos:

LOCAL loApplication, loNameSpace, loContacts, loNewContact

loApplication = CREATEOBJECT("Outlook.Application")
loNameSpace   = loApplication.GetNameSpace("MAPI")

loContacts    = loNameSpace.GetDefaultFolder(10)

loItens       = loContacts.Items

loItens.Sort("[CompanyName]", .T.)

FOR lnContador = 1 TO loItens.Count

    lcMensagem = "Nombre:   " + CHR(9) + loItens.Item(lnContador).FirstName + CHR(13) + CHR(10) +;
                 "Apellido: " + CHR(9) + loItens.Item(lnContador).LastName  + CHR(13) + CHR(10) +;
                 "Empresa:  " + CHR(9) + loItens.Item(lnContador).CompanyName
                 
    MSGSVC(lcMensagem)

ENDFOR

RELEASE ALL

RETURN

Como segundo ejemplo, usamos los métodos GetFirst y GetLast, que devuelven el primer y último ítem de un Folder, respectivamente. Después de posicionados, usamos los métodos GetPrevious y GetNext para movernos arriba y abajo por la lista de un Folder.

Para localizarnos en un ítem determinado, usamos el método Find. El criterio de selección es pasado como único parámetro. Así para listar los contactos cuyo nombre empiecen con la letra "J", tendríamos:

LOCAL loApplication, loNameSpace, loContacts, loItens, loItem, lcMensagem

loApplication = CREATEOBJECT("Outlook.Application")
loNameSpace   = loApplication.GetNameSpace("MAPI")

loContacts    = loNameSpace.GetDefaultFolder(10)

loItens       = loContacts.Items

loItens.Sort("[FirstName]", .F.)

loItem        = loItens.Find("[FirstName]>='J'")

DO WHILE .T.

    IF LEFT(loItem.FirstName, 1) <> "J"
 
        EXIT
  
    ENDIF

    lcMensagem = "Nombre:   " + CHR(9) + loItem.FirstName + CHR(13) + CHR(10) +;
                 "Apellido: " + CHR(9) + loItem.LastName  + CHR(13) + CHR(10) +;
                 "Empresa:  " + CHR(9) + loItem.CompanyName
                 
    MSGSVC(lcMensagem)
 
    loItem     = loItens.FindNext()

ENDDO

RELEASE ALL

RETURN

Conclusión

Este artículo es el primero de una serie sobre OLE Automation y más cosas interesantes están por venir. Usando Outlook para almacenar información de Contactos es posible economizar programas para entrada de datos y eliminar redundancias. En el próximo artículo echaremos una mirada a otras dos áreas de Microsoft Outlook: Tareas y Calendario.


  • Descargue el código fuente AQUI (4 Kb).


José Augusto Cavalcanti

5 de septiembre de 2009

Abrir, Modificar, Guardar e Imprimir archivos .doc usando OpenOffice Writer

El programa utiliza un documento (.odt o .doc) que se usa como modelo en el que se insertan unas etiquetas, que luego son reemplazadas por los datos que provienen de una base de datos. Es la misma idea que usa Word en el Combinar correspondencia ...
local array laNoArgs[1]
local loSManager, loSDesktop, loStarDoc, loReflection, loPropertyValue, loOpenDoc, loCursor, loFandR

loSManager = createobject( "Com.Sun.Star.ServiceManager.1" )

loSDesktop = loSManager.createInstance( "com.sun.star.frame.Desktop" )
comarray( loSDesktop, 10 )

loReflection = loSManager.createInstance( "com.sun.star.reflection.CoreReflection" )
comarray( loReflection, 10 )

loPropertyValue = THISFORM.createStruct( @loReflection, "com.sun.star.beans.PropertyValue" )

laNoArgs[1] = loPropertyValue
laNoArgs[1].name = "ReadOnly"
laNoArgs[1].value = .F.

* crea un archivo nuevo ...
* url = "private:factory/swriter"

* Datos que vienen de la base de datos ...

lcTmp = "nombre del origen de datos"

lcNro_infor = PADL(&lcTmp..nro_infor, 8, '0')
lcDetalle   = &lcTmp..detalle
lcFecha     = DTOC(&lcTmp..fecha)

* Puede usar archivos en los 2 formatos: .odt y .doc
lcArchivoOrigen  = "C:/temp/modelo1.odt"
lcArchivoDestino = "C:/temp/eco" + lcNro_infor + " - " + ALLTRIM(lcDetalle) + ".odt"
*                   c:\temp\eco00112638 - 53565 diaz de rodriguez claudia.odt

lcArchivoOrigen  = "C:/temp/modelo1.doc"
lcArchivoDestino = "C:/temp/eco" + lcNro_infor + " - " + ALLTRIM(lcDetalle) + ".doc"
*                   c:\temp\eco00112638 - 53565 diaz de rodriguez claudia.doc

COPY FILE (lcArchivoOrigen) TO (lcArchivoDestino)

url = "file:///" + lcArchivoDestino

loOpenDoc = loSDesktop.LoadComponentFromUrl(url, "_blank", 0, @laNoargs)

* escribir texto en el documento ...
loCursor = loOpenDoc.text.CreateTextCursor()
loOpenDoc.text.InsertString(loCursor, "HELLO FROM VFP", .f. )

* Objeto para buscar las Marcas en el Documento
* si las marcas son encontradas, son remplazadas
* si alguna marca no existiera, no hay mayor problema, simplemente no se remplaza

loFandR = loOpenDoc.createReplaceDescriptor
loFandR.searchRegularExpression = .T.

loFandR.setSearchString("«nro_infor»")
loFandR.setReplaceString(lcNro_infor)
loOpenDoc.ReplaceAll(loFandR)

loFandR.setSearchString("«fecha»")
loFandR.setReplaceString(lcFecha)
loOpenDoc.ReplaceAll(loFandR)

loFandR.setSearchString("«detalle»")
loFandR.setReplaceString(lcDetalle)
loOpenDoc.ReplaceAll(loFandR)

* imprime el documento
* loOpenDoc.printer()

* graba el documento ...
* loOpenDoc.store()

* grabar con otro nombre ...
* Url = "file:///C:/temp/test3.odt"
* loStarDoc.storeAsURL(URL, @laNoargs)

RETURN

*--------- CreateStruct -----------*
PARAMETERS toReflection, tcTypeName

 local loPropertyValue, loTemp

 loPropertyValue = createobject( "relation" )

 toReflection.forName( tcTypeName ).createobject( @loPropertyValue )
     
return ( loPropertyValue )
El CreateStruct, yo lo uso como un metodo del formulario ...

Marcelo ARDUSSO
Rafaela, Santa Fe. Argentina

* http://wiki.services.openoffice.org/wiki/Documentation/BASIC_Guide/StarDesktop
* http://user.services.openoffice.org/es/forum/viewtopic.php?f=50&t=1306

* vb_oo2.zip <- ejemplo en Visual Basic descargado de La Web del Programador
* http://www.lawebdelprogramador.com

El siguiente es el archivo Modelo 1

Modelo 1
----------------------------------------------
Protocolo Nº «nro_infor»
Fecha «fecha»

Paciente: «detalle»

Estimado/a «detalle»

Esta es una prueba para generar informes en 
WRITER desde un programa de Visual Foxpro 9.0

Sin otro particular lo saludamos atte.

powered by: Visual Foxpro 9.0