Using BindEvents to Get Feedback from Outlook 2000
This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
Using BindEvents to Get Feedback from Outlook 2000
In this article, Andrew Coates presents a technique for getting feedback from Microsoft Outlook 2000 about things users have done in their e-mail client.
One of the great things about the Microsoft Office suite of programs is how easy it is to automate tasks such as creating e-mails, printing documents, and so forth from other applications using OLE Automation. I've used this technique extensively in more than one application, for example to let users create e-mails, appointments, tasks, and notes directly into their Outlook folders. However, until the release of the VFPCOM add-in, VFP programmers were only getting half of the benefit. We could instigate actions by setting parameters and calling methods, but we couldn't respond to events raised by the server applications.
Binding the hand that feeds it
The VFPCOM utility (downloadable from http://msdn.microsoft.com/vfoxpro/downloads/updates.asp) provides programmatic access to the events raised by OLE Automation servers (it also provides a method of populating and reading ADO record sets, but that can be left to another article).
For binding to the events of an OLE Automation server, the important methods of the VFPCOM utility are ExportEvents(), BindEvents(), and UnBindEvents(). ExportEvents() creates a non-visual template class with method names corresponding to the events of the OLE Automation Server. BindEvents() creates a link between an object instance based on the template class and the OLE Automation Server. UnBindEvents releases the link.
**A VFP class with method names corresponding to the events exposed by OLE Automation Server is required so the developer has somewhere to place the code that will execute when an Automation Server event fires. While it's possible to create such a class manually, VFPCOM provides a method that automatically generates a non-visual class. To generate the template, pass ExportEvents an object reference to the applicable OLE Automation Server and a file name. For example, if you've got Outlook 2000 installed, running the code shown in Listing 1 will produce the template in Listing 2. I'll discuss what goes into the placeholders in the code a bit further on (all of the code in this article is available in the accompanying Download file).
Listing 1. Creating a bindable template.
#DEFINE VFPCOM_CLSID "VFPCOM.COMUTIL" #DEFINE OUTLOOK_CLSID "OUTLOOK.APPLICATION" local loVFPCOM, loOutlook loVFPCOM = create(VFPCOM_CLSID) loOutlook = create(OUTLOOK_CLSID) loVFPCOM.ExportEvents(loOutlook, ; 'Outlook_Events.prg')
Listing 2. The result from running the code in Listing 1—a bindable template for Outlook.
DEFINE CLASS ApplicationEvents AS custom PROCEDURE ItemSend(Item,Cancel) * Add user code here ENDPROC PROCEDURE NewMail * Add user code here ENDPROC PROCEDURE OptionsPagesAdd(Pages) * Add user code here ENDPROC PROCEDURE Quit * Add user code here ENDPROC PROCEDURE Reminder(Item) * Add user code here ENDPROC PROCEDURE Startup * Add user code here ENDPROC ENDDEFINE
The next step in the process is to add some code to the methods that respond to the firing of the OLE Automation Server's events. In Listing 3, I've simply displayed a message box for each event that tells the user what's happening (Okay, I'll get a little fancier as we go along—be patient <g>). Note that I've also renamed the class. ExportEvents() guesses at a good name to call the class, but it doesn't guess very well.
Listing 3. Code that will respond to Outlook events.
DEFINE CLASS OutlookApplicationEvents AS custom PROCEDURE ItemSend(Item,Cancel) MessageBox('User Sent an Item') ENDPROC PROCEDURE NewMail MessageBox('New Mail Has Arrived') ENDPROC PROCEDURE OptionsPagesAdd(Pages) MessageBox('Options Page Added') ENDPROC PROCEDURE Quit MessageBox('Outlook Shut Down') ENDPROC PROCEDURE Reminder(Item) MessageBox('Reminder Fired') ENDPROC PROCEDURE Startup MessageBox('Outlook Started') ENDPROC ENDDEFINE
**The next thing to do is to link the VFP code with the OLE Automation Server so that an event firing in the Automation Server runs the code in the VFP class. To do this, instantiate the OLE Automation Server and an instance of the VFP class, and then link them with the VFPCOM method BindEvents(). The code in Listing 4 demonstrates this.
Listing 4. Linking the VFP code to Outlook's events.
public goVFPCOM, goOutlook, goLink goVFPCOM = create(VFPCOM_CLSID) goOutlook = create(OUTLOOK_CLSID) goLink = create('OutlookApplicationEvents') goVFPCOM.BindEvents(goOutlook, goLink)
Now, each time Outlook fires one of the events listed in the OutlookApplicationEvents class, the corresponding VFP method code will be executed too. In this case, the VFP code will display a Message Box and that's about it.
Note that I've declared the three variables to be public. This is necessary so that they stay in scope for as long as you need the binding. The documentation warns of dire consequences if the VFPCOM object is released while the BindEvents() is still in force. There are alternatives to public variables. For example, you can assign the object references to properties of an application object or to a form. It doesn't matter, as long as the three objects remain in scope for as long as the link between the Automation Server and the VFP event sink is required. To sever the link between the Automation Server and the VFP object, release the object reference to either or both of the linked objects, or call the VFPCOM object's UnBindEvents() method as shown in Listing 5.
Listing 5. Severing the link between the Outlook application and the VFP event sink.
goVFPCOM.UnBindEvents(goOutlook, goLink) release goVFPCOM, goOutlook, goLink
Code example—Binding to Outlook
If you're as excited as I was when I first found out about this functionality, you'll be itching to see it used in reality. To demonstrate just a little of the potential, I've built a small form that allows the user to send an e-mail using their Outlook2000 interface, which logs exactly what was sent and to whom in a table. An approach like this means that no matter who sends e-mail, it can be logged in a central place. Your client's correspondence with customers is consolidated no matter how many of their reps might deal with the customer. The form is included in the accompanying Download file.To run the sample:
- Have Outlook 2000 installed
- Have the VFPCOM utility installed
- Unzip the accompanying Download file to a new directory
- Start VFP and CD to the directory you just created
- DO FORM ol2kbind.scx
The form shown in Figure 1 will appear. The form does all of the requisite event binding in the Init method, as shown in Listing 6. Note that there's no requirement to use a class definition generated by ExportEvents(). In this case, I added each of the methods to the form itself and linked the form directly to the Outlook instance. Note also that the Outlook instance and the VFPCOM instance are kept as properties of the form. The destroy event makes sure that the link is severed before the object references are released.
Listing 6. Sample form init() method.
with this .oOutlook = create('outlook.application') .oVFPCOM = create('VFPCOM.ComUtil') .oVFPCOM.BindEvents(.oOutlook, this) End With
The idea for our sample is that users can generate e-mail for customers on the list and have that correspondence logged in a items_sent table. There's a restriction on the customers who can receive special pricing attachments. Only those with the flag set to .T. in the customer table are allowed to receive items listed in the sp_attach table.
To start an e-mail, choose a customer in the grid and click the Send E-Mail button. This fires the form's CreateMail method, shown in Listing 7.
Listing 7. The sample form's CreateMail() method.
if eof('customer') messagebox('Please select a customer', ; MB_ICONEXCLAMATION, this.caption) return .f. endif WITH THIS LOCAL loMessage, loInspector loMessage = .oOutlook.createitem(olMailItem) loMessage.UserProperties.ADD('CustID', olNumber) loMessage.UserProperties('CustID').VALUE = ; customer.custid loMessage.Recipients.ADD(customer.cust_email) loMessage.Recipients.ResolveAll() loInspector = loMessage.GetInspector loInspector.ACTIVATE() End With
The CreateMail() method checks that the record pointer is on a valid customer. It then creates an Outlook mail item and sets a custom property containing the customer ID for use when the item is sent. Next, it sets the recipient's e-mail address and checks that Outlook can resolve it. Finally it opens Outlook's native default e-mail editing form (known as inspector—see the sidebar for more information on the inspector), and returns control to the user.
The user then composes and sends their message using a standard Outlook form, just as they would for any other e-mail. Have a play with this. Attach one of the items on the prohibited list (Special Price List.txt and Budget Estimates.txt, included in the accompanying Download file). When you're ready, press the send button and Outlook fires an ItemSend event.
This is what the VFPCOM link has been waiting for! It fires the form's ItemSend() method, shown in Listing 8.
Listing 8. The sample form's ItemSend() method.
lParameters Item, Cancel * if there is not a customerID attached to the item, * just let it go if ! type('Item.UserProperties("CustID").value') = "N" return .t. endif * look up the customerID if it's not there, * cancel the send if ! seek(Item.UserProperties("CustID").value, ; 'LookupCustomer') Cancel = .t. Messagebox('Customer not found - Send Cancelled', ; MB_ICONEXCLAMATION, this.caption) return .f. endif local loRecipient, loAttachment, lcSubject, lcFrom,; lcTo, lcCC, lcBCC, lcAttachments store '' to lcSubject, lcFrom, lcTo, lcCC, lcBCC, ; lcAttachments lcSubject = item.subject * get the from, to , cc and bcc for each loRecipient in item.recipients do case case loRecipient.type = olOriginator lcFrom = loRecipient.Name + ' <' + ; loRecipient.Address + '>' case loRecipient.type = olTo lcTo = lcTo + iif(empty(lcTo), '', CR) + ; loRecipient.Name + ' <' + ; loRecipient.Address + '>' case loRecipient.type = olCC lcCC = lcCC + iif(empty(lcCC), '', CR) + ; loRecipient.Name + ' <' + ; loRecipient.Address + '>' case loRecipient.type = olBCC lcBCC = lcBCC + iif(empty(lcBCC), '', CR) + ; loRecipient.Name + ' <' + ; loRecipient.Address + '>' endcase endfor if empty(lcFrom) lcFrom = item.SenderName endif * get a list of the attachments lcAttachments = '' FOR each loAttachment in item.attachments * if the customer is not a special pricing customer * and the attachment is on the special pricing list * inform the user and reject the send if (! lookupcustomer.spec_price) and ; seek(lower(alltrim(loAttachment.DisplayName)), ; 'sp_attach') cancel = .t. messagebox('This customer is not authorised ' + ; 'to receive special pricing information - ' + ; 'send cancelled', ; MB_ICONEXCLAMATION, this.caption) return .f. endif lcAttachments = lcAttachments + ; if(empty(lcAttachments), '', CR) + ; loAttachment.DisplayName ENDFOR &&* each loAttachment in item.attachments if empty(lcAttachments) lcAttachments = '(None)' endif * insert a record of the email into the log insert into items_sent ( ; custid, ; senttime, ; addr_from, ; addr_to, ; addr_cc, ; addr_bcc, ; msg_body, ; att_list ; ) values ( ; Item.UserProperties("CustID").value, ; datetime(), ; lcFrom, ; lcTo, ; lcCC, ; lcBCC, ; item.body, ; lcAttachments ; ) * done
The first bit of code ensures that the item that's being sent is one that was started by the form. It checks that the custom property set in the CreateMail() method exists. If it doesn't, the code assumes that the item was generated some other way and just returns. Next, the method checks that the customer ID specified in the item's custom property is valid. If it isn't, it displays a message to the user and cancels the send by setting the Cancel parameter to .F. Note that this doesn't mean that the message is discarded, just that it isn't sent. The user can return to the message, and edit and resend, or discard it.
Once the message's credentials have been checked, the information required for the sent items log is harvested. Recipient information is stored in the relevant local variables. Next, the code walks the item's Attachments collection. If the customer isn't authorized to receive special attachments, then each attachment is checked against the prohibited list. If an illegal attachment is detected, then the user is informed and the send is cancelled.
Finally, if the message has passed all of the tests, an entry is made in the sent items log with all of the applicable information.
I've only scraped the surface of the kind of functionality you can support with this technique. For a start, all of the mail item's properties can be read and written to in the itemsend() method, so you can add or remove recipients, add a footer to the message, attach a standard file and so on. Next, all of the other events are available too, so you can add code to them to control all kinds of things. Finally, I've only bound to the events of the Outlook.Application object; there are many sub-objects, each with their own events that are begging to be tapped.
VFPCOM provides us with an additional method to really leverage the tools that are available to us. Although this example concentrated on using Outlook, the techniques presented can be used with any OLE automation server that surfaces events. Go ahead—bind 'till it hurts.
The VFPCOM utility comes with a little documentation. There are also some useful Knowledge Base articles online, and included with MSDN and TechNet (do a search on "VFPCOM").
For more information about the object model for the Office applications, including the events and the parameters passed to them, see MSDN.
Sidebar: What's that Inspector gadget?
Like most object models, the Outlook object model has a distinct hierarchy. At the top of the tree is the Application object. Directly below that is the Namespace object. The namespace object represents an abstract root for the Outlook data store. Folders (like the inbox, the calendar, sent items, and any user-defined folders) belong to the namespace object or to higher-level folders. Finally, folders contain Items. Items are the mail messages, appointments, tasks, journal entries, and so on that live in the folders. In all there are 16 types of items listed in the documentation.
Outlook needs a way of displaying items. To do this it uses an Inspector object. MSDN simply defines the inspector object as "Represents the window in which an Outlook item is displayed." The inspector object is an item's user interface. The inspector object allows access to the toolbars (known in Office as Command Bars), to any pages if the item has been customized, and to the object model of the wordmail editor—if Word is being used as an e-mail editor.
For example, to display the first item in the inbox folder, you could use the following code:
#DEFINE olFolderInbox 6 * navigate down through the hierarchy to get the item loOutlook = createobject('outlook.application') loNameSpace = loOutlook.GetNamespace("MAPI") loInbox = loNameSpace.GetDefaultFolder(olFolderInbox) loItem = loInbox.Items(1) * get the item's default inspector loInspector = loItem.GetInspector() * display the item loInspector.Display()
Experiment by changing the parameter passed to GetDefaultFolder(). For example, passing it olFolderCalendar (9) will display the first item in the user's default calendar folder. You can also use the folder's collections index to return a non-default folder. If there's a folder in the top level of the hierarchy called "My User Defined Folder," then the following code will return a reference to that folder:
loNameSpace.Folders("My User Defined Folder")
For more detailed information on the Outlook object model, including diagrams and code samples, see the MSDN documentation.
To find out more about Fox Talk and Pinnacle Publishing, visit their website at http://www.pinpub.com/html/main.isx?sub=57
Note: This is not a Microsoft Corporation website. Microsoft is not responsible for its content.
This article is reproduced from the October 2000 issue of Fox Talk. Copyright 2000, by Pinnacle Publishing, Inc., unless otherwise noted. All rights are reserved. Fox Talk is an independently produced publication of Pinnacle Publishing, Inc. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of Pinnacle Publishing, Inc. To contact Pinnacle Publishing, Inc., please call 1-800-493-4867 x4209.