Monday, June 30, 2008

Set Follow Up Flag Icon For Unsent Mail

I am a big fan of the Follow Up Flag Icon (the coloured flags)in Outlook 2003 because it simplifies tracking all the important stuff that I have to do. By adding a coloured Flag Icon to email I can use the special For Follow Up search folder to just display the emails that I want to focus on. I also use Trog by Priacta which also uses these flags. But there is a problem (of course) and that is that Outlook won't allow me to set a Follow Up Flag Icon for an unsent email.

Well, I want to be able to do that so that I keep track of email that I have sent to people that I need to follow up on. I can send the email, then go to my Sent folder and then add the appropriate coloured flag but that is inefficient and just plain annoying. I don't send mail immediately anyway, so I have to use F9 and wait a bit or wait longer and remember to go and do it.

However after a bit of testing, I discovered that you can programmatically set the FlagIcon. But I didn't want to replace the inbuilt function completely, nor did I want to add another button to the commandbar. So I decided that I needed to be able to trap the button click and pop up my own form whenever I was looking at an unsent email. The trick is to sink the appropriate events using a class module.

Obviously, you can use these concepts to manage any number of tasks and in my example the form could be expanded to replace all of the functionality in Follow Up. I leave that to the reader as an exercise...

First, I designed a simple UserForm called frmSetFlagIcon. Here is an example, it doesn't take long to knock one up (email me if you want a copy sent to you).


Next, add the following code to the UserForm (make sure you use the same names for the controls or modify accordingly):

' frmSetFlagIcon

' Developed by Warren Bain on 30/06/2008
' Copyright (c) Thought Croft Pty Ltd
' All rights reserved.

' This module allows me to set the Follow Up Flag colour
' for unsaved new email which I want to do so I can track
' emails that I send and need to follow up using TROG by Priacta

Option Explicit

' -----------------
' Module variables
' -----------------

Dim moMail As MailItem
Dim mblnSent As Boolean
Dim moFlagStatus As OlFlagStatus
Dim moFlagIcon As OlFlagIcon

' -----------------
' Module procedures
' -----------------

Private Sub cboFlagIcon_Change()

' If we have unset the flag then the
' FlagStatus must be set appropriately

If IsNull(Me.cboFlagIcon) Then
moFlagIcon = olNoFlagIcon
Else
moFlagIcon = Me.cboFlagIcon
End If
If moFlagIcon = olNoFlagIcon Then
moFlagStatus = olNoFlag
Else
moFlagStatus = olFlagMarked
End If
Call DisplayControls

End Sub

Private Sub ckbCompleted_Change()

' If they mark it complete then alter
' FlagStatus accordingly

If Me.ckbCompleted Then
moFlagStatus = olFlagComplete
Else
moFlagStatus = olFlagMarked
End If
Call DisplayControls

End Sub

Private Sub cmdCancel_Click()

' Clear global object and close form

Set moMail = Nothing
Unload Me

End Sub

Private Sub cmdOK_Click()

If Not moMail Is Nothing Then
With moMail
If moFlagIcon <> .FlagIcon Or moFlagStatus <> .FlagStatus Then
' They altered something so update MailItem
.FlagStatus = moFlagStatus
.FlagIcon = moFlagIcon
.Save
End If
End With
End If

Set moMail = Nothing
Unload Me

End Sub

Private Sub UserForm_Initialize()

' Set up the different colours available
' to us when the form is first opened
' The two arrays must be kept
' in step and in the order required

Dim astrColours As Variant
Dim avarFlagIcons As Variant
Dim i As Integer
Dim oInsp As Inspector
Dim oItem As Object


With Me.cboFlagIcon
astrColours = Array("No flag set", "Red", "Blue", _
"Yellow", "Green", _
"Orange", "Purple")
avarFlagIcons = Array(olNoFlagIcon, olRedFlagIcon, olBlueFlagIcon, olYellowFlagIcon, _
olGreenFlagIcon, olOrangeFlagIcon, olPurpleFlagIcon)
For i = LBound(astrColours) To UBound(astrColours)
.AddItem
.List(i, 0) = avarFlagIcons(i)
.List(i, 1) = astrColours(i)
Next i
End With

' Now see which mail item is being referenced
' and prepare ourselves for working with it
Set oInsp = Application.ActiveInspector
If Not oInsp Is Nothing Then
Set oItem = oInsp.CurrentItem
If TypeOf oItem Is MailItem Then
' We have a mail item to work with so
' retrieve the current flag settings
Set moMail = oItem
With moMail
mblnSent = .Sent
moFlagStatus = .FlagStatus
moFlagIcon = .FlagIcon
End With
Me.ckbCompleted = (moFlagStatus = olFlagComplete)
Me.cboFlagIcon = moFlagIcon
End If
End If

Set oItem = Nothing
Set oInsp = Nothing

End Sub

Private Sub DisplayControls()

' Ensure that the controls are enabled
' consistently with their settings

If Not moMail Is Nothing Then

' Only allow OK if they have actually altered the
' controls to be different from the MailItem
With moMail
Me.cmdOK.Enabled = moFlagIcon <> .FlagIcon _
Or moFlagStatus <> .FlagStatus
End With

' Allow them to set Completed if the flag is set and SENT
' and allow Flag Icon change if incomplete
Me.cboFlagIcon.Enabled = Not Me.ckbCompleted
Me.ckbCompleted.Enabled = (Me.cboFlagIcon <> olNoFlagIcon) And mblnSent

End If

End Sub

Next, add a new class module to the Outlook Project to sink the events we are interested in. Essentially this will trap any click on the Add Reminder... menu item under Actions | Follow Up submenu, or click the Follow Up button on the Standard menubar, our form will be displayed instead of the inbuilt Outlook one. Name the class module clsInspectorHandler and paste this code into it.

Note that this will be instantiated for any Inspector that we are interested in and so we have to have a way of determining if a button click is for this instance of the Inspector object. We generate a unique key and set it in the button's Tag property. When the button is clicked on one Inspector, every CommandBarControl object referencing that button will have its Click event fired, so the instance ignores any for Controls that don't have its unique tag set in it.

' clsInspectorHandler

' Developed by Warren Bain on 30/06/2008
' Copyright (c) Thought Croft Pty Ltd
' All rights reserved.

Option Explicit

Dim WithEvents oInspector As Outlook.Inspector
Dim WithEvents oCBB1 As Office.CommandBarButton
Dim WithEvents oCBB2 As Office.CommandBarButton
Dim mstrUniqueTag As Integer

Private Sub Class_Initialize()
' Create a unique key for tag usage
Randomize
mstrUniqueTag = Int((9999 - 0 + 1) * Rnd + 0)
End Sub

Private Sub Class_Terminate()
Set oCBB1 = Nothing
Set oCBB2 = Nothing
Set oInspector = Nothing
End Sub

Private Sub oCBB1_Click(ByVal Ctrl As Office.CommandBarButton, CancelDefault As Boolean)
' We want to prevent this one from running and load our own
If Ctrl.Tag = mstrUniqueTag Then
frmSetFlagIcon.Show
CancelDefault = True
End If
End Sub

Private Sub oCBB2_Click(ByVal Ctrl As Office.CommandBarButton, CancelDefault As Boolean)
' We want to prevent this one from running and load our own
If Ctrl.Tag = mstrUniqueTag Then
frmSetFlagIcon.Show
CancelDefault = True
End If
End Sub

Private Sub oInspector_Close()
Set oCBB1 = Nothing
Set oCBB2 = Nothing
End Sub

Public Sub SetInspector(Inspector As Outlook.Inspector)
If Not Inspector Is Nothing Then
Set oInspector = Inspector
With oInspector
' 1678 = Standard menu button "Follow &Up"
' 7478 = Action menu Follow Up submenu item "&Add Reminder..."
Set oCBB1 = .CommandBars.FindControl(msoControlButton, 1678)
oCBB1.Tag = mstrUniqueTag
Set oCBB2 = .CommandBars.FindControl(msoControlButton, 7478)
oCBB1.Tag = mstrUniqueTag
End With
End If
End Sub

NOTE: the CommandBarControlButton ids that I have quoted are correct in my installation of Outlook 2003. You will need to check they are the same for your version. Sue Mosher has code that will allow you to enumerate all CommandBars in Outlook.

Then add the following code in the ThisOutlookSession module. When a new Inspector is opened (for example to compose an email), the NewInspector event will fire. If it is for an Unsent MailItem, we are interested in trapping the relevant button clicks so we create a new instance of the clsInspectorHandler, add it to a global collection object to keep it alive (as there may be multiple Inspectors open at any one time, we have to keep an instance for each one) and then call its SetInspector method to sink the required Click events.

Public WithEvents colInspectors As Outlook.Inspectors
Public gcolMyInspectors As Collection

Private Sub Application_Quit()
Set gcolMyInspectors = Nothing
Set colInspectors = Nothing
End Sub

Private Sub Application_Startup()
Set gcolMyInspectors = New Collection
Set colInspectors = Application.Inspectors
End Sub

Private Sub colInspectors_NewInspector(ByVal Inspector As Inspector)

' This will be called everytime we open
' a new Inspector, so check if this is
' one that we want to monitor

Dim MyInspectorHandler As clsInspectorHandler

If Inspector.CurrentItem.Class = olMail Then
If Not Inspector.CurrentItem.Sent Then
' This is an unsent email so we want to
' trap the buttons that can be clicked
Set MyInspectorHandler = New clsInspectorHandler
Call MyInspectorHandler.SetInspector(Inspector)
gcolMyInspectors.Add MyInspectorHandler
End If
End If
End Sub

Save the project, restart Outlook and you will be in business. Anytime you want to flag an unsent email, your form will popup in place of the Outlook inbuilt one.

1 comment:

Wazza said...

Note: Outlook 2007 now allows you to flag an email before you send it, so this code is no longer required...