Building a Custom Add-in for Outlook 2007 Using Windows Presentation Foundation

Summary:   Learn how to build a custom add-in for Microsoft Office Outlook 2007 using the Microsoft .NET Framework 3.0 and Microsoft Windows Presentation Foundation.

Fulvio Giaccari, SB Soft S.r.l.

April 2007

Applies to: Microsoft Office Outlook 2007, Microsoft Visual Studio Tools for Office 2005 SE, Microsoft .NET Framework 3.0, Microsoft Windows Presentation Foundation

Download the code for the custom add-in.

Contents

  • Building Custom Solutions

  • Creating the Project

  • Creating the AddMenuBar Method

  • Creating the Windows Form

  • Conclusion

  • Additional Resources

  • About the Author

Building Custom Solutions

Although the 2007 Microsoft Office system is both robust and powerful, it cannot solve every complex business problem. That is why the ability to build custom solutions and add-ins for Microsoft Office is a critical component of the 2007 Office system. Using Microsoft Visual Studio 2005 Tools for the 2007 Microsoft Office System Second Edition (VSTO), you can develop custom solutions to help solve the problems that your business needs to solve.

This article demonstrates how to develop an add-in for Microsoft Office Outlook 2007 to help a fictitious company, the World Tour Travel Agency, generate customer e-mail to confirm flight reservations.

As you build the custom add-in, you will gain experience developing solutions for Office Outlook 2007. This article introduces programming techniques for the Microsoft .NET Framework 3.0 and shows you how to use the Windows Presentation Foundation (WPF) to create the user interface controls that the custom add-in uses.

The custom add-in contains two parts: the first part handles linking the add-in to Office Outlook 2007; the second part handles the add-in's graphical layout. Although this add-in does not interact with a Microsoft SQL Server database, imagine how much more robust you can make it by adding database code. Figure 1 shows the class schema for the custom add-in.

Figure 1. Custom add-in class schema

Custom add-in class diagram

Creating the Project

To start, create an Office Outlook 2007 project in VSTO and call it WorldTravelAddIn (see Figure 2).

Figure 2. The Outlook 2007 add-in project

The Outlook 2007 add-in project

Notice that the solution has a project called WorldTravelAddinSetup that allows you to install the add-in on any computer that has Outlook 2007 and Windows Presentation Foundation installed.

The project has a file called ThisAddIn.cs (or ThisAddIn.vb if you created a Visual Basic project). The ThisAddIn class has two methods that manage events: ThisAddIn_Startup and ThisAddIn_Shutdown. ThisAddIn_Startup contains method calls that execute before Outlook 2007 is launched; ThisAddIn_Shutdown contains method calls that execute when Outlook 2007 is closed.

ThisAddIn.cs imports the following namespaces:

using Microsoft.VisualStudio.Tools.Applications.Runtime;
using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;

Creating the AddMenuBar Method

Next, add a method to the project to create a button inside the Outlook 2007 menu. When the button is pressed, Outlook displays a form that helps the user create a customized e-mail.

To request the classes that the add-in uses, add the following code to ThisAddIn.cs:

private Office.CommandBar menuBar;
private Office.CommandBarPopup newMenuBar;
private Office.CommandBarButton buttonOne;
private string menuTag = "WorldAddIn";

After you request the classes, add the AddMenuBar method to create a menu item in Outlook 2007. Add the following code to ThisAddIn.cs:

private void AddMenuBar()
{
}

The AddMenuBar method contains code that creates the menu and inserts it into an existing Outlook 2007 menu. The following code adds both a new menu item and a button that opens the WPF form:

            Try
            {
                //Define the existent Menu Bar
                menuBar = this.Application.ActiveExplorer().CommandBars.ActiveMenuBar;
                //Define the new Menu Bar into the old menu bar
                newMenuBar = (Office.CommandBarPopup)menuBar.Controls.Add(
                    Office.MsoControlType.msoControlPopup, missing,
                    missing, missing, false);
                //If I dont find the newMenuBar, I add it
                if (newMenuBar != null)
                {
                    newMenuBar.Caption = "World Tour Agency";
                    newMenuBar.Tag = menuTag;
                    buttonOne = (Office.CommandBarButton)newMenuBar.Controls.
                    Add(Office.MsoControlType.msoControlButton, missing,
                        missing, 1, true);
                    buttonOne.Style = Office.MsoButtonStyle.
                        msoButtonIconAndCaption;
                    buttonOne.Caption = "Send Confirmation Email";
                    //This is the Icon near the Text
                    buttonOne.FaceId = 610;
                    buttonOne.Tag = "c123";
                    //Insert Here the Button1.Click event    
                    newMenuBar.Visible = true;
                }
            }
            catch (Exception ex)
            {
                //This MessageBox is visible if there is an error
                System.Windows.Forms.MessageBox.Show("Error: " + ex.Message.ToString(), "Error Message Box", MessageBoxButtons.OK, 
                MessageBoxIcon.Exclamation);
            }

After you create the AddMenuBar method, add a call to it inside ThisAddIn_Startup:

//Method to create new menu
AddMenuBar();
NoteNote

If you launch the add-in in debug mode more than once, the add-in menu is inserted into the Outlook 2007 menu multiple times. To prevent this from happening, the add-in needs a RemoveMenubar method.

Next, add the RemoveMenubar method; this method checks to see if the add-in menu already exists inside Outlook 2007. If the add-in menu exists, this method deletes it. Add the following code to ThisAddIn.cs:

#region Remove Button from Outlook's Menu
        private void RemoveMenubar()
        {
            // If the menu already exists, remove it.
            try
            {
                Office.CommandBarPopup foundMenu = (Office.CommandBarPopup)
                    this.Application.ActiveExplorer().CommandBars.ActiveMenuBar.
                    FindControl(Office.MsoControlType.msoControlPopup,
                    missing, menuTag, true, true);
                if (foundMenu != null)
                {
                    foundMenu.Delete(true);
                }
            }
            catch (Exception ex)
            {
                System.Windows.Forms.MessageBox.Show(ex.Message);
            }
        }
#endregion

Next, place a call to this method inside ThisAddIn_Startup before the call to AddMenuBar:

//Search the menu and delete it if found
RemoveMenubar();

Next, the add-in needs to handle button-click events. Add the following code to AddMenuBar:

buttonOne.Click += new Office._CommandBarButtonEvents_ClickEventHandler(buttonOne_Click);

After you associate an event handler to the button click, add the buttonOne_Click method to the ThisAddIn class:

#region Create and Open WPF form
/// <param name="ctrl">Create WPF Form runtime</param>
private void buttonOne_Click(Office.CommandBarButton ctrl, ref bool cancel)
{

}
#endregion

Creating the Windows Form

The second part of the add-in creates the Windows Form to help generate a customer e-mail. To add an .ASMX form file to the project, right click the WorldTravelAddIn project, click Add and then click New Item (see Figure 3).

Figure 3. Insert a form file to the add-in

Insert a form file to the add-in

Next, click Windows(WPF) and name the form file window2.xaml; then click Add (see Figure 4).

Figure 4. Add a WPF file to the project

Add a WPF file to the project

After you add the form files, Visual Studio 2005 SE inserts all the namespaces that the add-in needs for the form to work correctly with WPF.

NoteNote

The namespaces that the add-in needs to function correctly in Outlook 2007 are automatically added during VSTO setup.

After all the necessary references and namespaces are added, your VSTO project should look like Figure 5.

Figure 5. The VSTO project

The VSTO 2005 SE project

The custom add-in creates all of the form controls at runtime; this is one aspect of how control management differs in .NET Framework 3.0.

Add the FormWPFCreate method that will contain the form controls:

private void FormWPFCreate()
{
}

Inside this method, add a request for the form window and set the initial dimensions and title of the form:

//Create a Windows form with WPF namespace
Window2 window = new Window2();
window.Title = "Form to create a Mail";
//Set the dimension
window.Height = 306;
window.Width = 464;

Next, add a Grid control to which the form controls will link. It is important to know that every time you define a form with WPF, you must also define a Grid component:

//Create the Grid Control
Grid newgrid = new Grid();
//Show the Grid Lines
newgrid.ShowGridLines = true;

Next, define the lines inside the newgrid component. Like all grids, the newgrid component has both rows and columns. The add-in defines two rows and zero columns:

//Define Row of Grid
RowDefinition rowDef1 = new RowDefinition();
rowDef1.Height = new GridLength(102, GridUnitType.Pixel);
RowDefinition rowDef2 = new RowDefinition();
rowDef2.Height = new GridLength(171, GridUnitType.Pixel);
//Add the row to the grid component
newgrid.RowDefinitions.Add(rowDef1);
newgrid.RowDefinitions.Add(rowDef2);
NoteNote

Rows and columns are measured in units of star or pixel. If the rows and columns are created at design time, VSTO automatically uses star as the measurement unit. If you want to measure units in pixels, you must click in the point indicated by the red circle in Figure 6.

Figure 6. Changing measurement units

Changing measurement units

After the lines in the Grid component are defined, the add-in needs to define a NameScope object.

Namescope objects are both a concept, and also the programming objects that store relationships between the XAML defined names of objects and their instance equivalents. Namescopes in the WPF managed code are created while loading the pages for a XAML application. Namescopes as the programming object are defined by the INameScope interface and are also implemented by the practical class NameScope.

To define the Namescope add the following code to the add-in source code:

// Create a name scope for the Grid
// and assign a name
NameScope.SetNameScope(newgrid, new NameScope());

Next, add all the controls that the form needs. Start with the first label:

#region Label Travel Agency
System.Windows.Controls.Label label1 = new System.Windows.Controls.Label();
label1.Height = 32.2766666666667;
label1.Margin = new System.Windows.Thickness(113.37, 4.7233333333333, 0, 0);
label1.VerticalAlignment = System.Windows.VerticalAlignment.Top;
label1.FontSize = 16;
label1.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
label1.Width = 112.63;
label1.Content = "Travel Agency";
// Add to Grid the component Label1
Grid.SetRow(label1, 0);
#endregion

To better understand what the code is doing, examine it closely. First, the code creates an instance of the Label object in the namespace System.Windows.Controls. This namespace is different from the namespace used in .NET Framework 2.0.

Next, the code sets the label's Height property and Margin property. The Margin property is a new concept in .NET Framework 3.0; it pinpoints the control's position. Without this information the control cannot be positioned.

The last control that the code inserts associates the Grid component of the form to the component label1 just created. If the code does not associate the control to the component Grid, the control will not display properly.

Next, add the other form components:

#region Label World Tour
System.Windows.Controls.Label label2 = new System.Windows.Controls.Label();
label2.Height = 34.2766666666667;
label2.Margin = new System.Windows.Thickness(184.37, 27.7233333333333, 142, 40.0000000000001);
label2.VerticalAlignment = System.Windows.VerticalAlignment.Top;
label2.FontSize = 20;
System.Windows.Media.FontFamily myFont = new System.Windows.Media.FontFamily("Arial");
label2.FontFamily = myFont;
label2.Foreground = Brushes.Blue;
label2.HorizontalAlignment = System.Windows.HorizontalAlignment.Right;
label2.Width = 125.63;
label2.Content = "World Tour";
Grid.SetRow(label2, 0);
#endregion

#region Image Travel Agency
System.Windows.Controls.Image image1 = new System.Windows.Controls.Image();
image1.Width = 100;
System.Windows.Media.Imaging.BitmapImage imageSource = new System.Windows.Media.Imaging.BitmapImage();
imageSource.BeginInit();
//Put your directory where is located
//your png image
imageSource.UriSource = new Uri(@"C:\Fulvio\img\yast_suse_tour.png");
imageSource.DecodePixelHeight = 100;
imageSource.EndInit();
image1.Margin = new System.Windows.Thickness(-180, 6, 175, 8);
image1.Source = imageSource;
//image1.Height = 88;
image1.VerticalAlignment = System.Windows.VerticalAlignment.Top;
image1.Stretch = Stretch.Uniform;
Grid.SetRow(image1, 0);
#endregion

#region Label "Fill Form"
System.Windows.Controls.Label label3 = new System.Windows.Controls.Label();
label3.Height = 23.2766666666667;
label3.Margin = new System.Windows.Thickness(20.37, 5.93852320675109, 0, 0);
label3.VerticalAlignment = System.Windows.VerticalAlignment.Top;
label3.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
label3.Width = 86.63;
label3.Content = "Fill Form:";
Grid.SetRow(label3, 1);
#endregion

#region Label Name
System.Windows.Controls.Label label4 = new System.Windows.Controls.Label();
label4.Height = 23.2766666666667;
label4.Margin = new System.Windows.Thickness(18.37, 28.938523206751, 0, 0);
label4.VerticalAlignment = System.Windows.VerticalAlignment.Top;
label4.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
label4.Width = 70.63;
label4.Content = "Name: ";
Grid.SetRow(label4, 1);
#endregion

#region Label Sourname
System.Windows.Controls.Label label5 = new System.Windows.Controls.Label();
label5.Margin = new System.Windows.Thickness(17.37, 53.938523206751, 0, 93.7848101265823);
label5.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
label5.Width = 71.63;
label5.Content = "Sourname: ";
Grid.SetRow(label5, 1);
#endregion

#region Label Email
System.Windows.Controls.Label label6 = new System.Windows.Controls.Label();
label6.Margin = new System.Windows.Thickness(18.3699999999999, 77.7233333333333, 0, 70);
label6.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
label6.Width = 35.63;
label6.Content = "Email:";
Grid.SetRow(label6, 1);
#endregion

#region Name's TextBox
System.Windows.Controls.TextBox TextBox1 = new System.Windows.Controls.TextBox();
TextBox1.Height = 19;
TextBox1.Width = 100;
TextBox1.Name = "Name";
// Register TextBox2's name with newgrid
newgrid.RegisterName(TextBox1.Name, TextBox1);
TextBox1.Margin = new System.Windows.Thickness(140, 31, 0, 0);
TextBox1.VerticalAlignment = System.Windows.VerticalAlignment.Top;
TextBox1.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
Grid.SetRow(TextBox1, 1);
#endregion

#region Sourname's TextBox
System.Windows.Controls.TextBox TextBox2 = new System.Windows.Controls.TextBox();
TextBox2.Height = 19;
TextBox2.Width = 100;
TextBox2.Name = "Sourname";
// Register TextBox2's name with newgrid
newgrid.RegisterName(TextBox2.Name, TextBox2);
TextBox2.Margin = new System.Windows.Thickness(140, 55, 0, 0);
TextBox2.VerticalAlignment = System.Windows.VerticalAlignment.Top;
TextBox2.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
Grid.SetRow(TextBox2, 1);
#endregion

#region Email's TextBox
System.Windows.Controls.TextBox TextBox4 = new System.Windows.Controls.TextBox();
TextBox4.Height = 19;
TextBox4.Width = 100;
TextBox4.Name = "Email";
//Register TextBox3's Name with newgrid
newgrid.RegisterName(TextBox4.Name, TextBox4);
TextBox4.Margin = new System.Windows.Thickness(141, 80, 0, 72);
TextBox4.VerticalAlignment = System.Windows.VerticalAlignment.Top;
TextBox4.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
Grid.SetRow(TextBox4, 1);
#endregion

#region Label Copyright
System.Windows.Controls.Label labelCopyright = new System.Windows.Controls.Label();
labelCopyright.Content = "Copyrights 2006-2007 Fulvio Giaccari";
labelCopyright.Background = Brushes.BurlyWood;
labelCopyright.HorizontalAlignment = System.Windows.HorizontalAlignment.Right;
labelCopyright.Margin = new Thickness(0, 1.7233333333333, -266, 0);
labelCopyright.Width = 269.63;
labelCopyright.Height = 24.2766666666667;
labelCopyright.VerticalAlignment = VerticalAlignment.Top;
labelCopyright.HorizontalContentAlignment = System.Windows.HorizontalAlignment.Center;
// Create a RotateTransform to rotate
// the labelCopyright 90 degrees about its
// top-left corner.
RotateTransform labelCopyrightRotateTransform = new RotateTransform(90);
labelCopyright.RenderTransform = labelCopyrightRotateTransform;
Grid.SetRow(labelCopyright, 0);
#endregion

The add-in displays some of the new components and abilities that are available in .NET Framework 3.0. For example, the code uses RotateTransform to rotate the labelCopyright component.

NoteNote

The add-in uses an image file stored on your hard drive as C:\Fulvio\img\yast_suse_tour.png. You need to supply your own image file, otherwise the form will not display.

Next, add the Button component. This component is added last because it requires more complex management than the other components. To define the button control, add the following code:

#region Button
System.Windows.Controls.Button button1 = new System.Windows.Controls.Button();
button1.Height = 39;
button1.Margin = new System.Windows.Thickness(14, 0, 0, 16);
button1.VerticalAlignment = System.Windows.VerticalAlignment.Bottom;
button1.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
button1.Width = 191;
button1.Content = "Create";
button1.Click += new RoutedEventHandler(button1_Click);
Grid.SetRow(button1, 1);
#endregion

Next, add the event handler for the button control:

#region Button1 Event
void button1_Click(object sender, RoutedEventArgs e)
        {
try
        {
         System.Windows.Controls.Button btn1 = sender as System.Windows.Controls.Button;
         Grid grid1 = (Grid)btn1.Parent;
         //Find from NameScope all TextBox controls 
         System.Windows.Controls.TextBox txtName = (System.Windows.Controls.TextBox)grid1.FindName("Name");
         System.Windows.Controls.TextBox txtSourname = (System.Windows.Controls.TextBox)grid1.FindName("Sourname");
         System.Windows.Controls.TextBox txtEmail = (System.Windows.Controls.TextBox)grid1.FindName("Email");
         //Call SendEmail Method to create email
         SendEmail(txtName.Text, txtSourname.Text, txtEmail.Text);
        }
       catch (Exception ex)
        {
         System.Windows.MessageBox.Show("Error: " + ex.Message.ToString());
        }          
        }
#endregion

This method demonstrates the potential of the NameScope class. Notice that the code links some controls (the TextBox controls) to the NameScope class in such a way as to call them back at any time.

The button1_Click method just defined calls a method called SendEmail. To avoid a compiler error, define the SendEmail method as follows:

#region Create Email
private void SendEmail(string name, string sourname, string email)
{
string _body;
_body = "Dear Mr. <STRONG>%name%</STRONG> <STRONG>%sourname%</STRONG>, <BR /> Your flight is now booked. Thank you for using our services. <BR /><BR /> Vincent Lauriat<BR />'World Tour' Travel Agency";
StringBuilder sb = new StringBuilder(_body);
sb.Replace("%name%", name.ToString());
sb.Replace("%sourname%", sourname.ToString());

Outlook.Application app1 = new Microsoft.Office.Interop.Outlook.Application();
Outlook.MailItem mail1 = (Outlook.MailItem)         
            app1.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);
mail1.To = email;
mail1.Subject = "Your flight is booked";
mail1.BodyFormat = Microsoft.Office.Interop.Outlook.OlBodyFormat.olFormatHTML;
mail1.HTMLBody = sb.ToString();
mail1.Display(true);
}
#endregion

This method creates a customized e-mail with the runtime data that the user enters into the form.

The last part of the add-in manages how the controls inside the newgrid component of the FormWPFCreate method are displayed. In the FormWPFCreate method, add the following code after the code that creates the Button1 control:

Try
      {
       //Add all controls to Grid
       newgrid.Children.Add(button1);
       newgrid.Children.Add(label1);
       newgrid.Children.Add(label2);
       newgrid.Children.Add(label3);
       newgrid.Children.Add(label4);
       newgrid.Children.Add(label5);
       newgrid.Children.Add(label6);
       newgrid.Children.Add(image1);
       newgrid.Children.Add(TextBox1);
       newgrid.Children.Add(TextBox2);
       newgrid.Children.Add(TextBox4);
       newgrid.Children.Add(labelCopyright);
       window.Content = newgrid;
       window.Show();
      }
    catch (Exception ex)
      {
       System.Windows.Forms.MessageBox.Show(ex.Message.ToString());
      }

Finally, add the following call to the FormWPFCreate method inside the buttonOne_Click method that handles events for the add-in menu:

private void buttonOne_Click(Office.CommandBarButton ctrl, ref bool cancel)
        {
            FormWPFCreate();
        }

Launch the application in debug mode to see the add-in menu (see Figure 7).

Figure 7. World Tour Agency custom menu

World Tour Agency custom menu

When you click the button inside the menu, you then see the Windows Form that the add-in creates at runtime (see Figure 8).

Figure 8. World Tour Agency custom form

World Tour Agency custom form

Fill in all the blanks in the form and click Create; the add-in then creates a customized e-mail using the form data (see Figure 9).

Figure 9. Customized customer e-mail

Customized customer e-mail

Conclusion

This article showed you how to develop a custom add-in for Outlook 2007 using Visual Studio 2005 Tools for Office SE. Although the add-in was relatively simple, you gained a sense of the combined potential of VSTO and Office Outlook 2007.

The add-in demonstrated how to use the Windows Presentation Foundation component of the .NET Framework 3.0 to create a form. The form added many controls at runtime and used the NameScope class to associate controls to user interface events that the form generated at runtime.

Finally, the add-in showed that when you use the .NET Framework 3.0 to create a form, the form contains a Grid control containing rows and columns and that you must associate every form control with a specific row or column of the Grid control.

Additional Resources

About the Author

Fulvio Giaccari is a Project Manager for SB Soft S.r.l., a Microsoft partner company. He is currently working on porting Windows Form applications to the .NET Framework 3.0. Fulvio is a published author of magazine articles in both English and Italian; he plans to write a book about VSTO that will be published by Apress Inc.

Fulvio is founder of www.freeaspx.it, an online community dedicated to ASP.NET. He is also founder of www.ShareOffice.it, the first Italian user group for Microsoft Office developers.

SB Soft S.r.l. is an Italian company that specializes in building solutions for Microsoft Office and Microsoft SharePoint Services as well as building Windows Form and Web applications for companies, banks, and public administrations.