Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Professional VSTO 2005 - Visual Studio 2005 Tools For Office (2006) [eng]

.pdf
Скачиваний:
121
Добавлен:
16.08.2013
Размер:
8.04 Mб
Скачать

Chapter 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