原文地址:http://www.360doc.com/content/17/0707/20/45184439_669663362.shtml
最近的一个工程中,需要将数据导入 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 Files/Common Files/Microsoft Shared/Office11/MSO.DLL"
#elif OFFICE_VER == OFFICE2000
#define _MSDLL_PATH "C:/Program Files/Microsoft Office/Office/MSO9.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 Files/Common Files/Microsoft Shared/VBA/VBA6/VBE6EXT.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 Files/Microsoft Office/Office11/excel.exe"
#elif OFFICE_VER == OFFICE2000
#define _MSEXCEL_PATH "C:/Program Files/Microsoft Office/Office/excel.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 类型承载。
如果有讲的不对的地方,欢迎加 MSN 和我交流: bwmwm@live.cn 。
1.导入Excel类型库
使用Visual C++的扩展指令#import导入Excel类型库:
|
1
2
3
4
5
6
7
8
9
10
11
12
|
#import "C:\\Program Files\\Common Files\\microsoft shared\\OFFICE14\\MSO.DLL" \ rename("RGB","MsoRGB") \ rename("SearchPath","MsoSearchPath")#import "C:\\Program Files\\Common Files\\Microsoft Shared\\VBA\\VBA6\\VBE6EXT.OLB"#import "C:\\Program Files\\Microsoft Office\\Office14\\EXCEL.EXE" \ rename( "DialogBox", "ExcelDialogBox" ) \ rename( "RGB", "ExcelRGB" ) \ rename( "CopyFile", "ExcelCopyFile" ) \ rename( "ReplaceText", "ExcelReplaceText" ) \ exclude( "IFont", "IPicture" ) no_dual_interfaces |
#import指令会从指定的可执行文件,动态链接库等COM组件中导出类型库(type lib),在Debug和Release临时目录中生成对应的类型库头文件(type lib header file),以供C++程序使用。如以上三条指令在编译后会生成excel.tlh, mso.lh和vbetext.olb三个头文件,可以在Debug和Release目录中找到。
2.访问Excel暴露的COM对象
下面是一段比较完整的访问Excel的实例代码。首先用生成的数据填充单元格,然后用这些单元格的数据生成了一个图表(Chart):
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
try{ Excel::_ApplicationPtr pExcelApp; HRESULT hr = pExcelApp.CreateInstance(L"Excel.Application"); ATLASSERT(SUCCEEDED(hr)); pExcelApp->Visible = true; // make Excel’s main window visible Excel::_WorkbookPtr pWorkbook = pExcelApp->Workbooks->Open(lpszPathName); // open excel file Excel::_WorksheetPtr pWorksheet = pWorkbook->ActiveSheet; pWorksheet->Name = L"Chart Data"; Excel::RangePtr pRange = pWorksheet->Cells; const int nplot = 100; const double xlow = 0.0, xhigh = 20.0; double h = (xhigh-xlow)/(double)nplot; pRange->Item[1][1] = L"x"; // read/write cell’s data pRange->Item[1][2] = L"f(x)"; for (int i=0;i<nplot;++i) { double x = xlow+i*h; pRange->Item[i+2][1] = x; pRange->Item[i+2][2] = sin(x)*exp(-x); } Excel::RangePtr pBeginRange = pRange->Item[1][1]; Excel::RangePtr pEndRange = pRange->Item[nplot+1][2]; Excel::RangePtr pTotalRange = pWorksheet->Range[(Excel::Range*)pBeginRange][(Excel::Range*)pEndRange]; Excel::_ChartPtr pChart = pExcelApp->ActiveWorkbook->Charts->Add(); // refer to : // http://msdn.microsoft.com/en-us/library/microsoft.office.tools.excel.chart.chartwizard(v=vs.80).aspx pChart->ChartWizard( (Excel::Range*)pTotalRange, (long)Excel::xlXYScatter, 6L, (long)Excel::xlColumns, 1L,1L, true, L"My Graph", L"x",L"f(x)"); pChart->Name = L"My Data Plot"; pWorkbook->Close(VARIANT_TRUE); // save changes pExcelApp->Quit();}catch (_com_error& error){ ATLASSERT(FALSE); ATLTRACE2(error.ErrorMessage());} |
在这段代码中,Excel::_ApplicationPtr , Excel::_WorkbookPtr 和 Excel::_WorksheetPtr 等均是Visual C++ 编译器根据#import指令自动生成的智能指针,实际上就是C++模板类_com_ptr_t<T>的typedef,其定义可在excel.tlh等类型库头文件中找到。
另外,由于#import指令中没有指定raw_interface_only修饰符,Visual C++对Excel的COM接口进行了适当的封装,以简化COM接口属性和方法的调用,并且将HRESULT返回值都转换成了C++异常,因此,上面的这段代码不需要每一步都坚持HRESULT,而是改为捕获C++异常。
浙公网安备 33010602011771号