VSTO 3.0

Developing Office Business Apps with Visual Studio 2008

Steve Fox

This article discusses:
  • New features in Visual Studio 2008
  • Creating custom form regions for Outlook
  • Connecting to a data source
  • Adding search capabilities
This article uses the following technologies:
Visual Studio 2008

Code download available at:CustomOutlookRegion2008_Launch.exe (4168 KB)
Browse the Code Online

Contents

Extend Outlook 2007 with Custom Form Regions
Building Your First Custom Form Region
Show Sales Data and Customer Search
Connecting the Data Source
The Customer Data Search Form
Adding the WPF Sales User Control
Adding the Final Piece
Building and Running the Custom Replacement Form Region
Further Reading

By now, I'm sure you've heard quite a few appealing things about Visual Studio® 2008. It has some awesome features, such as support for LINQ, improved Web development, and tighter integration with Windows Vista® and SharePoint®, to name just a few. One area where Visual Studio 2008 really stands out, though, is in its support for Microsoft® Office solutions development.

Visual Studio Team System 2005 included Visual Studio 2005 Tools for Office (VSTO)—you could also download VSTO Second Edition to begin solution development for both Office 2003 applications and the 2007 Microsoft Office system. However, in Visual Studio 2008, all the VSTO project templates are included.

You can use Visual Studio 2008, specifically VSTO 3.0, to develop Microsoft Outlook® customizations, which is what I'll cover in this article. I'll show you what you can do with a new type of form development technology called form regions, which can be special areas on existing Outlook forms. I'll also show you how to add Windows® Presentation Foundation (WPF) support to form regions to help improve their look, feel, and interactivity.

Extend Outlook 2007 with Custom Form Regions

VSTO 3.0 provides a Windows Forms-based design environment in Visual Studio 2008. This allows you to design and code the new Outlook form regions in one development environment and brings many advantages of Windows Forms to the hosting environment of Outlook. For example, you can create a VSTO form region that connects to a Web service to acquire customer relationship management (CRM) data and display it in a grid. You can even add a custom Ribbon to an Outlook 2007 inspector that will host the custom form region and add code to the Ribbon so that it interacts with the controls on the region.

Note that there are four types of Outlook 2007 form regions at your disposal:

Adjoining These appear at the bottom of the target Outlook form on the first Outlook form page and/or at the bottom of the Outlook reading pane.

Separate These add a new page to the form.

Replacement These cause the form region to replace all of the content on the first form page of the Outlook form (all existing form pages are left in place).

Replace All These cause the form region to replace all of the content on the first form page of the Outlook form, and they remove all other form pages from the form.

Each one of these form regions allows you to extend Outlook in interesting ways, but here I'll focus on the Replacement form region. The Replacement form region can be used to create a custom form for a custom message class. Unlike the Adjoining and Separate form regions that are created for existing Outlook message classes—for example, Mail Message (IPM.Note), Contact (IPM.Contact), and Appointment (IPM.Appointment)—when selecting the Replacement (or Replace All) form region, you create a custom message class that derives from one of the existing Outlook message classes. For example, in the sample, I'll walk through a custom message class called CustomersSalesData that derives from the Mail Message type; thus, what results is a custom message class called IPM.Note.RecentSales.

Building Your First Custom Form Region

To create this custom Replacement form region, I used Visual Studio 2008 to create an Outlook 2007 Add-in project (see Figure 1). I named the project CustomOFR and used the default project location. At this point, Visual Studio creates an Outlook 2007 Add-in project shell for you. This means that when you build and deploy your add-in (which will contain your custom Outlook form region), Visual Studio will automatically load your add-in when Outlook loads.

Figure 1 Selecting Outlook Add-In Project Template

Figure 1** Selecting Outlook Add-In Project Template **(Click the image for a larger view)

With your project shell now created, let's go ahead and add a custom Outlook form region to it. Select Outook Form Region from the Add New Item Dialog and name the region SalesData. This invokes the New Outlook Form Region Wizard, which involves a number of steps to create a Replacement form region. In the first step you indicate whether you're designing a new form region or importing an existing one (.ofs file). In this sample, you want to create a new form region, so go ahead and click Next. Then you'll be asked to select the type of form region you want to create. Select the Replacement type and then click Next. At this point, you'll set some properties for your form region; specifically a name, title, description, and the modes in which you want to display the form region (see Figure 2). After you've entered this information, click Next.

Figure 2 Custom Form Region Properties

Figure 2** Custom Form Region Properties **(Click the image for a larger view)

In the final step, you'll associate your form region with a specific message class. As I mentioned earlier, because this is the Replacement form region you must create a custom message class instead of using the stock message classes that ship with Outlook. You'll notice that in this case, this step has all of the stock message class options grayed out, and the only field available for this step is the custom message class textbox at the bottom of the dialog (see Figure 3). You'll see that in this field I've entered IPM.Note.CustomerSalesData, which means that I'm going to derive my custom message class from the Outlook IPM.Note message class type (or essentially create a custom message class type that inherits all of the same functionality in the stock Mail Message class).

Figure 3 Entering Custom Message Class Type

Figure 3** Entering Custom Message Class Type **(Click the image for a larger view)

All form regions target one or more specific message classes in the Outlook environment. These message classes are directly related to the Outlook item type. There are eight standard message classes (see Figure 3) and, theoretically, an infinite number of custom message classes. You could similarly derive your custom class from other custom class types. After you've entered your custom message class derivation and name, click Finish. The name will be validated when you click the Finish button.

When you've completed the steps in the wizard, a form region will be added to the Outlook add-in project. In my case, the following three items were added to the project:

RecentSales.cs The primary class file for the form region.

RecentSales.Designer.cs The designer file that contains some default code for the form region.

RecentSales.rexs The XML file that is required to display the form region.

Now let's begin customization. After creating the form region, the default view in Visual Studio should be the designer mode of the form region. If not, right-click on RecentSales.cs and select View Designer. You can now begin to add controls to your form.

To keep things simple, I'm going to add three primary things to my custom Replacement form region: a search control that lists customer information, a WPF control that displays updated sales data, and a checkbox for controlling whether the user control is displayed or hidden. Figure 4 illustrates the controls that I've added to my project in the context of my Visual Studio IDE.

Figure 4 Form Region Designer

Figure 4** Form Region Designer **(Click the image for a larger view)

Show Sales Data and Customer Search

The form region represents the design surface and supports the dragging and dropping of controls from the Toolbox to the design surface. So drag a checkbox to the design surface and set its Name property to showChart and its Text property to "Show Sales Data." Then double-click the checkbox control and add the following code to call the UpdateChart method—this method will update both the data and the visibility of the WPF sales chart:

private void checkBox1_CheckedChanged(object sender, EventArgs e)
  { 
    UpdateChart();
  }

I'll return to this method a little later on in the article.

The Sales Search user control allows you to enter some search criteria and browse a small database of customer information that is dynamically updated within the control. To enable this search functionality, I've added a new project called SalesControlLibrary (a Windows Forms Control Library project) to the CustomOFR solution and then added the search control from the Toolbox onto the form region. There are two main parts to the SalesControlLibrary project in my solution: a connection to the data source and the user control that I'll use in the custom Replacement form region.

Connecting the Data Source

With the new project added to the solution, you can now add a data source by clicking Data and Add New Data Source. As you might expect, this sets off the Add New Data Source Wizard, which first prompts you to define and configure a simple connection to a data source (database). For this sample I'm using an Access database called Sales.mdb (which is included in the code download) with the following four schema elements (as you can see in Figure 5): CustomerID is the unique ID for a customer, SalesQuarter represents the fiscal quarter for sales, SalesYear represents the fiscal year in which sales were recorded, and SalesAmount is the dollar amount of a sale.

Figure 5 Customer and Sales Data

Figure 5** Customer and Sales Data **(Click the image for a larger view)

If you were to integrate this add-in with a line-of-business (LOB) system, thus classifying this application as an Office Business Application (OBA), then you'd likely use a Windows Communication Foundation (WCF) service proxy to manage the connection and communication with your LOB system. Using services as proxies to LOB systems is a great way to circumnavigate difficult system interfaces or to pull specific business data directly into the context of your customer's common office applications.

When you think of specific value added when organizations build OBAs, certainly one of the major benefits is the ability to leverage the Office UI to integrate LOB data. This also offloads some of what would otherwise be custom development work to native Office features. And perhaps most importantly, it keeps end users in an environment that is comfortable and familiar (think native formatting and visualization of data in Excel® 2007). To see more information on the general area of OBAs, go online to msdn2.microsoft.com/aa905528.

The Customer Data Search Form

With the data source added to the SalesControlLibrary project, you can now design the component to search customer data. In the designer for my sales search form, I have added a number of controls to the project including some labels, fields, a data grid that is connected to the Sales database, and some fields that display the customer contact information that corresponds to the selected item in the data grid. Figure 6 shows the completed form with all of the controls added. Note that while most of these controls are straightforward to add, with the data-grid view, you'll need to first open the Data Sources pane, right-click the Sales data source and select Details, and then drag and drop the Sales records onto the data-grid view. Then right-click the control, select Edit Columns, and remove the SalesYear and SalesAmount, as you're only interested in showing the CustomerID and SalesQuarter in the data-grid view.

Figure 6 Building the Customer Data User Control

Figure 6** Building the Customer Data User Control **(Click the image for a larger view)

At this point, you've got the new controls added to your form and are ready to add some code to the controls. So, let's take a look at some of the key code that was added to this form. The first things I'll look at are the declarations that I've added to the SalesSearch class. Specifically, I declared a table adapter for Company and Sales, and I created an event handler for any sales data changes:

namespace SalesControlLibrary
{
  public partial class SalesSearch : UserControl
  {
    private SalesDataSetTableAdapters.CompaniesTableAdapter
      companiesAdapter;
    private SalesDataSetTableAdapters.SalesTableAdapter 
      salesAdapter;
    public event EventHandler<SalesSearchEventArgs> 
      SalesDataChangeEvent;
    
    ...
  {    
}

The primary event being triggered is the executeSearch_click event that fires when the Search button is clicked. It's found in the SalesSearch class, as you see in Figure 7.

Figure 7 Execute Search Triggered from Search Button

private void executeSearch_Click(object sender, EventArgs e)
{
  try
  {
    companiesAdapter = new 
      SalesDataSetTableAdapters.CompaniesTableAdapter();
    CompaniesBindingSource.DataSource =
      companiesAdapter.GetDataByCompanyAndIndustry(
        companyNameText.Text, 
        industryText.Text);
  }
  catch (Exception ex)
  {
    MessageBox.Show(ex.Message, "Sales Add-in", MessageBoxButtons.OK);
  }
}

You can see that this creates a new instance of the companiesAdapter and calls the GetDataByCompanyAndIndustry method to retrieve data according to the parameters that the user entered in the search fields (the percent sign in Figure 8 indicates a wildcard search). This data will then be used to populate the Contact and Address fields to display customer information.

Figure 8 Handling BindingSource Change

private void CompaniesBindingSource_CurrentChanged(object sender, 
  EventArgs e)
{
  DataRowView company = (DataRowView)(CompaniesBindingSource.Current);
  try
  {
    string[] xData = new string[4];
    double[] yData = new double[4];
    if (company != null)
    {
      salesAdapter = new SalesDataSetTableAdapters.SalesTableAdapter();
      SalesDataSet.SalesDataTable sales = 
        salesAdapter.GetDataByCustomerID((int)(company["ID"]));
      DataTableReader rdr = sales.CreateDataReader();
      int i = 0;
      while ((rdr.Read()) && (i < xData.Length) && (i < yData.Length))
      {
        xData[i] = String.Format("Q{0} {1}", 
        rdr["SalesQuarter"].ToString(), rdr["SalesYear"].ToString());
        yData[i] = Convert.ToDouble(rdr["SalesAmount"]);
        i++;
      }
      OnSalesDataChanged(company["CompanyName"].ToString(),
        xData, yData);
    }
  }
  catch (Exception ex)
  {
  MessageBox.Show(ex.Message, "Sales Add-in", MessageBoxButtons.OK);
  }
}

In the code behind this form, there are also a couple of helper functions. For example, I've added an event on the CompaniesBindingSource object to handle the case where a user is browsing through the sales data from within the controls (see Figure 8).

Also, I've added an event handler to process any changes to sales data. For example, the following code shows the OnSalesDataChanged method that is invoked if there has been a change in the selection of the current record in the binding source:

protected virtual void OnSalesDataChanged(string companyName, 
  string[] x, double[] y)
{
  if (SalesDataChangeEvent != null)
    SalesDataChangeEvent(this, 
      new SalesSearchEventArgs(companyName, x, y));
}

With the selection change, the OnSalesDataChanged event has parameters for the company name, the year, and the sales for the current record.

Adding the WPF Sales User Control

Now let's look at how to create the WPF control and add it to the custom Outlook form region. Two steps are required to add a WPF user control to the custom Outlook Replacement form region. The first is to create the WPF user control, and the second is to add it to the Replacement form region (that is the RecentSales form region created earlier).

To create the form, add a new WPF User Control Library project to the solution. Visual Studio will create a default control and display the XAML editor. At this point, you can design a WPF chart control and add event handlers. Figure 9 shows the WPF control in the Visual Studio 2008 XAML designer.

Figure 9 WPF Chart Control

Figure 9** WPF Chart Control **(Click the image for a larger view)

Since you've added a new project, you'll have some default XAML code; however, you won't have anything in the designer other than an empty container. Thus, you need to add some XAML code here to create the chart. To help you along, Figure 10 shows the XAML code I used to create the small WPF sales chart for the form region. The code is fairly straightforward in that it creates a small control with two columns and five rows and puts placeholder text on the chart. This text will be updated with data from the Sales database at run time. Note that the XAML is associated with the SalesChart class as declared in the opening line of XAML code.

Figure 10 XAML Code

<UserControl x:Class="WpfChart.SalesChart"
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  Height="300" Width="300">
  <Grid Name="Grid1">
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="80"/>
      <ColumnDefinition Width="190"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition Height="30"/>
      <RowDefinition Height="30"/>
      <RowDefinition Height="30"/>
      <RowDefinition Height="30"/>
      <RowDefinition Height="30"/>
    </Grid.RowDefinitions>
    <TextBlock Name="Category1" Text="X1" Grid.Column="0" 
      Grid.Row="0" VerticalAlignment="Center"
      HorizontalAlignment="Center"/>
    <TextBlock Name="Category2" Text="X2" Grid.Column="0" 
      Grid.Row="1" VerticalAlignment="Center" 
      HorizontalAlignment="Center"/>
    <TextBlock Name="Category3" Text="X3" Grid.Column="0" 
      Grid.Row="2" VerticalAlignment="Center" 
      HorizontalAlignment="Center"/>
    <TextBlock Name="Category4" Text="X4" Grid.Column="0" 
      Grid.Row="3" VerticalAlignment="Center" 
      HorizontalAlignment="Center"/>    
    <TextBlock Name="Title" FontSize="14" FontWeight="Bold" 
       Text="Chart Title" Grid.Column="1" Grid.Row="4" 
      VerticalAlignment="Center" HorizontalAlignment="Right"/>
    <Rectangle Name="Bar1"  Fill="LightSteelBlue" RadiusX="5" 
      RadiusY="5" HorizontalAlignment="Left" Grid.Column="1" 
      Grid.Row="0" Width="170" Height="18"/>
    <Rectangle Name="Bar2"  Fill=" LightSteelBlue" RadiusX="5" 
      RadiusY="5" HorizontalAlignment="Left" Grid.Column="1" 
      Grid.Row="1" Width="170" Height="18"/>
    <Rectangle Name="Bar3"  Fill=" LightSteelBlue" RadiusX="5" 
      RadiusY="5" HorizontalAlignment="Left" Grid.Column="1" 
      Grid.Row="2" Width="170" Height="18"/>
    <Rectangle Name="Bar4"  Fill=" LightSteelBlue" RadiusX="5" 
      RadiusY="5" HorizontalAlignment="Left" Grid.Column="1" 
      Grid.Row="3" Width="170" Height="18"/>
    <TextBlock Name="Label1" Text="$Y1" Grid.Column="1" 
      Grid.Row="0" VerticalAlignment="Center" 
      HorizontalAlignment="Right"/>
    <TextBlock Name="Label2" Text="$Y2" Grid.Column="1" 
      Grid.Row="1" VerticalAlignment="Center" 
      HorizontalAlignment="Right"/>
    <TextBlock Name="Label3" Text="$Y3" Grid.Column="1" 
      Grid.Row="2" VerticalAlignment="Center" 
      HorizontalAlignment="Right"/>
    <TextBlock Name="Label4" Text="$Y4" Grid.Column="1" 
      Grid.Row="3" VerticalAlignment="Center" 
      HorizontalAlignment="Right"/>
  </Grid>
</UserControl>

With the WPF chart created, the last step in the process is to ensure you create methods to update the chart in response to data changes. In the SalesChart class, the UpdateWPFChart method essentially takes a chart title (company name) and two string arrays representing sales data as parameters, and it scales the chart according to this data and updates the chart's text and bar indicators, as shown in Figure 11.

Figure 11 Code to Update Chart

public void UpdateWPFChart(string chartTitle, 
  string[] xVals, double[] yVals)
{
  double maxY = 0;
  foreach (double y in yVals)
  {
    if (y > maxY) 
      maxY = y;
  }
  try
  {
    for (int i = 0; i < 4; i++)
    {
      if ((i >= xVals.Length) || (i >= yVals.Length))
        break;
      else
      {
        TextBlock xText = (TextBlock)(FindName(
          String.Format("Category{0}", i + 1)));
        if (xText != null) 
          xText.Text = xVals[i];
        TextBlock yText = (TextBlock)(FindName(
          String.Format("Label{0}", i + 1)));
        if (yText != null) 
          yText.Text = yVals[i].ToString();
        Rectangle bar = (Rectangle)(FindName(
          String.Format("Bar{0}", i + 1)));
        bar.Width = (yVals[i] / maxY) *
          Grid1.ColumnDefinitions[1].Width.Value;
      }
    }
    Title.Text = chartTitle;
  }
  catch (Exception ex)
  {
    MessageBox.Show(ex.Message, "SalesChart", MessageBoxButton.OK);
  }
}

At this point, all of the controls for the form region are finished, so you can now return to the Outlook Replacement form region designer, where we will add the controls just created and wire them up to our custom form region.

Adding the Final Piece

The last step is to add the controls to the form region and then connect the code in the custom Outlook Replacement form region to our SalesSearch control and WPF chart control. To add the SalesSearch control and the WPF control to the form region, build all of the components in your project—this action will add the controls to your toolbox so that you can drag them onto the designer surface. Drag both the SalesSearch control and the WPF chart control to where you want them on the designer surface of your form region. With the controls in place, I can now create the code that will connect these controls with the form region and with each other.

First, in the RecentSales class I've added some important variable declarations representing the sales (year and amount of sales) and company data:

partial class RecentSales
{
  string[] currentXData;
  double[] currentYData;
  string currentCompany;
  ...
}

Second, in the RecentSales_FormRegionShowing method (which essentially invokes whatever code you add to it when Outlook loads the custom form region) I've set the visibility of the WPF sales chart to hidden by default when Outlook loads the form region:

private void RecentSales_FormRegionShowing(object sender,
  System.EventArgs e)
{
  salesChart1.Visibility = System.Windows.Visibility.Hidden;
}

Next, the UpdateChart method (remember this method from above) uses the sales and company data to call the UpdateWPFChart method and update the data that's presented in the sales chart—the WPF-based bars that indicate sales amounts. Note that we've also got an event handler, which responds to the click events from the checkbox and calls the UpdateChart method. Figure 12 shows the code for both the UpdateChart method and the checkbox's CheckChanged event.

Figure 12 UpdateChart Method and CheckChanged Event

private void UpdateChart()
{
  if (showChart.Checked)
  {
    if ((currentXData != null) && (currentYData != null))
    {
      salesChart1.UpdateWPFChart(
      currentCompany, currentXData, currentYData);
      salesChart1.Visibility = System.Windows.Visibility.Visible;
    }
    else
    {
      salesChart1.Visibility = System.Windows.Visibility.Hidden;
    }
  }
  else
  {
    salesChart1.Visibility = System.Windows.Visibility.Hidden;
  }
}

private void checkBox1_CheckedChanged(object sender, EventArgs e)
{ 
  UpdateChart();
}

Lastly, I have a method to handle data changed events on the SalesSearch control. My method updates the sales and company data and then it calls UpdateChart:

private void salesSearch1_SalesDataChangeEvent(object sender, 
  SalesControlLibrary.SalesSearchEventArgs e)
{
  currentCompany = e.CompanyName;
  currentXData = e.XValues;
  currentYData = e.YValues;
  UpdateChart();
}

Building and Running the Custom Replacement Form Region

With the code now in place, let's build and run the project. To build and run Outlook add-ins, you'll need to close all running instances of Outlook. If you open the Designer view for the custom form region, you should see something similar to Figure 13. You can now build and run the project. Visual Studio will build the solution and, in doing so, launch Outlook and run the add-in assembly.

Figure 13 Final Custom Replacement Form Region

Figure 13** Final Custom Replacement Form Region **(Click the image for a larger view)

To navigate to the custom Outlook Replacement form region, you click File, New, and Choose Forms. This will open the Choose Forms dialog that lists all of the custom Replacement forms that are installed on that client. In this case, you'll see that the Recent Sales form is the only one available in the custom Replacement form region library. To open the form region, select it and click Open.

At this point, Outlook will open the form, and what you should see is something similar to Figure 14. To test out your form region, click the Search button. It should load all of the data within your database (or in the case of the example above, the Sales.mdb database) into the data grid. You can then click the Show Sales Data checkbox to display the sales data for the selected company. Note that you can either select the company directly in the data grid or you can use the navigation strip along the bottom of the SalesSearch control to browse back and forth across the data. Each time you move across the data, the sales chart is automatically updated. Cool stuff!

Figure 14 Final Custom Outlook Replacement Form Region (with WPF Chart Visible)

Figure 14** Final Custom Outlook Replacement Form Region (with WPF Chart Visible) **(Click the image for a larger view)

Further Reading

You have available to you all of the code that goes along with this article, so feel free to experiment as you see fit. Further, we have created an online hands-on lab that walks you through an application similar to the one described in this article if you want some prescriptive guidance on how to build Outlook form regions. You can find this hands-on lab on the VSTO Developer Portal at msdn2.microsoft.com/aa905533. I also encourage you to check out OBA Central (obacentral.com), a Web portal dedicated to showcasing and educating you on all things OBA. Further, you might also pick up my new book, Programming Microsoft Office Business Applications (Microsoft Press®, 2008), which provides in-depth information on how to develop and deploy OBAs. OBA is an exciting area in Office development that you will surely hear more about in the future.

As a final note, I'd like to thank Lori Turner (who helped with the coding and the online labs), Mike Morton, and Andrew Whitechapel for reviewing this article.

Steve Fox is a Program Manager with the Visual Studio Tools for Office (VSTO) team at Microsoft. He divides his time working externally with VSTO customers, providing VSTO training, delivering VSTO sessions at conferences, and working internally with the development team to help evolve the VSTO product.