最近的一個工程中,需要將數據導入Excel表格中,項目經理知道我以前沒有接觸過操作Excel的經驗,於是給了一段小程序給我,讓我參考著做。
這段程序是使用智能指針操作Excel,在網絡上找了一個星期,居然沒有一片關於智能指針操作Excel的文章,只有Automation技術,而且所有介紹Automation技術的文章都是大同小異,並且代碼多,說明少。沒有任何幫助,光有一堆代碼,對於理解和使用沒有太大的幫助。在這樣一個艱苦的條件下,我決定利用手中僅有的工具:Microsoft Excel Visual Basic 參考 和 Microsoft Visual Studio 2005的提示功能,摸索出一些利用智能指針操作Excel的心得,寫出來,既是一次總結,也是一種分享,並且摸索還在繼續,心得也還陸續會有。
一、背景說明
1.Microsoft Excel Visual Basic 參考是提供給VB程序員的一個操作Excel的幫助,幫助中的對象、集合、方法、常量都已經在COM中實現,在VC中可以找到對應的實體。
2.既然是智能指針,那麼絕大多數的操作都是“->”,然而,如果安裝了Visual Assist X,使用“->”操作符的時候,是得不到任何提示的。這個時候,如果需要查看提示,則可以先使用“.”操作符,編譯時再將“.”改成“->”。
二、Excel概念介紹從MFC工程結構的角度來看,Excel屬於多文檔視圖結構,一個應用程序包含若干個文檔,稱作工作簿,每個文檔中包含若干個工作表。從智能指針對象模型來看可以做如下劃分:
1._ApplicationPtr :該對象即表示一個Excel應用程序。
2. WorkbooksPtr :在一個_ApplicationPtr對象中,包含一個工作簿集合。
3. _WorkbookPtr :在工作簿集合中包含若干的工作簿對象。一個工作簿對象對應一個xls文件。
4. WorksheetsPtr :在一個工作簿對象中,包含一個工作表集合。
5. _WorksheetPtr :在工作表集合中包含若干個工作表對象,工作表對象是操作Excel的基本單位。
6. Range :這是一個集合,工作表中單元格的集合,控制對單元格的操作。
三、准備工作1. 加載動態庫。
#define OfficeXP 1
#define Office2000 2
// 如果使用OFFICE2000的內核,手動將此處改為#define OFFICE_VER Office2000
#define OFFICE_VER OfficeXP
#define USE_PROGID 1
#define USE_LIBID 0
#define _M2STR(x) #x
#define M2STR(x) _M2STR(x)
#ifndef MSDLL_PATH
#if OFFICE_VER == OfficeXP
#define _MSDLL_PATH "C:Program FilesCommon FilesMicrosoft SharedOffice11MSO.DLL"
#elif OFFICE_VER == Office2000
#define _MSDLL_PATH "C:Program FilesMicrosoft OfficeOfficeMSO9.dll"
#endif
#else
#define _MSDLL_PATH M2STR(MSDLL_PATH)
#endif
#import _MSDLL_PATH rename("RGB", "MSRGB")
#ifdef VBE6EXT_PATH
#import M2STR(VBE6EXT_PATH)
#else
#import "C:Program FilesCommon FilesMicrosoft SharedVBAVBA6VBE6EXT.OLB"
#endif
#if USE_PROGID
#import "progid:Excel.Sheet" auto_search auto_rename rename_search_namespace("Office10")
#elif USE_LIBID
#import "libid:{00020813-0000-0000-C000-000000000046}" auto_search auto_rename version(1.3) lcid(0) no_search_namespace
#else
#ifndef MSExcel_PATH
#if OFFICE_VER == OfficeXP
#define _MSExcel_PATH "C:Program FilesMicrosoft OfficeOffice11Excel.exe"
#elif OFFICE_VER == Office2000
#define _MSExcel_PATH "C:Program FilesMicrosoft OfficeOfficeExcel.exe"
#endif
#else
#define _MSEXCEL_PATH M2STR(MSExcel_PATH)
#endif
#import _MSExcel_PATH auto_search auto_rename dual_interfaces
#endif
using namespace Excel;
2.初始化COM組件。
CoInitialize(NULL);程序結束時記得釋放資源CoUninitialize();
四、正式開始
1.操作Excel,首先要初始化一個應用程序實例,代碼如下:
_ApplicationPtr pApp;
pApp.CreateInstance(L"Excel.Application");pApp->PutVisible(0,VARIANT_TRUE);
使用ADO操作過數據庫的人對代碼的前兩句不會感到陌生,初始化的實例不同而已,而第三句,則是使Excel應用程序顯示出來,就像在“開始”菜單中運行Excel一樣,可以看到一個打開的Excel程序,如果賦值VARIANT_FALSE則看不到應用程序,但是在任務管理器中已經創建了一個Excel進程,這是前兩句的功勞。程序結束前退出應用程序:pBook.PutSaved(0,VARIANT_TRUE);pApp->Quit();
2.在這個空白的應用實例中,需要創建一個工作簿(即文檔)。代碼如下:WorkbooksPtr pBooks = pApp->GetWorkbooks();_WorkbookPtr pBook = pBooks->Add((long)xlWorkbook);前面講過,在應用實例中有一個工作簿集合,就算初始時集合是空的,它也是存在的,要創建一個工作簿,實際就是在這個集合中添加一個工作簿而已,第一句代碼獲得工作簿集合,第二句添加一個工作簿,並返回新創建工作簿的指針。由於一個工作簿對應一個“xls”文件,所以,大部分情況下我們在一個應用實例中都只會創建一個工作簿,這和習慣有關,但不是絕對,如果添加了多個,可以使用_WorkbookPtr Workbooks::GetItem(const _variant_t & Index)這個函數來獲得每個工作簿的指針。
通過Studio的提示功能,我們看到Add函數的原型:Excel::_WorkbookPtr Excel::Workbooks::Add(const _variant_t & Template,long lcid = 0)在Microsoft Excel幫助中,從“方法”,“A”,“Add”找到“應用於 Workbooks 對象的 Add 方法”,我們可以看到對第一個參數的說明(前面說過,由於VC缺少這類函數的說明,我們只能借助VB了),這個說明對VC同樣適用。小弟水平有限,沒有弄清第二個參數的所以然,姑且使用默認值,在這裡不影響對Excel的操作,這個參數大概是與COM機制有關的某個東西吧。
3.上一步使用xlWorkbook參數添加了一個工作簿,因此,這個工作簿中默認有一個工作表,同樣的道理,在工作簿中有一個工作表集合,要操作工作表,首先得到工作表集合。SheetsPtr pSheets = pBook->GetWorksheets();可以這樣_WorksheetPtr pSheet = pSheets->GetItem(1);也可以這樣_WorksheetPtr pSheet = pBook->GetActiveSheet();來得到默認創建的那個工作表。這是因為當前只有一個工組表,所以這個工組表就默認為激活的工作表(在工作簿中只會有一個工作表處於激活狀態,就是當前操作的這個工作簿)。
如果工作簿中有多個工組表,還是需要通過pSheets->GetItem函數獲得工作表對象。補充,有些操作可以在工作表處於非激活狀態下進行,這樣,使用pSheets->GetItem獲得工作表對象即可對工作表操作,但是有些操作必須是工作表處於激活狀態下進行,因此,獲得工作表對象後,還需要pSheet->Activate();激活它。
給工作表重命名吧:pSheet->PutName("Exp One Sheet");//如果運行有錯,可以pSheet->PutName(L"Exp One Sheet")插入一個工作表,函數原型:
_WorksheetPtr Worksheets::Add(
const _variant_t &Before = vtMissing, //在哪個工作表前插入
const _variant_t &After = vtMissing, //在哪個工作表後插入
const _variant_t &Count = vtMissing, //插入工組表個數
const _variant_t &Type = vtMissing) //插入工作表類型
有意思的是,不光這個函數,其他的有默認值的參數的默認值都是vtMissing 。
遺憾的是我沒能找到vtMissing的具體說明,姑且先用著。看下面的代碼:
_WorksheetPtr pSheet = pSheets->GetItem(2);
VARIANT var;
var.vt = VT_DISPATCH;var.pdispVal = pSheet;
pSheets->Add(); //在第一個工作表之前插入一個空白工作表
pSheets->Add(var); //在pSheet工作表之前插入一個空白工作表
pSheets->Add(vtMissing,var); //在pSheet工作表之後插入一個空白工作表
pSheets->Add(vtMissing,var,2); //在pSheet工作表之後插入兩個空白工作表
這裡僅僅涉及到_variant_t類型的使用,將不做說明。
注意前兩個參數的使用即可,其他類型的參數將報錯。
4.下面將讓你看到不同於C風格的代碼操作工作表中的單元格。
pSheet->Range["A1"][vtMissing]->Value2 = "EXP A1";
pSheet->Range["A1"][vtMissing]->Interior->Color = RGB(255,0,0);
pSheet->Range["A1"][vtMissing]->Font->Name = L"隸書";
pSheet->Range["A1"][vtMissing]->Font->FontStyle = L"Bold Italic";
pSheet->Range["A1"][vtMissing]->Font->Size = 10;
如何?是不是有點像VB?只需要賦值就能改變對象的屬性,以上代碼等價於:
RangePtr range = pSheet->GetRange("A3",vtMissing);
range->PutValue2("EXP A3");
InteriorPtr interior = range->GetInterior();
interior->PutColor(RGB(255,0,0));
Excel::FontPtr font = range->GetFont();
font->PutName(L"隸書");
font->PutFontStyle(L"Bold Italic");
font->PutSize(10);
VC的程序員看到這段代碼是不是就覺得親切了,這是COM技術的魔力,要想知道為何,自己研究COM技術吧。對上面的代碼只做兩點說明:
(1)Range["A1"][vtMissing]和GetRange("A3",vtMissing)表示獲得A3表示的單元格(用過Excel的人都知道A3表示什麼),通過集合的形式表現出來,該集合只有一個單元格。如果把vtMissing換成B5,那麼將獲得一個由A3和B5之間左右單元組成的集合。
(2)FontPtr之前的Excel::是必須的,缺少Excel::編譯器會報錯,提示不能確定是哪一個FontPtr,因為在不同的命名空間中存在若干個FontPtr。也許還存在其他的類型會有這樣的情況,因此,要特別留意命名空間的限制。
五、小經驗1.基於COM技術,一般的,I = A->GetP()可以等價成I = A->P;A->PutP(I)可以等價成A->P = I。
2.在Microsoft Excel Visual Basic 參考中查找對象,方法和屬性的時候,可以基於第一點將VB轉換成VC。根據VB提供的參數類型,在VC中使用VARIANT類型承載。