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

3 comentarios :

  1. Buen articulo José Augusto Cavalcantí, mi pregunta donde se puede sacar información de como utilizar mas objetos como "outlook.account" o para tratar otras bandejas que no sea la de entrada como la de enviados, me refiero a un manual con todos los objetos y para lo que sirven.

    ResponderBorrar
    Respuestas
    1. Este enlace pordria ayudarte... jejeje, mil años dspues...

      http://carloszuritha.blogspot.com/2011/04/manipular-mails-de-outlook-en-visual.html

      Borrar
  2. loInbox.Folders.Count me sale en cero ... no puedo ver el contenido del Inbox.

    ResponderBorrar

Los comentarios son moderados, por lo que pueden demorar varias horas para su publicación.