VSTO:使用C#開發Excel、Word【12】


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.ActivateChart.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.DeactivateChart.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對象模型中的主要對象。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM