简述
在 Java 中对 Excel 操作很方便,但是在 C++ 中对 Excel 操作却不是那么方便了,C++ 中有几种方法可以对 Excel 操作,但是大部分方法要么是读写较慢,要么是操作的功能不多,比如只可导入文本数据,不可导入图像。其中在 MFC 中操作 Office 这种方法功能较齐全,通过 vs 导入 OLE/COM 组件来实现对 Office 的操作,但是操作稍微复杂些,下面就针对这种方法来介绍:
环境
我开发的环境是:
Windows 10 (64位)
Microsoft Visual Studio 2013
Office 2013 (32位) 【最开始我的是64位,但是发现缺失了一些库文件,所以换成了32位,可能是我下载的64位ofiice2013存在问题】
步骤
1、安装 Office 2013
由于是用 vs 导入 Office 的 OLE/COM 组件来进行读写操作,所以必须先安装 Office。
2、创建一个空的 MFC 对话框项目
3、导入操作 Excel 的几个基本类
- 右击项目,选择“类向导”;
- “添加类” --> "类型库中的MFC类"
- 选择 Excel 的 OLE/COM 组件,默认在 C 盘,除非你修改了安装目录。【还有另一种方式可以弹出这个界面:右击项目-->添加 --> 类 --> MFC --> ActiveX 控件中的 MFC 类】
- 从左侧选择接口 _Application、Workbooks、_Workbook、Worksheets、_Worksheet、Range、Shapes、Shape、ShapeRange ,他们分别对应的类是 CApplication、CWorkbooks、CWorkbook、CWorksheets、CWorksheet、CRange、CShapes、CShape、CShapeRange。然后就可以看到导入的这些类。
CApplication:Excel 应用程序;
CWorkbooks:工作薄的容器;
CWorkbook:单个工作薄;
CWorksheets:单个工作簿中的Sheet表格的容器;
CWorksheet:单个Sheet表格;
CRange:单个或多个单元格;
CShapes:形状容器;
CShape:形状;
CShapeRange:形状所占的单元格范围;
4、解决导入 Excel 接口存在的问题
- 将每个类头文件中的 "#import "C:\\Program Files (x86)\\Microsoft Office\\Office15\\EXCEL.EXE" no_namespace"注释掉。
- 将头文件 CRange.h 中的 DialogBox 前面添加下划线 _DialogBox 即可解决问题,修改后如下所示。
VARIANT _DialogBox()
{
VARIANT result;
InvokeHelper(0xf5, DISPATCH_METHOD, VT_VARIANT, (void*)&result, NULL);
return result;
}
- 如果导入的类头文件中存在 “方法前无返回类型错误” 或 “方法携带参数无类型错误”,可参考本文最后发布的项目代码补全。(因为这个问题我碰到了,我在网上没有看到被人碰到这个问题【笑哭】)。
5、导入头文件和类型库
#include "CApplication.h"
#include "CRange.h"
#include "CWorkbook.h"
#include "CWorkbooks.h"
#include "CWorksheet.h"
#include "CWorksheets.h"
#include "CShapes.h"
#include "CShape.h"
#include "CShapeRange.h"
//导入Office的类型库
#import "C:\\Program Files (x86)\\Common Files\\microsoft shared\\OFFICE15\\MSO.DLL" rename("DocumentProperties", "DocumentPropertiesXL") exclude("'IAccessible")
//导入VB的类型库
#import "C:\\Program Files (x86)\\Common Files\\microsoft shared\\VBA\\VBA6\\VBE6EXT.OLB" no_namespace
//导入Excel的类型库
#import "C:\\Program Files (x86)\\Microsoft Office\\Office15\\EXCEL.EXE" rename("DialogBox", "DialogBoxXL") rename("RGB", "RBGXL") rename("DocumentProperties", "DocumentPropertiesXL") no_dual_interfaces
using namespace Office;
using namespace Excel;
编译程序后,会在Debug或Release目录下生成三个文件mso.tlh、vbe6ext.tlh和excel.tlh。通过打开文件可知,该三个文件的命名空间分别是 Office、VBIDE 和 Excel。导入了 Excel 的整个类型库后,就可以使用Excel中的所有类型了。
扩展:
第一次导入 Office 类型库时是这样的:
#import "C:\\Program Files (x86)\\Common Files\\microsoft shared\\OFFICE15\\MSO.DLL" no_namespace rename("DocumentProperties", "DocumentPropertiesXL")
然后就报错“ error C2504: “IAccessible”: 未定义”等类似错误,然后解决方法为:去掉no_namespace,在最后加上 exclude("'IAccessible") 即可。
6、操作 Excel 步骤
(1)创建一个Excel应用程序。
(2)得到Workbook的容器。
(3)打开一个Workbook或者创建一个Workbook。
(4)得到Workbook中的Worksheet的容器。
(5)打开一个Worksheet或者创建一个WorkSheet。
(6)通过Range对WorkSheet中的单元格进行读写操作。
(7)如果导入图片,通过Shape来操作。
(8)保存Excel。
(9)释放资源。
7、Excel 的保存
- 如果是已存在的 Excel 表格,使用 CApplication 类中的 Save() 函数保存。
- 如果是新建的 Excel 表格,或者需要执行 “另存为” 操作,使用 CWorkbook 类中的 SaveAs() 函数。SaveAs的参数比较多。其中,第1个参数是设置要保存文件的路径;第2个参数是设置文件的格式,可在MSDN中查看枚举类型XlFileFormat来了解Excel的文件格式。关于保存格式,39 是MicroSoft Excel 5.0/95格式,较为常用的是xlExcel8 —— 56 —— excel 97/2003,如果该参数不填写具体数值,会保存为默认的格式,所谓默认的格式就是在电脑上新建一个Excel文件的格式,比如在office2013下运行,生成的文件格式为xlsx。
- 可以通过CApplication的get_Version函数来获得Excel的版本,其中,Excel2007的主版本号是12,Excel2003的主版本号是11。
8、载入文本数据和图像数据(已存在的 Excel)
//载入数据(已存在的excel)
void CMFCApplication1Dlg::OnBnClickedExitfileButton()
{
CString PathName = L"demo.xlsx"; //文件路径(支持中文)
TCHAR szPath[MAX_PATH];//获得当前执行路径
GetModuleFileName(NULL, szPath, MAX_PATH);
CString path(szPath);
path = path.Left(path.ReverseFind(_T('\\')) + 1);
CString filePath = path + PathName;
CString imagePath("D:\\jj.jpg");
CString startIndex("H1");
CString endIndex("K10");
int ret = ImportImage2Excel(filePath, imagePath, startIndex, endIndex);
}
//向已存在的Excel中导入图片,放在固定位置
int CMFCApplication1Dlg::ImportImage2Excel(CString filePath, CString imagePath, CString startIndex, CString endIndex)
{
LPDISPATCH lpDisp;
COleVariant vResult;
COleVariant covOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
CApplication pXL;
//【1】打开excel表格
if (!pXL.CreateDispatch(L"Excel.Application"))
{
AfxMessageBox(L"无法启动Excel服务器!");
return 0;
}
// pXL.put_Visible(VARIANT_TRUE); //显示excel
CWorkbooks pBooks;
CWorkbook pBook;
CWorksheet pSheet;
CWorksheets pSheets;
pBooks.AttachDispatch(pXL.get_Workbooks());
lpDisp = pBooks.Open(filePath, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional);
pBook.AttachDispatch(lpDisp); //得到Workbook
pSheets.AttachDispatch(pBook.get_Worksheets()); //得到Worksheets
pSheet = pSheets.get_Item(COleVariant((short)1));
//【2】插入图像
//获取图像插入的范围
//从Sheet对象上获得一个Shapes
CShapes pShapes;
pShapes.AttachDispatch(pSheet.get_Shapes());
//获得Range对象,用来插入图片
CRange range = pSheet.get_Range(COleVariant(startIndex), COleVariant(endIndex));
VARIANT rLeft = range.get_Left();
VARIANT rTop = range.get_Top();
VARIANT rWidth = range.get_Width();
VARIANT rHeight = range.get_Height();
//添加图像到 H1 - K10范围区域
CShape pShape = pShapes.AddPicture(imagePath, msoFalse, msoTrue,
(float)rLeft.dblVal, (float)rTop.dblVal, (float)rWidth.dblVal, (float)rHeight.dblVal);
//设置图像所占的宽高
CShapeRange shapeRange = pShapes.get_Range(_variant_t(long(1)));
shapeRange.put_Height(float(300));
shapeRange.put_Width(float(200));
//【3】插入数据
CRange rangeA2 = pSheet.get_Range(COleVariant(L"A2"), vtMissing);
rangeA2.put_Value(vtMissing, COleVariant(L"Company A"));
CRange rangeB2 = pSheet.get_Range(COleVariant(L"B2"), vtMissing);
rangeB2.put_Value(vtMissing, COleVariant(L"Company B"));
CRange rangeC2 = pSheet.get_Range(COleVariant(L"C2"), vtMissing);
rangeC2.put_Value(vtMissing, COleVariant(L"Company C"));
CRange rangeD2 = pSheet.get_Range(COleVariant(L"D2"), vtMissing);
rangeD2.put_Value(vtMissing, COleVariant(L"Company D"));
CRange rangeA3 = pSheet.get_Range(COleVariant(L"A3"), vtMissing);
rangeA3.put_Value(vtMissing, COleVariant(75.0));
CRange rangeB3 = pSheet.get_Range(COleVariant(L"B3"), vtMissing);
rangeB3.put_Value(vtMissing, COleVariant(14.0));
CRange rangeC3 = pSheet.get_Range(COleVariant(L"C3"), vtMissing);
rangeC3.put_Value(vtMissing, COleVariant(7.0));
CRange rangeD3 = pSheet.get_Range(COleVariant(L"D3"), vtMissing);
rangeD3.put_Value(vtMissing, COleVariant(4.0));
Sleep(1000);
//保存
pXL.Save(covOptional);
//退出
pXL.Quit();
range.ReleaseDispatch();
pSheet.ReleaseDispatch();
pSheets.ReleaseDispatch();
pBook.ReleaseDispatch();
pBooks.ReleaseDispatch();
pXL.ReleaseDispatch();
return 0;
}
9、载入文本数据和图像数据(新建的Excel)
//载入数据(新建excel)
void CMFCApplication1Dlg::OnBnClickedNewfileButton()
{
COleVariant covOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
CApplication pXL;
//【1】打开excel表格
if (!pXL.CreateDispatch(L"Excel.Application"))
{
AfxMessageBox(L"无法启动Excel服务器!");
return;
}
pXL.put_Visible(VARIANT_TRUE);
CWorkbooks pBooks = pXL.get_Workbooks();
CWorkbook pBook = pBooks.Add(COleVariant((long)xlWorksheet));
CWorksheet pSheet = pXL.get_ActiveSheet();
pSheet.put_Name(L"MarketShare");
//【2】插入图像
//获取图像插入的范围
//从Sheet对象上获得一个Shapes
CShapes pShapes = pSheet.get_Shapes();
//获得Range对象,用来插入图片
CRange range = pSheet.get_Range(COleVariant(L"H1"), COleVariant(L"K10"));
VARIANT rLeft = range.get_Left();
VARIANT rTop = range.get_Top();
VARIANT rWidth = range.get_Width();
VARIANT rHeight = range.get_Height();
//添加图像到 H1 - K10范围区域
CShape pShape = pShapes.AddPicture(L"D:\\jj.jpg", msoFalse, msoTrue,
(float)rLeft.dblVal, (float)rTop.dblVal, (float)rWidth.dblVal, (float)rHeight.dblVal);
//设置图像所占的宽高
CShapeRange shapeRange = pShapes.get_Range(_variant_t(long(1)));
shapeRange.put_Height(float(300));
shapeRange.put_Width(float(200));
//【3】插入数据
CRange rangeA2 = pSheet.get_Range(COleVariant(L"A2"), vtMissing);
rangeA2.put_Value(vtMissing, COleVariant(L"Company A"));
CRange rangeB2 = pSheet.get_Range(COleVariant(L"B2"), vtMissing);
rangeB2.put_Value(vtMissing, COleVariant(L"Company B"));
CRange rangeC2 = pSheet.get_Range(COleVariant(L"C2"), vtMissing);
rangeC2.put_Value(vtMissing, COleVariant(L"Company C"));
CRange rangeD2 = pSheet.get_Range(COleVariant(L"D2"), vtMissing);
rangeD2.put_Value(vtMissing, COleVariant(L"Company D"));
CRange rangeA3 = pSheet.get_Range(COleVariant(L"A3"), vtMissing);
rangeA3.put_Value(vtMissing, COleVariant(75.0));
CRange rangeB3 = pSheet.get_Range(COleVariant(L"B3"), vtMissing);
rangeB3.put_Value(vtMissing, COleVariant(14.0));
CRange rangeC3 = pSheet.get_Range(COleVariant(L"C3"), vtMissing);
rangeC3.put_Value(vtMissing, COleVariant(7.0));
CRange rangeD3 = pSheet.get_Range(COleVariant(L"D3"), vtMissing);
rangeD3.put_Value(vtMissing, COleVariant(4.0));
Sleep(1000);
/*
SaveAs函数的第二个参数是指定保存的excel格式,39是MicroSoft Excel 5.0/95格式,
数字与版本对应关系可以查看Excel文件宏定义中的信息
较为常用的是xlExcel8 — 56 — excel 97/2003,如果该参数不填写具体数值,会保存为默认的格式,
所谓默认的格式就是在电脑上新建一个Excel文件的格式,比如在office2013下运行,生成的文件格式为xlsx
这里第二个参数使用_variant_t((long)56) 或 covOptional都可以
*/
pBook.SaveAs(COleVariant(L"D:\\ok.xls"), covOptional,
covOptional, covOptional,
covOptional, covOptional, (long)0,
covOptional, covOptional, covOptional,
covOptional, covOptional);
pXL.Quit();
return;
}
10、读取 Excel 中数据
//读取数据
void CMFCApplication1Dlg::OnBnClickedReadButton()
{
CString PathName = L"demo.xlsx"; //文件路径(支持中文)
TCHAR szPath[MAX_PATH];//获得当前执行路径
GetModuleFileName(NULL, szPath, MAX_PATH);
CString path(szPath);
path = path.Left(path.ReverseFind(_T('\\')) + 1);
CString filePath = path + PathName;
int ret = ReadDataFromExcel(filePath);
}
//读取已存在的Excel中的数据
int CMFCApplication1Dlg::ReadDataFromExcel(CString filePath)
{
LPDISPATCH lpDisp;
COleVariant vResult;
COleVariant covOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
CApplication pXL;
//【1】打开excel表格
if (!pXL.CreateDispatch(L"Excel.Application"))
{
AfxMessageBox(L"无法启动Excel服务器!");
return 0;
}
// pXL.put_Visible(VARIANT_TRUE); //显示excel
CWorkbooks pBooks;
CWorkbook pBook;
CWorksheet pSheet;
CWorksheets pSheets;
pBooks.AttachDispatch(pXL.get_Workbooks());
lpDisp = pBooks.Open(filePath, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional);
pBook.AttachDispatch(lpDisp); //得到Workbook
pSheets.AttachDispatch(pBook.get_Worksheets()); //得到Worksheets
pSheet = pSheets.get_Item(COleVariant((short)1));
//获得坐标为(A,2)的单元格
CRange range = pSheet.get_Range(COleVariant(L"A2"), COleVariant(L"A2"));
//获得单元格的内容
COleVariant rValue;
rValue = COleVariant(range.get_Value2());
//转换成宽字符
rValue.ChangeType(VT_BSTR);
//转换格式,并输出
this->MessageBox(CString(rValue.bstrVal));
//保存
pXL.Save(covOptional);
//退出
pXL.Quit();
pSheet.ReleaseDispatch();
pSheets.ReleaseDispatch();
pBook.ReleaseDispatch();
pBooks.ReleaseDispatch();
pXL.ReleaseDispatch();
return 0;
}
最后
这里附上一个学习 Office 开发的学习链接,是 VB 语言,我没有找到 C++ 版本的,如果有朋友知道 C++ 版本的学习链接,麻烦留言一起学习。Offiice开发学习链接