In this section, I'll describe a COM add-in for the Account Tracking application. COM add-ins allow you to extend Outlook in richer ways than when you use Outlook Forms. For more advanced functionality, such as showing property pages, we will use a COM add-in. We'll start by looking at how the COM add-in works and then delve into the code that implements it. The Account Tracking COM add-in includes the following features:
A property page that allows users to set different options.
On startup, checking for an Account Tracking group and a shortcut. If they do not exist, the add-in can create them.
When creating the shortcut, enabling a folder home page for the Account Tracking folder.
Custom buttons on the command bar.
Notification to users via e-mail when new tasks or accounts are assigned to them.
Notification to users via e-mail when changes to an account are made.
To set up the Account Tracking COM add-in, we first have to compile and register it. To compile it, you need a machine with Outlook and Visual Basic 6.0 installed. Follow these steps to compile the add-in:
Open the Account Admin folder from the sample.
Open AccountPP.vbp in Visual Basic 6.0.
Compile AccountPP.ocx. (This file is the ActiveX control property page. Compiling automatically registers it.)
Open AccountAdminDLL.vbp in Visual Basic 6.0.
Change the constant STRFOLDERHOMEPAGEPATH to the location of the FullContacts.htm file.
Search for the second occurrence of oNS.Folders , and change the statement
Set oFolder = oNS.Folders("Public Folders").Folders( _ "All Public Folders").Folders("Account Tracking")
to the location of your Account Tracking folder.
Compile AccountAdminDLL.dll.
Note | You might need to specify the location of the AccountPP.ocx in the References dialog box. If AccountPP is displayed as MISSING in the References dialog box, deselect it, click OK to close the dialog box, open the References dialog box again, and browse for the location of AccountPP.ocx. |
Double-click on AccountAdminDLL.reg to add the appropriate entries in the registry.
Launch Outlook.
From the Tools menu, choose Options. Click on the Other tab, click the Advanced Options button, and then click the COM Add-Ins button. In the COM Add-Ins dialog box, check that AccountAdmin is selected as an available add-in.
Log off, and close Outlook.
Restart Outlook.
To test some of the COM add-in options, you have to turn them on using the Account Tracking tab, shown in Figure 8-18. This property page is the AccountPP control we compiled in Visual Basic 6.0. (Later in the chapter, we will examine how it is constructed .) To display this property page, choose Options from the Tools menu in Outlook and then click on the Account Tracking tab. Be sure all options are selected, and then click OK. (The Account Tracking tab is also available from the folder Properties dialog box, which you open by right-clicking on the Account Tracking folder and choosing Properties.)
The Account Tracking settings selected by the user in the Options dialog box or the Properties dialog box are automatically written to the registry so that the COM add-in can track the settings for each Outlook session. Figure 8-19 shows the registry with the user 's settings for the COM add-in.
Now that the COM add-in is set up and the options are turned on, let's see how the add-in works. Select the Account Tracking folder in Outlook, and then choose New Account Info from the Actions menu. Fill out the information for a new account. Be sure to add yourself as a team member, using your full name , on the Account Team tab. Then click on the Account Tasks tab and add a new task.
Note | If you don't have Outlook Visual Basic Scripting Support installed, a message will appear when you try to display New Account Info, indicating that scripting is not supported. You can install Outlook Visual Basic Scripting Support by rerunning setup for Office or choosing Change from the Add/Remove programs interface for Office. |
If you left yourself as the task owner, after you add a new task the COM add-in should send two e-mail messages to your Inbox. The first message indicates that a new account has been created with you as a team member. The second message indicates that a task was assigned to you. Figure 8-20 shows an example of the second e-mail message.
The COM add-in can notify a user when the user is added to the account team or, if the user is already a member of the account team, when the account information changes in the application. The information change notification can be triggered by changing the revenue of the account, the team members on the account, or the address of the account. Figure 8-21 shows the e-mail message that's sent to the user when his account changes.
The Account Tracking COM add-in can also automatically search for an Account Tracking Outlook group and shortcut. This option was activated on the Account Tracking tab of the Options dialog box. When the user starts Outlook and an Account Tracking group or shortcut does not exist, the user is given the option to create a group or shortcut for the Account Tracking application, as shown in Figure 8-22. If the user clicks Yes, a new Account Tracking group and an Account Tracking shortcut are created on the Outlook Bar. If the Account Tracking Group dialog box is not displayed, try restarting Windows and opening Outlook.
If the When Creating Shortcut Enable Folder Homepage As Default View On Folder option is selected on the Account Tracking tab, the folder home page will be set as the default view for the folder. For the Account Tracking application, the folder home page is FullContacts.htm, which was specified earlier in the AccountAdminDLL project.
The COM add-in includes a new command bar and command buttons designed specifically for the Account Tracking application. After you restart Outlook, the add-in should display the message box shown in Figure 8-23, which asks the user whether to create and display these new buttons. If the message box is not displayed, try clicking on the newly created Account Tracking shortcut in the Account Tracking group.
The buttons make it easier for users to create new accounts, contacts, or tasks. Figure 8-24 shows the new command bar in Outlook. If these buttons are not displayed, right-click on the command bar and choose Account Tracking.
Let's review our assumptions about the implementation of the Account Tracking COM add-in. To make the code easier to digest, we'll assume that users will always load the COM add-in at startup. For this reason, as you'll see, the OnConnection event for the COM add-in is pretty bare. Loading the add-in at startup calls the OnConnection and the OnStartupComplete events, so most of the code is included in the OnStartupComplete event for simplicity. If you want to be able to disconnect and reconnect the Account Tracking COM add-in, you must move some of the code from the OnStartupComplete event into a subroutine and then call that subroutine from both events. You must also revise the code to properly initialize some of the Outlook object variables .
The COM add-in options are stored in the registry. The registry path to these options is included in a code module with the add-in. To change the registry location for the COM add-in options, you simply change this path .
The first section of the code we'll examine searches for the Account Tracking group and shortcut on the Outlook Bar. As I mentioned earlier, this code runs only when the add-in is loaded and connected upon startup in Outlook. The main portion of this code is implemented in the OnStartupComplete event for the add-in. Remember that you must place Implements IDTExtensibility2 in your project before you can create code for this event. The following code is from the general declarations and the OnStartupComplete event procedure:
Implements IDTExtensibility2 Dim WithEvents oApplication As Outlook.Application Dim WithEvents oNS As Outlook.NameSpace Dim WithEvents oItems As Outlook.Items Dim oFolder As Outlook.MAPIFolder Dim WithEvents oExplorer As Outlook.Explorer Dim oCommandBar As Office.CommandBar Dim WithEvents oCreateAccountBHandler As Office.CommandBarButton Dim WithEvents oCreateAcctContactBHandler As Office.CommandBarButton Dim WithEvents oCreateAcctTaskBHandler As Office.CommandBarButton Dim oAcctItem As Outlook.PostItem Dim oFolders As Outlook.Folders Dim prefLookForShortcuts As Integer Dim prefMakeFolderHomepage As Integer Dim prefNotifyWhenNewMember As Integer Dim prefNotifyWhenNewTask As Integer Dim prefEnableAcctToolbar As Integer Dim oNewPage As Object Const STRFOLDERHOMEPAGEPATH = _ "file://C:\Webview\fullcontacts.htm" Private Sub IDTExtensibility2_OnStartupComplete(custom() As Variant) On Error Resume Next Set oNS = oApplication.GetNamespace("MAPI") 'Replace with your folder location. 'This is for offline users since they must put the folder 'in their Favorites folder. Set oFolder = oNS.Folders("Public Folders"). _ Folders("Favorites").Folders("Account Tracking") If oFolder Is Nothing Then 'You may prefer to add an EntryID here. Set oFolder = oNS.Folders("Public Folders").Folders( _ "All Public Folders").Folders("Account Tracking") End If If oFolder Is Nothing Then Set oFolders = oNS.Folders("Public Folders").Folders( _ "All Public Folders").Folders 'The following code can be used if you want to search 'the entire public folder hierarchy for the folder. 'For performance reasons, this code is commented out. ' If Not (oFolders Is Nothing) Then ' Set otmpFolder = oFolders.GetFirst ' Do While Not (otmpFolder Is Nothing) ' ListFolders otmpFolder ' If otmpFolder Is Nothing Then ' Set otmpFolder = oFolders.GetNext ' Else ' Exit Do ' End If ' Loop ' End If ' If you do use this code, you need to uncomment the ' ListFolders subroutine as well as add a check here ' to see if oFolder is nothing after finishing. MsgBox "You have the Account Tracking COM add-in loaded" & _ " but you have no Account Tracking folder. " & _ "You may wish to unload the COM add-in.", _ vbOKOnly + vbInformation, "Account Tracking COM add-in" Exit Sub End If Set oItems = oFolder.Items Set oExplorer = oApplication.ActiveExplorer 'See if the user wants us to check for shortcuts If prefLookForShortcuts = 1 Then 'The following code checks to see if the user has 'an Outlook shortcut and group for the Account Tracking 'application. Dim oPane As OutlookBarPane Dim oOLBarStorage As OutlookBarStorage Dim oOLBarGroups As OutlookBarGroups Dim oOLBarGroup As OutlookBarGroup Dim oOLBarShortcuts As OutlookBarShortcuts Dim oOLBarShortcut As OutlookBarShortcut 'Used if shortcut found with no group Dim otmpOLAccountBarGroupIndex As Integer Dim otmpOLBarGroup As OutlookBarGroup Dim otmpOLShortcuts As OutlookBarShortcuts Dim BarCounter As Integer Dim ShortcutCounter As Integer Set oPane = oExplorer.Panes("OutlookBar") Set oOLBarStorage = oPane.Contents Set oOLBarGroups = oOLBarStorage.Groups boolFoundAcctGroup = 0 boolFoundAcctShortcut = 0 BarCounter = 1 For Each oOLBarGroup In oOLBarGroups 'For debugging purposes 'MsgBox "Group: " & oOLBarGroup.Name If oOLBarGroup.Name = "Account Tracking" Then boolFoundAcctGroup = BarCounter End If Set oOLBarShortcuts = oOLBarGroup.Shortcuts ShortcutCounter = 1 For Each oOLBarShortcut In oOLBarShortcuts 'For debugging purposes 'MsgBox oOLBarShortcut.Name Err.Clear If IsObject(oOLBarShortcut.Target) Then 'Check to see if this is the file target by 'checking error. If oOLBarShortcut.Target.Name = _ "Account Tracking" Then If Err.Number = -2147319765 Then 'File Target ElseIf Err.Number = 0 Then 'For Debugging purposes 'MsgBox "Account Tracking Folder: " & _ oOLBarShortcut.Target.Name boolFoundAcctShortcut = ShortcutCounter otmpOLAccountBarGroupIndex = BarCounter End If End If Else 'The target is a URL string End If ShortcutCounter = ShortcutCounter + 1 Next BarCounter = BarCounter + 1 Next 'For debugging purposes 'MsgBox boolFoundAcctShortcut & boolFoundAcctGroup. 'Check to see whether shortcut exists without group. If (boolFoundAcctShortcut <> 0) And _ (boolFoundAcctGroup = 0) Then 'Check to see whether they want to remove the 'shortcut and move it to a new group. Response = MsgBox("You have an Account " & _ "Tracking shortcut without an Account " & _ "Tracking group. Would you like to create a " & _ "new Account Tracking group and move the " & _ "Account Tracking shortcut there?", & _ vbYesNo + vbQuestion, "Account Tracking") If Response = vbYes Then 'Delete the old Account Tracking shortcut. 'Get the Outlook Bar for the shortcut. Set otmpOLBarGroup = _ oOLBarGroups.Item(otmpOLAccountBarGroupIndex) Set otmpOLShortcuts = otmpOLBarGroup.Shortcuts otmpOLShortcuts.Remove boolFoundAcctShortcut Dim otmp2OLBarGroup As OutlookBarGroup Dim otmp2OLShortcuts As OutlookBarShortcuts 'Create a new Account Tracking group Set otmp2OLBarGroup = oOLBarGroups.Add( _ "Account Tracking", oOLBarGroups.Count + 1) 'For debugging purposes 'MsgBox "Group: " & otmp2OLBarGroup.Name Set otmp2OLShortcuts = otmp2OLBarGroup.Shortcuts otmp2OLShortcuts.Add oFolder, "Account Tracking" 'Check to see whether they want us to create a 'Web view. If prefMakeFolderHomepage = 1 Then 'Create the Web view oFolder.WebViewAllowNavigation = True oFolder.WebViewOn = True oFolder.WebViewURL = STRFOLDERHOMEPAGEPATH End If End If 'Check to see whether group exists with no shortcut. ElseIf (boolFoundAcctShortcut = 0) And _ (boolFoundAcctGroup <> 0) Then 'See if user wants to add shortcut to group. Response = MsgBox("There is an Account " & _ "Tracking Group without a shortcut to the " & _ "Account Tracking folder. Do you want " & _ "to add a shortcut to the Account Tracking " & _ "folder in this group?", _ vbYesNo + vbQuestion, "Account Tracking") If Response = vbYes Then Dim otmpOLGroup As OutlookBarGroup Set otmpOLGroup = oOLBarGroups.Item(boolFoundAcctGroup) 'For debugging purposes 'MsgBox otmpOLGroup.Name Set otmpOLShortcuts = otmpOLGroup.Shortcuts otmpOLShortcuts.Add oFolder, "Account Tracking" 'Check to see whether user wants us to create a 'Web view If prefMakeFolderHomepage = 1 Then 'Create the Web view oFolder.WebViewAllowNavigation = True oFolder.WebViewOn = True oFolder.WebViewURL = STRFOLDERHOMEPAGEPATH End If End If 'Check to see whether there is neither ElseIf (boolFoundAcctGroup = 0) And _ (boolFoundAcctShortcut = 0) Then Response = MsgBox("You don't have an Account " & _ "Tracking Group or Shortcut. Would you like to " & _ "create them?", vbYesNo + vbQuestion, _ "Account Tracking Group") If Response = vbYes Then Set otmpOLGroup = oOLBarGroups.Add( _ "Account Tracking", oOLBarGroups.Count + 1) 'For debugging purposes 'MsgBox otmpOLGroup.Name Set otmpOLShortcuts = otmpOLGroup.Shortcuts otmpOLShortcuts.Add oFolder, "Account Tracking" 'Check to see whether user wants us to create a 'Web view If prefMakeFolderHomepage = 1 Then 'Create the Web view oFolder.WebViewAllowNavigation = True oFolder.WebViewOn = True oFolder.WebViewURL = STRFOLDERHOMEPAGEPATH End If End If 'There is one other scenario with an Account Tracking 'shortcut and an Account Tracking group. 'In this scenario, do nothing. End If End If End Sub Sub ListFolders(objFolder) ' On Error Resume Next ' If Not (objFolder Is Nothing) Then ' 'Check to see whether Account Tracking folder ' If objFolder.Name = "Account Tracking" Then ' Set oFolder = objFolder ' Exit Sub ' Else ' 'Check for child folders ' Set objFolders = objFolder.Folders ' Set objFolder = objFolders.GetFirst ' Do While Not (objFolder Is Nothing) ' ListFolders objFolder ' Set objFolder = objFolders.GetNext ' Loop ' End If ' End If End Sub
The code first sets some of the application's variables to their correct values. The commented sections of the code show how to search for the Account Tracking public folder within the public folder hierarchy. Obviously, if the public folder hierarchy is large, completing the search can take a long time; you might decide not to implement the code. You can replace the code in the event with code that retrieves the Account Tracking folder by EntryID, which allows you to eliminate any hardcoded paths to the folder.
The code then checks to see whether the user wants to look for the Account Tracking group and shortcut. This configuration information is pulled from the registry, an operation that I'll explain shortly. If the user wants to search for the group and the shortcut, the code uses some of the new objects and collections in the Outlook object model.
The code grabs the OutlookBar pane from the current Explorer object. Then the code retrieves the OutlookBarStorage object for the contents of that pane, and it retrieves the OutlookBarGroups in the storage object. From there, the code uses a For Each loop to find the Account Tracking group.
Note | You can replace the For Each loop with a simpler version that uses the Item method on the OutlookBarGroups collection. By using the Item method, you can retrieve the group by name. But to show you how to use this collection, the code sample uses the For Each loop. |
In the For Each loop, the code retrieves each shortcut in the group by using the OutlookBarShortcuts collection. It then loops through each shortcut, using the Account Tracking name to determine whether the target for that shortcut is the Account Tracking folder. You can also compare the EntryIDs of the target with the original folder we set earlier in the code. How you implement this is your choice.
In the IDTExtensibility2_OnStartupComplete procedure, you can see some error handling code. We have to make sure we do not error out on file system targets, which is a problem only with earlier versions of Outlook because Outlook 2003 has removed the file system functionality. This error-handling code skips file system targets.
Counters in the code let the application know the index of the Account Tracking group, as well as the index of the Account Tracking shortcut within that group if the shortcut exists. The code uses these counters to check a number of scenarios ”for example, whether the group and the shortcut both exist. This checking scenario occurs when both counters are 0. The code also checks to see whether the shortcut exists, but not the group. If the shortcut exists, the code can create a new group, removing the existing shortcut and placing it in the new group. The code counts where the shortcut exists in a certain group, which simplifies removing the shortcut by using its index. If the group exists without a shortcut, the code can create a shortcut in the group and associate the shortcut with the folder.
The code also checks the option settings to see whether it should make the default view on the folder the folder home page. If the option that enables the folder home page (the option appears on the Account Tracking tab of the Options dialog box) is selected, the code uses the WebViewAllowNavigation , WebViewOn , and WebViewURL properties to set up the folder home page. WebViewAllowNavigation returns or sets the navigation mode for the folder if the user is viewing a folder home page. When this property is set to True , Outlook allows users to navigate using the Forward and Back buttons on the Microsoft Web Control. When this property is set to False , Outlook displays the folder home page in Native mode, which makes the Forward and Back buttons unavailable. If you set this property to True , the folder home page will provide more functionality for the user but will run a bit more slowly.
The WebViewOn property returns or sets the state of the folder home page. If you set this property to True , as the preceding code does, Outlook will display the folder home page as the default view for the folder.
The WebViewURL property returns or sets the string that identifies the URL for the folder home page. You can use any valid URL in this property, including a URL that begins with file or http . The application sets this property to a constant string, which is set in the declarations section of the program.
The next section of the code we'll look at tracks when users add or change account or task items in the folder. The application does not track deleted items, and Outlook does not pass the deleted item in its ItemRemove event, which makes figuring out what was removed from the folder difficult.
To track additions and changes to items, the code declares the variable oItems as an Outlook.Items collection by using the WithEvents keyword. WithEvents lets you select the events you want to handle in the Visual Basic environment for the collection. The code for this application implements the ItemAdd and ItemChange events for the Items collection. Let's first review the ItemAdd event, shown here:
Private Sub oItems_ItemAdd(ByVal Item As Object) Dim oUser As Variant Dim oMail As Outlook.MailItem Dim oAttach As Outlook.Attachment Dim oItem As Outlook.TaskItem Dim oAccountItem As Outlook.PostItem Dim oUserProps As Outlook.UserProperties oUser = oNS.CurrentUser.Name 'Check to see what type of item was just created If Item.Class = olTask Then 'Check to see whether user wants notification If prefNotifyWhenNewTask = 1 Then 'Transform into TaskItem Set oItem = Item 'Check to see whether the current user is the owner If oItem.Owner = oUser Then 'Send to the user a message with a link to the item Set oMail = oApplication.CreateItem(olMailItem) With oMail .To = oUser .Subject = "New Account Task for the " & _ Item.ConversationTopic & _ " account is assigned to you." .Body = "A new task - " & Item.Subject & _ " - was assigned to you. " & _ "To view this task, please click" _ & " on the link below." End With Set oAttach = oMail.Attachments.Add(Item, _ olEmbeddeditem) oMail.Recipients.ResolveAll oMail.Send End If End If ElseIf Item.MessageClass = "IPM.Post.Account info" Then 'Check to see whether user wants notification If prefNotifyWhenNewMember = 1 Then Set oAccountItem = Item boolAccountMember = 0 Set oUserProps = oAccountItem.UserProperties If oUserProps.Find("txtAccountConsultant") = oUser Then boolAccountMember = 1 ElseIf oUserProps.Find("txtAccountExecutive") = _ oUser Then boolAccountMember = 1 ElseIf oUserProps.Find("txtAccountSalesRep") = _ oUser Then boolAccountMember = 1 ElseIf oUserProps.Find("txtAccountSE") = oUser Then boolAccountMember = 1 ElseIf oUserProps.Find("txtAccountSupportEngineer") = _ oUser Then boolAccountMember = 1 End If If boolAccountMember = 1 Then 'Send to the user a message with a link to the item Set oMail = oApplication.CreateItem(olMailItem) With oMail .To = oUser .Subject = "A New Account - " & _ Item.ConversationTopic & _ " - has been created with you as a " & _ "team member." .Body = "A new account - " & Item.Subject & _ " - was created with you as a team member." _ & " To view this account, please click" _ & " on the link below." End With Set oAttach = oMail.Attachments.Add(Item, _ olEmbeddeditem) oMail.Recipients.ResolveAll oMail.Send End If End If End If End Sub
The oItems_ItemAdd event procedure first retrieves the name of the current user. It then checks the class of the item that was added to the collection in the folder. If the item is a task, the code coerces the item into an Outlook TaskItem object before it attempts to call the methods and properties on that object type. If the current user is the owner of the new task, the application creates an e-mail message with the new task attached as a shortcut and then sends the e-mail to the user. The user receives the notification e-mail in the Inbox.
The code used to notify users of a new account in the folder is similar to the task code. However, instead of checking the owner property, the account code checks the item's custom properties, which correspond to the names of the team members for the account. If the user is found in one of these properties, the code sends an e-mail message to the user indicating that she has a new account for which she is a team member.
The only aspect of this subroutine and the next subroutine that you might want to change is the user who sends the item. In the current implementation, the user sends the update. You can change this functionality so that the public folder sends the message by giving your users Send On Behalf Of permissions in your Exchange Administrator program for the folder. You then either expose the folder in the address list so that it can be added to the From field, or you place the address of the folder in the From field. If you don't want the e-mail to come from the folder, you can create a mailbox to which you assign Send On Behalf Of permissions in the Exchange Administrator program.
To notify the user that she has been assigned an existing task or that a task for which she is the owner has changed, the code uses the ItemChange event for the Items collection. This event is also used to notify the user when she is added to an account team after the account is created or when an account for which she is a team member has been changed. The following code implements the ItemChange event:
Private Sub oItems_ItemChange(ByVal Item As Object) Dim oUser As Variant Dim oMail As Outlook.MailItem Dim oAttach As Outlook.Attachment Dim oTaskItem As Outlook.TaskItem Dim oAccountItem As Outlook.PostItem Dim oUserProps As Outlook.UserProperties 'Since the event doesn't show us how the item changed, 'we need to notify the user of the change but not what 'specifically changed on the item oUser = oNS.CurrentUser.Name 'Check to see what type of item was just created If Item.Class = olTask Then 'Check to see whether the user wants to be notified If prefNotifyWhenNewTask = 1 Then 'Transform into TaskItem Set oTaskItem = Item 'Check to see whether the current user is the owner If oTaskItem.Owner = oUser Then 'Send to the user a message with a link to the item Set oMail = oApplication.CreateItem(olMailItem) With oMail .To = oUser .Subject = "An Account Task for the " & _ Item.ConversationTopic & _ " account is assigned to you." .Body = "A task - " & Item.Subject & _ " - was assigned to you or was changed " & _ "by another user. " _ & "To view this task, please click" _ & " on the link below." End With Set oAttach = oMail.Attachments.Add(Item, _ olEmbeddeditem) oMail.Recipients.ResolveAll oMail.Send End If End If ElseIf Item.MessageClass = "IPM.Post.Account info" Then Set oAccountItem = Item boolAccountMember = 0 Set oUserProps = oAccountItem.UserProperties If oUserProps.Find("txtAccountConsultant") = oUser Then boolAccountMember = 1 ElseIf oUserProps.Find("txtAccountExecutive") = oUser Then boolAccountMember = 1 ElseIf oUserProps.Find("txtAccountSalesRep") = oUser Then boolAccountMember = 1 ElseIf oUserProps.Find("txtAccountSE") = oUser Then boolAccountMember = 1 ElseIf oUserProps.Find("txtAccountSupportEngineer") = _ oUser Then boolAccountMember = 1 End If If boolAccountMember = 1 Then 'Send to the user a message with a link to the item Set oMail = oApplication.CreateItem(olMailItem) With oMail .To = oUser .Subject = "You have been assigned to the - " & _ Item.ConversationTopic & _ " - account as a team member." .Body = "The account - " & Item.Subject & _ " now has you as a team member or someone " & _ "has changed a value " _ & "on the account. To view this account, " _ & "please click on the link below." End With Set oAttach = _ oMail.Attachments.Add(Item, olEmbeddeditem) oMail.Recipients.ResolveAll oMail.Send End If End If End Sub
The code that handles the ItemChange event is much like the code for ItemAdd , so I won't cover it in detail. The only difference between the two event handlers is the text of the message sent to the user. Because Outlook does not pass the property that was changed on the item as a parameter to ItemChange , the code can't know whether the user was assigned to an item or which property was changed. For this reason, the message text notifies the user only that a change to the item has occurred.
The next section of code we'll look at adds a custom command bar and command buttons to the Outlook toolbar. It also provides event handlers for the buttons when users click them. The code for this functionality is shown here:
Private Sub oExplorer_BeforeFolderSwitch( _ ByVal NewFolder As Object, Cancel As Boolean) 'Add CommandBar buttons to the Outlook user interface for easy 'creation Dim oTempFolder As Outlook.MAPIFolder Dim oCommandBars As Office.CommandBars Dim oCommandBar2 As Office.CommandBar Dim oControls As Office.CommandBarControls Dim oControl As Office.CommandBarButton Dim otmpCommandBar As Office.CommandBar 'Make sure they want to do this If prefEnableAcctToolbar = 1 Then 'First check to see whether the folder is the 'Account Tracking folder If Not (NewFolder Is Nothing) Then Set oTempFolder = NewFolder boolFoundCommandBar = 0 'You might want to put in the EntryID here rather than 'the name If oTempFolder.Name = "Account Tracking" Then 'Check to see whether command bar already exists Set oCommandBars = _ oApplication.ActiveExplorer.CommandBars For Each oCommandBar In oCommandBars If oCommandBar.Name = "Account Tracking" Then boolFoundCommandBar = 1 Set otmpCommandBar = oCommandBar Exit For End If Next If boolFoundCommandBar = 0 Then 'Need to create the command bar 'Maybe add text box for searching for account 'or contacts Response = MsgBox("The Account Tracking " & _ "application can create a toolbar with " _ & "the most commonly used commands. Do " & _ you want to have the application create" _ & " the toolbar and display it?", _ vbYesNo + vbQuestion, "Account Tracking") If Response = vbYes Then 'Create the command bar Set oCommandBar = oCommandBars.Add( _ "Account Tracking", Temporary:=False) Set oControls = oCommandBar.Controls 'Create the buttons, and set the 'event handler objects to the 'buttons. 'Create the first button. Set oControl = oControls.Add( _ Type:=msoControlButton, ID:=1, _ Temporary:=False) oControl.Caption = "Create New &Account" oControl.FaceId = 609 oControl.Style = msoButtonIconAndCaption Set oCreateAccountBHandler = oControl 'Create the second button 'Context menu Set oControl = oControls.Add( _ Type:=msoControlButton, ID:=1, _ Temporary:=False) oControl.Caption = "Create Account &Contact" oControl.FaceId = 607 oControl.Style = msoButtonIconAndCaption Set oCreateAcctContactBHandler = oControl 'Create the third button 'Context menu Set oControl = oControls.Add( _ Type:=msoControlButton, ID:=1, _ Temporary:=False) oControl.Caption = "Create Account &Task" oControl.FaceId = 329 oControl.Style = msoButtonIconAndCaption Set oCreateAcctTaskBHandler = oControl 'Make the command bar visible oCommandBar.Visible = True oCommandBar.Position = msoBarTop End If Else 'Account Tracking command bar already exists. 'See if they want to do this. If prefEnableAcctToolbar = 1 Then 'Check to see if visible; if not, make 'visible Dim oCBControls As Office.CommandBarControls Dim oCBButton As Office.CommandBarButton If otmpCommandBar.Enabled = False Then otmpCommandBar.Enabled = True End If If otmpCommandBar.Visible = False Then otmpCommandBar.Visible = True End If End If End If Else 'It's not the Account Tracking folder. 'Look for the toolbar and disable it. On Error Resume Next Set oCommandBars = oApplication.ActiveExplorer. _ CommandBars Set oCommandBar = oCommandBars("Account Tracking") oCommandBar.Enabled = False End If Else 'It's a file system folder! 'Disable toolbar. On Error Resume Next Set oCommandBars = _ oApplication.ActiveExplorer.CommandBars Set oCommandBar = oCommandBars("Account Tracking") oCommandBar.Enabled = False End If End If Set oTempFolder = Nothing Set oCommandBars = Nothing Set oCommandBar = Nothing Set oControls = Nothing Set oControl = Nothing End Sub
The application includes the Outlook Explorer object's BeforeFolderSwitch event, as shown in the preceding code. The oExplorer_BeforeFolderSwitch event procedure is passed, as a MAPIFolder , the folder that the user is trying to switch to. The code checks the folder's name to see whether it is the Account Tracking folder. You can also perform this comparison by using the EntryID of the folder.
If the folder is the Account Tracking folder, the code searches the CommandBars collection of the Explorer object to see whether an Account Tracking command bar exists. If the code finds the Account Tracking command bar, it simply makes the command bar visible. If the code doesn't find the Account Tracking command bar, it creates the command bar if the user selected to do so as a preference. The code adds a new CommandBar object to the CommandBars collection by passing the name of the command bar as well as the Temporary parameter. The Temporary parameter indicates that Outlook should persist the command bar between Outlook sessions. Then the code starts creating the buttons on the command bar.
To create the buttons, the code uses the Controls collection on the CommandBar object. The code then adds three button controls to the collection. The control type is identified with the msoControlButton constant. (You can create other types of controls on your command bars besides buttons ”drop-down lists, combo boxes, and popups.) The code also passes an ID of 1 for all the controls; this value indicates that the control is a custom control and is not built in. The code passes the Temporary parameter and sets it to False so Outlook persists the buttons between sessions. The Add method returns an appropriate object for the type of control you specify, such as a CommandBarButton , a CommandBarComboBox , or CommandBarPopup object.
After the code receives the CommandBarButton object from the Add method on the Controls collection, it starts setting properties on the CommandBarButton object. The first property it sets is the Caption property, which is a string containing the caption text for the control. Notice that you can place an ampersand before one of the letters in the control caption to provide a shortcut key for the control. This Caption property is the default screen tip for the control.
The second property the code sets is FaceId , which specifies how the button face should look. Office has a number of built-in faces that you can use. If you want to use a custom face on your buttons, you must specify for this property and copy your custom face to the Clipboard. Then you can use the PasteFace method on the CommandBarButton object to paste the face from the Clipboard onto your control.
The final property the code sets is the Style property. This property can have one of many values, such as the msoButtonIconAndCaption constant, which displays the button face as well as the caption text, or msoButtonCaption , which displays only the caption. To enhance usability of the buttons, the code displays both the icon and the caption. For a list of all the style values, refer to the general Office Help file.
After the new CommandBarButtons are created and set, they are assigned to other variables, for example oCreateAccountBHandler . If you take a look at the declarations section of the code, shown earlier in the chapter, you'll notice that oCreateAccountBHandler is declared as an Office.CommandBarButton using the WithEvents keyword. The WithEvents keyword specifies that oCreateAccountBHandler is used to respond to events for a CommandBarButton . The following code shows the event handlers for the three buttons on the Account Tracking command bar:
Private Sub oCreateAccountBHandler_Click(ByVal Ctrl As _ Office.CommandBarButton, CancelDefault As Boolean) Dim oAccount As Outlook.PostItem Set oAccount = oFolder.Items.Add("IPM.Post.Account info") oAccount.Display End Sub Private Sub oCreateAcctContactBHandler_Click(ByVal Ctrl As _ Office.CommandBarButton, CancelDefault As Boolean) Dim oSelection As Outlook.Selection On Error Resume Next boolFoundAccountItem = 0 Set oSelection = oExplorer.Selection For Each oItem In oSelection If oItem.MessageClass = "IPM.Post.Account info" Then boolFoundAccountItem = boolFoundAccountItem + 1 'Set the item found to a global variable just in case 'it is the only one found Set oAcctItem = oItem End If Next If boolFoundAccountItem = 0 Then MsgBox "You have no account selected. Please select " & _ "an account and try again.", _ vbOKOnly + vbExclamation, "Create Contact" Exit Sub ElseIf boolFoundAccountItem > 1 Then MsgBox "You have more than one account selected. " & _ "Please select only one account and try again.", _ vbOKOnly + vbExclamation, "Create Contact" Exit Sub ElseIf boolFoundAccountItem = 1 Then Set AccountContactForm = oAcctItem.Actions( _ "Create New Account Contact").Execute AccountContactForm.Display (True) End If End Sub Private Sub oCreateAcctTaskBHandler_Click( _ ByVal Ctrl As Office.CommandBarButton, CancelDefault As Boolean) On Error Resume Next boolFoundAccountItem = 0 Set oSelection = oExplorer.Selection For Each oItem In oSelection If oItem.MessageClass = "IPM.Post.Account info" Then boolFoundAccountItem = boolFoundAccountItem + 1 'Set the item found to a global variable just in case 'it is the only one found Set oAcctItem = oItem End If Next If boolFoundAccountItem = 0 Then MsgBox "You have no account selected. Please select " & _ "an account and try again.", _ vbOKOnly + vbExclamation, "Create Contact" Exit Sub ElseIf boolFoundAccountItem > 1 Then MsgBox "You have more than one account selected. " & _ "Please select only one account and try again.", _ vbOKOnly + vbExclamation, "Create Contact" Exit Sub ElseIf boolFoundAccountItem = 1 Then Set AccountTaskForm = oAcctItem.Actions( _ "Create New Account Task").Execute AccountTaskForm.Display (True) End If End Sub Private Function CheckSelection(strMessageClass) As Integer On Error Resume Next boolFoundAccountItem = 0 Set oSelection = oExplorer.Selection For Each oItem In oSelection If oItem.MessageClass = strMessageClass Then boolFoundAccountItem = boolFoundAccountItem + 1 'Set the item found to a global variable just in case 'it is the only one found Set oAcctItem = oItem End If Next CheckSelection = boolFoundAccountItem End Function
Notice that we use the standard Outlook object model to implement all three event handlers. The oCreateAccountBHandler_Click event handler is the simplest of the three because it only adds a new account form to the folder and displays this form to the user. The other two event handlers, oCreateAcctContactBHandler_Click and oCreateAcctTaskBHandler_Click , also use some of the features in the Outlook object model. Before a user can create either an account contact or a task, he must first select an account. The code for these handlers uses the Selection collection on the Explorer object to determine what the user has selected in the user interface.
The oCreateAcctContactBHandler_Click and oCreateAcctTaskBHandler_Click event handlers both loop through the collection of selected items to see whether any items are account items. Users can select multiple items in the user interface, so the code tracks how many account items it counts in the Selection collection. Both handlers set the last account item they see to a global variable just in case this account item is the only one in the selection. Because the add-in cannot guess for which account the user wants to create a new contact or task, the subroutines display error messages when the user has more than one account selected in the user interface. If no accounts are selected, the application displays an error message telling the user to select an account. If only one account is selected, the application calls the custom actions on the account form to create either a new account contact or a new task.
The final section of code implements the property pages that allow users to pick their custom settings for the application. We'll look at this code and also quickly look at how the registry is used to store these settings for the user. As you examine this code, you'll see some interesting objects implemented in Outlook ”for example, the PropertyPage object, the PropertyPageSite object, and the PropertyPages collection object.
Custom property pages allow you to integrate your applications more tightly into the Outlook application. They also enable users to configure your application more easily because your customizations are part of the standard Outlook configuration pages. The following code implements the property page extension code in the COM add-in. Then we will look at the code for the ActiveX control, which creates the actual property page that appears.
Private Sub SetDefaultProps() oNewPage.prefLookForShortcuts = prefLookForShortcuts oNewPage.prefEnableAcctToolbar = prefEnableAcctToolbar oNewPage.prefMakeFolderHomepage = prefMakeFolderHomepage oNewPage.prefNotifyWhenNewMember = prefNotifyWhenNewMember oNewPage.prefNotifyWhenNewTask = prefNotifyWhenNewTask End Sub Private Sub oNS_OptionsPagesAdd(ByVal Pages As _ Outlook.PropertyPages, ByVal Folder As Outlook.MAPIFolder) If Folder.Name = "Account Tracking" Then 'Add the Options page to the folder Set oNewPage = CreateObject("AccountPP.UCAdminPage") SetDefaultProps oNewPage.oAdminDLL = Me Pages.Add oNewPage End If End Sub Private Sub oApplication_OptionsPagesAdd(ByVal Pages As _ Outlook.PropertyPages) Set oNewPage = CreateObject("AccountPP.UCAdminPage") SetDefaultProps oNewPage.oAdminDLL = Me Pages.Add oNewPage End Sub Public Sub SetRegistryValues(prefShortcuts, prefAcctToolbar, _ prefFolderHomepage, prefNotifyMember, prefNotifyTask) 'This subroutine is called by the Property page to have the 'Options page persist its values boolSuccess = SetAppRegValue("CheckShortcuts", REG_DWORD, _ prefShortcuts) boolSuccess = SetAppRegValue("AcctToolbar", REG_DWORD, _ prefAcctToolbar) boolSuccess = SetAppRegValue("FolderHomepage", REG_DWORD, _ prefFolderHomepage) boolSuccess = SetAppRegValue("NotifyTeamMember", REG_DWORD, _ prefNotifyMember) boolSuccess = SetAppRegValue("NotifyAcctTask", REG_DWORD, _ prefNotifyTask) End Sub
In this code, two subroutines handle the OptionsPagesAdd event. The first subroutine uses the NameSpace object. The OptionsPagesAdd event fires for the NameSpace object when the user clicks on a folder in the namespace you are monitoring and then selects Properties. The NameSpace OptionsPagesAdd event procedure is passed two parameters: Pages , which is a collection of Outlook PropertyPages , and Folder , which is the folder the user is trying to retrieve properties for.
The second subroutine uses the Outlook Application object. The OptionsPagesAdd event fires the Application object when a user chooses Options from the Tools menu to configure the overall application settings for Outlook. Both OptionsPagesAdd event handlers call the same code because there is only one way to customize the Account Tracking application. However, for your add-ins, you can have two different property pages for these two different events if needed.
The first step both event handlers perform is to create an object. This object, an ActiveX control, is the actual property page that the subroutine will add to the PropertyPages collection. We'll look at the code for the control later in this chapter.
The next step the event handler performs is to set the default properties for the new property page. It does this by setting some of the variables on the control to the values that are currently stored in the add-in. All of these values are originally retrieved from the registry.
After all properties for the controls on the form are set, the code passes an add-in reference to the new property page. You might be wondering why it does this. The main reason is to allow the ActiveX control property page to call back into the add-in when a user makes a change and applies it. If the ActiveX control does not call back into the add-in, the add-in will not know that something has changed and will not behave as expected.
The final step in the code is to add the new page to the PropertyPages collection using the Add method of the collection. You can call this method in two ways. The first way, which is shown in the code, passes an object to the method so the object is displayed as a property page. The second way passes in the ProgID of the control as a string, which enables Outlook to create the control. If you use the second approach, you can also pass an optional string that is the caption for the property page. You'll see how to set the caption when we pass an ActiveX control later on.
Now that you know how to add our pages to the PropertyPages collection, we need to look at what the actual page should implement. The following code implements the ActiveX control, which is the property page extension. Figure 8-25 shows the interface for the control in Visual Basic 6.0 design mode.
Implements Outlook.PropertyPage Private oSite As Outlook.PropertyPageSite Dim m_prefLookForShortcuts As Integer Dim m_prefMakeFolderHomepage As Integer Dim m_prefNotifyWhenNewMember As Integer Dim m_prefNotifyWhenNewTask As Integer Dim m_prefEnableAcctToolbar As Integer Dim m_fDirty As Boolean Dim m_AdminDLL As Object Private boolInitializing As Boolean Private Sub SetDirty() If Not oSite Is Nothing Then m_fDirty = True oSite.OnStatusChange End If End Sub Public Sub RefreshControls() checkNotifyAccount.Value = m_prefNotifyWhenNewMember checkNotifyTask.Value = m_prefNotifyWhenNewTask checkPerformCheck.Value = m_prefLookForShortcuts CheckToolbar.Value = m_prefEnableAcctToolbar CheckWebShortcut.Value = m_prefMakeFolderHomepage End Sub Private Sub checkNotifyAccount_Click() If boolInitializing = False Then SetDirty m_prefNotifyWhenNewMember = checkNotifyAccount.Value End If End Sub Private Sub checkPerformCheck_Click() If boolInitializing = False Then SetDirty m_prefLookForShortcuts = checkPerformCheck.Value End If End Sub Private Sub checkNotifyTask_Click() If boolInitializing = False Then SetDirty m_prefNotifyWhenNewTask = checkNotifyTask.Value End If End Sub Private Sub CheckToolbar_Click() If boolInitializing = False Then SetDirty m_prefEnableAcctToolbar = CheckToolbar.Value End If End Sub Private Sub CheckWebShortcut_Click() If boolInitializing = False Then SetDirty m_prefMakeFolderHomepage = CheckWebShortcut.Value End If End Sub Private Sub PropertyPage_Apply() On Error GoTo PropertyPageApply_Err m_fDirty = False If Not m_AdminDLL Is Nothing Then m_AdminDLL.SetRegistryValues m_prefLookForShortcuts, _ m_prefEnableAcctToolbar, m_prefMakeFolderHomepage, _ m_prefNotifyWhenNewMember, m_prefNotifyWhenNewTask 'Refresh the add-in DLL settings m_AdminDLL.CheckRegistryValues End If Exit Sub PropertyPageApply_Err: MsgBox "Error in PropertyPage_Apply. Err# " & Err.Number _ & " and Err Description: " & Err.Description End Sub Private Property Get PropertyPage_Dirty() As Boolean PropertyPage_Dirty = m_fDirty End Property Private Sub PropertyPage_GetPageInfo(HelpFile As String, _ HelpContext As Long) HelpFile = "nothing.hlp" HelpContext = 102 End Sub Private Sub UserControl_EnterFocus() boolInitializing = False End Sub Private Sub UserControl_Initialize() m_fDirty = False boolInitializing = True End Sub Private Sub UserControl_InitProperties() On Error Resume Next Set oSite = Parent RefreshControls End Sub Public Property Get prefLookForShortcuts() As Variant prefLookForShortcuts = m_prefLookForShortcuts End Property Public Property Let prefLookForShortcuts(ByVal vNewValue As Variant) m_prefLookForShortcuts = vNewValue End Property Public Property Get prefMakeFolderHomepage() As Variant prefMakeFolderHomepage = m_prefMakeFolderHomepage End Property Public Property Let prefMakeFolderHomepage( _ ByVal vNewValue As Variant) m_prefMakeFolderHomepage = vNewValue End Property Public Property Get prefNotifyWhenNewMember() As Variant prefNotifyWhenNewMember = m_prefNotifyWhenNewMember End Property Public Property Let prefNotifyWhenNewMember( _ ByVal vNewValue As Variant) m_prefNotifyWhenNewMember = vNewValue End Property Public Property Get prefNotifyWhenNewTask() As Variant prefNotifyWhenNewTask = m_prefNotifyWhenNewTask End Property Public Property Let prefNotifyWhenNewTask( _ ByVal vNewValue As Variant) m_prefNotifyWhenNewTask = vNewValue End Property Public Property Get prefEnableAcctToolbar() As Variant prefEnableAcctToolbar = m_prefEnableAcctToolbar End Property Public Property Let prefEnableAcctToolbar( _ ByVal vNewValue As Variant) m_prefEnableAcctToolbar = vNewValue End Property Public Property Get Caption() As Variant Caption = "Account Tracking" End Property Public Property Get oAdminDLL() As Variant End Property Public Property Let oAdminDLL(ByVal vNewValue As Variant) Set m_AdminDLL = vNewValue End Property
As you can see from the preceding code, not much code is implemented in the extension. Property page extensions are actually pretty easy to write. You need to implement just a few important elements.
The first of these elements, which is at the top of the code, implements the PropertyPage interface. To implement this interface, you add a reference to the Outlook object model and then type Implements Outlook.PropertyPage in your declarations section. You can then select the methods and properties you need to implement for your property page. You must implement two methods, GetPageInfo and Apply , and one property, Dirty .
The GetPageInfo method is called by Outlook to retrieve help information about the property page for your users. In this method, you can set two parameters: HelpFile and HelpContext . HelpFile is a string that points to your help file. (The sample code points to a nonexistent help file.) The HelpContext parameter is a Long data type that specifies the context ID of the help topic associated with your property page.
The Apply method and the Dirty property work together with another method on the Outlook PropertyPageSite object, which we haven't discussed yet. The PropertyPageSite object, which in the sample code is declared in the declarations section, points to the container for your property page object. In this case, the container that holds the object is Outlook.
After you declare a variable to be a PropertyPageSite , you must initialize it. The best place to do this is in the intrinsic InitProperties event procedure of your ActiveX control. As you can see in the code, oSite , which is the PropertyPageSite variable, is set to the intrinsic Parent property of the ActiveX control. The Parent property in this case returns an Outlook PropertyPageSite object.
Now that you know how to retrieve the PropertyPageSite , we can continue looking at the Apply method and the Dirty property. When users make changes to your page, the code must be able tell Outlook that the page has become dirty and the Apply button should become active. To do this, you can keep a private variable that tracks whether the user has changed an option ”in essence, a dirty flag. When the user changes an option, you set this flag to True and then call the OnStatusChange method of the PropertyPageSite object.
The OnStatusChange method, in turn, forces Outlook to try to retrieve the Dirty property for your property page. You can see this operation implemented in the Property Get PropertyPage_Dirty procedure. The code sets the PropertyPage_Dirty variable to the value of the private dirty flag, which should be True , and returns that value to Outlook. Once Outlook receives a True value, it enables the Apply button.
So what happens when the user clicks this newly enabled Apply button? Well, your PropertyPage_Apply subroutine is called. In this subroutine, you should take whatever steps are necessary to apply the changes and also set your private dirty flag back to False . You can see in the code for our property page that I set the dirty flag to False and then attempt to save the values the user selected back to the registry.
You might be wondering what the m_AdminDLL object in this subroutine is. It is the reference to the add-in, which we passed to the property page when we created it. Because the registry functions are already implemented in the add-in, the property page just calls back to the add-in. The CheckRegistryValues call forces the add-in to refresh its internal values with the new values the user has selected.
The Apply method is also called when there is a dirty setting in your property page and the user clicks OK in the Properties or Options dialog box. The Apply method is not called when the user clicks Cancel.
The only other element you must implement in your property page is its caption. To set the caption, you must add a property to your application. To do this, from the Tools menu, select Add Procedure and then select the settings for creating a new public property, as shown in Figure 8-26. After you add the Caption property procedure, choose Procedure Attributes from the Tools menu and select the property you just created from the drop-down list. Click the Advanced button, and then select the Caption procedure ID in the Procedure ID dialog box, as shown in Figure 8-27. By doing this, you associate your property with the identifier for the caption property on a control. Outlook will query this property ID for the caption for your property page. You then simply implement the code to set this property to the value you want for your caption. You can see an example of the Caption property procedure in the previous code.