Professional VSTO 2005 - Visual Studio 2005 Tools For Office (2006) [eng]
.pdfChapter 5
Figure 5-9
Consider Listing 5-13, which manipulates the address book.
Visual Basic
Private Function GetInfoFromAddressBook(ByVal contact As String, ByVal name As String) As Outlook.AddressEntry
If name = Nothing OrElse name.Trim().Length = 0 Then Return Nothing
End If
If contact = Nothing OrElse contact. Trim()Length = 0 Then contact = “Contacts”
End If
contact = contact.Trim() name = name.Trim()
If Not Session.AddressLists Is Nothing And Session.AddressLists.Count > 0
Then
Dim addressLists As Outlook.AddressLists addressLists = Session.AddressLists
Dim address As Outlook.AddressList For Each address In addressLists
If address.Name.Trim() = contact Then Dim enTry As Outlook.AddressEntry
For Each enTry In address.AddressEntries If enTry.Name.Trim() = name Then
Return enTry End If
Next End If
Next
178
Outlook Automation
End If
Return Nothing
End Function
C#
private Outlook.AddressEntry GetInfoFromAddressBook(string contact, string name)
{
if (name == null || name.Trim().Length == 0) return null;
if (contact == null || contact. Trim().Length == 0) contact = “Contacts”;
contact = contact.Trim(); name = name.Trim();
if (Session.AddressLists != null && Session.AddressLists.Count > 0)
{
Outlook.AddressLists addressLists = Session.AddressLists as Outlook.AddressLists;
foreach (Outlook.AddressList address in addressLists)
{
if (address.Name.Trim() == contact)
{
foreach (Outlook.AddressEntry entry in
address.AddressEntries)
{
if (entry.Name.Trim() == name)
{
return entry;
}
}
}
}
}
return null;
}
Listing 5-13: Outlook address book probe routine
There’s quite a lot going on in the code so let’s take it slowly. First, the code performs some rudimentary sanity checks with if statements. Then, an active session is used to grab the address lists. We talked about MAPI sessions at the beginning of the chapter so you should be familiar with the concept. An address book may contain one or more address lists that each contains unique identifiers. Figure 5-10 shows the default address list available in the right drop-down box. It is this list that is returned through the AddressList collection object.
The code begins an iterative dive into the AddressList collection. The address objects are exactly equal to the values that appear in the drop-down box in Figure 5-13. Finally, each address object is made up of one or more address entry objects. These address entry objects contain the actual address information that we are after. If a match is found, we simply return the match. Otherwise, we continue searching.
The code is only constructed to find a single instance of the address. However, for real-world applications, there may be duplicate addresses so the code should be amended to behave correctly. An implementation is trivial and best left as an exercise to the reader.
179
Chapter 5
Figure 5-10
Events
Recall from earlier in the chapter that most Microsoft Outlook features are built from a few objects such as the Application, Inspector, and Explorer objects. As it turns out, these objects also expose numerous events. For instance, the application object exposes the startup and shutdown events. If you examine the default OfficeCodeBehind’s VSTO-generated code, you will notice that these events are wired to event handlers. The ThisApplication_Startup handler may be used to initialize application-level objects, while the ThisApplication_Shutdown handler may be used to cleanup application-level objects.
Consider the code in Listing 5-14.
Visual Basic
Public Class ThisApplication
Private Sub ThisApplication_MAPILogonComplete() Handles Me.MAPILogonComplete
MessageBox.Show(“Welcome “ &
Me.GetNamespace(“MAPI”).CurrentUser.Name.ToString())
End Sub
Private Sub ThisApplication_Startup(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Startup
End Sub
Private Sub ThisApplication_Shutdown(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Shutdown
End Sub
End Class
180
Outlook Automation
C#
using System;
using System.Windows.Forms;
using Microsoft.VisualStudio.Tools.Applications.Runtime; using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;
namespace MAPLogon
{
public partial class ThisApplication
{
private void ThisApplication_Startup(object sender, System.EventArgs e)
{
this.MAPILogonComplete += new Microsoft.Office.Interop.Outlook.ApplicationEvents_11_MAPILogonCompleteEventHandler (ThisApplication_MAPILogonComplete);
}
void ThisApplication_MAPILogonComplete()
{
MessageBox.Show(“Welcome “ + this.GetNamespace(“MAPI”).CurrentUser.Name.ToString());
}
}
}
Listing 5-14: Event handling code to display a welcome message prompt
The code is straightforward enough so that an explanation is not necessary. However, Visual Basic programmers will note that the MAPILogonComplete event must be explicitly added in the VSTO designer. Once the handler appears in the OfficeCodeBehind file, you may then place the code in the event handler. You should note also that the GetNamespace method call presented in Listing 5-13 is an alternative approach to retrieving a MAPI session.
The Explorer window drives the folder navigation functionality of Microsoft Outlook. The events that are exposed through the Explorer window relate in part to these folders and the data they may contain. If you would like to apply functionality to the Explorer, it’s a good idea to examine the events that it exposes.
The Inspector object displays data based on selections in the Explorer window. For instance, doubleclicking on a folder in the Explorer window launches the Inspector window. As part of the Inspector launch, several events are fired. To apply functionality, you might consider subscribing to some of these events.
For both of these objects, the approach is the same. You will need to wire up your handlers to the events that you are interested in and place code in the event handler so that it may be executed when the event handler is called. For Visual Basic applications, you will need to add the event handlers by clicking on the event drop-down list.
Since VSTO-based application code is executed as an add-in, your calling code will simply react to Microsoft Outlook events. For this reason, Microsoft Outlook add-ins typically follow a predefined format of subscribing to Microsoft Outlook events and handling these events appropriately. Consider the code in Listing 5-15.
181
Chapter 5
Visual Basic
Public Class ThisApplication
Private Declare Auto Function PlaySound Lib “winmm.dll” _ (ByVal lpszSoundName As String, ByVal hModule As Integer, _ ByVal dwFlags As Integer) As Integer
Private Sub ThisApplication_NewMail() Handles Me.NewMail
PlaySound(“C:\program files\messenger\newalert.wav”, 0, &H20000)
End Sub
Private Sub ThisApplication_Startup(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Startup
End Sub
Private Sub ThisApplication_Shutdown(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Shutdown
End Sub
End Class
C#
using System;
using System.Windows.Forms;
using Microsoft.VisualStudio.Tools.Applications.Runtime; using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;
using System.Runtime.InteropServices;
namespace YouGotMail
{
public partial class ThisApplication
{
[DllImport(“WinMM.dll”)]
public static extern bool PlaySound(byte[] filename, int typeOfSound);
private void ThisApplication_Startup(object sender, System.EventArgs e)
{
this.NewMail += new Microsoft.Office.Interop.Outlook.ApplicationEvents_11_NewMailEventHandler(ThisAppli cation_NewMail);
}
void ThisApplication_NewMail()
{
[DllImport(“winmm.dll”, EntryPoint = “PlaySound”, CharSet = CharSet.Auto)]
private static extern int PlaySound(String pszSound, int hmod, int falgs); static void Main(string[] args)
{
PlaySound(@”C:\program files\messenger\newalert.wav”, 0, 0x0000);
}
182
Outlook Automation
}
private void ThisApplication_Shutdown(object sender, System.EventArgs e)
{
}
}
}
Listing 5-15
This application simply subscribes to the new mail event. Once new mail is received in Microsoft Outlook, the event handler is called. The code in the event handler plays a sound file for each piece of mail received. For Visual Basic, you will need to add the NewMail event from the events drop-down list.
Microsoft Outlook exposes a number of events that calling code may subscribe to in an application. The basic code for event hook up is essentially the same. However, the events exposed are not as rich as the Microsoft Word or Microsoft Excel.
Data Manipulation
Microsoft Outlook is designed to process large amounts of data to include raw text as well as files in the form of attachments. Eventually, end users may need to sift through these documents in search of data. The find functionality is a common tool used to search for pieces of text inside folders. The find functionality works by passing the text search string into the Outlook API. While the Outlook API executes the search request, the user interface remains fairly responsive. Figure 5-11 shows the Outlook search dialog.
Figure 5-11
183
Chapter 5
Searching for Data
There are several options to implementing search functionality in Microsoft Outlook. We examine these in terms of complexity. If you take the course of least resistance, the simplest approach to searching is to show the find dialog in Microsoft Outlook. However, Outlook does not easily expose any of the common dialogs that are found in Microsoft Word. So, the next best thing is to build a simple loop to probe for the contents, as in Listing 5-16. The behavior roughly replicates the end-user supported Find dialog provided by Microsoft Outlook.
Visual Basic
Private Sub SearchRoutine(ByVal searchToken As String) Dim inbox As Outlook.MAPIFolder
inbox = Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox) Dim count As Integer
count = inbox.Items.Count Dim foundItem As String foundItem = String.Empty
Dim found As Object
For Each found In inbox.Items
foundItem = CallByName(note, “Subject”, CallType.Get).ToString() If foundItem = searchToken Then
MessageBox.Show(“Found a suspicious item”) End If
Next End Sub
C#
public void SearchRoutine(string searchToken)
{
Outlook.MAPIFolder oInbox = Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
int count = oInbox.Items.Count; string retVal = string.Empty;
foreach (object oResult in oInbox.Items)
{
retVal = (string)oResult.GetType().InvokeMember(“Subject”, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.GetProperty, null, oResult, null, null);
if (retVal == searchToken)
{
MessageBox.Show(“Found a suspicious item”);
}
}
}
Listing 5-16: Simple iterative loop to find data
The code retrieves a reference to the inbox folder and begins an iterative descent into the items collection searching for items with the subject matching the keyword “virus.” I’ll concede that though the approach is simple, the C# code is ugly. Part of the eyesore is the reflection inside the for loop. The VB code is a
184
Outlook Automation
lot more presentable because the reflection code is wrapped in the CallByName method. C# offers no such luxury. The reflection expense is also costly.
For efficiency reasons, you should avoid using a for loop. You can improve the performance if you choose another approach. Consider Listing 5-17.
Visual Basic
Dim inbox As Outlook.MAPIFolder
inbox = Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox) Dim oResult As Object
oResult = inbox.Items.Find(“[Subject] = “”virus”””) If Not oResult IsNot Nothing Then
Dim data as Outlook.MailItem = DirectCast(oResult,Outlook.MailItem) If data IsNot Nothing Then
End If
End If
C#
//OutlookSearch.Properties. Outlook.MAPIFolder oInbox =
Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
object oResult = oInbox.Items.Find(@”[Subject] = “”virus”””);
if (oResult != null)
{
Outlook.MailItem data = (Outlook. MailItem)oResult; if (data != null)
{
}
}
Listing 5-17: Search functionality code using the Find method
The code first obtains a reference to the inbox folder and invokes the Find method for the Items collection. Notice that the Find method accepts a filter string parameter. If the find is successful, the returned object contains the items matching the filter. This filter is fast and more efficient than writing a loop to iterate the contents of the collection so its use is preferred over the for loop approach.
The filter parameter passed in roughly resembles the .NET filter mechanism commonly found in dataset manipulation. The property to be searched is marked with square brackets, and the actual search request item must be placed inside double quotation marks.
Filters are specific to the folder being searched so passing in an invalid filter results in an exception. To find all the available filters for a specific folder, it’s easier to let Microsoft Outlook display a valid list for you. Click the Inbox folder in the left pane of Microsoft Outlook. Select Arrange by from the Tools Menu. Select Fields and a dialog should appear, as shown in Figure 5-11. Notice that this dialog allows you to add custom fields that in turn may be used in your calling code. The mechanism is very flexible.
However, one limitation exists. The find returns only the first item matching the filter. If other items exist, they are simply ignored. So let’s amend the code to get Listing 5-18.
185
Chapter 5
Visual Basic
Dim oInbox As Outlook.MAPIFolder =
Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox)
Dim oResult As Object = oInbox.Items.Find(“[Subject] = “”virus”””)
Do
oResult = oInbox.Items.FindNext()
Loop While Not oResult Is Nothing
Dim data As Outlook.MailItem If Not oResult Is Nothing Then MessageBox.Show(“Found”)
data = CType (oResult, Outlook.MailItem) End If
C#
Outlook.MAPIFolder oInbox = Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
object oResult = oInbox.Items.Find(@”[Subject] = “”virus”””); do
{
oResult = oInbox.Items.FindNext(); } while (oResult != null);
Outlook.MailItemdata; if (oResult != null)
{
MessageBox.Show(“Found”);
data = (Outlook.MailItem)oResult;
}
Listing 5-18: Search functionality code for multiple terms
The extra code is simply a do loop that fires the FindNext method. The FindNext method executes the parameters of the previous find call; this is the reason for the empty parameter list. The loop executes until all items matching the filter criteria is found. This implementation is still much faster than iterating the items collection with a home grown for loop.
The VB code snippet shows the CType operator being used to perform the cast. Strictly speaking, CType is a conversion operator. For improved efficiency, you should consider using a specialized casting operator such as DirectCast or TryCast, since these operators do not use Visual Basic’s runtime helper routines for conversion.
While the Find method is certainly efficient for probing the items collection, it isn’t without limitations. For instance, the filter parameter cannot grow in sophistication. It is limited to simple AND, OR, or NOT constructs. Consider these limits of filtering:
oInbox.Items.Find(@”[Subject] = “”virus”” AND [To] = “”Microsoft@hotmail.com”””) oInbox.Items.Find(@”[Subject] = “”virus”” OR [To] = “” Microsoft@hotmail.com”””) oInbox.Items.Find(@”[Subject] = “”virus”” AND NOT [To]= “”Microsoft@hotmail.com”””)
The AND, OR, and NOT operators retain their logical meaning and an explanation is not necessary at this point.
186
Outlook Automation
Advanced Data Search
The filter parameter used in the Find method cannot execute partial matches. A match for a keyword virus in the subject field will return no results if the items being searched contain the word viruses. In some use cases, that lack of flexibility is a significant deterrent. To addresses use cases like this, a more powerful search approach must be adopted. Listing 5-19 shows an example.
Visual Basic
Private Sub ThisApplication_Startup(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Startup
Dim scope As String = “‘Inbox’”
Dim filter As String = “””subject”” LIKE ‘%vir%’” Dim tag As String = “uniqueTag”
Dim subFolders As Object = False
Dim retVal As Outlook.Search = Me.AdvancedSearch(scope, Filter, subFolders,
tag)
If Not retVal Is Nothing Then System.Threading.Thread.Sleep(200) If retVal.IsSynchronous = False Then
retVal.Stop() End If
End If
End Sub
C#
private void ThisApplication_Startup(object sender, System.EventArgs e)
{
string scope = @”’Inbox’”;
string filter= @”””subject”” LIKE ‘%vir%’”; string tag = “uniqueTag”;
object subFolders = false;
Outlook.Search retVal = this.AdvancedSearch(scope, filter, subFolders,
tag);
if (retVal != null)
{
System.Threading.Thread.Sleep(200); if (retVal.IsSynchronous == false)
retVal.Stop();
}
}
Listing 5-19: Advanced search method
You will notice that Listing 5-19 essentially replicates the search functionality coded in Listing 5-17. However, the search filter has been extended to include text matching. The search should return items that contain partial matches for the keyword vir. For instance, there will be a successful match for viruses.
It may surprise you to know that this code may fail more often than necessary. In fact, for large folders, retVal may almost always be equal to null even though matches exist. The reason for this quirky behavior is that the AdvancedSearch method executes asynchronously; that is, it occurs on a separate thread.
187