Viewing Multiple Picture Attachments in Outlook 2003
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.
The Picture Attachments Helper
Creating the VBA Project
Launching the Picture Attachments Helper
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
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
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
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!
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.
- Using Visual Basic for Applications in Outlook (Microsoft Office Outlook 2003 Visual Basic Reference)
- Microsoft Knowledge Base article – 291163: How to Create a COM Add-in for Outlook 2002
- Find more information about Visual Basic and VBA coding in Microsoft Outlook at outlookcode.com (http://www.outlookcode.com/d/vb.htm).
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.