Excel、Exchange 和 C#
Eric Gunnerson
Microsoft Corporation
2003年4月21日
摘要:Eric Gunnerson 將向您介紹如何使用 Outlook、Excel 和 C# 創建自定義的日歷,該日歷可以提供適用於短期項目和長期項目的清晰明了的版式。
下載 csharp05152003_sample.exe 示例文件(英文)。
雖然一月份已經過去了,我還是決定為您介紹這個遲到的新年解決方案。我決定不再談論我的下一個專欄要說什麼,因為每提到一個主題似乎就預示著我將來不會說到它。所以,這個月我將不談論 DirectX。(如果下次談到它,就是違背諾言了。)
在開始之前,先簡短回顧一下上月專欄的內容。雖然我使用了 NUnit 來完成我的單元測試,但您也可以使用 csunit 或 .NETUnit 來完成您的單元測試。有關詳細信息,請參閱 C# 工具頁面(英文)。
C# 程序管理組最近開始使用 Microsoft Outlook® 的日歷,來安排我們各項活動的日程,所以我們全都知道下一次討論安排在什麼時間、小組成員何時休假以及要召開的會議安排在什麼時間。查看短期日程安排時,這個日歷非常好用,但要查看未來幾個月的日程安排時,它就不那麼管用了。我查找了適用於查看長期日程安排的實用程序,但是沒有找到。
看來,應該好好利用 MSDN 了。我寫了一些用於訪問電子郵件的代碼,這些代碼看起來相當簡單,但是我需要一種方法來創建並打印日程安排網格。網格很容易畫,但是要實現跨多個頁面打印並不容易,所以我開始尋找可以打印矩形網格的程序,並了解如何跨頁打印。看起來 Microsoft Excel 是比較理想的選擇。
要從 C# 訪問 Outlook 和 Excel,需要使用 COM 互操作。要使用 COM 互操作,需要具備互操作程序集以從 C# 端進行引用。您可以從 C# 項目中引用適當的 COM 組件來生成程序集,也可以下載適用於所有 Microsoft Office 組件的互操作程序集。
如果需要將程序集安裝到 GAC 中,則不能引用任何未簽名的程序集,因此如果您的程序集需要使用 Office,就需要下載已簽名的程序集(英文)。下載程序集之後,需要向已簽名的程序集添加引用。我將從在 Excel 中創建工作表並設置單元格開始。
使用 Excel
創建項目後,我找到項目中的引用節點,浏覽到 PIA 所在的目錄,然後添加對 Excel 的引用。
現在我已准備好使用 Excel 開始工作,但要這樣做,還需要了解 Excel 對象模型。遺憾的是,很難找到正確的信息,所以我嘗試了兩種方法。
第一種方法是使用對象浏覽器,浏覽互操作程序集中可用的對象。要了解可以使用哪些方法和屬性,這是一個不錯的方法。
第二個方法是在 Excel 中錄制宏,讓宏完成我需要的操作,然後將 VBA 代碼作為要編寫的 C# 代碼的參考。通常這很容易完成,但是 C# 中的代碼與 VBA 中的有些不同,所以我想簡單介紹一下這種方法。
Excel 宏
我打算寫一個“探測”應用程序,以了解如何完成我要在 Excel 中進行的操作。首先,啟動 Excel 並使其可見,創建一個新的工作表,在其中一個單元格中放入值,然後設置單元格的背景顏色。
但在操作之前,我想簡單介紹一下 Excel 對象模型。Excel 是最早提供對象模型的 Microsoft 應用程序之一,當時提供的幾種選項現在仍在使用。這意味著有時用起來會不太方便。遇到這些情況時,我會指出來。
啟動 Excel 很容易,使其可見也是如此:
using Microsoft.Office.Interop.Excel;
using ExcelApplication = Microsoft.Office.Interop.Excel.Application;
ExcelApplication excel = new ExcelApplication();
excel.Visible = true;
第一個 using 語句引用 Excel 對象和方法。但在 Windows 窗體應用程序中使用這個語句時,我發現 Excel 和 Windows 窗體都有 Application 對象。我為 Excel 的 Application 定義了別名,而沒有使用完全限定名稱。在第二個 using 語句中,我將 ExcelApplication 作為 Excel Application 對象,然後我就可以使用它而不必使用完全限定名稱。
我將需要的操作錄制為 Excel 宏,如下所示:
Workbooks.Add
Range("C6").Select
ActiveCell.FormulaR1C1 = "Hello"
Range("C6").Select
With Selection.Interior
.ColorIndex = 6
.Pattern = xlSolid
End With
這看起來不太象 C# 代碼。在 Excel 宏中,有一些特定的假設值和結果,因此我們必須進行一些轉換。例如:
Workbooks.Add
轉換為:
Workbook workbook = excel.Workbooks.Add(Missing.Value);
我怎麼知道要這樣轉換呢?我首先查看 Application 對象,發現它有一個名為 Workbooks 的屬性可以返回 Workbooks 對象(這並不奇怪)。所以,在 VBA 代碼中有一個假設的“excel.”。我鍵入 Workbooks.Add( 時,IntelliSense® 提示我 Add 方法接受一個名為 template 的參數。
但在 VBA 代碼中並沒有參數,顯然,這是一個可選參數。我們使用的包裝類僅定義了函數的一個版本,因此我們必須傳遞一個表示“使用默認值”的值,該值就是 System.Reflection 命名空間中的 Missing.Value。
下一步,在單元格 C6 中設置值。由於 VBA 代碼中的 Workbooks 表示 C# 代碼中的 excel.Workbooks,因此我們可以嘗試使用 excel.Range 來獲取區域。遺憾的是,我們的嘗試失敗了。
實際上,在 Excel VBA 中編程時,根據您編寫的內容,會有多個假設的前綴。如果您使用 Range,那麼實際上就是在使用 excel.ActiveSheet.Range。因此,我們編寫以下代碼:
excel.ActiveSheet.Range("C4").Select();
至少我們可以嘗試這樣寫,但是會發現這樣不能編譯。原來,excel.ActiveSheet 是某種類型的對象。我不能確定這是為什麼,只能推測,它可能是工作表或其他對象,也可能只是最初設定的類型的對象。
所以,我們嘗試:
((Worksheet) excel.ActiveSheet).Range("C4").Select();
這樣會好一些,但在 Worksheet 類中沒有 Range 函數。Range 在 VBA 領域裡是一個屬性,但是在 C# 中,它只是一個接受兩個參數的方法。所以,我們得到以下代碼:
((Worksheet) excel.ActiveSheet).get_Range("C4", Missing.Value).Select();
excel.ActiveCell.Value2 = "Hello";
為什麼是 Value2 而不是 FormulaR1C1?這也是我尚未查明的問題。
有兩種方法可以使代碼更簡潔一些。第一種方法是將 Worksheet 對象存儲在變量中,這樣就可以避免類型轉換;第二種方法是對 Range 對象執行操作,而不是選擇它並使用活動的單元格。
最後一步是保存工作表,可以通過調用 Worksheet.SaveAs() 來完成。此方法接受十個參數,因此可以將其余參數作為 Missing.Value 傳遞。以下是最終的代碼:
ExcelApplication excel = new ExcelApplication();
excel.Visible = true;
excel.Workbooks.Add(Missing.Value);
Worksheet worksheet = (Worksheet) excel.ActiveSheet;
Range r = worksheet.get_Range("C6", Missing.Value);
r.Value2 = "Hello";
r.Interior.ColorIndex = 6;
worksheet.SaveAs(@"c:ExcelExample.xls",
Missing.Value, Missing.Value, Missing.Value, Missing.Value,
Missing.Value, Missing.Value, Missing.Value, Missing.Value,
Missing.Value);
excel.Quit();
創建一個工作表,設置一些值,然後保存並退出,共九行代碼。真是好極了。這些代碼保存在 ExcelExample 項目中。
使用電子郵件
要訪問 Exchange 電子郵件,可以使用 Outlook 對象模型,也可以使用 CDO(協作數據對象,以前稱為 MAPI)模型。因為我不關心圖形的顯示,所以我要使用 CDO。CDO 不是 Office 的一部分,所以沒有 PIA。
我創建一個新項目,並添加對 COM 對象 Microsoft CDO 1.21 Library 的引用。然後編寫以下代碼,以獲取收件箱中郵件的數量:
using MAPI;
using System.Reflection;
Session session = new Session();