Viewing Multiple Picture Attachments in Outlook 2003

 

Eric Legault
Collaborative Innovations

January 2005

Applies to:
    Microsoft Office Outlook 2003
    

Summary: Outlook MVP Eric Legault shows how to build a custom VBA User Form that lets you select and view e-mail picture attachments without opening and closing them one after another. (14 printed pages)

You can download the code for the Picture Attachments Helper form at Eric's blog.

Contents

Introduction
The Picture Attachments Helper
Creating the VBA Project
Launching the Picture Attachments Helper
Conclusion
Additional Resources

Introduction

This is one of my most heavily used tools in my Microsoft Outlook macro toolbox, and it saves me a lot of work. Take a look at the screenshot in Figure 1.

Figure 1. Picture attachment madness

Isn't this annoying? You can select all of the attachments in an e-mail message, but you can't launch all of them at the same time. You have to double-click each one individually. I'm sure there have been many times when you've received a message from one of your friends with one or two dozen joke pictures, or Aunt Rose sends you five photos of her new cat, and it quickly becomes a real chore to open, view, and then close every single attachment.

Read on, and I'll show you how to create a much better way to handle this.

The Picture Attachments Helper

When you are finished, Figure 2 is what you will see when you click the View Attachments custom button that you will create on the Standard toolbar for the e-mail form.

Figure 2. The Picture Attachments Helper form

This new form will contain a list of all the picture attachments in the current message. You'll allow for the option to choose from the attachment list using multiple methods—non-contiguous (separate Shift-clicks), all entries, or one selection (with double-clicks enabled)—and then click a button to show the pictures.

Creating the VBA Project

The first thing you need to do is to select the appropriate COM references. First, open the Microsoft Office Outlook Visual Basic Editor (on the Tools menu in Outlook, select Macro and then Visual Basic Editor, or press ALT+F11). On the Tools menu in the Visual Basic Editor, select References. The Microsoft Outlook 11.0 Object Library (for Outlook 2003) should already be selected, and the only other reference you need to select for this project is Microsoft Scripting Runtime (drive:\Windows\System32\scrrun.dll).

Next, on the Insert menu, click UserForm. Make sure that the Project Explorer is visible. (If it is not, open the View menu and select Project Explorer (or press CTRL+R) and then select UserForm1 (or whatever default name the editor gave it, if you already have a form named UserForm1). If the Properties Window is not visible, then on the View menu, select Properties Window (or press F4) and change the form name to frmPictureAttachments.

Our Global Module

Before you add any code to the form, you also need a module to store your global variables and a procedure to launch the form. To create a module, open the Insert menu and select Module, and then name the module basPictureAttachments. Add the following code to the new module:

Option Explicit
Enum ImageViewers
    Default = 1
    Custom = 2
    IE = 3
End Enum
Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" _
   (ByVal hwnd As Long, ByVal lpOperation As String, ByVal lpFile As String, _
ByVal lpParameters As String, ByVal lpDirectory As String, _
ByVal nShowCmd As Long) As Long
Public Const conSwNormal = 1
Public Const strIEPath = "C:\Program Files\Internet Explorer\iexplore.exe"

Later, you'll add the remainder of the code to actually launch the Picture Attachments Helper form.

Building the Form

Now you have to design the form. On the View menu, select Toolbox, and then drag and arrange all the controls needed. I won't outline all the control-specific properties that need to be set; you can download the form source from the link at the end of the article and import it to save time, or use it as a reference to build your own GUI. Table 1 shows the controls that you will use.

Table 1. Controls needed for Picture Attachment Helper form

Name Type
lstAtts ListBox
fraOptions Frame
optRegisteredViewer OptionButton
optCustom OptionButton
optIE OptionButton
fraCustomViewer Frame
lblCustomViewerPath Label
txtApplicationPath TextBox
cmdOpen CommandButton
cmdOpenAll CommandButton
cmdClose CommandButton

After you've designed the form, open the View menu and select Code (or press F7) to go into Code view, and add these local variables to frmPictureAttachments:

Option Explicit
Dim objCurrentMessage As MailItem
Dim objFS As New Scripting.FileSystemObject
Dim objTempFolder As Scripting.Folder
Dim strTempFolderPath As String
Dim strTempFilesUsed() As String
Dim lngTempFileCount As Long
Dim strCustomImageViewerFilePath As String
Dim lngViewerType As ImageViewers

You also need a procedure to call from the basPictureAttachments module to fill the ListBox with any existing picture attachments before you show the form. By default, the Helper processes only the common image formats: JPEG, GIF, BMP, and TIF. You may want to add others that you might use on a regular basis.

Public Sub FillList()
On Error Resume Next

    'PRE-POPULATE THE LIST BOX WITH PICTURE ATTACHMENT FILE NAMES
    Dim objAtt As Attachment, objAtts As Attachments
    
    Set objCurrentMessage = ActiveInspector.CurrentItem
    Set objAtts = objCurrentMessage.Attachments
    lstAtts.Clear
    For Each objAtt In objAtts
        Select Case LCase(Right(objAtt.FileName, 3))
            Case "jpg", "peg", "gif", "bmp", "tif", "iff"
            'Note: iff and peg will handle .tiff and .jpeg extensions
                Me.lstAtts.AddItem objAtt.Index
                Me.lstAtts.List(lstAtts.ListCount - 1, 1) = objAtt.FileName
        End Select
    Next
End Sub

Here you set a reference to the currently open MailItem through the ActiveInspector.CurrentItem property, and then iterate through its Attachments collection to get the FileName property for storage in the ListBox.

Storing Temp Files

Next, add the required procedures that need to be fired during the UserForm.Activate event when the form is displayed. Because Outlook usually automatically handles the caching of attachment files when you manually launch them from the message, you need to implement your own caching mechanism by temporarily storing these files in the Temp folder defined by Microsoft Windows:

Function GetTempFolder() As Boolean
On Error Resume Next

    Dim objTempFolder As Scripting.Folder
    
    'GET THE TEMP FOLDER
    Set objTempFolder = objFS.GetSpecialFolder(2)
    'returns the path found in the TMP environment variable
    
    If objTempFolder Is Nothing Then Exit Function
    
    'Get or create the AttachmentsTemp folder
    If objFS.FolderExists(objTempFolder & "\AttachmentsTemp") = False Then
        Set objTempFolder = objFS.CreateFolder(objTempFolder _
        & "\AttachmentsTemp")
    Else
        Set objTempFolder = objFS.GetFolder(objTempFolder _
        & "\AttachmentsTemp")
    End If
    
    If Err.Number <> 0 Then
        'UNABLE TO RETRIEVE TEMP FOLDER
        'YOU MAY WANT TO HARD-CODE A FOLDER HERE THAT WILL WORK ON YOUR SYSTEM
        strTempFolderPath = "C:\Temp"
        
        If objFS.FolderExists(strTempFolderPath) = False Then
            objFS.CreateFolder strTempFolderPath
        End If
        Set objTempFolder = objFS.GetFolder(strTempFolderPath)
    Else
        strTempFolderPath = objTempFolder.Path
    End If
    
    GetTempFolder = True
End Function

Retrieving Viewer Settings

A good practice is to store in the Windows Registry any configuration choices made by the user, and load them every time the form is launched:

Sub RetrieveViewerSettings()
On Error Resume Next

    'Retrieve previous settings from the registry
    
lngViewerType = CLng(GetSetting("Picture Attachments Helper", _
                "Settings", "ViewerType", "1"))
strCustomImageViewerFilePath = GetSetting( _
"Picture Attachments Helper", "Settings", "CustomImageViewerFilePath")
    
    Select Case lngViewerType
        Case ImageViewers.Custom
            Me.fraCustomViewer.Enabled = True
            Me.optCustom.Value = True
            Me.txtApplicationPath.Text = strCustomImageViewerFilePath
        Case ImageViewers.Default
            Me.optRegisteredViewer.Value = True
        Case ImageViewers.IE
            Me.optIE.Value = True
    End Select
End Sub

Now wire these procedures to the form's Activate event:
Private Sub UserForm_Activate()
On Error Resume Next

    If GetTempFolder = False Then
        MsgBox "Unable to cache the picture attachments for viewing.",_
                vbOKOnly + vbExclamation, "Picture Attachments Helper Error"
        Exit Sub
    End If
    
    RetrieveViewerSettings
End Sub

The Brains

At the center of this solution is the OpenImage procedure to load the attachments in the chosen image viewer. Because you need to handle this differently from the cmdOpen and cmdOpenAllCommandButtons, you'll force the calling control to pass the value of the ListBox.ListIndex property, and use a return Boolean argument parameter for error notifications.

The ListIndex property also tells you where in the MailItem.Attachments collection the current image lives, because it was loaded into the ListBox control in the same order that it was added when you iterated through the Attachments collection in FillList.

The strTempFilesUsed array serves as a placeholder for any image files that are saved to the cache, and is used to delete cached images when the form closes. These images are output using the Attachment.SaveAsFile method, where the image file name is passed as a parameter after it is appended to the value of the Temp folder path you retrieved earlier.

Sub OpenImage(intListIndex As Integer, ByRef blnCancel As Boolean)
On Error GoTo EH:

    Dim strImageFile As String, varRet As Variant
    Dim strExecutePath As String

strImageFile = strTempFolderPath & "\" & _
objCurrentMessage.Attachments.Item(lstAtts.List( _
  intListIndex, 0)).DisplayName
objCurrentMessage.Attachments.Item(lstAtts.List( _
  intListIndex, 0)).SaveAsFile strImageFile
    ReDim Preserve strTempFilesUsed(lngTempFileCount)
    strTempFilesUsed(lngTempFileCount) = strImageFile
    lngTempFileCount = lngTempFileCount + 1
                
    'LAUNCH IMAGES IN DEFINED IMAGE VIEWER
    Select Case lngViewerType
        Case ImageViewers.Default
            ShellExecute 0, "open", strImageFile, _
            vbNullString, strTempFolderPath, conSwNormal
            Exit Sub
        Case ImageViewers.Custom
            strExecutePath = strCustomImageViewerFilePath & " " & strImageFile
        Case ImageViewers.IE
            strExecutePath = strIEPath & " " & strImageFile
    End Select
    
    varRet = Shell(strExecutePath, vbNormalFocus)
    
    
EH:
    If Err.Number <> 0 Then
        MsgBox Err.Number & vbCrLf & Err.Description & vbCrLf & vbCrLf _
            & "[error in OpenImage; file = '" & strImageFile & "']" _
            , vbOKOnly + vbExclamation, "Picture Attachments Helper Error"
        blnCancel = True
        Exit Sub
    End If
End Sub

Now, the current image viewer choice is evaluated against the ImageViewers enumeration to determine which of two different ways the image should be viewed. If the default application registered for image files is used, you have to use the Win32 API's ShellExecute function because it can take a document file as an argument—you don't have to worry which application is eventually going to display the image. On the other hand, the intrinsic Visual Basic Shell function handles only application files and will be used to display the images in Microsoft Internet Explorer or any provided custom image-viewer application.

When using a custom viewer, see if the application can be loaded from the Run menu with just the file name. If it can, it is stored in the Windows Path system environment variable, and you can just enter application name.exe in the Application Path text box. Otherwise, add it to the Path variable or put the full path in the text box.

Also, image viewers that use a Multiple Document Interface (MDI) are best suited as a custom viewer. It's better to have one application handle several open image windows than individual windows cluttering your Taskbar! A perfect application for this scenario is the Microsoft Office Photo Editor, but it is no longer bundled with Office 2003.

One warning: The default image viewer in Windows XP is the Windows Picture and Fax Viewer (unless you've configured Windows otherwise), which gets reused every time a new image is opened from the Windows Shell. That means the Open All button will not work in this situation. It'll simply display the last picture in the list. Internet Explorer can provide a viable option, or you can use Microsoft Paint (mspaint.exe or pbrush.exe) as a custom viewer.

Coding the Controls

Here is the remainder of the code for the controls on the form:

Private Sub cmdOpen_Click()
On Error Resume Next

    Dim intX As Integer, blnCancel As Boolean
    
    If lstAtts.ListIndex = -1 Then Exit Sub
    
    'LOOP THROUGH SELECTED PICTURE ATTACHMENTS
    For intX = 0 To lstAtts.ListCount - 1
        If lstAtts.Selected(intX) = True Then
            OpenImage intX, blnCancel
            If blnCancel = True Then Exit Sub
        End If
    Next
End Sub

Private Sub cmdOpenAll_Click()
On Error Resume Next

    Dim intX As Integer, blnCancel As Boolean
        
    If Me.lstAtts.ListCount <= 0 Then Exit Sub
    
    'LOOP THROUGH ALL PICTURE ATTACHMENTS
    For intX = 0 To lstAtts.ListCount - 1
        OpenImage intX, blnCancel
        If blnCancel = True Then Exit Sub
    Next
End Sub

Private Sub lstAtts_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
    cmdOpen_Click
End Sub

Private Sub optCustom_Click()
    lngViewerType = Custom
    Me.fraCustomViewer.Enabled = True
End Sub

Private Sub optIE_Click()
    lngViewerType = IE
    Me.fraCustomViewer.Enabled = False
End Sub

Private Sub optRegisteredViewer_Click()
    lngViewerType = Default
    Me.fraCustomViewer.Enabled = False
End Sub

Private Sub cmdClose_Click()
    Unload Me
End Sub

Cleaning Up

As a good programming practice, delete any images that were viewed from the cache by using the following code:

Sub DeleteTempFiles()
On Error GoTo EH:
    
    Dim objFile As Scripting.File
    Dim intX As Integer
    
    For intX = 0 To UBound(strTempFilesUsed)
        Set objFile = objFS.GetFile(strTempFilesUsed(intX))
        objFile.Delete True
    Next

EH:
    If Err.Number <> 0 Then
        If Err.Number = 9 Then
            'strTempFilesUsed ARRAY IS EMPTY; NO FILES WERE OPENED
            Exit Sub
        End If
        If Err.Number = 53 Then
            'FILE NOT FOUND; MAY HAVE BEEN DELETED ALREADY IF THE SAME FILE WAS
            'OPENED MORE THAN ONCE, AS THE FILE NAME WOULD HAVE BEEN
            'DUPLICATED IN THE ARRAY YOU ARE PARSING
            Resume Next
        End If
        MsgBox Err.Number & vbCrLf & Err.Description & vbCrLf & vbCrLf & _
            "[error in DeleteTempFiles]", vbOKOnly + vbExclamation _
            , "Picture Attachments Helper Error"
        Exit Sub
    End If
End Sub

Finally, add a procedure to save the selected options for retrieval the next time the form is launched, and call this and the last procedure in the form's Terminate event with additional code to destroy the form-level global object variables:

Sub SaveViewerSettings()
SaveSetting "Picture Attachments Helper", "Settings", _
"ViewerType", lngViewerType
SaveSetting "Picture Attachments Helper", "Settings", _
"CustomImageViewerFilePath", txtApplicationPath.Text
End Sub

Private Sub UserForm_Terminate()
    DeleteTempFiles
    SaveViewerSettings
    Set objCurrentMessage = Nothing
    Set objFS = Nothing
    Set objTempFolder = Nothing
End Sub

Launching the Picture Attachments Helper

The last thing to do is to complete the code for the basPictureAttachments module. Use the LaunchPictureAttachmentsHelper macro to map to a custom toolbar button. Add the following code for basPictureAttachments:

Sub LaunchPictureAttachmentsHelper()
'Map toolbar button to this macro

On Error Resume Next

    If Application.ActiveInspector Is Nothing Then Exit Sub
If Application.ActiveInspector.CurrentItem.Attachments.Count = 0 Then 
   Exit Sub
    frmPictureAttachments.FillList
    frmPictureAttachments.Show
    
End Sub

To map the macro to a button, open the View menu, select Toolbars, and then select Customize from an open e-mail form. Click the Commands tab, and from the Categories list, choose Macros, and then select OutlookVBA.ViewAttachments (check the project name in the Project Explorer window, because your project name may differ from OutlookVBA). Now drag that to a spot on any of your toolbars and you're finished!

Conclusion

You can extend this project in several ways. Here are a few ideas:

  • Add a "Save To File" command to enable a user to save the attachment in the Picture Attachment Helper form, rather than going back to the e-mail message to do it.
  • Some custom image viewers may require that special executable arguments be passed to the application, such as the '/' or '-' switches. Add another Label and TextBox control, and rework the logic in the OpenImage procedure to accommodate these.
  • Include separate viewer options for single and multiple images. This could provide a workaround to the Windows Picture and Fax Viewer problem, and also provide an option for using a lightweight image application to view single images quickly.
  • Recode basPictureAttachments as a class that can be instantiated from the ThisOutlookSession.Application_Startup event and automatically create the custom toolbar button.
  • Redo the solution as a COM add-in in Visual Basic 6.0 or Visual Basic .NET. Microsoft Visual Studio offers advanced APIs and Windows form controls to help you design a superior GUI for the Picture Attachments Helper and can enable you to implement advanced functionality.

Have fun using this! I hope it makes your life easier when working with multiple picture attachments.

Additional Resources

About the Author

Eric Legault, a Microsoft Most Valuable Professional for Outlook since 2003, is the founder of Collaborative Innovations, a Micro ISV and consulting services provider specializing on Microsoft messaging and collaboration solutions. Eric has over 13 years experience in the IT industry and has focused his energies developing solutions based on Microsoft application platforms. He has published articles in MSDN, Office Online, Windows IT Pro magazine, edited books on Outlook, SharePoint and Access, and maintains a blog on Outlook programming and SharePoint technologies. His current focus is on developing custom Outlook Add-Ins to integrate line of business applications or enhance collaboration processes and workflows. Eric is also the co-founder of the Winnipeg SharePoint User Group and a guest speaker at conferences around the globe.

© Microsoft Corporation. All rights reserved.