第2章Office解决方案介绍
    Office解决方案的三种基本模式
    现在,您了解Office对象模型的基本模式,本章将介绍开发人员如何模拟和构建其Office解决方案。 使用Office构建的大多数解决方案都遵循三种模式之一

    • Office 自动化执行
    • Office 加载项
    • Office 文档背后的代码

    一个自动化可执行文件是一个独立于Office控制和自动化Office应用程序的程序。可以使用Visual Studio等开发工具创建自动化可执行文件。一个典型的例子是独立的控制台应用程序或Windows窗体应用程序启动Office应用程序,然后自动执行一些任务。要启动以这种方式构建的解决方案,解决方案的用户启动自动化可执行文件,从而启动Office应用程序。与其他两种模式不同,自动化代码不会在Office进程中运行,而是运行在自己的进程中,并且会将自动化的Office进程交给过程。
    加载项是Office加载并在需要时创建的程序集(DLL)中的类。一个加载项与Office应用程序进程正在运行,而不是要求自己的进程与Office应用程序进程分开。要启动以这种方式构建的解决方案,解决方案的用户启动与加载项关联的Office应用程序。 Office启动时检测到已注册的加载项,并加载它们。加载项可以以与文档背后的代码相同的方式自定义Office应用程序。然而,当与代码关联的文档是关闭的时候,文档背后的代码会被卸载,因此可以在Office应用程序的整个生命周期内保持加载。
    模式后面的代码被Visual Basic for Applications(VBA)推广,该应用程序是一个包含在Office中的简单开发环境,它使开发人员能够针对特定Office应用程序的对象模型编写Visual Basic代码,并将该代码与特定文档相关联或模板。文档可以使用Visual Studio 2005 Tools for Office(VSTO)后面的C#或Visual Basic代码关联。要以这种方式构建解决方案,解决方案的用户将打开一个文档,该文档在其背后具有代码,或者从具有代码的模板创建新文档。文档背后的代码将在文档打开时以某种方式自定义Office应用程序。例如,文档后面的代码可能会添加仅当文档打开时才会显示的菜单项,或者将文档与文档打开时发生的事件相关联。
    本书稍后将讨论两种高级模式。服务器文档模式涉及在服务器上运行代码来操作Office文档中存储的数据,而不启动Office应用程序。 VSTO通过称为缓存数据的功能使这种情况成为可能。第18章“服务器数据场景”讨论了这种模式。 XML和XSLT模式类似于服务器文档模式,并涉及编写代码,以WordprocessingML或SpreadsheetML格式生成Word或Excel文档,而不启动Office应用程序。您还可以通过将XSLT转换应用于某些XML数据来生成这些格式。第21章“在Excel中使用XML”和第22章“在Word中使用XML”讨论了这些情况。
    托管代码
    模式中的加载项和代码有时称为托管代码,这意味着您的代码与Office应用程序的运行方式相同。
    发现托管代码
    对于在Office应用程序进程中运行的代码,Office应用程序必须能够发现代码,将代码加载到其进程空间中并运行代码。 Office加载项在Windows注册表中注册,以便Office可以找到并启动它们。使用注册表似乎有点非.NET,但这是必要的,因为Office 2003会将加载项与通过COM互操作的COM对象进行通信。
    文档模式背后的代码不需要注册表项。相反,通过向文档文件添加一些特殊属性,代码与文档相关联。当文档打开时,Office会读取这些属性,然后Office将加载与文档关联的代码。
    提供给托管代码的上下文
    至关重要的是,您的托管代码获取上下文需要为要加载的Office应用程序获取Application对象或Document对象。 COM加载项通过由加载项类实现的接口提供上下文。在VSTO中的Outlook加载项通过项目中创建的类来提供上下文,代表正在定制的应用程序。在VSTO中的文档背后的代码通过在项目中创建的类来提供上下文,表示正在自定义的文档。
    托管代码入口点
    在启动时,Office调用一个入口点,您的代码可以首次运行,并注册会话中稍后可能发生的事件。对于COM加载项,此入口点是由COM加载项实现的IDTExtensibility2接口的OnConnection方法。对于文档背后的VSTO Outlook加载项和VSTO代码,此入口点是启动事件处理程序。
    启动后代码如何运行
    托管代码启动后,代码继续以一种或多种以下方式运行
    代码运行响应Office提出的事件
    启动后代码运行的最常见方法是响应Office应用程序中发生的事件。例如,当文档打开或电子表格中的单元格更改时,Office会引发事件。列表1-24显示了一个简单的类,它监听Excel的Worksheet对象引发的Activate事件。通常,当调用代码的初始入口点时,您将连接事件侦听器,如清单1-24所示。
    接口方法调用对象提供给Office
    诸如COM加载项的启动类的对象实现称为IDTExtensibility2的接口,该接口具有Office应用程序运行期间Office调用的方法。例如,如果用户关闭COM加载项,Office将调用由COM加载项实现的IDTExtensibility2接口上的OnDisconnection方法。这样,在运行初始入口点之后运行附加代码。
    事件发生在类后面的事件
    在VSTO项目中生成的代表定制应用程序或文档的类处理启动和关闭事件。在类的构造函数执行后,Office会引发启动事件。当文档即将关闭时,Office会引发关闭事件。
    代码如何卸载
    您的代码可以通过多种方式卸载,具体取决于您使用的开发模式。如果您使用自动化可执行模式,则当您写入的自动化可执行文件退出时,代码将被卸载。如果您正在使用加载项模式,则当Office应用程序退出或用户通过加载项管理对话框关闭加载项时,代码将卸载。如果您正在使用模式后面的代码,与代码关联的文档关闭时,您的代码将被卸载。
    在运行代码的托管模式中,有一些被称为或事件的方法通知您即将卸载。对于COM加载项,Office调用OnDisconnection方法。对于文档和Outlook加载项后面的VSTO代码,Office会在您卸载代码之前引发关闭事件。

    办公自动化可执行文件
    本部分将更详细地讨论这三种Office解决方案中的每一种。使用自动化可执行文件的Office解决方案通过创建与Office应用程序相关联的Application对象的新实例,以非常直接的方式启动Office应用程序。因为自动化可执行程序控制Office应用程序,所以自动化可执行文件在启动时运行代码,此后任何时候执行控制返回到自动化可执行文件。
    当自动化可执行文件使用新建创建Application对象时,自动化可执行文件通过将创建的Application对象保存在变量中来控制应用程序的生命周期。 Office应用程序通过确定使用其Application对象的引用计数或客户端数量来确定是否可以关闭它。
    在清单2-1中,一旦使用new来创建myExcelApp变量,Excel会启动它并添加一个它的客户端计数,它知道它持有对Excel的Application对象的引用。当myExcelApp变量超出范围(当Main退出时),.NET垃圾收集器释放对象,并通知Excel控制台应用程序不再需要Excel的Application对象。这导致Excel对Excel的Application对象引用的客户端计数为零,并且Excel退出,因为没有客户端再次使用Excel。
    当您通过创建Application对象的新实例创建Office应用程序时,应用程序将启动而不显示其窗口,这被证明是有用的,因为您可以通过弹出窗口来自动化应用程序而不会分散用户的注意力。如果需要显示应用程序窗口,可以将Application对象的Visible属性设置为TRue。如果使主窗口可见,则用户控制应用程序的生命周期。在Excel中,应用程序将不会退出,直到用户退出应用程序,并且保存Excel Application对象的变量被垃圾回收。当用户退出应用程序时,即使变量仍保留Word应用程序对象的实例,Word也会以不同的方式退出。
    列表2-1将Excel的状态栏设置为“Hello World”,并通过调用Excel的Workbooks集合的Add方法在Excel中打开一个新的空白工作簿。第3章至第5章“编程Excel”,“使用Excel事件”和“使用Excel对象”分别更详细地显示Excel对象模型。
    清单2-1 通过控制台应用程序自动化Excel
    VSTO:使用C#开发Excel、Word【7】 - 图1
    using System;
    using Excel = Microsoft.Office.Interop.Excel;
    using System.Windows.Forms;

    namespace ConsoleApplication
    {
    class Program
    {
    static bool exit = false;

    1. static void Main(string[] args)<br /> {<br /> Excel.Application myExcelApp = new Excel.Application();<br /> myExcelApp.Visible = true;<br /> myExcelApp.StatusBar = "Hello World";<br /> myExcelApp.Workbooks.Add(System.Type.Missing);
    2. myExcelApp.SheetBeforeDoubleClick += <br /> new Excel.AppEvents_SheetBeforeDoubleClickEventHandler(<br /> myExcelApp_SheetBeforeDoubleClick);
    3. while (exit == false)<br /> System.Windows.Forms.Application.DoEvents();<br /> }
    4. static void myExcelApp_SheetBeforeDoubleClick(object sheet,<br /> Excel.Range target, ref bool cancel)<br /> {<br /> exit = true;<br /> }<br /> }<br />}<br />![](https://cdn.nlark.com/yuque/0/2020/gif/586817/1597633170284-e55953e1-a5b9-4e9a-af7c-c51a28fad775.gif#align=left&display=inline&height=20&margin=%5Bobject%20Object%5D&originHeight=20&originWidth=20&size=0&status=done&style=none&width=20)<br />清单2-1还说明了一个自动化可执行文件如何产生时间回到Office应用程序。 对System.Windows.Forms程序集的引用必须添加到项目中。 事件处理程序挂起后,System.Windows.Forms.Application.DoEvents()在循环中调用,以允许Excel应用程序正常运行。 如果用户双击单元格,Office会自动生成自动执行文件中的事件处理程序。 在Double-Click事件的处理程序中,我们将静态变量exit设置为true,这将导致调用DoEvents的循环退出,并且自动执行文件可以退出。<br />您可以通过运行清单2-1中的自动化可执行文件并退出Excel,而无需双击单元格即可查看Excel的生命周期管理。 Excel将继续运行在隐藏状态,等待控制台应用程序释放其对Excel的Application对象的引用。<br />**清单2-2 表2-1的WiKi文本表示**<br />||Property or Method||Name||Return Type||<br />||Property||Application||Application||<br />||Property||Autoload||Boolean||<br />||Property||Compiled||Boolean||<br />||Property||Creator||Int32||<br />||Method||Delete||Void||<br />||Property||Index||Int32||<br />||Property||Installed||Boolean||<br />||Property||Name||String||<br />||Property||Parent||Object||<br />||Property||Path||String||<br />**表2-1 显示Word的加载项对象的属性和方法的简单表**<br />![](https://cdn.nlark.com/yuque/0/2020/png/586817/1597633170374-40c222e5-f5de-4fa3-b96a-55dfc5fb1b7d.png#align=left&display=inline&height=314&margin=%5Bobject%20Object%5D&originHeight=314&originWidth=598&size=0&status=done&style=none&width=598)<br />我们将使用Visual Studio 2005创建一个控制台应用程序。启动Visual Studio后,从文件菜单中选择新建项目。 “新建项目”对话框显示各种项目类型。从项目类型列表中选择Visual C#节点,并选择Visual C#节点下的Windows节点。这样做有点违反直觉,因为也有一个Office节点可用,但Office节点只显示文档项目和VSTO Outlook加载项目后的VSTO代码。<br />选择Windows节点后,您将在右侧的窗口中看到可用的模板。选择控制台应用程序模板。命名您的控制台应用程序项目,然后单击确定按钮创建您的项目。在图2-1中,我们创建了一个名为WordWiki的控制台应用程序。请注意,新项目对话框的外观可能与图2-1所示的外观不同,具体取决于您使用的配置文件。在本书中,我们假设您正在使用Visual C#开发设置配置文件。您可以通过从“工具”菜单中选择“导入和导出设置”来更改配置文件。<br />**图2-1 从“新建项目”对话框创建控制台应用程序。**<br />**![](https://cdn.nlark.com/yuque/0/2020/png/586817/1597633170331-aec2c589-0918-46c4-aa1a-92c66688c38e.png#align=left&display=inline&height=731&margin=%5Bobject%20Object%5D&originHeight=731&originWidth=1003&size=0&status=done&style=none&width=1003)**<br />当您单击确定按钮时,Visual Studio为您创建一个控制台应用程序项目。 Visual Studio在“解决方案资源管理器”窗口中显示项目的内容,如图2-2所示。<br />**图2-2。 控制台应用程序WordWiki在Solution Explorer中显示。**<br />![](https://cdn.nlark.com/yuque/0/2020/png/586817/1597633170386-150c1f5f-4bac-4f23-a78f-8feea5a26cf8.png#align=left&display=inline&height=289&margin=%5Bobject%20Object%5D&originHeight=289&originWidth=298&size=0&status=done&style=none&width=298)<br />默认情况下,新创建的控制台应用程序引用程序集System,System.Data和System.Xml。 我们还需要添加对Word 2003 PIA的引用。 我们通过右键单击“引用”文件夹,然后从出现的弹出菜单中选择“添加引用”。 这将显示图2-3中的“添加引用”对话框。 单击COM选项卡并选择Microsoft Word 11.0对象库以添加对Word 2003 PIA的引用,然后单击确定按钮。<br />图2-3 添加对Microsoft Word 2003 PIA的引用。<br />![](https://cdn.nlark.com/yuque/0/2020/png/586817/1597633170408-9f53590c-546b-484b-847d-5d11a5f62402.png#align=left&display=inline&height=678&margin=%5Bobject%20Object%5D&originHeight=678&originWidth=1005&size=0&status=done&style=none&width=1005)<br />Visual Studio添加了对Word 2003 PIA的引用,并添加了对stdole,VBIDE和Microsoft.Office.Core PIAs的其他引用,如图2-4所示。 这些额外的PIA是Word PIA所依赖的。 Stdole是一个包含COM对象模型所需类型的定义的PIA。 VBIDE是与集成到Office中的VBA编辑器关联的对象模型的PIA。 Microsoft.Office.Core(office.dll)是所有Office应用程序共享的常用功能的PIA,例如工具栏和菜单的对象模型。<br />**图2-4 当您添加Word 2003 PIA时,依赖的PIA引用将自动添加到项目中**<br />**![](https://cdn.nlark.com/yuque/0/2020/png/586817/1597633170343-782b13bd-9544-4502-bfdd-7de059fc79d0.png#align=left&display=inline&height=338&margin=%5Bobject%20Object%5D&originHeight=338&originWidth=366&size=0&status=done&style=none&width=366)**<br />现在,正确的引用已经添加到控制台应用程序中,让我们开始编写代码。 双击解决方案资源管理器窗口中的Program.cs,编辑控制台应用程序的主要源代码文件。 如果您打开了大纲,您将在Program.cs文件的顶部看到文本“using ...”,其旁边带有+号。 单击+号以展开放置using指令的代码。 使用指令添加以下三个,以便您可以更轻松地使用Word PIA和Microsoft.Office.Core PIA中的对象以及System.IO命名空间中的类。<br />using Office = Microsoft.Office.Core;<br />using Word = Microsoft.Office.Interop.Word;<br />using System.IO;<br />我们别名这些命名空间中的一些,所以我们不需要输出整个命名空间,例如Microsoft.Office.Interop.Word,每次我们要声明一个Word对象。使用别名,我们可以键入Word来指定命名空间。我们为Word和Office保留别名命名空间,而不是仅使用Microsoft.Office.Interop.Word键入并将所有类型导入全局范围。这是因为Word和Office定义了数百种类型,我们不希望所有这些类型名称可能与我们在代码中或与其他引用类型定义的类型相冲突。也是为了本书的目的,当代码说明Word.Application而不是应用程序时,代码更清楚,所以你知道应用程序类型来自哪个命名空间。<br />我们现在可以编写一些自动化Word,以阅读wiki表格式的文本输入文件来创建表格。清单2-3显示了我们程序的完整列表。而不是解释该列表中的每一行代码,我们专注于自动化Word的代码行。我们假设读者有一些了解如何在.NET中读取文本文件,并通过拆分方法解析字符串。我们在这里简要介绍了Word对象模型中的某些对象,但第6至8章“编程Word”,“使用Word事件”和“使用Word对象”分别更详细地介绍了Word对象模型。<br />清单2-3中的第一件事是通过将这行代码添加到程序类的Main方法来声明Word应用程序对象的新实例。<br />Word.Application theApplication = new Word.Application();<br />虽然Word.Application是一个接口,但我们可以创建一个这个接口的新实例,因为编译器知道Word.Application接口与它知道如何启动的COM对象相关联。 当Word响应自动化可执行文件创建其Application对象的新实例时,它将启动而不显示任何窗口。 当您想通过打开Word窗口来使用户自动化而不会混淆用户时,您可以在此隐形状态下自动化Word。 对于这个例子,我们想让Word显示它的主窗口,我们通过添加这一行代码:<br />theApplication.Visible = true;<br />接下来,我们要创建一个新的空Word文档,我们将生成我们的表。 我们通过调用Word应用程序对象返回的文档集合中的添加方法来执行此操作。 Add方法需要我们要省略的四个可选参数。 通过引用传递包含特殊值Type.Missing的变量来指定Word方法中的可选参数。 我们声明一个名为missing的变量,我们设置为Type.Missing,并通过引用传递给我们要忽略的每个参数,如下所示:<br />object missing = Type.Missing;<br />Word.Document theDocument = theApplication.Documents.Add(<br /> ref missing, ref missing, ref missing, ref missing);<br />创建文档时,我们要读取传递给我们的控制台应用程序的命令行参数指定的输入文本文件。 我们要解析该文本文件来计算列和行的数量。 当我们知道列和行的数量时,我们使用以下代码行从Document对象获取Range对象。 通过将我们的缺失变量传递给可选参数,Range方法将返回一个包含文档整个文本的范围。<br />Word.Range range = theDocument.Range(ref missing, ref missing);<br />然后,我们使用Range对象通过调用由Range对象返回的Tables集合的Add方法来添加表。 我们再次传递Range对象作为Add方法的第一个参数,以指定我们要用表替换文档的全部内容。 我们还指定了我们想要的行数和列数:<br /> <br />Word.Table table = range.Tables.Add(range, rowCount,columnCount, ref missing, ref missing);<br />Table对象有一个Cell方法,它接受一行和一列,并返回一个Cell对象。 Cell对象具有一个Range属性,它返回所讨论的单元格的Range对象,我们可以使用它来设置单元格的文本和格式。 此处显示了设置表格单元格的代码。 请注意,与大多数Office对象模型一样,索引为1,这意味着它们以1为起始值,而不是以0为基,从0开始为最小值:<br />for (columnIndex = 1; columnIndex <= columnCount; columnIndex++)<br />{<br /> Word.Cell cell = table.Cell(rowIndex, columnIndex);<br /> cell.Range.Text = splitRow[columnIndex];<br />}<br />通过将表格设置为大小以适合内容并粗体显示标题行来设置表格格式的代码如下所示。 我们使用由table.Rows [1]返回的Row对象,该对象也有一个Range属性,它返回该对象的Range对象。 另外,我们遇到代码设置表的第一行为粗体。 人们期望能够编写代码表.Rows [1] .Range.Bold = true,但是Word的对象模型需要一个int值(0为false,1为true)而不是一个bool。 Bold属性不返回bool,因为文本的范围可以是大胆的,全部不是粗体或部分粗体。 Word使用枚举的常量WdConstants.WdUndefined来指定部分粗体大小写。<br />// Format table<br />table.Rows[1].Range.Bold = 1;<br />table.AutoFitBehavior(Word.WdAutoFitBehavior.wdAutoFitContent);<br />最后,程序结束时的一些代码强制Word退出而不保存更改:<br />// Quit without saving changes<br />object saveChanges = false;<br />theApplication.Quit(ref saveChanges, ref missing, ref missing);<br />如果您不写此代码,即使在控制台应用程序退出后,Word仍将保持运行。当您通过将Application对象的Visible属性设置为TRue来显示Word窗口时,Word会将应用程序的生命周期放在最终用户的手中,而不是自动化程序。所以即使自动执行程序可以退出,Word也会继续运行。要强制Word退出,您必须在Word的Application对象上调用Quit方法。如果这个程序没有使Word窗口可视化,例如,它创建了文档与表,然后将其保存到一个文件,而不显示Word窗口将不必调用退出,因为Word将退出,当程序退出和释放所有对Word对象的引用。<br />要运行清单2-3中的控制台应用程序,必须创建一个文本文件,其中包含代码2-2中的文本。然后将文本文件的文件名作为命令行参数传递给控制台应用程序。您可以通过右键单击解决方案资源管理器中的WordWiki项目并选择属性来设置调试器。然后单击调试选项卡,并将命令行参数字段设置为文本文件的名称。<br />**清单2-3 完整的WordWiki实现**<br /> <br />![](https://cdn.nlark.com/yuque/0/2020/gif/586817/1597633170369-29df01c6-d6b2-42bd-8dae-da4b48f6eee8.gif#align=left&display=inline&height=20&margin=%5Bobject%20Object%5D&originHeight=20&originWidth=20&size=0&status=done&style=none&width=20)<br />using System;<br />using System.Collections.Generic;<br />using System.Text;<br />using System.IO;<br />using Office = Microsoft.Office.Core;<br />using Word = Microsoft.Office.Interop.Word;

    namespace WordWiki
    {
    class Program
    {
    static void Main(string[] args)
    {
    Word.Application theApplication = new Word.Application();
    theApplication.Visible = true;

    1. object missing = System.Type.Missing;<br /> Word.Document theDocument = theApplication.Documents.Add(<br /> ref missing, ref missing, ref missing, ref missing);
    2. TextReader reader = new System.IO.StreamReader(args[0]);
    3. string[] separators = new string[1];<br /> separators[0] = "||";<br /> int rowCount = 0;<br /> int columnCount = 0;
    4. // Read rows and calculate number of rows and columns<br /> System.Collections.Generic.List<string> rowList = <br /> new System.Collections.Generic.List<string>();
    5. string row = reader.ReadLine();
    6. while (row != null)<br /> {<br /> rowCount++;<br /> rowList.Add(row);
    7. // If this is the first row, <br /> // calculate the number of columns<br /> if (rowCount == 1)<br /> {<br /> string[] splitHeaderRow = row.Split(<br /> separators, StringSplitOptions.None);
    8. // Ignore the first and last separator <br /> columnCount = splitHeaderRow.Length - 2; <br /> }
    9. row = reader.ReadLine();<br /> }
    10. // Create a table<br /> Word.Range range = theDocument.Range(ref missing, <br /> ref missing);<br /> Word.Table table = range.Tables.Add(range, rowCount, <br /> columnCount, ref missing, ref missing);<br /> <br /> // Populate table<br /> int columnIndex = 1;<br /> int rowIndex = 1;
    11. foreach (string r in rowList)<br /> {<br /> string[] splitRow = r.Split(separators, <br /> StringSplitOptions.None);
    12. for (columnIndex = 1; columnIndex <= columnCount; <br /> columnIndex++)<br /> {<br /> Word.Cell cell = table.Cell(rowIndex, columnIndex);<br /> cell.Range.Text = splitRow[columnIndex];<br /> }
    13. rowIndex++;<br /> }
    14. // Format table<br /> table.Rows[1].Range.Bold = 1;<br /> table.AutoFitBehavior(Word.WdAutoFitBehavior.<br /> wdAutoFitContent);
    15. // Wait for input from the command line before exiting<br /> System.Console.WriteLine("Table complete.");<br /> System.Console.ReadLine();
    16. // Quit without saving changes<br /> object saveChanges = false;<br /> theApplication.Quit(ref saveChanges, ref missing, <br /> ref missing);<br /> }<br /> }<br />}<br />![](https://cdn.nlark.com/yuque/0/2020/gif/586817/1597633170330-76ca9f2b-6536-45db-8cc7-81e069753630.gif#align=left&display=inline&height=20&margin=%5Bobject%20Object%5D&originHeight=20&originWidth=20&size=0&status=done&style=none&width=20)