技术开发 频道

用VS2008开发Office业务应用程序

  【IT168 技术文档】到目前为止,我敢确定您已经听说了一些有关 Visual Studio® 2008 的趣闻,它具有一些强大的功能,如支持 LINQ、改进了 Web 开发以及与 Windows Vista® 和 SharePoint® 紧密集成等等。不过 Visual Studio 2008 真正突出的一个特点是支持 Microsoft® Office 解决方案开发。

  您也可下载 VSTO Second Edition 开发 Office 2003 应用程序和 2007 Microsoft Office 系统的解决方案。但是,在 Visual Studio 2008 中包含了所有的 VSTO 项目模板。

  可以使用 Visual Studio 2008,尤其是 VSTO 3.0 来开发 Microsoft Outlook® 自定义,这是本文的内容。我将向您显示用称为窗体区域(可以是现有 Outlook 窗体上的专门区域)的新型窗体开发技术可以做的工作。也将向您显示如何将 Windows® Presentation Foundation (WPF) 支持添加到窗体区域,以帮助提高其外观、印象和交互性。

  用自定义窗体区域扩展 Outlook 2007

  VSTO 3.0 在 Visual Studio 2008 中提供了一个基于 Windows 窗体的设计环境,可用于在一个开发环境中设计和编码新的 Outlook 窗体区域,并为 Outlook 的托管环境带来许多 Windows 窗体的有利条件。例如,可以创建一个连接到 Web 服务的 VSTO 窗体区域,以采集顾客关系管理 (CRM) 数据并在网格中加以显示。甚至可以将一个自定义的“功能区”添加到将托管自定义窗体区域的 Outlook 2007 检查器中,并向其中添加代码,使其与区域中的控件进行交互。
请注意,有四种类型的 Outlook 2007 窗体区域供您使用:
相邻 出现在第一个 Outlook 窗体页面中目标 Outlook 窗体的底部和/或 Outlook 阅读窗格的底部。
单独 向窗体添加新的页面。
替换 它们用窗体区域替换 Outlook 窗体第一个窗体页面中的所有内容(所有现有窗体页面都原地保留)。
全部替换 它们用窗体区域替换 Outlook 窗体的第一个窗体页面中的所有内容,并删除窗体的所有其他窗体页面。
每一个这样的窗体区域都能以有趣的方式丰富 Outlook,这里我主要说明“替换”窗体区域。“替换”窗体区域可用于为自定义消息类创建一个自定义窗体。与为现有 Outlook 消息,如“邮件消息”(IPM.Note)、“联系人”(IPM.Contact) 和“约会”(IPM.Appointment) 类创建的“相邻”和“单独”窗体区域不同,选择“替换”(或“全部替换”)窗体区域时,创建了一个从现有 Outlook 消息类之一派生的自定义消息类。例如,在示例中,我将演示从“邮件消息”类型派生的名为 CustomersSalesData 的自定义消息类,因此其结果是名为 IPM.Note.RecentSales 的消息类。

  构建第一个自定义窗体区域

  要创建此自定义“替换”窗体区域,使用 Visual Studio 2008 创建一个 Outlook 2007 加载项项目(请参见图 1)。将项目命名为 CustomOFR 并使用默认的项目位置。此时,Visual Studio 创建一个 Outlook 2007 加载项项目外壳。即意味着在构建并部署加载项(将包含自定义的 Outlook 窗体区域)时,Visual Studio 将在加载 Outlook 时自动载入加载项。

 
                                   图 1 选择 Outlook 加载项项目模板

  创建项目外壳后,继续向它添加一个自定义 Outlook 窗体区域。在“Add New Item”(添加新项目)对话框中选择“Outook Form Region”(Outlook 窗体区域),并将其命名为 SalesData。它调用“新建 Outlook 窗体区域向导”,该向导包含创建“替换”窗体区域的多个步骤。在第一步中,指示是要设计一个新的窗体区域还是要导入一个现有的窗体区域(.ofs 文件)。在本示例中,要创建一个新的窗体区域,因此继续进行并单击“Next”(下一步)。然后要求选择要创建的窗体区域的类型。选择“替换”类型,然后单击“Next”(下一步)。现在为窗体设置一些属性,具体而言,即名称、标题、描述和以及窗体区域的显示模式(请参见图 2)。输入这些信息后,单击“Next”(下一步)。

 
                                      图 2 自定义窗体区域属性 
 
  在最后一步中,要将窗体区域与特定的消息类相关联。如前所述,因为它是“替换”窗体区域,因此必须创建自定义消息类而不是使用 Outlook 附带的常备消息类。请注意,在此例中这一步骤将所有的常备消息类选项都变灰了,并且此步骤唯一可用的字段是对话框底部的自定义消息类文本框(请参见图 3)。在此字段中我已经输入了 IPM.Note.CustomerSalesData,即表示要从 Outlook IPM.Note 消息类类型派生自己的自定义消息类(实际上是创建继承常备“邮件消息”类中所有相同功能的自定义消息类)。

 
                                     图 3 输入自定义消息类的类型
 
   所有窗体区域都以 Outlook 环境中的一个或多个具体的消息类为目标。这些消息类直接与 Outlook 项目类型相关联。标准的消息类有八个(请参见图 3),理论上有无限量的自定义消息类。也可从其他类型的自定义类派生自己的自定义类。输入自定义消息类的派生和名称后,单击“Finish”(完成)。单击“Finish”(完成)按钮时将验证名称。

完成向导中的步骤后,会将一个窗体区域添加到 Outlook 加载项项目中。在我的示例中,会在项目中添加下列三个项目:
窗体区域的主类文件 RecentSales.cs。
包含一些窗体区域默认代码的设计器文件 RecentSales.Designer.cs。
显示窗体区域所必需的 XML 文件 RecentSales.rexs。

  现在开始自定义。创建窗体区域后,Visual Studio 中的默认视图将是窗体区域设计器模式。如果不是,则右键单击 RecentSales.cs 并选择“View Designer”(视图设计器)。现在可以开始向窗体添加控件。

  为了尽量简化,将三个主要控件添加到我自定义的“替换”窗体区域中:一个列出客户信息的搜索控件、一个显示更新的销售数据的 WPF 控件以及一个控制用户控件显示状态的复选框。图 4 说明了已添加到 Visual Studio IDE 上下文环境项目中的控件。
 

 
                                          图 4 窗体区域设计器

  显示销售数据和客户搜索

  窗体区域显示设计图面并支持从“工具箱”向设计图面拖放控件。因此将一个复选框拖到设计图面中,并将其“名称”属性设置为 showChart,将“文本”属性设置为“显示销售数据”。然后双击复选框控件并添加下列代码,调用 UpdateChart 方法,此方法将更新数据和 WPF 销售图表的可见性:

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

  我稍后将在本文章中探讨此方法。
“销售搜索”用户控件允许输入某些搜索条件,并浏览在控件中动态更新的小型客户信息数据库。为了启用此搜索功能,我已经将一个名为 SalesControlLibrary(“Windows 窗体控件库”项目)的新项目添加到 CustomOFR 解决方案中,然后从“工具箱”中将搜索控件添加到窗体区域中。解决方案的 SalesControlLibrary 项目中有两个主要部件:一个数据源连接和将用于自定义“替换”窗体区域中的用户控件。

  连接数据源

  将新项目添加到解决方案后,即可通过单击“Data”(数据)和“Add New Data Source”(添加新数据源)来添加数据源。如您所料,这会显示“Add New Data Source Wizard”(添加新数据源向导),首先提示定义和配置一个数据源(数据库)的简单连接。在本示例中,我使用了一个名为 Sales.mdb(包含在下载的代码中)的 Access 数据库,它具有以下四个架构元素(如图 5 所示):CustomerID 是客户的唯一 ID、SalesQuarter 代表销售的财务季度、SalesYear 代表已记录销售的财务年、SalesAmount 是销售额(美元)。
 
                                               图 5 客户和销售数据

  如果要将此加载项和业务线 (LOB) 系统集成,以便将此应用程序归类为 Office 业务应用程序 (OBA),则您会使用 Windows Communication Foundation (WCF) 服务代理来管理与 LOB 系统的连接和通信。使用服务作为 LOB 系统的代理是绕过复杂的系统接口,或将特定的业务数据直接引入客户普通 office 应用程序上下文中的最好方法。

  在考虑组织构建 OBA 时添加的特定值时,一个主要优点当然是能够利用 Office UI 来集成 LOB 数据。这样就不必对 Office 固有的功能做自定义开发。最重要的是它使最终用户处于适宜和熟悉的环境中(考虑 Excel® 2007 中数据的本机格式和可视化)。要了解 OBA 常规领域的详细信息,请访问 msdn2.microsoft.com/aa905528。

 

  客户数据搜索窗体

  将数据源添加到 SalesControlLibrary 项目后,即可以设计搜索客户数据的组件。在我的销售搜索窗体设计器中,已经向项目中添加了多个控件,包括一些标签、字段、连接“销售”数据库的数据网格、一些显示客户联络信息(与数据网格中选定项目对应)的字段。图 6 显示了已添加所有控件的完整窗体。请注意,虽然大部分控件是直接添加的,但是对于数据网格视图,需要首先打开数据源窗格,右键单击“Sales”(销售)数据源并选择详细信息,然后将“Sales”(销售)记录拖放到数据网格视图中。然后右键单击控件,选择“Edit Columns”(编辑列)并删除 SalesYear 和 SalesAmount(如果只对在数据网格视图中显示 CustomerID 和 SalesQuarter 感兴趣)。
 
                                      图 6 构建客户数据用户控件

  此时,已经将新的控件添加到窗体中,并准备向控件添加一些代码。因此让我们看看添加到此窗体的一些关键代码。首先要看的是添加到 SalesSearch 类的声明。具体地说,为“公司”和“销售”声明了一个表适配器,并为所有销售数据更改创建了一个事件处理程序:

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

  要触发的主要事件是 executeSearch_click 事件,在单击“Search”(搜索)按钮时启动。它可在 SalesSearch 类中找到,如图 7 所示。

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);
  }
}

  可以看到它创建了一个 companiesAdapter 的新实例,并调用 GetDataByCompanyAndIndustry 方法,根据用户在搜索字段中输入的参数检索数据(图 8 中的百分号表示一个通配符搜索)。此数据然后用于填充“Contact”(联系人)和“Address”(地址)字段,以显示客户的信息。

 

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);
  }
}

 

  在此窗体后面的代码中,还有几个帮助程序函数。例如,已经在 CompaniesBindingSource 对象上添加了一个事件,以处理用户通过控件中的销售数据窗体进行浏览的情况(请参见图 8)。

  另外,我还添加了一个事件处理程序以处理对销售数据的任何更改。例如,下列代码显示如果绑定源中选择的当前记录发生更改时,所调用 OnSalesDataChanged 方法:

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

选择发生更改时,OnSalesDataChanged 事件有针对当前记录的公司名称、年份和销售的参数。

  添加 WPF 销售用户控件

  现在看看如何创建 WPF 控件并将其添加到自定义 Outlook 窗体区域中。将 WPF 用户控件添加到自定义 Outlook 替换窗体区域中需要两个步骤。首先创建 WPF 用户控件,接着是将其添加到“替换”窗体区域中(它是稍早前创建的 RecentSales 窗体区域)。

  要创建窗体,请将一个新的“WPF 用户控件库”项目添加到解决方案中。Visual Studio 将创建一个默认的控件并显示 XAML 编辑器。此时,可以设计 WPF 图表控件并添加事件处理程序。图 9 显示了 Visual Studio 2008 XAML 设计器中的 WPF 控件。
 
                                          图 9 WPF 图表控件

  由于已经添加了新的项目,因此将具有一些默认的 XAML 代码,但是设计器中只有空容器。因此,需要在此添加一些 XAML 代码来创建图表。为了对您有所帮助,图 10 显示了用于为窗体区域创建小型 WPF 销售图表的 XAML 代码。代码相当直接了当地创建了一个小控件,具有两列五行并将占位符文本放在图表上。此文本在运行时将被“销售”数据库中的数据更新。请注意,XAML 会根据在 XAML 代码头行的声明与 SalesChart 类相关联。
 

<UserControl x:Class="WpfChart.SalesChart"
  xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x
="http://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>

  创建 WPF 图表后,过程的最后一步是确保创建方法,以便随着数据的更改而更新图表。在 SalesChart 类中,UpdateWPFChart 方法主要以 chart title(公司名称)和代表销售数据的两个字符串数组作为参数,根据此数据调整图表并更新图表的文本和条形指示器,如图 11 所示。

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);
  }
}

  此时即完成了窗体区域的所有控件,因此现在可以返回到 Outlook 替换窗体区域设计器,添加刚刚创建的控件并将它们连接到自定义窗体区域。

  添加结尾

  最后一步是将控件添加到窗体区域,然后将自定义 Outlook 替换窗体区域中的代码连接到 SalesSearch 控件和 WPF 图表控件。要将 SalesSearch 控件和 WPF 控件添加到窗体区域中,请在项目中构建所有组件,此操作将控件添加到工具箱中,以便可以将它们拖到设计图面中。将 SalesSearch 控件和 WPF 图表控件拖到窗体区域的设计图面中所需的位置。控件就位后,现在可以创建代码,将这些控件与窗体区域相连接并彼此连接。

  首先,我在 RecentSales 类中已经添加了一些代表销售(年份和销售额)和公司数据的重要变量声明:

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

 

  其次,在 RecentSales_FormRegionShowing 方法(在 Outlook 加载自定义窗体区域时主要调用所添加的任何代码)中,已经将 WPF 销售图表的可见性设置为在 Outlook 加载窗体区域时默认隐藏:

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

  随后,UpdateChart 方法(在以上提到的方法)使用销售和公司数据调用 UpdateWPFChart 方法并更新销售图表(指示销售额的 WPF 形式条形图)中出现的数据。请注意,我们还构建了一个事件处理程序,响应复选框中的单击事件并调用 UpdateChart 方法。图 12 显示了 UpdateChart 方法和复选框的 CheckChanged 事件的代码。

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();
}

  最后,采用一个方法来处理 SalesSearch 控件上的数据更改事件。此方法更新销售和公司数据,然后调用 UpdateChart:
 

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

  构建并运行自定义替换窗体区域

  准备好代码后,即可构建并运行项目。要构建并运行 Outlook 加载项,需要关闭 Outlook 所有运行的实例。如果从自定义窗体区域打开“设计器”视图,应看到类似于图 13 所示的内容。现在可构建并运行项目。Visual Studio 将构建解决方案,为此启动 Outlook 并运行加载项程序集。

 
                                   图 13 最终的自定义替换窗体区域

  要导航到自定义 Outlook 替换窗体区域,单击“File”(文件)、“New”(新建)和“Choose Forms”(选择窗体)。这将打开“Choose Forms”(选择窗体)对话框,它列出安装在该客户端上的所有自定义替换窗体。在此情况下,将看到“Recent Sales”(最近的销售)窗体在自定义替换窗体区域库中是唯一可用的。要打开窗体区域,请将其选中并单击“Open”(打开)。
此时,Outlook 会打开窗体,将看到的内容与图 14 类似。要测试窗体区域,请单击“Search”(搜索)按钮。它应将数据库(或上述示例中为 Sales.mdb 数据库)中的所有数据加载到数据网格中。然后可以单击“Show Sales Data”(显示销售数据)复选框,显示选定公司的销售数据。请注意,可以在数据网格中直接选择公司,也可使用 SalesSearch 控件底部的导航条来回浏览数据。每次改变数据时,销售图表都自动更新。太酷了!

 
            图 14 最终的自定义 Outlook 替换窗体区域(WPF 图表可见)

  延伸阅读

  本文的所有代码都向您介绍完毕了,您可以自由选择时机试用它们。我们还创建了一个在线动手实验室,逐步指导您完成类似本文所述的应用程序,为您提供一些有关如何构建 Outlook 窗体区域的说明性指导。可以在 msdn2.microsoft.com/aa905533 的“VSTO 开发人员门户”中找到此动手实验室。还建议您访问 OBA 中心 (obacentral.com),它是一个专门从事 OBA 指导和培训的门户网站。另外,还请阅读拙作《Programming Microsoft Office Business Applications》(Microsoft Office 业务应用程序编程)(Microsoft Press®, 2008),它提供了 OBA 开发和部署的深层信息。OBA 是 Office 开发中充满活力的领域,将来一定会有它的更多信息。

  最后,非常感谢 Lori Turner(帮助编写代码和在线实验室)、Mike Morton 和 Andrew Whitechapel 审阅本文。

  Steve Fox 是 Microsoft 的 Visual Studio Tools for Office (VSTO) 团队的项目经理。他对外与 VSTO 客户一起合作,提供 VSTO 培训,在会议上就 VSTO 发表演讲,对内与开发团队一起合作,协助开发 VSTO 产品。
 

0
相关文章