Excel对象模型中的事件
了解excel对象模型中的事件至关重要,因为这通常是代码运行的主要方式。本章将检查Excel对象模型中的所有事件,引发事件以及可能与这些事件关联的代码类型。
Excel对象模型中的许多事件在应用程序,工作簿和工作表对象上重复。此重复允许您决定是否要处理所有工作簿,特定工作簿或特定工作表的事件。例如,如果您想知道任何打开的工作簿中的任何工作表是否双击,您将处理Application对象的SheetBeforeDoubleClick事件。如果您想知道在特定工作簿中的任何工作表何时被双击,您将处理该工作簿对象上的SheetBeforeDoubleClick事件。如果您想知道双击某个特定工作表时,您将处理该Worksheet对象上的BeforeDoubleClick事件。当在应用程序,工作簿和工作表对象上重复事件时,它通常首先在工作表上,然后是工作簿,最后是应用程序。
新工作簿和工作表事件
当创建新的空白工作簿时,Excel的Application对象会引发NewWorkbook事件。当从模板或现有文档创建新的工作簿时,不会引发此事件。在特定工作簿中创建新工作表时,Excel也会引发事件。同样,这些事件仅在用户首次创建新工作表时引发。在随后打开工作簿时,他们再也不会再提出。
现在讨论的重点是提出新的工作簿和工作表事件的各种方式:
- Application.NewWorkbook:创建新的空白工作簿时。 Excel将新的Workbook对象作为参数传递给此事件。
注: NewWorkbook是Application对象上的属性和事件的名称。 由于此冲突,您将不会在Visual Studio的弹出菜单中看到与Application对象关联的属性,事件和方法的NewWorkbook事件。 此外,当您尝试处理此事件时,会在编译时显示警告。 要使Visual Studio的弹出菜单工作,并且警告消失,您可以将Application对象转换到AppEvents_Event接口,如清单4-1所示。
- Application.WorkbookNewSheet:在打开的工作簿中创建新工作表。 Excel将创建新工作表的Workbook对象作为参数传递给此事件。 它也传递新的工作表对象。 因为工作簿可以包含工作表和图表工作表,所以新的工作表对象作为对象传递。 然后,您可以将其转换为工作表或图表。
- Workbook.NewSheet:在工作簿上创建了一个新的工作表。 Excel将新的工作表对象作为参数传递给此事件。 新的工作表对象作为一个对象传递,您可以将其转换为工作表或图表。
清单4-1显示了一个处理Application对象的NewWorkbook和WorkbookNewSheet事件的控制台应用程序。 它还创建一个新的工作簿,并处理新创建的工作簿的NewSheet事件。 控制台应用程序处理工作簿的关闭事件,因此当您关闭工作簿时,控制台应用程序将退出并退出Excel。 清单4-1显示了其他几种常用技术。 对于作为对象传递的工作表,我们使用as操作符将对象转换为工作表或图表。 然后,我们将检查结果,以验证它不是null,以确定演员是否成功。 这种方法证明比使用is运算符跟随as运算符更有效率,因为后一种方法需要两个转换。
清单4-1 处理新工作簿和工作表事件的控制台应用程序
using System; using Excel = Microsoft.Office.Interop.Excel; using System.Windows.Forms; namespace ConsoleApplication { class Program { static private Excel.Application app; static private Excel.Workbook workbook; static bool exit = false; static void Main(string[] args) { app = new Excel.Application(); app.Visible = true; // We cast to AppEvents_Event because NewWorkbook // is the name of both a property and an event. ((Excel.AppEvents_Event)app).NewWorkbook += new Excel.AppEvents_NewWorkbookEventHandler( App_NewWorkbook); app.WorkbookNewSheet +=new Excel.AppEvents_WorkbookNewSheetEventHandler( App_WorkbookNewSheet); workbook = app.Workbooks.Add(Type.Missing); workbook.NewSheet += new Excel.WorkbookEvents_NewSheetEventHandler( Workbook_NewSheet); workbook.BeforeClose +=new Excel.WorkbookEvents_BeforeCloseEventHandler( Workbook_BeforeClose); while (exit == false) System.Windows.Forms.Application.DoEvents(); app.Quit(); } static void App_NewWorkbook(Excel.Workbook workbook) { Console.WriteLine(String.Format( "Application.NewWorkbook({0})", workbook.Name)); } static void App_WorkbookNewSheet(Excel.Workbook workbook, object sheet) { Excel.Worksheet worksheet = sheet as Excel.Worksheet; if (worksheet != null) { Console.WriteLine(String.Format( "Application.WorkbookNewSheet({0},{1})", workbook.Name, worksheet.Name)); } Excel.Chart chart = sheet as Excel.Chart; if (chart != null) { Console.WriteLine(String.Format( "Application.WorkbookNewSheet({0},{1})", workbook.Name, chart.Name)); } } static void Workbook_NewSheet(object sheet) { Excel.Worksheet worksheet = sheet as Excel.Worksheet; if (worksheet != null) { Console.WriteLine(String.Format( "Workbook.NewSheet({0})", worksheet.Name)); } Excel.Chart chart = sheet as Excel.Chart; if (chart != null) { Console.WriteLine(String.Format( "Workbook.NewSheet({0})", chart.Name)); } } static void Workbook_BeforeClose(ref bool cancel) { exit = true; } } }
当您考虑清单4-1中的代码时,您可能会想知道如何记住复杂代码行的语法,例如:
app.WorkbookNewSheet += new Excel.AppEvents_WorkbookNewSheetEventHandler( App_WorkbookNewSheet);
幸运的是,Visual Studio 2005有助于自动生成大部分代码行以及相应的事件处理程序。 如果您键入这行代码,键入+ =后,Visual Studio将显示一个弹出工具提示(参见图4-1)。 如果您按Tab键两次,Visual Studio会自动生成代码行的其余部分和事件处理程序。
图4-1 如果按Tab键,Visual Studio会为您生成事件处理程序代码
如果您使用Visual Studio 2005 Tools for Office(VSTO),还可以使用“属性”窗口将事件处理程序添加到工作簿或工作表类中。双击工作簿类的项目项(通常称为ThisWorkbook.cs)或您的工作表类之一(通常称为Sheet1.cs,Sheet2.cs等)。确保属性窗口可见。如果不是,请从“视图”菜单中选择“属性窗口”以显示“属性”窗口。确保在属性窗口顶部的组合框中选择了工作簿类(通常称为ThisWorkbook)或工作表类(通常称为Sheet1,Sheet2等)。然后单击闪电图标以显示与工作簿或工作表相关的事件。在要处理的事件右侧的编辑框中键入要用作事件处理程序的方法的名称。
激活和停用事件
激活或停用各种对象时,Excel对象模型中的16个事件会被提升。一个对象被认为是在其窗口接收到焦点时被激活,或者被选中或被激活的对象。例如,在工作簿中从一个工作表切换到另一个工作表时,工作表将被激活和停用。单击当前具有Sheet1的工作簿中Sheet3的选项卡,为Sheet1(它正在失去焦点)提供一个Deactivate事件,并为Sheet3启动一个Activate事件(它正在得到关注)。您可以以相同的方式激活/禁用图表。这样做会引发激活和停用与激活或停用的图表表相对应的图表对象上的事件。
您也可以激活/停用工作表。考虑您同时打开Book1和Book2的工作簿的情况。如果您当前正在编辑Book1,并从“窗口”菜单中选择Book2,则从Book1切换到Book2,则会引发Book1的“停用”事件,并提高Book2的Activate事件。
Windows是激活和停用的对象的另一个例子。工作簿可以打开多个窗口来显示工作簿。考虑您打开Book1的工作簿Book1的情况。如果从“窗口”菜单中选择“新建窗口”,则在Excel中将打开两个窗口来查看Book1。一个窗口的标题为Book1:1,另一个窗口的标题为Book1:2。当您在Book1:1和Book1:2之间切换时,会为工作簿引发WindowActivate事件。在Book1:1和Book1:2之间切换不会引发Workbook激活或停用事件,因为Book1仍然是活动的工作簿。
请注意,切换到Excel之外的应用程序,然后切换回Excel时,不会引发激活和停用事件。您可能希望如果您的Excel和Word并排在您的显示器上,通过从Excel转换为Word切换焦点将提高Excel中的停用事件。这不是caseExcel不考虑切换到另一个应用程序停用其任何工作簿,工作表或窗口。
现在的讨论转向激活和停用事件的各种方式:
- 在Excel中激活工作簿时,将引发Application.WorkbookActivate。 Excel将作为参数激活的Workbook对象传递给此事件。
- Workbook.Activate是在激活的特定工作簿上引发的。 没有参数传递给此事件,因为激活的工作簿是提高事件的Workbook对象。
-
注:Activate是Workbook对象上的方法和事件的名称。 由于此冲突,您将不会在Visual Studio的弹出菜单中看到与Application对象关联的属性,事件和方法的Activate事件。 此外,当您尝试处理此事件时,会在编译时显示警告。
要使Visual Studio的弹出菜单工作并删除警告,您可以将Workbook对象转换为WorkbookEvents_Event界面,如清单4-1所示。
- 每当在Excel中停用任何工作簿时,都会引发Application.WorkbookDeactivate。 Excel将作为参数停用的Workbook对象传递给此事件。
- Workbook.Deactivate是在停用的特定工作簿上引发的。没有参数传递给此事件,因为已停用的工作簿是提高事件的Workbook对象。
- 当Excel中激活工作表时,将引发Application.SheetActivate。 Excel将作为参数激活的工作表对象传递给此事件。因为工作簿可以包含工作表和图表表,所以激活的工作表作为对象传递。然后,您可以将其转换为工作表或图表。
- Workbook.SheetActivate是在已激活工作表的工作簿上引发的。 Excel将作为参数激活的工作表对象传递给此事件。因为工作簿可以包含工作表和图表表,所以激活的工作表作为对象传递。然后,您可以将其转换为工作表或图表。
- Worksheet.Activate和Chart.Activate在激活的工作表或图表表上提出。没有参数传递给这些事件,因为激活的工作表是提高此事件的工作表或图表对象。
-
注:Activate是工作表和图表对象上的方法和事件的名称。 由于此冲突,您将无法在Visual Studio的与工作表或图表对象关联的属性,事件和方法的弹出菜单中看到Activate事件。 此外,当您尝试处理此事件时,会在编译时显示警告。 要使Visual Studio的弹出菜单工作并且警告消失,您可以将Worksheet对象转换为DocEvents_Event接口,并将Chart对象转换为ChartEvents_Events界面,如清单4-2所示。
很奇怪,您将Worksheet对象的界面称为DocEvents_Event。 这是由于生成PIAs的方式COM对象上的事件接口工作表被称为DocEvents而不是WorksheetEvents。 Application对象发生同样的不一致; 它有一个名为AppEvents而不是ApplicationEvents的事件接口。
- 每当在Excel中停用任何工作表时,都会引发Application.SheetDeactivate。 Excel将作为参数停用的工作表对象传递给此事件。因为工作簿可以包含工作表和图表表,所以禁用的工作表作为对象传递。然后,您可以将其转换为工作表或图表。
- Workbook.SheetDeactivate是在已禁用工作表的工作簿上引发的。 Excel将作为参数停用的工作表对象传递给此事件。因为工作簿可以包含工作表和图表表,所以禁用的工作表作为对象传递。然后,您可以将其转换为工作表或图表。
- Worksheet.Deactivate和Chart.Deactivate在禁用的工作表或图表表上提出。没有参数传递给这些事件,因为停用的工作表是提高此事件的工作表或图表对象。
- 当窗口在Excel中被激活时,将引发Application.WindowActivate。 Excel将与作为参数激活的窗口对应的Workbook对象传递给此事件。 Excel还传递已激活的Window对象。
- Workbook.WindowActivate是在已激活的窗口的工作簿上引发的。 Excel将作为参数激活的Window对象传递给此事件。
- 当窗口在Excel中停用时,Application.WindowDeactivate将被引发。 Excel将与停用的窗口对应的Workbook对象作为参数传递给此事件。 Excel还会传递被禁用的Window对象。
- Workbook.WindowDeactivate是在已禁用窗口的工作簿上引发的。 Excel将作为参数停用的Window对象传递给此事件。
清单4-2显示了一个处理所有这些事件的类。 它将Excel Application对象传递给其构造函数。 构造函数创建一个新的工作簿,并获取工作簿中的第一个工作表。 然后它创建一个图表表。 它处理在Application对象以及创建的工作簿,工作簿中的第一个工作表以及它添加到工作簿的图表中引发的事件。 因为几个事件作为参数作为一个表作为对象传递,所以使用一个名为ReportEvent-WithSheetParameter的辅助方法来确定传递的工作表的类型,并向控制台显示一条消息。
清单4-2 处理激活和停用事件的类
using System; using Excel = Microsoft.Office.Interop.Excel; namespace ActivationAndDeactivation { public class TestEventHandler { private Excel.Application app; private Excel.Workbook workbook; private Excel.Worksheet worksheet; private Excel.Chart chart; public TestEventHandler(Excel.Application application) { this.app = application; workbook = application.Workbooks.Add(Type.Missing); worksheet = workbook.Worksheets.get_Item(1) as Excel.Worksheet; chart = workbook.Charts.Add(Type.Missing, Type.Missing,Type.Missing, Type.Missing) as Excel.Chart; app.WorkbookActivate += new Excel.AppEvents_WorkbookActivateEventHandler( App_WorkbookActivate); ((Excel.WorkbookEvents_Event)workbook).Activate += new Excel.WorkbookEvents_ActivateEventHandler(Workbook_Activate); app.WorkbookDeactivate += new Excel.AppEvents_WorkbookDeactivateEventHandler(App_WorkbookDeactivate); workbook.Deactivate += new Excel.WorkbookEvents_DeactivateEventHandler( Workbook_Deactivate); app.SheetActivate += new Excel.AppEvents_SheetActivateEventHandler( App_SheetActivate); workbook.SheetActivate += new Excel.WorkbookEvents_SheetActivateEventHandler( Workbook_SheetActivate); ((Excel.DocEvents_Event)worksheet).Activate += new Excel.DocEvents_ActivateEventHandler( Worksheet_Activate); ((Excel.ChartEvents_Event)chart).Activate += new Excel.ChartEvents_ActivateEventHandler( Chart_Activate); app.SheetDeactivate += new Excel.AppEvents_SheetDeactivateEventHandler( App_SheetDeactivate); workbook.SheetDeactivate += new Excel.WorkbookEvents_SheetDeactivateEventHandler( Workbook_SheetDeactivate); worksheet.Deactivate += new Excel.DocEvents_DeactivateEventHandler( Worksheet_Deactivate); chart.Deactivate += new Excel.ChartEvents_DeactivateEventHandler( Chart_Deactivate); app.WindowActivate += new Excel.AppEvents_WindowActivateEventHandler( App_WindowActivate); workbook.WindowActivate += new Excel.WorkbookEvents_WindowActivateEventHandler( Workbook_WindowActivate); app.WindowDeactivate += new Excel.AppEvents_WindowDeactivateEventHandler( App_WindowDeactivate); workbook.WindowDeactivate += new Excel.WorkbookEvents_WindowDeactivateEventHandler( Workbook_WindowDeactivate); } void ReportEventWithSheetParameter(string eventName,object sheet) { Excel.Worksheet worksheet = sheet as Excel.Worksheet; if (worksheet != null) { Console.WriteLine(String.Format("{0} ({1})", eventName, worksheet.Name)); } Excel.Chart chart = sheet as Excel.Chart; if (chart != null) { Console.WriteLine(String.Format("{0} ({1})", eventName, chart.Name)); } } void App_WorkbookActivate(Excel.Workbook workbook) { Console.WriteLine(String.Format( "Application.WorkbookActivate({0})", workbook.Name)); } void Workbook_Activate() { Console.WriteLine("Workbook.Activate()"); } void App_WorkbookDeactivate(Excel.Workbook workbook) { Console.WriteLine(String.Format( "Application.WorkbookDeactivate({0})", workbook.Name)); } void Workbook_Deactivate() { Console.WriteLine("Workbook.Deactivate()"); } void App_SheetActivate(object sheet) { ReportEventWithSheetParameter( "Application.SheetActivate", sheet); } void Workbook_SheetActivate(object sheet) { ReportEventWithSheetParameter( "Workbook.SheetActivate", sheet); } void Worksheet_Activate() { Console.WriteLine("Worksheet.Activate()"); } void Chart_Activate() { Console.WriteLine("Chart.Activate()"); } void App_SheetDeactivate(object sheet) { ReportEventWithSheetParameter("Application.SheetDeactivate", sheet); } void Workbook_SheetDeactivate(object sheet) { ReportEventWithSheetParameter("Workbook.SheetDeactivate", sheet); } void Worksheet_Deactivate() { Console.WriteLine("Worksheet.Deactivate()"); } void Chart_Deactivate() { Console.WriteLine("Chart.Deactivate()"); } void App_WindowActivate(Excel.Workbook workbook, Excel.Window window) { Console.WriteLine(String.Format( "Application.WindowActivate({0}, {1})", workbook.Name, window.Caption)); } void Workbook_WindowActivate(Excel.Window window) { Console.WriteLine(String.Format("Workbook.WindowActivate({0})", window.Caption)); } void App_WindowDeactivate(Excel.Workbook workbook, Excel.Window window) { Console.WriteLine(String.Format("Application.WindowDeactivate({0}, {1})",workbook.Name, window.Caption)); } void Workbook_WindowDeactivate(Excel.Window window) { Console.WriteLine(String.Format("Application.WindowActivate({1})", window.Caption)); } } }
双击和右键单击事件
当双击或右键单击工作表或图表工作表(单击鼠标右键)时,会引发几个事件。双击事件时,双击工作表或图表工作表中单元格的中心。如果双击单元格的边框,则不会引发任何事件。如果您双击列标题或行标题,则不会引发任何事件。如果双击工作表中的对象(对象模型中的Shape对象)(如嵌入式图表),则不会引发任何事件。双击Excel中的单元格后,Excel将进入编辑模式,该单元格光标显示在单元格中,允许您键入单元格。如果在编辑模式下双击单元格,则不会引发任何事件。
当您右键单击工作表或图表工作表中的单元格时,会发生右键单击事件。当您右键单击列标题或行标题时,还会提示右键单击事件。如果右键单击工作表中的对象,例如嵌入式图表,则不会引发任何事件。
用于图表表的右键单击和双击事件不会引发应用程序和工作簿对象上的事件。而是直接在Chart对象上引发BeforeDoubleClick和BeforeRightClick事件。
所有右键单击和双击事件的名称中都有“Before”。这是因为Excel在Excel执行默认行为进行双击和右键单击之前提高这些事件,例如,显示上下文菜单或进入双击单元格的编辑模式。这些事件都有一个bool参数,它被一个引用称为cancel的参数传递,它允许您通过将cancel参数设置为true来取消Excel的双击或右键单击的默认行为。
许多右键单击和双击事件会传递一个Range对象作为参数。 Range对象表示cellit的范围可以表示单个单元格或多个单元格。例如,如果选择多个单元格,然后右键单击所选单元格,则Range对象将传递给表示所选单元格的右键单击事件。
双击并以各种方式提供右键单击事件,如下所示:
- 当Excel中任何工作表中的任何单元格被双击时,都会引发Application.SheetBeforeDoubleClick。 Excel将作为双击的工作表作为对象传递,双击的单元格范围,以及通过引用传递的bool取消参数。您可以通过事件处理程序将cancel参数设置为TRue,以防止Excel执行其默认双击行为。这是一个情况,因为没有传递图表,工作表作为对象传递确实没有意义。您将始终将对象转换为工作表。
- Workbook.SheetBeforeDoubleClick是在工作簿上引发的,该工作簿中的单元格已在工作表中双击。 Excel传递与应用程序级SheetBeforeDoubleClick相同的参数。
- Worksheet.BeforeDoubleClick是在双击工作表上引发的。 Excel通过双击单元格范围和通过引用传递的bool取消参数的范围。事件处理程序可以将cancel参数设置为true,以防止Excel执行其默认双击行为。
- Chart.BeforeDoubleClick在双击的图表表上生成。 Excel将作为int元素传递一个元素ID和两个称为arg1和arg2的参数。通过这三个参数的组合,您可以确定双击图表中的哪些元素。 Excel也通过引用通过bool cancel参数。事件处理程序可以将cancel参数设置为true,以防止Excel执行其默认双击行为。
- 只要右键单击Excel中任何工作表中的任何单元格,就会引发Application.SheetBeforeRightClick。 Excel将作为对象右键单击的工作表,右击单元格范围,以及通过引用传递的bool cancel参数作为对象。 cancel参数可以由事件处理程序设置为TRue,以防止Excel执行其默认的右键单击行为。这是一个情况,因为没有传递图表,因此工作表作为对象传递确实没有意义。您将始终将对象转换为工作表。
- Workbook.SheetBeforeRightClick是在一个工作簿上生成的,该工作簿在右侧工作表中有一个单元格。 Excel传递与应用程序级SheetBeforeRightClick相同的参数。
- Worksheet.BeforeRightClick是在右键单击的工作表上引发的。 Excel通过右键单元格范围和通过引用传递的bool取消参数的范围。您可以通过事件处理程序将cancel参数设置为true,以防止Excel执行其默认的右键单击行为。
- Chart.BeforeRightClick是在右键单击的图表表上生成的。奇怪的是,Excel不会传递它传递给Chart.BeforeDoubleClickEvent的任何参数。 Excel通过引用传递一个bool cancel参数。您可以通过事件处理程序将cancel参数设置为true,以防止Excel执行其默认的右键单击行为。
清单4-3显示了一个处理所有这些事件的VSTO Workbook类。此代码假定您已将图表表添加到工作簿,称为Chart1。在VSTO中,当处理由这些对象引发的事件时,您不需要保留对Workbook对象或Worksheet或Chart对象的引用,因为它们已被VSTO项目中生成的项目项目所保留。在处理由Application对象引发的事件时,您需要保留对Application对象的引用,因为它不会保留在VSTO项目的任何位置。
由VSTO生成的ThisWorkbook类派生自具有Excel工作簿对象的所有成员的类,因此可以通过添加引用此代码的代码添加工作簿事件处理程序,如代码清单4-3所示。我们可以使用this.Application获取一个Application对象,因为Application是Workbook的一个属性。因为返回的应用程序对象不被任何其他代码保留为引用,所以我们必须声明一个类成员变量来保持此Application对象,以便我们的事件处理程序可以正常工作。第1章“办公编程介绍”更详细地讨论了这个问题。
要获取我们VSTO项目中的图表和工作表,我们使用VSTO的Globals对象,它可以让我们进入在其他项目项目中声明的类Chart1和Sheet1。我们不必在类成员变量中保存这些对象,因为它们的生命周期与VSTO代码的生命周期相匹配。
我们还在清单4-3中声明了两个帮助函数。一个将作为对象传递的工作表转换为工作表,并返回工作表的名称。另一个获取传递给许多事件的Range的地址作为目标参数。
右键单击事件的处理程序都将引用传递的bool cancel参数设置为true。这将使得Excel不会在右键单击时执行其默认行为,通常会弹出菜单。
清单4-3 处理双击和右键单击事件的VSTO工作簿自定义
using System; using System.Data; using System.Drawing; using System.Windows.Forms; using Microsoft.VisualStudio.Tools.Applications.Runtime; using Excel = Microsoft.Office.Interop.Excel; using Office = Microsoft.Office.Core; namespace ExcelWorkbook1 { public partial class ThisWorkbook { private Excel.Application app; private void ThisWorkbook_Startup(object sender, EventArgs e) { app = this.Application; app.SheetBeforeDoubleClick += new Excel.AppEvents_SheetBeforeDoubleClickEventHandler(App_SheetBeforeDoubleClick); this.SheetBeforeDoubleClick += new Excel.WorkbookEvents_SheetBeforeDoubleClickEventHandler(ThisWorkbook_SheetBeforeDoubleClick); Globals.Sheet1.BeforeDoubleClick += new Excel.DocEvents_BeforeDoubleClickEventHandler(Sheet1_BeforeDoubleClick); Globals.Chart1.BeforeDoubleClick += new Excel.ChartEvents_BeforeDoubleClickEventHandler(Chart1_BeforeDoubleClick); app.SheetBeforeRightClick += new Excel.AppEvents_SheetBeforeRightClickEventHandler(App_SheetBeforeRightClick); this.SheetBeforeRightClick += new Excel.WorkbookEvents_SheetBeforeRightClickEventHandler(ThisWorkbook_SheetBeforeRightClick); Globals.Sheet1.BeforeRightClick += new Excel.DocEvents_BeforeRightClickEventHandler( Sheet1_BeforeRightClick); Globals.Chart1.BeforeRightClick += new Excel.ChartEvents_BeforeRightClickEventHandler( Chart1_BeforeRightClick); } private void ThisWorkbook_Shutdown(object sender, EventArgs e) { } private string RangeAddress(Excel.Range target) { return target.get_Address(missing, missing, Excel.XlReferenceStyle.xlA1, missing, missing); } private string SheetName(object sheet) { Excel.Worksheet worksheet = sheet as Excel.Worksheet; if (worksheet != null) return worksheet.Name; else return String.Empty; } void App_SheetBeforeDoubleClick(object sheet, Excel.Range target, ref bool cancel) { MessageBox.Show(String.Format("Application.SheetBeforeDoubleClick({0},{1})",SheetName(sheet), RangeAddress(target))); } void ThisWorkbook_SheetBeforeDoubleClick(object sheet, Excel.Range target, ref bool cancel) { MessageBox.Show(String.Format("Workbook.SheetBeforeDoubleClick({0}, {1})",SheetName(sheet), RangeAddress(target))); } void Sheet1_BeforeDoubleClick(Excel.Range target, ref bool cancel) { MessageBox.Show(String.Format( "Worksheet.SheetBeforeDoubleClick({0})", RangeAddress(target))); } void Chart1_BeforeDoubleClick(int elementID, int arg1, int arg2, ref bool cancel) { MessageBox.Show(String.Format( "Chart.SheetBeforeDoubleClick({0}, {1}, {2})", elementID, arg1, arg2)); } void App_SheetBeforeRightClick(object sheet, Excel.Range target, ref bool cancel) { MessageBox.Show(String.Format( "Application.SheetBeforeRightClick({0},{1})", SheetName(sheet), RangeAddress(target))); cancel = true; } void ThisWorkbook_SheetBeforeRightClick(object sheet, Excel.Range target, ref bool cancel) { MessageBox.Show(String.Format( "Workbook.SheetBeforeRightClick({0},{1})", SheetName(sheet), RangeAddress(target))); cancel = true; } void Sheet1_BeforeRightClick(Excel.Range target, ref bool cancel) { MessageBox.Show(String.Format( "Worksheet.SheetBeforeRightClick({0})", RangeAddress(target))); cancel = true; } void Chart1_BeforeRightClick(ref bool cancel) { MessageBox.Show("Chart.SheetBeforeRightClick()"); cancel = true; } #region VSTO Designer generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InternalStartup() { this.Startup += new System.EventHandler(ThisWorkbook_Startup); this.Shutdown += new System.EventHandler(ThisWorkbook_Shutdown); } #endregion } }
可取消事件和事件冒泡
清单4-3提出了一个有趣的问题。当多个对象处理多个级别的BeforeRightClick之类的事件时会发生什么?清单4-3在Worksheet,Workbook和Application级别处理BeforeRightClick事件。 Excel首先在Worksheet级别为针对Worksheet级事件注册的所有代码引发事件。请记住,其他加载项也可以在Excel中处理,也可以处理Worksheet级事件。您的代码可能会获得Worksheet.BeforeRightClick事件,之后是其他一些加载项,也正在处理Worksheet.BeforeRightClick事件。当多个加载项处理相同对象上相同的事件时,您无法依赖任何确定的顺序来确定谁将首先获取事件。因此,不要编写代码来依赖任何特定的顺序。
在工作表级别提出事件之后,然后在“工作簿”级别,最后在“应用程序”级别引导。对于可取消事件,即使一个事件处理程序将cancel参数设置为true,事件将继续提升到其他事件处理程序。因此,即使清单4-3中的代码将Sheet1_BeforeRightClick中的cancel参数设置为true,Excel将继续在WorkReports的其他处理程序BeforeRightClick上引发事件,然后再处理Workbook.SheetBeforeRightClick的处理程序,后跟Application.SheetBeforeRightClick的处理程序。
您应该了解可取消事件的另一件事情是,您可以检查事件处理程序中的传入取消参数,以查看最后一个事件处理程序设置为何值。因此,在Sheet1_BeforeRightClick处理程序中,假设没有其他代码处理该事件,传入的cancel参数将为false。在ThisWorkbook_SheetBeforeRightClick处理程序中,传入的cancel参数将为true,因为最后一个处理程序Sheet1_BeforeRightClick将其设置为TRue。这意味着,作为事件通过多个处理程序发生的事件,每个后续处理程序可以覆盖先前处理程序在本示例中取消默认右键单击行为方面所做的工作。应用程序级处理程序得到最终的说明,如果同一事件存在多个应用程序级处理程序,则不管事件是否被取消,都是不确定的,因为没有规则规定多个应用程序级事件处理程序中的哪个处理程序首先或最后获取事件。
计算事件
当重新计算工作表中的公式时,会引发四个事件。当您更改影响到该单元格的公式的单元格或添加或修改公式时,将重新计算工作表:
- 只要重新计算Excel中的任何工作表,就会引发Application.SheetCalculate。 Excel将作为对该事件重新计算的对象作为参数传递给该表。 可以将工作表对象转换为工作表或图表。
- Workbook.SheetCalculate是在具有重新计算的工作表的工作簿上引发的。 Excel将作为对该事件重新计算的对象作为参数传递给该表。 可以将工作表对象转换为工作表或图表。
- Worksheet.Calculate是在重新计算的工作表上引发的。
- Calculate是Worksheet对象上的方法和事件的名称。 由于此冲突,您将无法在Visual Studio的与Worksheet对象关联的属性,事件和方法的弹出菜单中看到“计算”事件。 此外,当您尝试处理此事件时,会在编译时显示警告。 要使Visual Studio的弹出菜单工作并且警告消失,可以将Worksheet对象转换为DocEvents_Event接口,如清单4-4所示。
- Chart.Calculate在已更新的图表表上生成,因为其引用的数据已更改。直到图表被强制重新绘制,如果图表当前不可见,因为它没有被选中或显示在自己的窗口中,则不会发生此事件,直到图表可见为止,事件才会被提升。
清单4-4显示了一个处理所有计算事件的控制台应用程序。控制台应用程序创建一个新的工作簿,获取工作簿中的第一个工作表,并在工作簿中创建一个图表。控制台应用程序还处理创建的工作簿的关闭事件,以使工作簿关闭时控制台应用程序退出。获取Excel以提高工作表和工作簿计算事件,将一些值和公式添加到工作簿中的第一个工作表。要升高Chart对象的Calculate事件,您可以右键单击处理事件的图表表,然后从弹出菜单中选择Source Data。然后,单击数据范围文本框右侧的按钮,切换到第一个工作表,然后为要显示的图表表选择一系列值。当您更改这些值并切换回图表表时,图表的“计算”事件将被提升。
清单4-4 处理计算事件的控制台应用程序
using System; using Excel = Microsoft.Office.Interop.Excel; namespace ConsoleApplication { class Program { static private Excel.Application app; static private Excel.Workbook workbook; static private Excel.Worksheet worksheet; static private Excel.Chart chart; static bool exit = false; static void Main(string[] args) { app = new Excel.Application(); app.Visible = true; workbook = app.Workbooks.Add(Type.Missing); worksheet = workbook.Sheets.get_Item(1) as Excel.Worksheet; chart = workbook.Charts.Add(Type.Missing, Type.Missing, Type.Missing, Type.Missing) as Excel.Chart; app.SheetCalculate += new Excel.AppEvents_SheetCalculateEventHandler( App_SheetCalculate); workbook.SheetCalculate += new Excel.WorkbookEvents_SheetCalculateEventHandler( Workbook_SheetCalculate); ((Excel.DocEvents_Event)worksheet).Calculate += new Excel.DocEvents_CalculateEventHandler( Worksheet_Calculate); chart.Calculate += new Excel.ChartEvents_CalculateEventHandler( Chart_Calculate); workbook.BeforeClose += new Excel.WorkbookEvents_BeforeCloseEventHandler( Workbook_BeforeClose); while (exit == false) System.Windows.Forms.Application.DoEvents(); app.Quit(); } static void Workbook_BeforeClose(ref bool cancel) { exit = true; } static string SheetName(object sheet) { Excel.Worksheet worksheet = sheet as Excel.Worksheet; if (worksheet != null) { return worksheet.Name; } Excel.Chart chart = sheet as Excel.Chart; if (chart != null) { return chart.Name; } return String.Empty; } static void App_SheetCalculate(object sheet) { Console.WriteLine(String.Format( "Application.SheetCalculate({0})", SheetName(sheet))); } static void Workbook_SheetCalculate(object sheet) { Console.WriteLine(String.Format( "Workbook.SheetCalculate({0})", SheetName(sheet))); } static void Worksheet_Calculate() { Console.WriteLine("Worksheet.Calculate()"); } static void Chart_Calculate() { Console.WriteLine("Chart.Calculate()"); } } }
change事件
当工作表中更改单元格或单元格范围时,Excel会引发多个事件。必须由用户编辑要更改事件的单元格来更改单元格。当单元格链接到外部数据并且由于从外部数据刷新单元格而改变时,也可以引发更改事件。由于重新计算更改单元格时,更改事件不会引发。当用户更改单元格的格式时,不会改变它们,而不更改单元格的值。当用户正在编辑单元格并处于单元格编辑模式时,在用户退出单元格编辑模式之前,不改变事件,直到离开单元格或按Enter键:
当用户更改任何工作簿中的单元格或单元格范围或从外部数据更新时,将引发Application.SheetChange。 Excel将作为更改发生的对象作为对象传递给此事件的参数。您可以随时将工作表参数转换为工作表,因为不会为图表工作表提供更改事件。 Excel还会将范围作为更改单元格范围的参数。
当工作簿中的单元格或单元格范围由用户更改或从外部数据更新时,Workbook.SheetChange将在工作簿上引发。 Excel将作为更改发生的对象作为对象传递给此事件的参数。您可以随时将工作表参数转换为工作表,因为不会为图表工作表提供更改事件。 Excel还会将范围作为更改单元格范围的参数。
Worksheet.Change在工作表中引发,当工作表中的单元格或单元格范围由用户更改或从外部数据更新时。 Excel将范围作为更改单元格范围的参数。
清单4-5显示了一个处理所有Change事件的类。它将Excel Application对象传递给其构造函数。构造函数创建一个新的工作簿,并获取工作簿中的第一个工作表。它处理在应用程序对象,工作簿和工作簿中的第一个工作表中引发的事件。
清单4-5 处理变更事件的班级
using System; using Excel = Microsoft.Office.Interop.Excel; namespace ChangeEvents { public class ChangeEventHandler { private Excel.Application app; private Excel.Workbook workbook; private Excel.Worksheet worksheet; object missing = System.Type.Missing; public ChangeEventHandler(Excel.Application application) { this.app = application; workbook = app.Workbooks.Add(missing); worksheet = workbook.Worksheets.get_Item(1) as Excel.Worksheet; app.SheetChange += new Excel.AppEvents_SheetChangeEventHandler( App_SheetChange); workbook.SheetChange += new Excel.WorkbookEvents_SheetChangeEventHandler( Workbook_SheetChange); worksheet.Change += new Excel.DocEvents_ChangeEventHandler( Worksheet_Change); } // Change events only pass worksheets, never charts. private string SheetName(object sheet) { Excel.Worksheet worksheet = sheet as Excel.Worksheet; return worksheet.Name; } private string RangeAddress(Excel.Range target) { return target.get_Address(missing, missing, Excel.XlReferenceStyle.xlA1, missing, missing); } void App_SheetChange(object sheet, Excel.Range target) { Console.WriteLine(String.Format( "Application.SheetChange({0},{1})", SheetName(sheet), RangeAddress(target))); } void Workbook_SheetChange(object sheet, Excel.Range target) { Console.WriteLine(String.Format( "Workbook.SheetChange({0},{1})", SheetName(sheet), RangeAddress(target))); } void Worksheet_Change(Excel.Range target) { Console.WriteLine(String.Format( "Worksheet.Change({0})", RangeAddress(target))); } } }
Hyperlink 事件
单击单元格中的超链接时,Excel会引发多个事件。你可能会认为这个事件并不是很有趣,但是您可以将其用作在您的自定义中调用操作的简单方法。诀窍是创建一个不执行任何操作的超链接,然后处理FollowHyperlink事件并在该事件处理程序中执行该操作。
要创建不执行任何操作的超链接,请右键单击要放置超链接的单元格,然后选择HyperLink。对于我们的例子,我们选择单元格C3。在出现的对话框中,单击对话框左侧的“放置在此文档”按钮(参见图4-2)。在“键入”单元格参考文本框中,键入C3或要添加超链接的单元格的引用。执行此操作的逻辑是,在单击超链接之后,以及事件处理程序运行后,Excel将选择C3链接到的单元格。如果您选择用户单击的单元格以外的单元格,则选择将移动,这是令人困惑的。所以我们有效地将单元格链接到自身,创建一个无关的链接。在文本显示文本框中,键入您要在单元格中显示的名称命令的名称。在这个例子中,我们命名为Print。
图4-2 插入超链接对话框
单击超链接时会引发以下事件:
当在Excel中打开的任何工作簿中单击超链接时,将引发Application.SheetFollowHyperlink。 Excel将超链接对象作为参数传递给此事件。超链接对象提供了有关被点击的超链接的信息。
当在该工作簿中单击超链接时,Workbook.SheetFollowHyperlink在工作簿上引发。 Excel将超链接对象作为参数传递给此事件。超链接对象提供了有关被点击的超链接的信息。
当工作表中单击超链接时,Worksheet.FollowHyperlink在工作表上引发。 Excel将超链接对象作为参数传递给此事件。超链接对象提供了有关被点击的超链接的信息。
清单4-6显示了工作簿项目项目的VSTO自定义类。该类假定工作簿中有一个Print超链接,如图4-2所示创建。应用程序或工作簿级超级链接事件的处理程序中的自定义功能不起作用,但会记录到控制台窗口。 Worksheet级处理程序检测到单击一个名为Print的超链接,并调用Workbook对象上的PrintOut方法以打印工作簿。
清单4-6 处理超链接事件的VSTO工作簿定制
using System; using System.Data; using System.Drawing; using System.Windows.Forms; using Microsoft.VisualStudio.Tools.Applications.Runtime; using Excel = Microsoft.Office.Interop.Excel; using Office = Microsoft.Office.Core; namespace ExcelWorkbook1 { public partial class ThisWorkbook { private Excel.Application app; private void ThisWorkbook_Startup(object sender, EventArgs e) { app = this.Application; app.SheetFollowHyperlink += new Excel.AppEvents_SheetFollowHyperlinkEventHandler( App_SheetFollowHyperlink); this.SheetFollowHyperlink += new Excel.WorkbookEvents_SheetFollowHyperlinkEventHandler( Workbook_SheetFollowHyperlink); Globals.Sheet1.FollowHyperlink += new Excel.DocEvents_FollowHyperlinkEventHandler( Sheet_FollowHyperlink); } private string SheetName(object sheet) { Excel.Worksheet worksheet = sheet as Excel.Worksheet; if (worksheet != null) return worksheet.Name; else return String.Empty; } void App_SheetFollowHyperlink(object sheet, Excel.Hyperlink target) { MessageBox.Show(String.Format( "Application.SheetFollowHyperlink({0},{1})", SheetName(sheet), target.Name)); } void Workbook_SheetFollowHyperlink(object sheet, Excel.Hyperlink target) { MessageBox.Show(String.Format( "Workbook.SheetFollowHyperlink({0},{1})", SheetName(sheet), target.Name)); } void Sheet_FollowHyperlink(Excel.Hyperlink target) { if (target.Name == "Print") { this.PrintOut(missing, missing, missing, missing, missing, missing, missing, missing); } } private void ThisWorkbook_Shutdown(object sender, EventArgs e) { } #region VSTO Designer generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InternalStartup() { this.Startup += new System.EventHandler(ThisWorkbook_Startup); this.Shutdown += new System.EventHandler(ThisWorkbook_Shutdown); } #endregion } }
选择change事件
当所选单元格或单元格更改时,或当图表表中所选图表元素更改时,将在Chart.Select事件的情况下发生选择更改事件:
当Excel中任何工作表中选定的单元格或单元格更改时,将引发Application.SheetSelectionChange。 Excel将选择更改的工作表传递给事件处理程序。但是,事件处理程序的参数键入对象,因此如果要使用Worksheet的属性或方法,则必须将其转换为工作表。您保证始终能够将参数转换为工作表,因为在Chart上进行选择更改时,不会引发SheetSelectionChange事件。 Excel也会传递新选择的单元格范围。
当工作簿中选定的单元格或单元格更改时,Workbook.SheetSelectionChange在Workbook上生成。 Excel作为对象将选择更改的工作表传递给对象。您可以随时将工作表对象转换为工作表,因为不会为图表工作表上的选择更改引发此事件。 Excel还会传递一个Range作为新选择的单元格范围。
Worksheet.SelectionChange在工作表中引发,只要该工作表中选定的单元格更改。 Excel传递一个范围,作为新选择的单元格范围。
当图表表中的选定元素发生变化时,图表会在图表上生成。 Excel将作为int元素传递一个元素ID和两个称为arg1和arg2的参数。通过这三个参数的组合,您可以确定选择图表的哪个元素。
注:Select是Chart对象上的方法和事件的名称。 由于此冲突,您将不会在Visual Studio的与图表对象关联的属性,事件和方法的弹出菜单中看到Select事件。 此外,当您尝试处理此事件时,会在编译时显示警告。 要使Visual Studio的弹出菜单工作,并且警告消失,您可以将Chart对象转换为ChartEvents_Events界面,如清单4-2所示。
WindowResize事件
调整工作簿窗口大小时会引发WindowResize事件。只有当工作簿窗口未最大化才能填满Excel的外部应用程序窗口(见图4-3)时,才会引发这些事件。如果调整非最大化的工作簿窗口大小或最小化工作簿窗口,则会引发事件。调整大小并最小化外部Excel应用程序窗口时,不会调整大小事件。
- 当任何非最大化的工作簿窗口被调整大小或最小化时,将引发Application.WindowResize。 Excel将与调整大小或最小化的窗口相对应的Window对象作为参数传递给此事件。 Excel还将作为参数影响的Workbook对象传递给此事件。
- 当与该工作簿关联的非最大化窗口调整大小或最小化时,Workbook.WindowResize在Workbook上引发。 Excel将作为参数调整大小或最小化的窗口传递给此事件。
图4-3 仅当工作簿窗口未最大化以填充应用程序窗口时,才会引发Window Resize事件
加载项安装和卸载事件
通过从“文件”菜单中选择“另存为”,然后选择Microsoft Office Excel加载项作为所需的格式,可将工作簿保存为特殊的附加格式(XLA文件)。然后将该工作簿保存到用户文档和设置目录下的Application Data \ Microsoft \ AddIns目录中。当您从“工具”菜单中选择“加载项”时,它将显示在可用的加载项列表中。当您单击复选框以启用加载项时,工作簿加载为隐藏状态,并引发Application.AddinInstall事件。当用户单击复选框以禁用加载项时,将引发Application.AddinUninstall事件。
虽然您理论上可以将VSTO定制的工作簿保存为XLA文件,但Microsoft不支持此方案,因为许多VSTO功能(如“文档操作”任务窗格和“智能标记”)在工作簿保存为XLA文件时不起作用。
XML导入和导出事件
Excel支持自定义XML数据文件的导入和导出,允许您使用XML模式并将其映射到工作簿中的单元格。然后可以将这些单元格导出或导入到符合映射模式的XML数据文件。 Excel在引入或导出XML文件之前和之后引发应用程序和工作簿对象上的事件,从而允许开发人员进一步自定义和控制此功能。第21章“使用Excel中的XML”详细讨论了Excel的XML映射功能。
关闭事件之前
Excel在工作簿关闭之前引发事件。这些事件是给你的代码一个机会来防止关闭工作簿。 Excel将bool cancel参数传递给事件。如果事件处理程序将cancel参数设置为true,则工作簿的待处理关闭将被取消,并且工作簿保持打开状态。
这些事件不能用于确定工作簿是否实际关闭。另一个事件处理程序可能会在事件处理程序之后运行,例如,另一个加载项中的事件处理程序,该事件处理程序可能将cancel参数设置为true,从而防止工作簿关闭。此外,如果用户更改了工作簿,并且在工作簿关闭时提示保存更改,则用户可以单击“取消”按钮,导致工作簿保持打开状态。
如果您只需要在工作簿实际关闭时运行代码,则VSTO会提供一个关闭事件,直到所有其他事件处理程序和用户都允许关闭工作簿为止。
- Application.WorkbookBeforeClose在任何工作簿关闭之前被提出,使事件处理程序有机会阻止工作簿关闭。 Excel传递即将关闭的Workbook对象。 Excel也通过引用bool取消参数。取消参数可以由事件处理程序设置为TRue,以防止Excel关闭工作簿。
- Workbook.BeforeClose是在即将关闭的工作簿上引发的,给予事件处理程序阻止工作簿关闭的机会。 Excel通过引用bool取消参数。您可以通过事件处理程序将cancel参数设置为true,以防止Excel关闭工作簿。
print前事件
Excel在打印工作簿之前引发事件。当用户从文件菜单中选择打印或打印预览或按打印工具栏按钮时,会引发这些事件。 Excel将bool cancel参数传递给事件。如果事件处理程序将cancel参数设置为true,则工作簿的待处理打印将被取消,并且不会显示打印对话框或打印预览视图。您可能想要这样做,因为您要将Excel的默认打印行为替换为您自己的某些自定义打印行为。
这些事件不能用于确定工作簿是否实际打印。另一个事件处理程序可能会在您的事件处理程序之后运行,并阻止打印工作簿。用户还可以按“打印”对话框中的“取消”按钮停止打印。
Application.WorkbookBeforePrint在任何工作簿打印或打印预览之前被提升,使得事件处理程序有机会在打印工作簿之前更改工作簿或更改默认打印行为。 Excel将作为要打印的工作簿的参数传递。 Excel也通过引用bool取消参数。事件处理程序可以将cancel参数设置为true,以防止Excel执行其默认打印行为。
Workbook.BeforePrint是在要打印或打印预览的工作簿上提出的,给予事件处理程序一次更改打印工作簿或更改默认打印行为的机会。 Excel通过引用bool取消参数。您可以通过事件处理程序将cancel参数设置为true,以防止执行其默认打印行为。
save前事件
在保存工作簿之前,Excel会引发可取消事件,允许您在保存文档之前执行一些自定义操作。当用户选择“保存”,“另存为”或“另存为网页”命令时,会引发这些事件。当用户关闭已修改的工作簿并在出现提示时选择保存,也会引发它们。 Excel将bool cancel参数传递给事件。如果事件处理程序将cancel参数设置为true,则保存将被取消,并且不会显示保存对话框。您可能想要这样做,因为您要将Excel的默认保存行为替换为您自己的某些自定义保存行为。
这些事件不能用于确定工作簿是否实际上将被保存。另一个事件处理程序可能在您的事件处理程序之后运行,并阻止保存工作簿。用户还可以在保存对话框中按取消停止工作簿的保存。
在保存任何工作簿之前,将引发Application.WorkbookBeforeSave,使事件处理程序有机会阻止或覆盖工作簿的保存。 Excel作为参数即将传入即将被保存的工作簿。 Excel还传递一个bool saveAsUI参数,该参数告知事件处理程序是否选择了Save或Save As。 Excel也通过引用bool取消参数。您可以通过事件处理程序将cancel参数设置为TRue,以防止Excel执行其默认保存行为。
Workbook.BeforeSave是在即将被保存的工作簿上提出的,给事件处理程序一个机会来防止或覆盖工作簿的保存。 Excel传递一个bool saveAsUI参数,它指示事件处理程序是否选择了Save或Save As。 Excel通过引用bool取消参数。您可以通过事件处理程序将cancel参数设置为true,以防止Excel执行其默认保存行为。
open事件
当打开工作簿或从模板或现有文档创建新工作簿时,Excel会引发事件。如果创建了新的空白工作簿,则会引发Application.WorkbookNew事件。
当打开任何工作簿时,将引发Application.WorkbookOpen。 Excel将作为参数打开的工作簿传递给此事件。创建新的空白工作簿时,不会引发此事件。提出Application.WorkbookNew事件。
Workbook.Open在打开时在工作簿上提出。
清单4-7显示了一个处理BeforeClose,BeforePrint,BeforeSave和Open事件的控制台应用程序。它在BeforeSave和BeforePrint处理程序中将cancel参数设置为TRue,以防止保存和打印工作簿。
清单4-7 处理关闭,打印,保存和打开事件的控制台应用程序
using System; using Excel = Microsoft.Office.Interop.Excel; namespace ConsoleApplication { class Program { static private Excel.Application app; static private Excel.Workbook workbook; static private bool exit = false; static void Main(string[] args) { app = new Excel.Application(); app.Visible = true; workbook = app.Workbooks.Add(Type.Missing); app.WorkbookBeforeClose += new Excel.AppEvents_WorkbookBeforeCloseEventHandler( App_WorkbookBeforeClose); workbook.BeforeClose += new Excel.WorkbookEvents_BeforeCloseEventHandler( Workbook_BeforeClose); app.WorkbookBeforePrint += new Excel.AppEvents_WorkbookBeforePrintEventHandler( App_WorkbookBeforePrint); workbook.BeforePrint += new Excel.WorkbookEvents_BeforePrintEventHandler( Workbook_BeforePrint); app.WorkbookBeforeSave += new Excel.AppEvents_WorkbookBeforeSaveEventHandler( App_WorkbookBeforeSave); workbook.BeforeSave += new Excel.WorkbookEvents_BeforeSaveEventHandler( Workbook_BeforeSave); app.WorkbookOpen += new Excel.AppEvents_WorkbookOpenEventHandler( App_WorkbookOpen); while (exit == false) System.Windows.Forms.Application.DoEvents(); app.Quit(); } static void App_WorkbookBeforeClose(Excel.Workbook workbook, ref bool cancel) { Console.WriteLine(String.Format( "Application.WorkbookBeforeClose({0})", workbook.Name)); } static void Workbook_BeforeClose(ref bool cancel) { Console.WriteLine("Workbook.BeforeClose()"); exit = true; } static void App_WorkbookBeforePrint(Excel.Workbook workbook, ref bool cancel) { Console.WriteLine(String.Format( "Application.WorkbookBeforePrint({0})", workbook.Name)); cancel = true; // Don't allow printing } static void Workbook_BeforePrint(ref bool cancel) { Console.WriteLine("Workbook.BeforePrint()"); cancel = true; // Don't allow printing } static void App_WorkbookBeforeSave(Excel.Workbook workbook, bool saveAsUI, ref bool cancel) { Console.WriteLine(String.Format( "Application.WorkbookBeforeSave({0},{1})", workbook.Name, saveAsUI)); cancel = true; // Don't allow saving } static void Workbook_BeforeSave(bool saveAsUI, ref bool cancel) { Console.WriteLine(String.Format( "Workbook.BeforePrint({0})", saveAsUI)); cancel = true; // Don't allow saving } static void App_WorkbookOpen(Excel.Workbook workbook) { Console.WriteLine(String.Format( "Appplication.WorkbookOpen({0})", workbook.Name)); } } }
工具栏和菜单事件
运行代码的常见方法是将自定义工具栏按钮或菜单项添加到Excel,并处理由该按钮或菜单项引发的点击事件。 工具栏和菜单栏都由Office对象模型中的相同对象表示,即一个名为CommandBar的对象。 CommandBar相关对象的层次结构如图4-4所示。 Application对象具有CommandBars的集合,它们表示主菜单栏和Excel中的所有可用工具栏。 您可以通过从“工具”菜单中选择“自定义”来查看Excel中的所有可用工具栏。
图4-4 CommandBar对象的层次结构
通过添加对Microsoft Office 11.0对象库PIA(office.dll)的引用,CommandBar对象可用于您的应用程序。 CommandBar对象位于Microsoft.Office.Core命名空间中。
CommandBar具有CommandBarControls的集合,它包含CommandBarControl类型的对象。 CommandBarControl通常可以转换为CommandBarButton,CommandBarPopup或CommandBarComboBox。也可能有一个CommandBarControl不能转换为其他类型之一,例如,它只是一个CommandBarControl,不能转换为CommandBarButton,CommandBarPopup或CommandBarComboxBox。
清单4-8显示了一些代码,它遍历Excel中可用的所有CommandBars。代码显示每个CommandBar和相关CommandBarControls的名称或标题。当清单4-8获取到CommandBarControl时,它首先检查它是否为CommandBarButton,CommandBarComboBox或CommandBarPopup,然后转换为相应的对象。如果不是任何这些对象类型,代码将使用CommandBarControl属性。请注意,CommandBarPopup具有返回CommandBarControls集合的Controls属性。我们的代码使用递归来迭代与CommandBarPopup控件相关联的CommandBarControls集合。
清单4-8 控制台应用程序,迭代Excel中的所有CommandBars和CommandBarControls
using System; using Excel = Microsoft.Office.Interop.Excel; using Office = Microsoft.Office.Core; using System.Text; namespace ConsoleApplication { class Program { static private Excel.Application app; static void Main(string[] args) { app = new Excel.Application(); Office.CommandBars bars = app.CommandBars; foreach (Office.CommandBar bar in bars) { Console.WriteLine(String.Format( "CommandBar: {0}", bar.Name)); DisplayControls(bar.Controls, 1); } Console.ReadLine(); } static void DisplayControls(Office.CommandBarControls ctls, int indentNumber) { System.Text.StringBuilder sb = new System.Text.StringBuilder(); sb.Append(' ', indentNumber); foreach (Office.CommandBarControl ctl in ctls) { Office.CommandBarButton btn = ctl as Office.CommandBarButton; Office.CommandBarComboBox box = ctl as Office.CommandBarComboBox; Office.CommandBarPopup pop = ctl as Office.CommandBarPopup; if (btn != null) { sb.Append("CommandBarButton: "); sb.Append(btn.Caption); Console.WriteLine(sb.ToString()); } else if (box != null) { sb.Append("CommandBarComboBox: "); sb.Append(box.Caption); Console.WriteLine(sb.ToString()); } else if (pop != null) { DisplayControls(pop.Controls, indentNumber + 1); } else { sb.Append("CommandBarControl: "); sb.Append(ctl.Caption); Console.WriteLine(sb.ToString()); } } } } }
Excel在CommandBar,CommandBarButton和CommandBarComboBox对象上引发了几个事件:
CommandBar.OnUpdate在CommandBar或相关CommandBarControls发生任何更改时引发。此事件频繁出现,甚至可以在Excel中进行选择更改时加注。处理此事件可能会减慢Excel,因此您应该谨慎处理此事件。
CommandBarButton.Click是在单击的CommandBarButton上引发的。 Excel将作为参数单击的CommandBarButton传递给此事件。它也通过参考传递bool cancelDefault参数。事件处理程序可以将cancelDefault参数设置为true,以防止Excel执行与该按钮相关联的默认操作。例如,您可以处理现有按钮(例如打印按钮)的此事件。通过将cancelDefault设置为TRue,您可以防止Excel在用户单击按钮时执行其默认打印行为,而将其替换为您自己的行为。
CommandBarComboBox.Change在CommandBarComboBox上引发,其文本值已更改,因为用户从下拉列表中选择了一个选项,或者由于用户直接在组合框中键入了新值。 Excel将作为参数更改的CommandBarComboBox传递给此事件。
清单4-9显示了一个创建CommandBar,CommandBarButton和CommandBarComboBox的控制台应用程序。它处理CommandBarButton.Click事件以退出应用程序。它还显示在控制台窗口中对CommandBarComboBox所做的更改。 CommandBar,CommandBarButton和CommandBarComboBox临时添加;当应用程序退出时,Excel将自动删除它们。这通过将true传递给CommandBarControls.Add方法的最后一个参数来完成。
清单4-9 添加CommandBar和CommandBarButton的控制台应用程序
using System; using Office = Microsoft.Office.Core; using Excel = Microsoft.Office.Interop.Excel; namespace ConsoleApplication { class Program { static private Excel.Application app; static bool close = false; static Office.CommandBarButton btn; static Office.CommandBarComboBox box; static object missing = Type.Missing; static void Main(string[] args) { app = new Excel.Application(); app.Visible = true; Office.CommandBars bars = app.CommandBars; Office.CommandBar bar = bars.Add("My Custom Bar", missing, missing, true); bar.Visible = true; btn = bar.Controls.Add(Office.MsoControlType.msoControlButton, missing, missing, missing, true) as Office.CommandBarButton; btn.Click += new Office._CommandBarButtonEvents_ClickEventHandler( Btn_Click); btn.Caption = "Stop Console Application"; btn.Tag = "ConsoleApplication.btn"; btn.Style = Office.MsoButtonStyle.msoButtonCaption; box = bar.Controls.Add( Office.MsoControlType.msoControlComboBox, missing, missing, missing, true) as Office.CommandBarComboBox; box.AddItem("Choice 1", 1); box.AddItem("Choice 2", 2); box.AddItem("Choice 3", 3); box.Tag = "ConsoleApplication.box"; box.Change += new Office._CommandBarComboBoxEvents_ChangeEventHandler( Box_Change); while (close == false) System.Windows.Forms.Application.DoEvents(); } static void Btn_Click(Office.CommandBarButton ctrl, ref bool cancelDefault) { close = true; } static void Box_Change(Office.CommandBarComboBox ctrl) { Console.WriteLine("Selected " + ctrl.Text); } } }
其他事件
表4-1列出了Excel对象模型中的其他一些较不常用的事件。 图4-17显示了本表中提及的信封UI。
表4-1 其他Excel事件
图4-5 Excel中的信封界面
Visual Studio 2005 Office for Office中的事件
在Visual Studio 2005 Tools for Office对象中找到几个事件,这些事件在单独使用Excel PIA时找不到。 表4-2列出了这些事件。 几乎所有这些都是来自不同对象重新提出的Excel PIA的事件。 例如,在Excel PIA中,在Range对象上没有BeforeDoubleClick事件,事实上,在Range对象上没有任何事件。 在VSTO中,VSTO定义的两个表示Range(NamedRange和XMLMappedRange)的对象都有一个BeforeDoubleClick事件。 VSTO将BeforeDoubleClick事件添加到这些对象中,并在每当引发Worksheet.BeforeDoubleClick事件并传递与给定的NamedRange或XMLMappedRange对象匹配的Range对象时引发事件。
表4-2 在VSTO中添加的事件
VSTO更改事件的另一种情况是激活事件的命名和Worksheet对象上的Select事件。 这两个事件名称都与Worksheet上的方法名称冲突。 为了避免这种冲突,VSTO将这些事件重命名为ActivateEvent和SelectEvent。
还有一些新事件,例如在VSTO项目主机项目(如Workbook,Worksheet和ChartSheet)上引发的启动和关闭事件。 ListObject也有数据绑定的几个新事件。
结论
本章已经对Excel对象模型中对象引发的各种事件进行了研究。 本章还介绍了Excel对象模型中的一些主要对象,如应用程序,工作簿和文档。 您还学习了VSTO对象在Excel中引发的其他事件。
第5章“使用Excel对象”更详细地讨论如何使用Excel对象模型中的主要对象。