Microsoft Outlook Programming: Jumpstart for Administrators, Developers, and Power Users

This content is no longer actively maintained. It is provided as is, for anyone who may still be using these technologies, with no warranties or claims of accuracy with regard to the most recent product version or service release.

 

Aa155701.odc_ch11oleventscover(en-us,office.10).gif

Chapter 11: Responding to Outlook Events in VBA

Sue Mosher
Slipstick Systems Outlook & Exchange Solutions

December 2002

Applies to:
     Microsoft® Outlook® 2002

Buy this book

Summary: This article is adapted from the book Microsoft Outlook Programming: Jumpstart for Administrators, Developers, and Power Users by Sue Mosher – ISBN - 55558-286-9, Digital Press, 2002, pages 191 to 217. (25 printed pages)

Contents

11.1 Application object events
11.2 Writing handlers for other object events
11.3 Explorer events
11.4 Inspector events
11.5 Folders and Items events
11.6 Reminders Events
11.7 Other Events
11.8 Summary

Before Microsoft® Outlook® 2000, the only way to respond to events in Outlook was through VBScript code on a form. Programmers were limited to just the few item events. Outlook 2000 opened the door to programming event handlers at the application level with the more powerful VBA language. Although not every possible event is included in the Outlook object model, the range of available events is enough to keep any programmer busy for a long, long time. Here are just a few of the events for which you can write code:

  • Starting Outlook
  • Sending an item
  • Receiving new mail
  • Creating or modifying items or folders
  • Switching to a different folder or to a different view

Highlights of this chapter include discussions of the following:

  • What event code to place in the ThisOutlookSession module
  • How to set up folders for monitoring new and changed items
  • How to automatically add reminders to birthdays and anniversaries

If you skipped ahead to this chapter, you might want to make sure you understand the material in Part II, "Adding VBA Code," since this chapter requires a good understanding of basic coding techniques.

11.1 Application object events

The Application object stands at the top of the object model hierarchy and offers events that are useful to any Outlook 2000 or 2002 programmer, plus one (OptionsPagesAdd) of interest mainly to developers building COM add-ins (which is beyond the scope of this book). Table 11.1 lists the key events. Only ItemSend can be cancelled. The arguments for both ItemSend and Reminder include the associated item so that you work with it in your code.

Table 11.1 Key Application object events

Aa155701.odc_ch11oleventstable(en-us,office.10).gif

**Note   **Two other Application events,

AdvancedSearchComplete

and

AdvancedSearchStopped

, work with the

Search

object added in Outlook 2002 to make it possible to perform more precise data searches, in some case across multiple folders. You will see how to build these searches in Chapter 14, "Working with Items and Recipients."

In the object browser, you can see the events for various Outlook objects more easily if you right-click in the Members pane on the right and choose Group Members. Build code for any of these events in the ThisOutlookSession module found in the Project Explorer under Project1 and then Microsoft Outlook Objects, as shown in Figure 11.1. Double-click ThisOutlookSession to open it in a module window. This is a special kind of module, called a class module, that can respond to events. We'll come back to class modules when we consider handlers for events for objects other than the Application object.

Click here for larger image.

Figure 11.1 Place application-level event code in the ThisOutlookSession class module (click picture for larger image).

Since the ThisOutlookSession module was created automatically, it will not include an Option Explicit statement to force you to declare variables. You should go ahead and add that to the module's declarations section.

To add an Application event handler, select Application from the list at the top left of the module window. Then, from the list on the right, select the event for which you want to write code. VBA places a wrapper for the procedure in the module window, with the correct syntax. Figure 11.1 depicts a wrapper for the ItemSend and Startup events.

11.1.1 Startup, MAPILogonComplete, and Quit events

One use for the Startup event and, in Outlook 2002, MAPILogonComplete, is to initialize global variables. For example, in any module (a regular module, not ThisOutlookSession or another class module) besides, add this declaration:

Public g_strUser as String

Then use the code in Listing 11.1 to initialize this variable when Outlooks starts. That way, you can use it in any VBA procedure. (See Listing 10.10 for the code for the WSHUserName() function.)

Listing 11.1 Initialize and dereference key object variables

Private Sub Application_Startup()
    g_strUser = WSHUserName()
End Sub

As you will see later in the chapter, another important use of the Startup event is to instantiate other Outlook objects that you plan to write event handlers for. In Outlook 2002, you can also use the MAPILogonComplete event instead of Startup, especially if you are using a profile that connects to Microsoft Exchange Server.

The Quit event is not very useful, because all Outlook windows have already closed by the time the Quit event fires as you exit Outlook. Therefore, you no longer have access to Outlook items and folders. Also, by the time Quit fires, Outlook has already released any global variables.

11.1.2 Using NewMail to trigger a new mail pop-up

Outlook offers several built-in options for notifying the user that new mail has arrived, but maybe you want something more customized. Try creating a VBA form, such as that in Figure 11.2, which pops up when new mail arrives and displays the time of the latest mail delivery. If you're often out of your office during the day, this form will make it easy to see at a glance whether any new messages arrived while you were gone—and at what time.

Aa155701.odc_ch11olevents02(en-us,office.10).gif

Figure 11.2 This VBA form is displayed whenever the NewMail event fires.

The form in Figure 11.2 has just two label controls. The one to hold the date and time information is named lblReceived. Name the form Ch11NewMail and set its ShowModal property to False. Add a command button named cmdHide, and add this code to the form:

Private Sub cmdHide_Click()
    Me.Hide
End Sub

To make the form display the most recent mail delivery time, add the following code to the Application_NewMail event in the ThisOutlookSession module:

Private Sub Application_NewMail()
    Ch11NewMail.Show 
    With Ch11NewMail
        .lblReceived.Caption = Now
        .Repaint
    End With
End Sub

The new mail notification options that you can set through the Outlook user interface do not fire on every incoming message. This applies to both the Rules Wizard and code that you supply to the NewMail event handler. We will look at another way of processing new mail by monitoring the Inbox, later in this chapter. If you set ShowModal property to False, you can leave the form on the screen while you do other work. Click the Hide button to make the form disappear until the next new mail comes in.

11.1.3 Using Reminder to put reminders in the Inbox

Not everyone likes to be reminded of tasks and appointments with a pop-up message. Some people prefer to see reminders as items in their Inbox. Because the Reminder event gives you access to the item that triggered the reminder, you can place a message in your own Inbox. The code in Listing 11.2 creates a new message from the reminder item and then places the new message in the Inbox. Place the code in the ThisOutlookSession module.

Listing 11.2  Placing Reminders in the Inbox

Private Sub Application_Reminder(ByVal Item As Object)
    Dim objNS As Outlook.NameSpace
    Dim objItem As Outlook.MailItem
    Dim objFolder As Outlook.MAPIFolder
    Dim strDue As String
    Dim objCDOItem As MAPI.Message
    Set objNS = Application.GetNamespace("MAPI")
    Set objItem = Application.CreateItem(olMailItem)
    Select Case Item.Class
        Case olMail
            strDue = " Due " & Item.FlagDueBy
        Case olAppointment
            If Item.Location <> "" Then
                strDue = " (" & Item.Location & ")"
            End If
            strDue = strDue & " At " & Item.Start
        Case olContact
            Set objCDOItem = GetCDOItemFromOL(Item)
            If Not objCDOItem Is Nothing Then
                strDue = " Due " & _
                  objCDOItem.Fields(CdoPR_REPLY_TIME)
            End If
        Case olTask
            strDue = " Due " & Item.DueDate
    End Select
    If Item.Importance = olImportanceHigh Then
        strDue = strDue & " (High)"
    End If
    With objItem
        .Subject = Replace(TypeName(Item), "Item", "") & _
                    ": " & Item.Subject & strDue
        .Body = Item.Body
        .Save
        Set objFolder = _
          objNS.GetDefaultFolder(olFolderInbox)
        .Move objFolder
    End With
    Set objNS = Nothing
    Set objItem = Nothing
    Set objFolder = Nothing
    Set objCDOItem = Nothing
End Sub 

Because the Application_Reminder subroutine is in the ThisOutlookSession module, Application is an intrinsic object; you do not have to declare an object variable for it.

If you look in the object browser, you'll see that the ContactItem has no FlagDueBy property like the MailItem does. The due date is, however, stored in the item in a field that CDO can access, so the code passes the item to CDO, using the GetCDOItemFromOL() function from Listing 10.5 and obtains the value for the flag's due date with the expression. objCDOItem.Fields(CdoPR_REPLY_TIME). The CdoPR_REPLY_TIME constant represents the due date for the flag set on the contact. Appendix A has information on resources for learning more about CDO fields and how they related to Outlook items.

TypeName() is a function that returns a string with the type of object, for example, "ContactItem" for an Outlook contact. Outlook automatically saves unsent messages in the Drafts folder. That's why the code uses the Move method to get the reminder notice into the Inbox. Moving an item to the Inbox does not trigger the NewMail event.There are lots of variations on this technique for processing reminders. For example, you could substitute this code for the With...End With block to put a shortcut to the original item into the Inbox message:

objItem.Subject = "Reminder - " & Item.Subject & strDue
objItem.Body = Item.Body
Set objAttachment = _
  objItem.Attachments.Add(Item, olEmbeddeditem)
objItem.Save
objItem.Move objFolder

Only items in your default Outlook Inbox, Calendar, Contacts, and Tasks folders will trigger the Application.Reminder event.

Another possible application for the Reminder event is to forward reminders to another e-mail account or perhaps even to an email address for a mobile phone. However, as you will see in Chapter 13, "Understanding Outlook Security," many Outlook installations cannot send items automatically without user intervention.

11.1.4 Using the ItemSend event

When the ItemSend event fires, Outlook has not yet sent the item. This means that you can use the ItemSend event to change the item before it leaves your Outbox.

Some mail applications, such as Lotus Notes, can prompt the sender to specify what folder a message should be saved in. In Outlook, you can set the storage folder for an individual message on the message's Options dialog. If you want to approximate the way Notes works, you can use the code in Listing 11.3 to pop up a dialog box when the user sends the message and then set the SaveSentMessageFolder property to whatever folder the user chooses. Place the code in the ThisOutlookSession module.

Listing 11.3 Setting the folder for saving an outgoing message

Private Sub Application_ItemSend(ByVal Item As Object, _
                                 Cancel As Boolean)
    Dim objNS As Outlook.NameSpace
    Dim objFolder As Outlook.MAPIFolder
    Set objNS = Application.GetNamespace("MAPI")
    Set objFolder = objNS.PickFolder
    If Not objFolder Is Nothing Then
        If IsInDefaultStore(objFolder) Then 
            Set Item.SaveSentMessageFolder = objFolder
        End If 
    End If
    Set objFolder = Nothing
    Set objNS = Nothing
End Sub

Since you are working in the ThisOutlookSession module you can use the same Application object that fires the ItemSend event. The PickFolder method of the Namespace object is one of several techniques for getting a particular MAPIFolder object that we'll cover in Chapter 12, "Working with Stores and Folders." Did you notice that setting the SaveSentMessageFolder property required the Set keyword, because it's an object property? Listing 11.3 uses an IsInDefaultStore() function, which you will see in Chapter 12, to comply with Outlook's requirement that the SaveSentMessageFolder be in the same information store as the Sent Items folder. Another common use for the ItemSend event is to check for conditions under which you might want to cancel sending the item—such as a message that says it contains an attachment, but doesn't. To cancel the sending of an item in the ItemSend event handler, add this statement to your code:

Cancel = True

For example, Outlook 2002 introduces a new ShowCategoriesDialog method that pops up the Categories dialog, where the user can choose to apply one or more categories to an item. If you want to make sure that all outgoing items have a category, you could use the code in listing 11.4.

Listing 11.4 Require a category on all outgoing items

Private Sub Application_ItemSend(ByVal Item As Object, _
                                 Cancel As Boolean)
    If Item.Categories = "" Then
        Item.ShowCategoriesDialog
        If Item.Categories = "" Then
            Cancel = True
            MsgBox "This item can't be sent " & _
                   "until you choose a category."
        End If
    End If
End Sub

Be careful with the technique in Listing 11.4, since the categories you choose will be visible to recipients who also have Outlook. Don't use category names that you might find embarrassing.

11.2 Writing handlers for other object events

VBA handling of Outlook events is not limited to events associated with the Application object. You can write event handlers for other Outlook objects, too. Setting this up is a little more involved because you must first declare object variables using a Dim WithEvents statement. WithEvents can be used only in class modules—special code modules that establish and work with object classes and their methods, events, and properties. The ThisOutlookSession module itself is a class module.VBA forms also have associated class modules. The code placed behind forms like the birthday/anniversary reminder form that you worked on in earlier chapters is actually code in a class module. You can also declare WithEvents statements in VBA form code.Follow this basic procedure to set up an event handler for any Outlook event other than Application events:

  1. Declare an object variable WithEvents in the ThisOutlookSession module or in a class module that you add with the Insert, Class Module command in the VBA environment.
  2. Initialize the declared object with a statement in either the Application_Startup procedure, if you always want it to run, or some other procedure, if you want to run it only on demand or in certain situations.
  3. Write code in the ThisOutlookSession module to respond to the declared object's events.The individual Outlook items – MailItem, ContactItem, etc. – all have their own set of events. However, handling events for Outlook items in VBA is beyond the scope of this book, because it involves a fairly complex class module and detection of each item that the user selects in a folder or opens in its own window. We will deal with events for Outlook items solely in the context of Outlook forms, in Chapter 18, "Writing Code to Respond to Outlook Form Events."

11.3 Explorer events

You should remember by now that each window with an open Outlook folder is represented by an Explorer object in the Explorers collection. Events related to the Explorer object fire when the user changes views, selects items, or switches to a new folder. Table 11.2 summarizes the Explorer events.

Table 11.2 Explorer events

Event Description
Activate Occurs when the user switches to the Explorer window.
BeforeFolderSwitch Occurs just before the Explorer displays a new folder. Includes the new folder as an argument. Cancelable.
BeforeViewSwitch Occurs just before the Explorer displays a new view. Includes the new view as an argument. Cancelable.
Close Occurs when the Explorer closes.
Deactivate Occurs just before the focus switches from the Explorer to another window.
FolderSwitch Occurs just after the Explorer displays a new folder.
SelectionChange Occurs when the user selects different items. Does not apply to Outlook Today or file system folders.
ViewSwitch Occurs after the Explorer displays a new view.
Added in Outlook 2002  
BeforeItemCopy Occurs when the user copies an item. Cancelable.
BeforeItemCut Occurs when the user cuts an item. Cancelable.
BeforeItemPaste Occurs when the user pastes an item. Cancelable.
BeforeMaximize Occurs when the user maximizes the window. Cancelable.
BeforeMinimize Occurs when the user minimizes the window. Cancelable.
BeforeMove Occurs when the user moves the window. Cancelable.
BeforeSize Occurs when the user resizes the window. Cancelable.

The BeforeFolderSwitch, BeforeViewSwitch, FolderSwitch, and ViewSwitch events can be triggered either by the user changing the folder or view or by code that assigns a new value to the Explorer's CurrentFolder or CurrentView property.

To make use of these events, you must declare appropriate object variables in the ThisOutlookSession module or another class module and should initialize those variables with code in the Application_Startup event handler in ThisOutlookSession. If you want to detect when a user has opened a new window and then change the appearance of that window, SelectionChange is the best event to use, because it ensures that the full user interface is available.

11.3.1 Automatically showing the Outlook Bar on new folder windows

You might have discovered that you can open multiple Outlook windows by right-clicking on the name of any folder, then choosing Open in New Window. However, these windows do not show the Outlook Bar. To show how you can use the SelectionChange event to work with the appearance of an Explorer window, we will create the Explorer event handlers in a separate class module. This will also give you a little practice working with class modules and make it easier to back up your event handler module by exporting the class module from VBA. You can export the ThisOutlookSession module, but when you try to import it, it doesn't replace or update the existing ThisOutlookSession module. Instead, it imports as ThisOutlookSession1. You would then have to copy and paste the code from ThisOutlookSession1 to ThisOutlookSession. Follow these step-by-step instructions to create your class module and update ThisOutlookSession with the necessary code:

  1. Choose Insert, Class Module to create a new class module in VBA.
  2. In the Properties window, change the value of the (Name) property to ExplEvents.
  3. Add the code in Listing 11.5 to the ExplEvents module.
  4. Edit the ThisOutlookSession module to include the code in Listing 11.6. If you already have an Application_Startup procedure, do not create a new one. Simply add the code from the procedure in Listing 11.6 to your existing routine.
  5. Either exit and restart Outlook, or run the Application_Startup procedure to initialize the new event handlers.

Listing 11.5 Class module code to handle Explorer and Explorers events

Private WithEvents m_colExplorers As Outlook.Explorers
Private WithEvents m_objExplorer As Outlook.ExplorerPrivate Sub Class_Terminate()
    Call DeRefExplorers
End Sub

Public Sub InitExplorers(objApp As Outlook.Application)
    Set m_colExplorers = objApp.Explorers
    If m_colExplorers.Count > 0 Then
        Set m_objExplorer = objApp.ActiveExplorer
    End If
End Sub

Public Sub DeRefExplorers()
    Set m_colExplorers = Nothing
    Set m_objExplorer = Nothing
End Sub

Private Sub m_colExplorers_NewExplorer _
  (ByVal Explorer As Explorer)
    Set m_objExplorer = Explorer
End Sub

Private Sub m_objExplorer_SelectionChange()
    If Not m_objExplorer.IsPaneVisible(olOutlookBar) Then
        m_objExplorer.ShowPane olOutlookBar, True
    End If
End Sub

Listing 11.6 ThisOutlookSession code to handle an Explorers collection

Dim m_explevents As New ExplEventsPrivate Sub Application_Startup()
    m_explevents.InitExplorers Application
End Sub

It is possible to start Outlook without first displaying a folder—for example, by using a shortcut to display a new message window. Therefore, the InitExplorers procedure only instantiates the m_objExplorer if it can confirm that at least one Explorer object (that is, Outlook folder window) is available.

Listing 11.6 introduces a new declaration technique—the New keyword. When you use the New keyword in a declaration, you are creating a new instance of a class, in this case the class module that you named ExplEvents. We need to create an instance of the class before we can call any of the procedures in that class module, for example the m_events.InitExplorers procedure called in Application_Startup. Explorer events allow you to gain sure access only to the last Explorer window opened. Handling all events for all open Explorer windows requires a "wrapper" class module and is beyond the scope of this book. Appendix A has further resources. Other practical applications for Explorer events include the following:

  • Turning off the preview pane when you switch to a view with "AutoPreview" in its name, so that you don't have two different kinds of preview in a single view
  • Turning on a custom toolbar when you switch to a particular folder and turning it off again when you switch to a different folder
  • Automatically showing a certain view when you switch to a folder, rather than showing the last view used on that folder

11.3.2 Setting a default folder view

If you're like me and have several thousand items in your Sent Items folder, viewing just the last few days' worth makes the folder seem to run faster. Outlook includes a Last Seven Days view that filters out all but the last week's worth of items.

**Note   **You may want to create your own Sent in Last Seven Days view, replacing the Received and From fields with the Sent and To fields.

To make Outlook automatically turn on the Last Seven Days view, you must create an event handler for the FolderSwitch event. Add the code in Listing 11.7 to your ExplEvents class module.

This code depends on having an m_objExplorer object declared WithEvents and initialized, as described in the previous section.

Listing 11.7 Enforcing a Default Folder View

Private Sub m_objExplorer_FolderSwitch()
    Dim objApp As Outlook.Application
    Dim objNS As Outlook.NameSpace
    Dim objSentItems As Outlook.MAPIFolder
    Set objApp = CreateObject("Outlook.Application")
    Set objNS = objApp.GetNamespace("MAPI")
    Set objSentItems = _
      objNS.GetDefaultFolder(olFolderSentMail)
    If m_objExplorer.CurrentFolder = objSentItems Then
       m_objExplorer.CurrentView = "Last Seven Days"
    End If
    Set objApp = Nothing
    Set objNS = Nothing
    Set objSentItems = Nothing
End Sub

11.4 Inspector events

Just as Outlook has an Explorers collection with each individual Explorer object representing a folder window, it also has an Inspectors collection, where each individual Inspector object represents an individual Outlook item window. The Inspectors collection has one event, NewInspector, which fires whenever a new Inspector opens. Unfortunately, the NewInspector event does not fire in all cases where the user opens a new Outlook message. In Outlook 2000, it does not fire when Word is used as the editor (a configuration known as WordMail). In both Outlook 2000 and 2002, you will get no NewInspector event when you invoke a Send or Send To command from other Office programs, Windows Explorer, or Internet Explorer. Furthermore, using the Next or Previous buttons in an open item window reuses the corresponding Inspector, so you don't get a NewInspector event there either, even though the item being viewed changes.

Further complicating the picture is the fact that, like NewExplorer, the NewInspector event provides only the most recently opened Inspector. To handle events for all open Outlook Inspector windows would require a "wrapper" class module, which is beyond the scope of this book.

Perhaps the most practical use of the NewInspector event is to make sure that a particular custom toolbar or toolbar button is visible.

An individual Inspector object has the events shown in Table 11.3, including several that apply only to Outlook 2002.

Table 11.3 Inspectors and Inspector events

Event Description
Inspectors event
NewInspector Occurs when an item opens in its own window (but not for Send or SendTo commands from Office programs, Windows Explorer, or Internet Explorer or in WordMail in Outlook 2000).
Inspector Events
Activate Occurs when the user switches to the Inspector window or when the Next or Previous button is used to view another item in an open window.
Close Occurs when the Inspector closes.
Deactivate Occurs just before the focus switches from the Inspector to another window or when the Next or Previous button is used to view another item in an open window.
Added in Outlook 2002  
BeforeMaximize Occurs when the user maximizes the window. Cancelable.
BeforeMinimize Occurs when the user minimizes the window. Cancelable.
BeforeMove Occurs when the user moves the window. Cancelable.
BeforeSize Occurs when the user resizes the window. Cancelable.

11.5 Folders and Items events

Another major category of events is those that affect the Folders and Items collections—in other words, Outlook folders and the items they contain. This is where Outlook reacts to the creation of a new folder or item, a change to an existing folder or item, or the deletion of a folder or item. Table 11.4 summarizes these events.

Table 11.4 Folders and Items Events

Event Description
Folders Events
FolderAdd Occurs when a new folder is created. Includes the new MAPIFolder as an argument.
FolderChange Occurs when a folder is modified. Includes the modified MAPIFolder as an argument.
FolderRemove Occurs after a folder has been deleted.
Items Events
ItemAdd Occurs when a new item is created. Includes the new item as an argument.
ItemChange Occurs when an item is modified. Includes the modified item as an argument.
ItemRemove Occurs after an item has been deleted.

The FolderRemove and ItemRemove events have a severe limitation in that they fire only after the folder or item has been deleted—in other words, when it's too late to do anything about it! One workaround is to set up event handlers on the Deleted Items folder itself to watch for the addition of new folders and items. You will do a folder deletion monitor in the next section. Putting an event handler on the Deleted Items folder's Items collection to watch for deleted items does not help you recover data if the user presses Shift+Delete to delete the item without going through Deleted Items. Outlook 2002 introduces a BeforeDelete event for each type of Outlook item. You may want to incorporate it into your code for custom forms, as discussed in Chapter 18, "Writing Code to Respond to Outlook Form Events."

11.5.1 Preventing folder deletion

If you work in Outlook with the Folder List turned on, sooner or later you're bound to delete a folder accidentally. Outlook is good about asking you whether you really want to delete a folder, but it doesn't hurt to have extra protection. One application of Folders events is to monitor the Deleted Items folder for any new folders added to it and ask users whether they really want to delete that folder.

As with other events, you must declare a Folders or Items object variable WithEvents, initialize it, and write code for the event handler. As with the Explorer and Explorers events, we will create a new class module for folder events. Follow these steps:

  1. Choose Insert, Class Module to create a new class module in VBA.
  2. In the Properties window, change the value of the (Name) property to FolderEvents.
  3. Add the code in Listing 11.8to the FolderEvents module.
  4. Edit the ThisOutlookSession module to include the code in Listing 11.9. If you already have an Application_Startup procedure, do not create a new one. Simply add the code from the procedure in Listing 11.9 to your existing routine.
  5. Either exit and restart Outlook, or run the Application_Startup procedure to initialize the new event handlers.

Listing 11.7 Watching for deleted folders

Private WithEvents m_colDeletedItemsFolders _
  As Outlook.FoldersPrivate Sub Class_Terminate()
    Call DeRefFolders
End Sub

Public Sub InitFolders(objApp As Outlook.Application)
    Dim objNS As Outlook.NameSpace
    Set objNS = objApp.GetNamespace("MAPI")
    Set m_colDeletedItemsFolders = _
      objNS.GetDefaultFolder(olFolderDeletedItems).Folders
    Set objNS = Nothing
End Sub

Public Sub DeRefFolders()
    Set m_colDeletedItemsFolders = Nothing
End Sub

Private Sub m_colDeletedItemsFolders_FolderAdd _
  (ByVal Folder As Outlook.MAPIFolder)
    Dim objNS As Outlook.NameSpace
    Dim objDestFolder As Outlook.MAPIFolder
    Dim strMsg As String
    Dim intRes As Integer
    If Folder.Items.Count <> 0 Then
        strMsg = "Did you really mean to delete the " & _
                 Folder.Name & " Folder?" & vbCrLf & _
                 vbCrLf & "If you click No, you " & _
                 "will need to choose the parent " & _
                 "folder where it belongs."
        intRes = MsgBox(strMsg, _
                 vbYesNo + vbDefaultButton2 + vbQuestion, _
                 "Delete Folder?")
        If intRes = vbNo Then
            Set objNS = _
              Folder.Application.GetNamespace("MAPI")
            Set objDestFolder = objNS.PickFolder
            If Not objDestFolder Is Nothing Then
                Folder.MoveTo objDestFolder
            End If
        End If
    End If
    Set objNS = Nothing
    Set objDestFolder = Nothing
End Sub 

Listing 11.8 ThisOutlookSession code to handle Folders events

Dim m_folderevents As New FolderEventsPrivate Sub Application_Startup()
    m_folderevents.InitFolders Application
End Sub

It's too bad that Outlook can't remember the original location of the folder before it was deleted. That's why Listing 11.8 must include a PickFolder method (which you first saw in Listing 11.3), so the user can indicate where the folder should be relocated.

11.5.2 Automatically adding reminders to birthdays and anniversaries

In Chapters 3 and 9, we worked on a birthday/anniversary reminder tool, a VBA form that updated all existing birthdays and/or anniversaries in your Calendar folder. But wouldn't it be nice if Outlook would automatically add a reminder without the need to run the VBA form periodically? This is a perfect job for event handlers that monitor the Calendar folder for new and modified items.

Add the code in Listing 11.10 to the FolderEvents class module created in the previous section. If you created the InitFolders and DeRefFolders procedures in Listing 11.8, you don't need to add new ones. Just update the existing procedures to include the code from the corresponding subroutines in Listing 11.10. You can put all the initialization code for the various objects declared WithEvents in this module into the InitFolders and DeRefFolders procedures.

Also put the code in Listing 11.9 in the previous section in the ThisOutlookSession module, if you haven't done so already. Restart Outlook or run the Application_Startup procedure, then add a new birthday to a contact, and check the corresponding entry in the Calendar folder.

Listing 11.10 Adding reminders to birthday and appointments events

Private WithEvents m_colCalendarItems As Outlook.ItemsPrivate Sub Class_Terminate()
    Call DeRefFolders
End Sub

Public Sub InitFolders(objApp As Outlook.Application)
    Dim objNS As Outlook.NameSpace
    Set objNS = objApp.GetNamespace("MAPI")
    Set m_colCalendarItems = _
      objNS.GetDefaultFolder(olFolderCalendar).Items
    Set objNS = Nothing
End Sub

Public Sub DeRefFolders()
    Set m_colCalendarItems = Nothing
End Sub

Private Sub m_colCalendarItems_ItemAdd _
  (ByVal Item As Object)
    Call UpdateReminder(Item)
End Sub

Private Sub m_colCalendarItems_ItemChange(ByVal Item As Object)
    Call UpdateReminder(Item)
End Sub

Sub UpdateReminder(Item As Object)
    Dim intDays As Integer
    Dim strSubject As String
    Dim blnDoUpdate As Boolean
    ' *** USER OPTIONS ***
    ' set number of days before event that the
    '   reminder should fire
    intDays = 5
    If Item.Class = olAppointment Then
        strSubject = Item.Subject
        If (InStr(strSubject, "Birthday") > 0 Or _
          InStr(strSubject, "Anniversary") > 0) Then
            blnDoUpdate = False
            If Item.ReminderSet = False Then
                blnDoUpdate = True
            ElseIf Item.ReminderMinutesBeforeStart _
              < 24 * 60 Then
                blnDoUpdate = True
            End If
            If blnDoUpdate Then
                With Item
                    .ReminderSet = True
                    .ReminderMinutesBeforeStart = _
                       intDays * 24 * 60
                    .Save
                End With
            End If
        End If
    End If
End Sub

A few things worth noticing in the code:

  • Even though it's rare for a non-appointment item to get added to the Calendar folder, it's still a good idea to always check the Class property of an item of unknown type, as we do with the If Item.Class = olAppointment Then statement before using any properties specific to a certain type of item.
  • The *** USER OPTIONS *** section calls attention to the key setting for your reminder updates—the number of days ahead of the event.
  • The statement ElseIf Item.ReminderMinutesBeforeStart < 24 * 60 Then checks whether a reminder has been set for less than a day before the event, since Outlook will sometimes add its own reminder automatically.

11.5.3 Processing new items in the Inbox

Just as you might use the ItemAdd event to monitor the Calendar folder for new birthdays or anniversaries, you can also monitor the Inbox for new incoming mail messages and thus build your own alternative to the Outlook Rules Wizard. There are a few caveats: If too many messages are received at once, Outlook will not fire the ItemAdd event for each one. Also, it might not be a good idea to mix Rules Wizard rules and VBA processing of the Inbox, since it's not possible to predict which will process a message first. You might even see the extreme case: A rule fires and performs part of its actions, then ItemAdd fires, then the rule finishes.

For a demonstration of the ItemAdd technique, you need to create a new subfolder under your Inbox named Quarantine. (If you don't create the folder, the code in Listing 11.11 will create it for you.) Then add the code in Listing 11.11 to the FolderEvents module, adding to the InitFolders and DeRefFolders subroutines, if you already created those following the steps in the previous two sections, rather than adding new ones. The DoQuarIFrame subroutine moves any HTML mail message that contains an <iframe> tag into the Quarantine folder; such messages often carry viruses.

Listing 11.11 Moving suspicious mail messages to a Quarantine folder

Private WithEvents m_colInboxItems As Outlook.ItemsPrivate Sub Class_Terminate()
    Call DeRefFolders
End Sub

Public Sub InitFolders(objApp As Outlook.Application)
    Dim objNS As Outlook.NameSpace
    Set objNS = objApp.GetNamespace("MAPI")
    Set m_colInboxItems = _
      objNS.GetDefaultFolder(olFolderInbox).Items
    Set objNS = Nothing
End Sub

Public Sub DeRefFolders()
    Set m_colInboxItems = Nothing
End Sub

Private Sub m_colInboxItems_ItemAdd(ByVal Item As Object)
    Dim blnItemMoved As Boolean
    blnItemMoved = QuarIFrame(Item)
End Sub

Function QuarIFrame(objItem As Outlook.MailItem) As Boolean
    Dim objNS As Outlook.NameSpace
    Dim objInbox As Outlook.MAPIFolder
    Dim objQuarFolder As Outlook.MAPIFolder
    On Error Resume Next
    Set objNS = objItem.Application.GetNamespace("MAPI")
    Set objInbox = objNS.GetDefaultFolder(olFolderInbox)
    Set objQuarFolder = objInbox.Folders.Item("Quarantine")
    If objQuarFolder Is Nothing Then
        Set objQuarFolder = _
          objInbox.Folders.Add("Quarantine")
    End If
    If Not objQuarFolder Is Nothing Then
        If objItem.Class = olMail Then
            If InStr(1, objItem.HTMLBody, _
                     "<IFRAME", vbTextCompare) > 0 Then
               objItem.Move objQuarFolder
               QuarIFrame = True
            End If
        End If
    End If
    Set objNS = Nothing
    Set objInbox = Nothing
    Set objQuarFolder = Nothing
End Function

This time, instead of calling a subroutine, as the event handlers in Listing 11.10 called the UpdateReminder procedure, the m_colInboxItems_ItemAdd event handler gets the value of a function QuarIFrame(), which returns True whenever it moves a suspicious messages containing an <iframe> tag to the Quarantine folder. This modular approach sets up a framework for putting several VBA-based "rules" in the m_colInboxItems_ItemAdd event handler. If an item has already been handled and moved from the Inbox, you probably don't want to handle it again, so you can check the value of blnItemMoved before proceding to a second function:

blnItemMoved = QuarIFrame(Item)
If blnItemMoved Then Exit Sub
blnItemMoved = SecondRule(Item)

**Note   **If

blnItemMoved

is

True

, code execution exits the subroutine and does no further processing on the message. But if

blnItemMoved

is

False

, the

SecondRule()

function is processed. Building an

ItemAdd

event handler for the Items collection of the Inbox folder is not your only option for automatically processing incoming items. You could also use the

Application.NewMail

event. However, since the

NewMail

event does not provide any details about the arriving item(s), you would have to keep track of what items were previously processed and, if you are also running Rules Wizard rules, examine multiple folders for new items.

In Outlook 2002, you have the option of running a VBA subroutine from a rule, as described in Chapter 4, "Code Basics."

11.6 Reminders Events

Outlook 2002 adds a new Reminders collection of Reminder objects with associated events that fire when reminders are created, modified, or removed; when reminders fire; and when the user snoozes or dismisses a reminder. Table 11.5 lists the Reminders events.

Table 11.5 Reminders events

Event Description
BeforeReminderShow Occurs before Outlook displays a reminder (before ReminderFire). Cancelable.
ReminderAdd Occurs after a new reminder has been created; includes the item that fired the reminder as an argument
ReminderChange Occurs after a reminder has been changed; includes the item that fired the reminder as an argument
ReminderFire Occurs right before a reminder fires; includes the item that fired the reminder as an argument
ReminderRemove Occurs when a user dismisses a reminder, deletes an item that contains a reminder, or turns off the reminder for an item; also occurs when a reminder is dismissed programmatically with the Reminder.Dismiss method or removed from the Reminders collection
Snooze Occurs when the user clicks the Snooze button on the Reminders dialog or when a reminder is snoozed programmatically with the Reminder.Snooze method; includes the item that fired the reminder as an argument

For those events with a ReminderObject argument, this argument represents a Reminder object. Keep in mind that only items in the default Inbox, Calendar, Contacts, and Tasks folders can have reminders associated with them.

To work with Reminders events, you must declare a Reminders object WithEvents and instantiate it, just as you did with the Explorers, Folders, and Items collections. Follow these steps to create a RemindersEvents class module and update ThisOutlookSession with the necessary code:

  1. Choose Insert, Class Module to create a new class module in VBA.
  2. In the Properties window, change the value of the (Name) property to RemindersEvents.
  3. Add the code in Listing 11.12 to the RemindersEvents module.
  4. Edit the ThisOutlookSession module to include the code in Listing 11.13. If you already have an Application_Startup procedures, do not create a new one. Just add the code from the procedure in Listing 11.13 to your existing routine.
  5. Either exit and restart Outlook, or run the Application_Startup procedure to initialize the new event handlers.

Listing 11.12 Reminders event handlers and initialization code

Dim WithEvents m_colReminders  As Outlook.Reminders
Dim m_intBusyStatus As IntegerPrivate Sub Class_Terminate()
    Call DeRefReminders
End Sub

Public Sub InitReminders(objApp As Outlook.Application)
    Set m_colReminders = objApp.Reminders
    m_intBusyStatus = 0
End Sub

Public Sub DeRefReminders()
    Set m_colReminders = Nothing
End Sub

Private Sub m_colReminders_ReminderFire _
  (ByVal ReminderObject As Outlook.Reminder)
    Dim strMsg As String
    Const ME_BUSY = vbYes
    Const ME_NOT_BUSY = vbNo
    Const ME_UNKNOWN_BUSY = 0
    If m_intBusyStatus = ME_UNKNOWN_BUSY Then
        strMsg = "Are you really busy today?"
        m_intBusyStatus = MsgBox(strMsg, _
          vbYesNo + vbDefaultButton2 + vbQuestion, _
          "Busy Day?")
    End If
    If m_intBusyStatus = ME_BUSY Then
        If ReminderObject.IsVisible Then
            ReminderObject.Snooze (24 * 60)
        End If
    End If
End Sub

Private Sub m_colReminders_Snooze _
  (ByVal ReminderObject As Outlook.Reminder)
    Dim objItem As Object
    Dim dteNextReminder As Date
    Dim strItemType As String
    Dim strMsg As String
    Dim intRes As Integer
    Set objItem = ReminderObject.Item
    dteNextReminder = ReminderObject.NextReminderDate
    If objItem.Importance = olImportanceHigh Then
        If DateDiff("h", Date, dteNextReminder) >= 24 Then
            strMsg = Replace(TypeName(objItem), "Item", "")
            strMsg = "You just snoozed the reminder " & _
                     " for " & vbCrLf & vbCrLf & vbTab & _
                     strMsg & ": " & objItem.Subject & _
                     vbCrLf & vbCrLf & " until " & _
                     FormatDateTime(dteNextReminder) & _
                     "." & vbCrLf & vbCrLf & _
                     "Did you really want to do that? " & _
                     "Click No to edit the item and " & _
                     "change the reminder."
            intRes = MsgBox(strMsg, _
              vbYesNo + vbDefaultButton2 + vbQuestion, _
              "You Snoozed an Important Reminder !")
            If intRes = vbNo Then
                objItem.Display
            End If
        End If
    End If
    Set objItem = Nothing
End Sub

Listing 11.13 ThisOutlookSession code to handle Reminders events

Dim m_remindevents As New RemindersEventsPrivate Sub Application_Startup()
    m_remindevents.InitReminders Application
End Sub

Notice that using Reminder.Snooze in a ReminderFire event handler means that the reminder will not be displayed to the user.

11.7 Other Events

A few more sets of events deserve attention. Outlook supports several events associated with synchronization of Outlook with an Exchange Server mailbox using a SyncObject object. The SyncObject object is a named set of synchronization settings or, in Outlook 2002, send/receive group settings. Table 11.6 lists its events.

Table 11.6 SyncObject Events

Event Description
OnError Occurs when an error occurs during synchronization. Includes the error Code and Description as arguments.
Progress Occurs periodically during a synchronization session. Includes several arguments providing information on the process, including the number of items to be synchronized.
SyncEnd Occurs after synchronization has completed.
SyncStart Occurs when synchronization begins.

An event handler for a SyncObject event might be initialized in a procedure that uses the Start method to perform synchronization using a particular SyncObject object. You could also choose to initialize it in the Application_StartUp procedure by referencing a particular item in the Application.Session.SyncObjects collection.

Outlook 2002 adds a Views collection of all folder views plus ViewAdd and ViewRemove objects that fire, respectively, when a new view is added or an existing view is removed.

Finally, in Chapter 21, "Menus, Toolbars, and the Outlook Bar," you will examine events related to the Outlook Bar.

11.8 Summary

VBA event handling takes Outlook programming to a new level of utility not available in versions before Outlook 2000. Through events related to the application itself—and Outlook's folders, windows, reminders, and other components—you gain much greater control over what happens in Outlook.

Beyond the events, you also learned two useful methods for popping up dialogs where users can select a folder or choose categories—PickFolder and ShowCategoriesDialog (which is available only in Outlook 2002). Furthermore, you saw an example of working with the CDO Fields collection to access a property that the Outlook object model does not expose.

In the Chapter 12, you will learn more techniques for working with folders and the Explorer windows that display folders.