演练:Office 编程(C# 和 Visual Basic)

Visual Studio 在 C# 和 Visual Basic 中提供了改进 Microsoft Office 编程的功能。 有用的 C# 功能包括命名参数和可选参数以及类型为 dynamic 的返回值。 在 COM 编程中,可以省略 ref 关键字并获得索引属性的访问权限。 Visual Basic 中的功能包括自动实现的属性、Lambda 表达式语句和集合初始值设定项。

两种语言都支持嵌入类型信息,从而允许在不向用户的计算机部署主互操作程序集 (PIA) 的情况下部署与 COM 组件交互的程序集。 有关详细信息,请参见演练:嵌入托管程序集中的类型

本演练演示 Office 编程上下文中的这些功能,但其中许多功能在常规编程中也极为有用。 本演练将使用 Excel 外接应用程序创建 Excel 工作簿。 然后,将创建包含工作簿链接的 Word 文档。 最后,将介绍如何启用和禁用 PIA 依赖项。

先决条件

若要完成本演练,计算机上必须安装 Microsoft Office Excel 和 Microsoft Office Word。

注意

以下说明中的某些 Visual Studio 用户界面元素在计算机上出现的名称或位置可能会不同。 这些元素取决于你所使用的 Visual Studio 版本和你所使用的设置。 有关详细信息,请参阅个性化设置 IDE

设置 Excel 外接应用程序

  1. 启动 Visual Studio。

  2. “文件” 菜单上,指向 “新建” ,然后单击 “项目”

  3. 在“安装的模板”窗格中,展开“Visual Basic”或“Visual C#”,再展开“Office”,然后单击 Office 产品的版本年份。

  4. 在“模板”窗格中,单击“Excel<版本>加载项”。

  5. 查看“模板”窗格的顶部,确保“.NET Framework 4”或更高版本出现在“目标框架”框中。

  6. 如果需要,在“名称”框中键入项目的名称。

  7. 单击 “确定”

  8. 新项目将出现在“解决方案资源管理器”中。

添加引用

  1. 在“解决方案资源管理器”中,右键单击你的项目名称,然后单击“添加引用”。 此时会显示“添加引用”对话框。

  2. 在“程序集”选项卡上,在“组件名称”列表中选择“Microsoft.Office.Interop.Excel”版本 <version>.0.0.0(有关 Office 产品版本号的键,请参阅 Microsoft 版本),然后按住 Ctrl 键并选择“Microsoft.Office.Interop.Word”,version <version>.0.0.0。 如果未看到程序集,则可能需要确保安装并显示它们(参阅如何:安装 Office 主互操作程序集)。

  3. 单击 “确定”

添加必要的 Imports 语句或 using 指令

  1. 在“解决方案资源管理器”中,右键单击“ThisAddIn.vb”或“ThisAddIn.cs”文件,然后单击“查看代码”。

  2. 将以下 Imports 语句 (Visual Basic) 或 using 指令 (C#) 添加到代码文件的顶部(如果不存在)。

    using System.Collections.Generic;
    using Excel = Microsoft.Office.Interop.Excel;
    using Word = Microsoft.Office.Interop.Word;
    
    Imports Microsoft.Office.Interop
    

创建银行帐户列表

  1. 在“解决方案资源管理器”中,右键单击你的项目名称,单击“添加”,然后单击“类”。 如果使用的是 Visual Basic,则将类命名为 Account.vb;如果使用的是 C#,则将类命名为 Account.cs。 单击 添加

  2. Account 类的定义替换为以下代码。 类定义使用自动实现的属性。 有关详细信息,请参阅自动实现的属性

    class Account
    {
        public int ID { get; set; }
        public double Balance { get; set; }
    }
    
    Public Class Account
        Property ID As Integer = -1
        Property Balance As Double
    End Class
    
  3. 若要创建包含两个帐户的 bankAccounts 列表,请将以下代码添加到 ThisAddIn.vb 或 ThisAddIn.cs 中的 ThisAddIn_Startup 方法。 列表声明使用集合初始值设定项。 有关详细信息,请参阅集合初始值设定项

    var bankAccounts = new List<Account>
    {
        new Account
        {
            ID = 345,
            Balance = 541.27
        },
        new Account
        {
            ID = 123,
            Balance = -127.44
        }
    };
    
    Dim bankAccounts As New List(Of Account) From {
        New Account With {
                              .ID = 345,
                              .Balance = 541.27
                         },
        New Account With {
                              .ID = 123,
                              .Balance = -127.44
                         }
        }
    

将数据导出到 Excel

  1. 在相同的文件中,将以下方法添加到 ThisAddIn 类。 该方法设置 Excel 工作薄并将数据导出到工作簿。

    void DisplayInExcel(IEnumerable<Account> accounts,
               Action<Account, Excel.Range> DisplayFunc)
    {
        var excelApp = this.Application;
        // Add a new Excel workbook.
        excelApp.Workbooks.Add();
        excelApp.Visible = true;
        excelApp.Range["A1"].Value = "ID";
        excelApp.Range["B1"].Value = "Balance";
        excelApp.Range["A2"].Select();
    
        foreach (var ac in accounts)
        {
            DisplayFunc(ac, excelApp.ActiveCell);
            excelApp.ActiveCell.Offset[1, 0].Select();
        }
        // Copy the results to the Clipboard.
        excelApp.Range["A1:B3"].Copy();
    }
    
    Sub DisplayInExcel(ByVal accounts As IEnumerable(Of Account),
                   ByVal DisplayAction As Action(Of Account, Excel.Range))
    
        With Me.Application
            ' Add a new Excel workbook.
            .Workbooks.Add()
            .Visible = True
            .Range("A1").Value = "ID"
            .Range("B1").Value = "Balance"
            .Range("A2").Select()
    
            For Each ac In accounts
                DisplayAction(ac, .ActiveCell)
                .ActiveCell.Offset(1, 0).Select()
            Next
    
            ' Copy the results to the Clipboard.
            .Range("A1:B3").Copy()
        End With
    End Sub
    

    此方法使用 C# 的两项新功能。 Visual Basic 中已存在这两项功能。

    • 方法 Add 有一个可选参数,用于指定特定的模板。 如果希望使用形参的默认值,你可以借助可选形参以忽略该形参的实参。 由于上一个示例中未发送任何参数,Add 将使用默认模板并创建新的工作簿。 C# 早期版本中的等效语句要求提供一个占位符参数:excelApp.Workbooks.Add(Type.Missing)

      有关详细信息,请参阅命名参数和可选参数

    • Range 对象的 RangeOffset 属性使用“索引属性”功能。 此功能允许你通过以下典型 C# 语法从 COM 类型使用这些属性。 索引属性还允许你使用 Value 对象的 Range 属性,因此不必使用 Value2 属性。 Value 属性已编入索引,但索引是可选的。 在以下示例中,可选自变量和索引属性配合使用。

      // Visual C# 2010 provides indexed properties for COM programming.
      excelApp.Range["A1"].Value = "ID";
      excelApp.ActiveCell.Offset[1, 0].Select();
      

      在早期版本的语言中,需要以下特殊语法。

      // In Visual C# 2008, you cannot access the Range, Offset, and Value
      // properties directly.
      excelApp.get_Range("A1").Value2 = "ID";
      excelApp.ActiveCell.get_Offset(1, 0).Select();
      

      你不能创建自己的索引属性。 该功能仅支持使用现有索引属性。

      有关详细信息,请参阅如何在 COM 互操作编程中使用索引属性

  2. DisplayInExcel 的末尾添加以下代码以将列宽调整为适合内容。

    excelApp.Columns[1].AutoFit();
    excelApp.Columns[2].AutoFit();
    
    ' Add the following two lines at the end of the With statement.
    .Columns(1).AutoFit()
    .Columns(2).AutoFit()
    

    这些新增内容介绍了 C# 中的另一功能:处理从 COM 主机返回的 Object 值(如 Office),就像它们具有 dynamic 类型一样。 当“嵌入互操作类型”设置为其默认值 True 时,或者由 EmbedInteropTypes 编译器选项引用程序集时,会自动发生这种情况。

    例如,excelApp.Columns[1] 返回 Object,并且 AutoFit 是 Excel Range 方法。 如果没有 dynamic,你必须将 excelApp.Columns[1] 返回的对象强制转换为 Range 的实例,然后才能调用 AutoFit 方法。

    // Casting is required in Visual C# 2008.
    ((Excel.Range)excelApp.Columns[1]).AutoFit();
    
    // Casting is not required in Visual C# 2010.
    excelApp.Columns[1].AutoFit();
    

    有关嵌入互操作类型的详细信息,请参阅本主题后面部分的“查找 PIA 引用”和“还原 PIA 依赖项”程序。 有关 dynamic 的详细信息,请参阅 dynamic使用类型 dynamic

调用 DisplayInExcel

  1. ThisAddIn_StartUp 方法的末尾添加以下代码。 对 DisplayInExcel 的调用包含两个参数。 第一个参数是要处理的帐户列表的名称。 第二个参数是定义如何处理数据的多行 lambda 表达式。 每个帐户的 IDbalance 值都显示在相邻的单元格中,如果余额小于零,则相应的行显示为红色。 有关详细信息,请参阅 Lambda 表达式

    DisplayInExcel(bankAccounts, (account, cell) =>
    // This multiline lambda expression sets custom processing rules
    // for the bankAccounts.
    {
        cell.Value = account.ID;
        cell.Offset[0, 1].Value = account.Balance;
        if (account.Balance < 0)
        {
            cell.Interior.Color = 255;
            cell.Offset[0, 1].Interior.Color = 255;
        }
    });
    
    DisplayInExcel(bankAccounts,
           Sub(account, cell)
               ' This multiline lambda expression sets custom
               ' processing rules for the bankAccounts.
               cell.Value = account.ID
               cell.Offset(0, 1).Value = account.Balance
    
               If account.Balance < 0 Then
                   cell.Interior.Color = RGB(255, 0, 0)
                   cell.Offset(0, 1).Interior.Color = RGB(255, 0, 0)
               End If
           End Sub)
    
  2. 若要运行程序,请按 F5。 出现包含帐户数据的 Excel 工作表。

添加 Word 文档

  1. ThisAddIn_StartUp 方法末尾添加以下代码,以创建包含指向 Excel 工作簿的链接的 Word 文档。

    var wordApp = new Word.Application();
    wordApp.Visible = true;
    wordApp.Documents.Add();
    wordApp.Selection.PasteSpecial(Link: true, DisplayAsIcon: true);
    
    Dim wordApp As New Word.Application
    wordApp.Visible = True
    wordApp.Documents.Add()
    wordApp.Selection.PasteSpecial(Link:=True, DisplayAsIcon:=True)
    

    此代码展示 C# 中的几项新功能:省略 COM 编程中的 ref 关键字、命名参数以及可选参数的能力。 Visual Basic 中已存在这些功能。 PasteSpecial 方法有七个参数,所有参数都定义为可选引用参数。 通过命名实参和可选实参,你可以指定希望按名称访问的形参并仅将实参发送到这些形参。 在本示例中,发送实参以指示应创建指向剪贴板上工作簿的链接(形参 Link)并指示该链接应在 Word 文档中显示为图标(形参 DisplayAsIcon)。 Visual C# 还允许忽略这些参数的 ref 关键字。

要运行应用程序

  1. 按 F5 运行该应用程序。 Excel 启动并显示包含 bankAccounts 中两个帐户的信息的表。 然后,出现包含指向 Excel 表的 Word 文档。

清理已完成的项目

  1. 在 Visual Studio 中,单击“生成”菜单上的“清理解决方案”。 否则,每次在计算机上打开 Excel 时都会运行外接应用程序。

查找 PIA 引用

  1. 再次运行应用程序,但不单击“清理解决方案”。

  2. 选择“开始”。 找到“Microsoft Visual Studio <版本>”,然后打开开发人员命令提示。

  3. 在“Visual Studio 的开发人员命令提示”窗口中键入 ildasm,然后按 Enter。 此时将出现 IL DASM 窗口。

  4. 在 IL DASM 窗口的“文件”菜单上,选择“文件”>“打开”。 双击“Visual Studio <版本>”,然后双击“项目”。 打开项目的文件夹,在 bin/Debug 文件夹中查找项目名称.dll。 双击 项目名称.dll。 新窗口将显示项目的属性以及对其他模块和程序集的引用。 注意,命名空间 Microsoft.Office.Interop.ExcelMicrosoft.Office.Interop.Word 包含在程序集中。 在 Visual Studio 中,编译器默认将所需的类型从引用的 PIA 导入程序集。

    有关详细信息,请参阅如何:查看程序集内容

  5. 双击“清单”图标。 此时将出现包含程序集列表的窗口,这些程序集包含项目所引用的项。 Microsoft.Office.Interop.ExcelMicrosoft.Office.Interop.Word 未包含在列表中。 由于项目需要的类型已导入程序集中,因此不需要引用 PIA。 这使得部署变得更加容易。 用户的计算机上不必存在 PIA,因为应用程序不需要部署特定版本的 PIA,应用程序可设计为与多个版本的 Office 配合使用,前提是所有版本中都存在必要的 API。

    由于不再需要部署 PIA,你可以提前创建可与多个版本的 Office(包括之前的版本)配合使用的应用程序。 但是,仅当你的代码不使用你当前所使用 Office 版本中不可用的任何 API 时,此情况才适用。 特殊 API 在早期版本中是否可用并不始终明确,因此不建议使用早期版本的 Office。

    注意

    在 Office 2003 以前,Office 并不发布 PIA。 因此,生成适用于 Office 2002 或早期版本的互操作程序集的唯一方法是导入 COM 引用。

  6. 关闭清单窗口和程序集窗口。

还原 PIA 依赖项

  1. 在“解决方案资源管理器”中,单击“显示所有文件”按钮。 展开“引用”文件夹并选择“Microsoft.Office.Interop.Excel”。 按 F4 以显示“属性”窗口。

  2. 在“属性”窗口中,将“嵌入互操作类型”属性从“True”更改为“False”。

  3. Microsoft.Office.Interop.Word 重复此程序中的步骤 1 和 2。

  4. 在 C# 中,在 Autofit 方法的末尾注释掉对 DisplayInExcel 的两次调用。

  5. 按 F5 以验证项目是否仍正确运行。

  6. 重复上一个程序的步骤 1-3 以打开程序集窗口。 注意,Microsoft.Office.Interop.WordMicrosoft.Office.Interop.Excel 不再位于嵌入程序集列表中。

  7. 双击“清单”图标并滚动引用程序集的列表。 Microsoft.Office.Interop.WordMicrosoft.Office.Interop.Excel 均位于列表中。 由于应用程序引用 Excel 和 Word PIA 并且“嵌入互操作类型”属性设置为“False”,因此最终用户的计算机上必须存在两个程序集。

  8. 在 Visual Studio 中,单击“生成”菜单上的“清理解决方案”以清理完成的项目。

请参阅