Creating an Outlook Task Add-in Solution with Visual Studio 2005 Tools for Office

This content is no longer actively maintained. It is provided as is, for anyone who may still be using these technologies, with no warranties or claims of accuracy with regard to the most recent product version or service release.

Summary: Using new Microsoft Office Outlook 2003 add-in support in Microsoft Visual Studio 2005 Tools for the Microsoft Office System, create Microsoft Office Word 2003 task reports using WordProcessingML and XML expansion packs, and create a context menu for tasks in Outlook. (24 printed pages)

John R. Durant, Microsoft Corporation

Revised: December 2005

Applies to: Microsoft Visual Studio 2005 Tools for the Microsoft Office System, Microsoft Office Outlook 2003, Microsoft Office Word 2003

Download VSTO-OutlookTasksAddin.msi.

Contents

  • Overview

  • Creating the Add-in Project

  • Working with Custom Menus

  • Exporting Tasks

  • Using the XML Expansion Pack

  • Using Custom Forms and Controls in the Add-In

  • Additional Resources

Overview

People commonly use Tasks in Microsoft Office Outlook 2003 to manage the status of important things they need to do. However, the default Task experience is only the start. For example, users often want more flexibility and power in how they print Outlook data. The dialog boxes for printing provide some configurations and options, but some users want to use a more familiar and richer formatting environment, such as Microsoft Office Word 2003, to create text-heavy reports.

Also, Outlook Tasks are great on their own, but users benefit when they can easily associate the data in Tasks with other Outlook items, such as e-mail messages or contacts from a third-party system. This solution sample provides a way for users to select a Task and view a form that shows a list of e-mail messages from the target Inbox, where the subject is similar to the Task subject. Additionally, users can view a form that shows contact information from a third-party customer relationship management (CRM) system.

Note

This add-in incorporates some code from two article samples already available on MSDN. The two other articles are Office Talk: Search Inbox Data Using Smart Tags in Word 2003 and Office Talk: Easy Task Reports with Outlook and WordProcessingML. However, in those samples, the Outlook programmability code is not part of an add-in, which yields a very different user experience. This article focuses on the benefits of the add-in approach and the ease of creating add-ins using Microsoft Visual Studio 2005 Tools for the Microsoft Office System. Visual Studio 2005 Tools for Office is included with Microsoft Visual Studio 2005.

Creating the Add-in Project

With Microsoft Visual Studio 2005 Tools for the Microsoft Office System (Visual Studio 2005 Tools for Office), you can easily create custom add-ins for Outlook. Rather than creating them from the Microsoft Visual Studio Shared Add-in project type, you use the Outlook Add-in project template. This provides a number of advantages, and they are explained in great detail in the article Introducing Outlook Add-in Support in Visual Studio 2005 Tools for Office. You should read this article to learn how to obtain Visual Studio 2005 Tools for Office, along with many training materials.

To summarize the benefits, think of creating an Outlook add-in with streamlined and reliable wiring between Outlook and your custom add-in. The debugging experience is consistently dependable. You only need to worry about two events concerning the interaction between the host application and the add-in. Also, because it is a managed add-in, you get the full power of the Microsoft .NET Framework, and you can deploy your solution with greater confidence in its security.

To create the project and form

  1. Start Microsoft Visual Studio .NET 2005.

  2. On the File menu, point to New, and then click Project.

  3. In the New Project dialog box, in the Project Types pane, open the Visual Basic node, and then select Office (Figure 1).

    Note

    You can also select a Microsoft Visual C# project type.

    Figure 1. Creating the project in Visual Studio

  4. In the Templates pane, click Outlook Add-in.

  5. Assign a unique name to the project that conforms to the naming conventions you use.

  6. Click OK to create the project. Visual Studio displays the project.

  7. In the Solution Explorer, right-click ThisApplication.vb and select View Code.

  8. Verify that two event procedures are showing: ThisApplication_Startup and ThisApplication_Shutdown.

    Note

    You will add more code to the ThisApplication class definition as you construct the sample, and you will add custom forms and a custom control.

One advantage of Visual Studio 2005 Tools for Office is that you do not need to add the reference to the Outlook object library or add code to connect your add-in to the host application. This is all done automatically so that you can begin coding your solution right away.

Next, you need to add some Imports statements to clean up your code. At the very top of the ThisApplication.vb file, add the following Imports statements:

Imports Office = Microsoft.Office.Core
Imports System.Xml
Imports System.IO
Imports System.Configuration
Imports System.Collections
Imports System.Net

You also need to add some global variables and constants to the ThisApplication class definition. These variables are important for the rest of the solution; they allow you to work with various Outlook object instances and their events. The only constant that you use helps the custom menu of your add-in to know the menu it precedes.

    Private WithEvents _Explorers As Outlook.Explorers
    Private WithEvents _Explorer As Outlook.Explorer
    Private _taskFolder As Outlook.Explorer
    Private _mapiTasks As Outlook.MAPIFolder
    Private _helpMenuIndex As Object
    Private _topMenu As Office.CommandBarPopup
    Private WithEvents _exportTasks As Office.CommandBarButton
    Private WithEvents _messages As Office.CommandBarButton
    Private WithEvents _contacts As Office.CommandBarButton
    Private _menuBar As Office.CommandBar
    Private WithEvents _Task As Outlook.TaskItem
    Private Const _MENU_BEFORE As String = "Help"

With these declarations in place, you are ready to add code that manages your custom menu.

Working with Custom Menus

One way that users can interact with your add-in is through a custom menu (Figure 2) on the standard menu bar. When a user clicks the menu item, code loops through all of your Outlook Tasks and exports specific data properties for each Task to one well-formed XML file.

Figure 2. The custom menu in Outlook

Your add-in enables the Export Outlook Tasks button when the user selects the Outlook Tasks folder. When the user chooses to explore a different folder, the menu item is unavailable. To achieve this result, you need to code the add-in so that it reliably determines which folder the user is viewing. You do this by setting a reference to the active Explorer in Outlook. As the user switches folders, the Explorer.FolderSwitch event occurs, so you add code to check which folder is appearing in the Explorer. It is best to gain the reference to the Outlook ActiveExplorer object when the application starts and your add-in loads:

    Private Sub ThisApplication_Startup( _
    ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles Me.Startup
        _Explorer = Me.ActiveExplorer()
        _mapiTasks = Me.ActiveExplorer(). _
        Session.GetDefaultFolder( _
        Outlook.OlDefaultFolders.olFolderTasks)
        BuildTopMenu()
    End Sub

When your add-in loads, you need to do a few other things. This is a good time to build your custom menu and gain a specific reference to the default Outlook Tasks folder. Rather than include the menu-building code directly in the ThisApplication_Startup event, isolate it in its own procedure that you call at startup:

    Private Sub BuildTopMenu()
        _menuBar = Me.ActiveExplorer(). _
        CommandBars.ActiveMenuBar
        _helpMenuIndex = _menuBar.Controls( _
        _MENU_BEFORE).Index

        _topMenu = CType(_menuBar.Controls.Add( _
        Office.MsoControlType.msoControlPopup, , , _helpMenuIndex, _
        True), Office.CommandBarPopup)
        _topMenu.Caption = "Tasks Add-in"
        _topMenu.Visible = True
        _exportTasks = CType(_topMenu.Controls.Add( _
        Office.MsoControlType.msoControlButton, _
        , , , True), Office.CommandBarButton)
        _exportTasks.Caption = "Export Outlook Tasks"
        _exportTasks.Visible = True
        _exportTasks.Enabled = False
        _messages = CType(_topMenu.Controls.Add( _
         Office.MsoControlType.msoControlButton, _
         , , , True), Office.CommandBarButton)
        _contacts = CType(_topMenu.Controls.Add( _
        Office.MsoControlType.msoControlButton, _
        , , , True), Office.CommandBarButton)
        _messages.Caption = "View Messages"
        _messages.Visible = True
        _messages.Enabled = True
        _contacts.Caption = "View Contacts"
        _contacts.Visible = True
        _contacts.Enabled = True
    End Sub

This custom procedure places a custom pop-up menu before the Help menu on the standard menu bar. It also adds a CommandBarButton with a caption of "Export Outlook Tasks". The menu button variable is _exportTasks, which you declare at a global level by using the WithEvents keyword. This keyword adds the event procedures for the object to your environment so that you can add custom code to them (this is explained later in this article). The custom procedure adds two more buttons that enable two key additional features in the solution sample.

Because you want to enable the menu item with its buttons only when the user views the Outlook Tasks folder, you need to add code to the _Explorer_FolderSwitch event. This code compares the EntryID property of the folder displayed in the Explorer with the EntryID for the default Tasks folder to which you acquired a reference at startup. If they match, the user is viewing the Tasks folder, and the menu item is enabled. If they do not match, the menu item is unavailable.

    Private Sub _Explorer_FolderSwitch() _
    Handles _Explorer.FolderSwitch
        If _Explorer.CurrentFolder.EntryID = _
        _mapiTasks.EntryID Then
            _exportTasks.Enabled = False
        Else
            _exportTasks.Enabled = True
        End If
    End Sub

Exporting Tasks

Previously, you added code to get a pointer to the CommandBarButton instance that is your custom menu item. You now need to add code to its Click event procedure to export the task information to a well-formed XML file.

    Private Sub _exportTasks_Click( _
    ByVal Ctrl As Microsoft.Office.Core.CommandBarButton, _
    ByRef CancelDefault As Boolean) Handles _exportTasks.Click
        Try
            ExportTasks(Me.ActiveExplorer(). _
            Session.CurrentUser.Name.ToString())
        Catch exc As Exception
            MessageBox.Show(exc.Message)
        End Try
    End Sub

As you can see, the code calls a custom event procedure, ExportTasks, which accepts a single string argument for the current user's name. The exported XML includes the name, to give the report a personal touch. The ExportTasks procedure contains the following code:

Public Sub ExportTasks(ByVal userName As String)
        Dim xmlDoc As XmlDocument = New XmlDocument
        xmlDoc.AppendChild(xmlDoc. _
        CreateProcessingInstruction("xml", "version=""1.0"""))
        xmlDoc.AppendChild(xmlDoc. _
        CreateProcessingInstruction("mso-solutionextension", _
        "URI=""OutlookTaskExporter""" _
        & " manifestPath=""OutlookTaskExporterSolution.xml"""))
        Dim newRoot As XmlElement = xmlDoc.CreateElement( _
        "Tasks", "OutlookTaskExporter")
        xmlDoc.AppendChild(newRoot)
        Dim root As XmlNode = newRoot
        Dim newNode As XmlNode
        newNode = xmlDoc.CreateElement("SummaryInfo", _
        "OutlookTaskExporter")
        newNode.InnerText = "Status Report for: " _
        & userName & "--" & System.DateTime.Now.ToShortDateString
        root.AppendChild(newNode)
        Dim attNode As XmlAttribute

        Dim categories As String
        For Each t As Outlook.TaskItem In _mapiTasks.Items
            Try
                categories = t.Categories
                Dim str As String() = _
                categories.Split(New Char() {","c})
                newNode = xmlDoc.CreateElement("Task", _
                "OutlookTaskExporter")
                attNode = xmlDoc.CreateAttribute("Subject")
                attNode.InnerText = t.Subject
                newNode.Attributes.Append(attNode)
                attNode = xmlDoc.CreateAttribute("Due")
                attNode.InnerText = t.DueDate.ToShortDateString
                newNode.Attributes.Append(attNode)
                attNode = xmlDoc.CreateAttribute("PercentComplete")
                attNode.InnerText = t.PercentComplete.ToString
                newNode.Attributes.Append(attNode)
                Dim i As Integer = 0
                While i <= str.GetUpperBound(0)
                    Dim catNode As XmlNode = _
                    xmlDoc.CreateElement("category", "OutlookTaskExporter")
                    catNode.InnerText = str(i).TrimStart(New Char() {" "c})
                    newNode.AppendChild(catNode)
                    System.Math.Min(System.Threading. _
                    Interlocked.Increment(i), i - 1)
                End While
                Dim notesNode As XmlNode = xmlDoc.CreateElement( _
                "Notes", "OutlookTaskExporter")
                notesNode.InnerText = t.Body
                newNode.AppendChild(notesNode)
                root.AppendChild(newNode)
            Catch nullExc As System.NullReferenceException
                Exit Try
            Catch exc As System.Exception
                MessageBox.Show("Export failed.", "Export")
            End Try
        Next
        Try
            Dim filePath As String = My.Settings.Item( _
            "XMLExportDirectory").ToString()
            filePath = Path.Combine(filePath, My.Settings.Item( _
            "XMLFileName").ToString())
            Dim outputFile As FileInfo = New FileInfo(filePath)

            If outputFile.Exists Then
                File.Delete(filePath)
            End If
            xmlDoc.Save(filePath)
        Catch fileExc As UnauthorizedAccessException
            MessageBox.Show(fileExc.Message, "Export")
        End Try
    End Sub

Most of the code in the procedure creates the XML document, loops through tasks in the folder, extracts task data, adds the data to an XmlNode object, and appends that node to the document. Then, the XmlDocument instance is saved to a specified directory.

The location of the export directory comes from a user-scoped setting prescribed using the Application Settings architecture in Visual Studio 2005. This architecture features a My.Settings class that provides easy access to user-scoped or application-scoped settings for a project.

Note

For more information about the Application Settings architecture, see Using My.Settings in Visual Basic 2005.

In this example, the file name is set as "c:\XMLExport\MyReport.xml".

Using the XML Expansion Pack

The XML expansion pack is defined by its manifest file. In this sample, the exported XML file containing the task data includes a reference to an expansion pack manifest file called OutlookTaskExporterSolution.xml. The contained reference specifies that the manifest is in the same directory as the exported file, because it contains just the file name with no other directory included. This manifest file contains information about what the expansion pack includes. In this sample, the expansion pack includes a schema file and two Extensible Stylesheet Language (XSL) style sheets. These files are located in the same directory as the manifest for the expansion pack. The full contents of the manifest look like this:

<manifest xmlns="http://schemas.microsoft.com/office/xmlexpansionpacks/2003">
   <version>1.0</version>
   <updateFrequency>20160</updateFrequency>
   <uri>OutlookTaskExporter</uri>
   <solution>
      <solutionID>OutlookTaskExporterXSLT-1</solutionID>
      <type>transform</type>
      <alias>Personal Tasks</alias>
      <context>http://schemas.microsoft.com/office/word/2003/wordml</context>
      <file>
         <type>primaryTransform</type>
         <version>1.0</version>
         <filePath>TaskReport_Personal.xsl</filePath>
      </file>
   </solution>
   <solution>
      <solutionID>OutlookTaskExporterXSLT-2</solutionID>
      <type>transform</type>
      <alias>Personal Tasks (75 percent complete)</alias>
      <context>http://schemas.microsoft.com/office/word/2003/wordml</context>
      <file>
         <type>primaryTransform</type>
         <version>1.0</version>
         <filePath>TaskReport_Percent.xsl</filePath>
      </file>
   </solution>
   <solution>
      <solutionID>Outlook Task ExporterXSD</solutionID>
      <type>schema</type>
      <alias>OutlookTaskExporter Schema</alias>
      <file>
         <type>schema</type>
         <version>1.0</version>
         <filePath>Tasks.xsd</filePath>
      </file>
   </solution>
</manifest>

Note

You can find a full explanation of the expansion pack aspect of this solution sample in the article Office Talk: Easy Task Reports with Outlook and WordProcessingML.

When a user opens the exported XML file, Word prompts the user to download the XML expansion pack (Figure 3).

Note

Office 2003 Editions do not allow XML expansion packs to download and run on a computer unless the XML manifest is signed. You can read more information about signing these manifests in the Microsoft Office 2003 Smart Document Software Development Kit.

Click Yes to download the target files specified in the manifest and opens the document.

Figure 3. Downloading the XML expansion pack

However, remember that the XML file contains only data, not formatting. But the document is fully formatted when it opens in Word. Word applies formatting by using one of the XSL style sheets specified in the expansion pack (Figure 4).

Figure 4. The opened document with XML expansion pack applied

The solution uses two XSL style sheets (you can add more as you prefer), each one accessing the same source XML file and expressing it using WordProcessingML. But each style sheet targets different data in the source XML file, bringing it forward into the final Word document. For example, one style sheet targets only tasks assigned to a specific category. Another style sheet targets only tasks assigned to the same category, with the added criteria that the percent-complete attribute is of a certain value. Again, the Extensible Stylesheet Language Transformations (XSLT) instructions with the articulated WordProcessingML are too long to fully explain in this article, but the following is a portion of one style sheet that includes the key filtering statement:

<w:body>
   <wx:sect>
      <wx:sub-section>
         <wx:sub-section>
            <xsl:apply-templates select="tns:SummaryInfo"/>
            <xsl:apply-templates 
             select="tns:Task
             [@PercentComplete=75 and
             contains(tns:category,'Personal')]"/>      
         </wx:sub-section>
      </wx:sub-section>
   </wx:sect>
</w:body>

This style sheet specifies only tasks whose category is "Personal" and where 75 percent of the task is completed.

As Word displays the document, the XML Document task pane appears, showing a list of XSLTs that the user can apply to the document. By default, one of the XSLTs is already applied. When a user clicks another XSLT in the list, the document changes. For example, notice in Figure 5 how a different XSLT is selected, and the document reflects the change.

Figure 5. Selecting a different XSLT changes the document

The add-in's XML export feature is only part of the solution. The rest of the solution enables users to display custom forms with data from both Outlook and an external system.

Using Custom Forms and Controls in the Add-In

Outlook does a great job of pulling together a lot of information of many types: e-mail messages, contacts, appointments, tasks, notes, documents, and more. But sometimes users want to quickly relate items to each other. Using this solution sample, users can select a task and then view e-mail messages associated with that task. They can also see information from a custom CRM system in which the contact names are contained in the task item.

To allow users to view custom forms with this additional data, the add-in provides two buttons on the main custom menu (Figure 6). These are enabled when the user has selected a task in the tasks folder.

Figure 6. Displaying buttons for task-based options

The add-in uses the Explorer.SelectionChange event and checks the type of item that is selected. If the item is an Outlook task, it enables the buttons.

    Private Sub _Explorer_SelectionChange() _
    Handles _Explorer.SelectionChange
      Dim objItem As Object
        Dim objAction As Outlook.Action
        If _Explorer.Selection.Count > 0 Then
            objItem = _Explorer.Selection(1)
            If objItem.Class = _
            Outlook.OlObjectClass.olTask Then
                _Task = objItem
                _messages.Enabled = True
                _contacts.Enabled = True
            Else
                _messages.Enabled = False
                _contacts.Enabled = False
            End If
        Else
            _messages.Enabled = False
            _contacts.Enabled = False
        End If
        objItem = Nothing
        objAction = Nothing
    End Sub

When the user clicks on the button labeled "View Messages", the following code executes:

    Private Sub _messages_Click(ByVal Ctrl _
    As Microsoft.Office.Core.CommandBarButton, _
    ByRef CancelDefault As Boolean) _
    Handles _messages.Click
        Dim messagesForm As New Messages()
        messagesForm.SearchTerm = _Task.Subject
        messagesForm._Application = Me
        messagesForm.ShowDialog()
    End Sub

The code creates and displays a custom form for searching e-mail messages.

Displaying the Custom Messages Form

The custom form for task-related e-mail messages contains a ListView control with messages whose subject contains the subject of the target task (Figure 7).

Figure 7. Displaying the custom form for e-mail messages

The ListView displays the subject and sender information. When you select an item in the ListView, the large text box on the form is filled with the body text of Inbox e-mail messages returned from a programmatic search. The custom form class definition has one public, write-only property—for the search term used to search the Inbox. The other variables are used for working with Outlook object instances required for searching the Inbox and collecting results.

    Private _searchTerm As String
    Public WriteOnly Property SearchTerm() As String
        Set(ByVal value As String)
            _searchTerm = value
        End Set
    End Property
    Public WithEvents _Application As Outlook.Application
    Dim _results As Outlook.Results

When the form loads, it first searches the Inbox by calling a custom procedure, UseAdvancedSearch, which uses the AdvancedSearch method in the Outlook object model.

    Private Sub Messages_Load( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load
       UseAdvancedSearch()
    End Sub

Searching the Inbox using the AdvancedSearch method requires passing a scope, filter, and search term parameters, as shown in the following code:

    Private Sub UseAdvancedSearch()
        Dim search As Outlook.Search = Nothing
        Dim inbox As Outlook.MAPIFolder = _
        _Application.ActiveExplorer().Session. _
        GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox)
        Try
            Dim filter As String = _
            String.Format("{0} LIKE '%{1}%'", _
            "urn:schemas:httpmail:subject", _
            _searchTerm)
            Dim scope As String = String.Format _
            ("SCOPE ('shallow traversal of ""{0}""')", _
            inbox.FullFolderPath)
            search = _Application.AdvancedSearch( _
            scope, filter, True, _searchTerm)
        Catch comExc As System.Runtime.InteropServices.COMException
            'Do nothing
        Catch ex As Exception
            MessageBox.Show("Search error", "Search")
        End Try
    End Sub

The AdvancedSearch method works asynchronously. In other words, Outlook can do other things, respond to user input, or fire other events while the search executes. When Outlook has finished collecting the results of its search, it fires the AdvancedSearchComplete event. It is in this event procedure that code executes to loop through the results collection and populate the ListView, as shown in Figure 7.

   Private Sub _Application_AdvancedSearchComplete _
    (ByVal SearchObject As _
    Microsoft.Office.Interop.Outlook.Search) _
    Handles _Application.AdvancedSearchComplete

        If SearchObject.Tag = _searchTerm Then
            _results = SearchObject.Results
            If _results.Count > 0 Then
                Dim i As Integer
                Dim _item As Outlook.MailItem
                For i = 1 To _results.Count
                    If _results.Item(i).Class = _
                    Outlook.OlObjectClass.olMail Then
                        _item = _results.Item(i)
                        Dim item1 As New ListViewItem( _
                        _item.SenderName, 0)
                        item1.SubItems.Add(_item.Subject)
                        item1.SubItems.Add(_item.Body)
                        ListView1.Items.AddRange( _
                        New ListViewItem() {item1})
                    End If
                Next
                TextBox1.Text = ListView1.Items(0).SubItems(2).Text
                ListView1.Items(0).Selected = True
            Else
                Label1.Visible = True
            End If
        End If
    End Sub

Listview controls are convenient because they allow you to display multiple columns in an orderly way. Selecting an item in the ListView causes the e-mail item's main body text to populate the text box on the form.

    Private Sub ListView1_ItemSelectionChanged( _
    ByVal sender As Object, ByVal e As _
    System.Windows.Forms.ListViewItemSelectionChangedEventArgs) _
    Handles ListView1.ItemSelectionChanged
        TextBox1.Text = e.Item.SubItems(2).Text
    End Sub

Displaying the Custom Contacts Form

The add-in also provides a feature to display custom contact information. When a user clicks the button labeled "View Contacts" on the custom menu, a form loads.

    Private Sub _contacts_Click(ByVal Ctrl _
    As Microsoft.Office.Core.CommandBarButton, _
    ByRef CancelDefault As Boolean) _
    Handles _contacts.Click
        Dim contactsForm As New Contacts()
        contactsForm.CustomerData = _Task.ContactNames
        contactsForm.TaskSubject = _Task.Subject
        contactsForm.ShowDialog()
    End Sub

This custom form contains only one control, a button for closing the form. When the form loads, it calls a custom procedure, GetCustomers, that receives an argument containing a delimited string. This string contains the list of contacts from the target task. Figure 8 shows a task item with a populated Contacts field.

Figure 8. Task item with populated Contacts field

The add-in code for the custom form extracts this list of contacts from the task item and passes them to a custom Web service exposed by the CRM system. The Web service parses the list, queries the CRM data store, and returns search results, if there are any. The search results for each contact contain the name, phone number, and photo of the contact person.

When the results return, the add-in code loads a custom user control for each contact in the results. Each control loads and appears on the custom form. To make sure that the form displays the control instances properly, you need a little code to manage the spacing of the controls. The following code shows property, constant, and variable declarations for the form:

    Private Const TOPSTART As Integer = 40
    Private Const VERTSPACE As Integer = 105
    Private currentTop As Integer = TOPSTART
    Private _customerData As String
    Public WriteOnly Property CustomerData() As String
        Set(ByVal value As String)
            _customerData = value
        End Set
    End Property
    Public WriteOnly Property TaskSubject() As String
        Set(ByVal value As String)
            Me.Text = "Contacts for task: " & value
        End Set
    End Property

The TaskSubject property simply alters the form's caption in a custom way. The CustomerData property holds the delimited list of customers from the task item. You use the constants and the currentTop variable for proper spacing of the dynamically loaded controls.

The form calls the custom GetCustomers procedure as the form loads.

    Private Sub Contacts_Load( _
    ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles Me.Load
        GetCustomers()
    End Sub

The GetCustomers procedure calls a custom Web service that receives the delimited list of contacts and returns an array of XmlNode objects, one for each of the contacts found in the CRM data source.

   Private Sub GetCustomers()
       Dim custWS As New CustomerWS.Service
        Dim nodes() As System.Xml.XmlNode
        Dim customerName As String
        Dim customerPhoto As String
        Dim customerPhone As String
        Dim customerNode As XmlNode
        Dim bytes() As Byte
        Try
            nodes = custWS.GetCustomers(_customerData)
            For Each customerNode In nodes
                customerName = customerNode.ChildNodes(0).InnerText
                customerPhoto = customerNode.ChildNodes(1).InnerText
                bytes = Convert.FromBase64String(customerPhoto)
                customerPhone = customerNode.ChildNodes(2).InnerText
                Dim cc As New ContactControl()
                cc.CustomerName = customerName
                cc.CustomerPhone = customerPhone
                cc.CustomerPhoto = bytes
                cc.Top = currentTop
                cc.Left = 20
                Me.Controls.Add(cc)
                cc.Visible = True
                currentTop = currentTop + VERTSPACE
            Next
        Catch ex As Web.Services.Protocols.SoapException
            Label1.Visible = True
        End Try
    End Sub

In the For...Next loop, the add-in loops through all nodes in the array returned by the Web service. It then loads an instance of the ContactControl user control. This control has three public properties—one for the contact's name, one for the phone number, and one for a photo. The first two properties are simple strings. The third is an array of bytes. This array of bytes is for the Base64-encoded string for the contact's photo. After setting these properties, the add-in positions the control on the form, adds it to the form's Controls collection, and makes it visible.

The custom control is designed with three controls—one for the name, one for the phone number, and a PictureBox control for the photo (Figure 9).

Figure 9. The custom control design

The code for the custom control is contained in its Load event.

    Private Sub ContactControl_Load( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load
        lblName.Text = _customerName
        lblPhone.Text = _customerPhone
        Dim newBitMap As Bitmap
        Dim scaledBitMap As Bitmap
        newBitMap = New Bitmap( _
        New MemoryStream(_customerPhoto))
        scaledBitMap = New Bitmap( _
        newBitMap, New Size(78, 78))
        pictBox1.Image = scaledBitMap
    End Sub

Most of the code ensures that the contact's photo appears properly. Because the Web service returns a Base64-encoded string that is converted to an array of bytes, the user control converts the bytes into a bitmap instance. Also, because the pictures may be scaled differently than the dimensions of the PictureBox control, the bitmap is rescaled to fit properly.

To call the custom Web service from the contacts form, you need to create a Web reference.

To add a reference to the Web service

  1. Right-click the project in the Solution Explorer window and then click Add Web Reference.

    The Add Web Reference dialog box appears. In the URL text box, type the address of the Web service (this solution uses http://localhost/CRM1/service.asmx), and press Enter.

  2. In the Web reference name text box, type a meaningful, developer-friendly name for your Web service. You use this name to reference the Web service in your code.

  3. Your dialog box should look like Figure 10. Click Add Reference to add the reference to your project.

    Figure 10. Adding a Web reference to a project

The code in the GetCustomers procedure includes statements for creating an instance of the proxy for this Web service and for calling its external GetCustomers Web method.

    Dim custWS As New CustomerWS.Service
    Dim nodes() = custWS.GetCustomers(_customerData)

The code for the Web method is fairly straightforward:

   <WebMethod()> _
    Public Function GetCustomers( _
    ByVal CustomersList As String) As System.Xml.XmlNode()
        Dim xmlDoc As New System.Xml.XmlDocument()
        Dim xmlList As System.Xml.XmlNodeList
        Dim customersArray() As String
        customersArray = CustomersList.Split(New Char() {","c})
        xmlDoc.Load(Server.MapPath("Customers.xml"))
        Dim myPath As String
        myPath = "/contacts/contact[]/."
        Dim clause As String = String.Empty
        Dim customerName As String
        Dim customerNode As System.Xml.XmlNode
        Dim i As Integer = 0
        For Each customerName In customersArray
            If clause.Length = 0 Then
                clause = clause & "contains(name,'" _
                & customerName & "')"
            Else
                If customerName.StartsWith(" ") _
                Then customerName = _
                customerName.Substring(1)
                clause = clause & " or contains(name,'" _
                & customerName & "')"
            End If
        Next
        myPath = myPath.Insert(myPath.IndexOf("[]") + 1, clause)
        xmlList = xmlDoc.SelectNodes(myPath)
        Dim nodes(xmlList.Count) As System.Xml.XmlNode
        For Each customerNode In xmlList
            nodes(i) = customerNode
            i = i + 1
        Next
        Return nodes
    End Function

The main purpose of the Web service code is to parse the delimited string passed to the Web method and to look within the data source to see if any contacts match the received list. In this sample, the data source is a simple XML file containing the contact information and photo data. If results are found, the Web service returns the node as an item in an array of XmlNode objects.

When the custom contacts form receives the array of XmlNode objects it loops through the array and loads the custom control on the form for each contact. The final form (Figure 11) shows the contact information with photos.

Figure 11. Displaying the contacts form

New Outlook support in Visual Studio 2005 Tools for the Microsoft Office System greatly improves the add-in development experience. Historically, creating managed add-ins for Outlook, while not burdensome or overly complex, required you to set, verify, and revisit many different things to achieve adequate design-time, debugging, and run-time experiences. Visual Studio 2005 Tools for Office makes it much easier to create and run an Outlook add-in and, at the same time, it brings the full power of the .NET Framework and its security model into the realm of Outlook productivity solutions. This solution sample demonstrates how to use the add-in architecture and tools for working with menus, Outlook objects, custom forms, and other external resources.

Additional Resources

For more information about Outlook add-in tools, see these resources: