上一篇: 在Qt中用QAxObject来操作Excel
1.系统环境:Qt 4.7+MinGW、Win7、QtCreator、Excel 2007
2.生成excel.h、excel.cpp、office.h和office.cpp
dumpcpp ---- ActiveQt模块的命令行工具,可以导出Qt能识别的COM组件的接口。这个工具在Qt库的bin目录下可以找到。如果没有,也可以在
Qt库\tools\activeqt\dumpcpp文件夹中找到源码,自己编译一下就有了。在命令行中进入某个目录,比如D:\,然后执行
dumpcpp {00020813-0000-0000-C000-000000000046}就可以在D:\下生成
excel.h和
excel.cpp文件了。这两个文件中就包含了Qt导出的操纵Excel的类和申明。
如果找不到dumpcpp这个工具,可能是Qt库的bin目录没有加入到环境变量path中,或者bin目录下没有这个工具。如果dumpcpp提示
dumpcpp: type library '' not found 或者
QAxBase::setControl: requested control {00020813-0000-0000-C000-000000000046} could not be instantiated,那么可以将
{00020813-0000-0000-C000-000000000046}改成Excel.Application或者{00024500-0000-0000-C000-000000000046}试试。如果还不行,就去注册表HKEY_LOCAL_MACHINE\SOFTWARE\Classes\TypeLib\中找吧。。。比如HKEY_LOCAL_MACHINE\SOFTWARE\Classes\TypeLib\{00020813-0000-0000-C000-000000000046}\1.6\0\win32下有个键值C:\Program Files\Microsoft Office\Office12\EXCEL.EXE,那么 {00020813-0000-0000-C000-000000000046} 就是正确的CSSID了。
有了excel.h和excel.cpp还不行,加入到工程,编译后会发现其中很多类型不全,会出现诸如excel.h:26: 错误:use of enum 'MsoCalloutDropType' without previous declaration之类的很多错误。这是因为还需要office.h和office.cpp。同样用dumpcpp,执行dumpcpp {2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}就可得到office.h和office.cpp文件。这是Microsoft Office 12.0 Object Library,在注册表中位置是HKEY_LOCAL_MACHINE\SOFTWARE\Classes\TypeLib\{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}\2.4\0\win32。若提示找不到qpixmap类,则在excel.h的头文件中添加#include <QtGui/qpixmap.h>。
将office.h和office.cpp也包含进工程,并在excel.h中添加#include"office.h"后再次编译,还是不行。会出现:office.h:1666: 警告:this decimal constant is unsigned only in ISO C90这样的错误。这是由编译器引起的。找到这个常数,在常数后面增加一个UL标识,或者ULL表示,如4294967295UL,这样就不会报警了。再次编译,会出现:
In file included from ..\testExcel\/excel.h:20,
from ..\testExcel\main.cpp:4:
..\testExcel\/office.h: In copy constructor 'Office::ChartColorFormat::ChartColorFormat(const Office::ChartColorFormat&)':
..\testExcel\/office.h:25460: note: synthesized method 'QAxObject::QAxObject(const QAxObject&)' first required here
In file included from ..\..\Qt\4.7.2\include/QtCore/qmetatype.h:1,
from ..\..\Qt\4.7.2\include/QtCore/../../src/corelib/kernel/qvariant.h:48,
from ..\..\Qt\4.7.2\include/QtCore/qvariant.h:1,
from ..\..\Qt\4.7.2\include/ActiveQt/../../src/activeqt/container/qaxbase.h:47,
from ..\..\Qt\4.7.2\include/ActiveQt/qaxbase.h:1,
from ..\..\Qt\4.7.2\include\ActiveQt/../../src/activeqt/container/qaxobject.h:44,
from ..\..\Qt\4.7.2\include\ActiveQt/qaxobject.h:1,
from ..\..\Qt\4.7.2\include\ActiveQt/QAxObject:1,
from ..\testExcel\main.cpp:2:
..\..\Qt\4.7.2\include/QtCore/../../src/corelib/kernel/qmetatype.h: In function 'void* qMetaTypeConstructHelper(const T*) [with T = Office::ChartColorFormat]':
..\..\Qt\4.7.2\include/QtCore/../../src/corelib/kernel/qmetatype.h:142: note: synthesized method 'Office::ChartColorFormat::ChartColorFormat(const Office::ChartColorFormat&)' first required here
mingw32-make[1]: *** [release/main.o] Error 1
这样的错误。找到office.h的52460行,将函数
inline Office::ChartColorFormat* ChartFillFormat::BackColor()和函数
Office::ChartColorFormat* ChartFillFormat::ForeColor()中的前三行注释掉。例如:
inline Office::ChartColorFormat* ChartFillFormat::ForeColor() const
{
/* Office::ChartColorFormat* qax_pointer = 0;
qRegisterMetaType("ChartColorFormat*", &qax_pointer);
qRegisterMetaType("ChartColorFormat", qax_pointer);*/
QVariant qax_result = property("ForeColor");
if (!qax_result.constData()) return 0;
Q_ASSERT(qax_result.isValid());
return *(Office::ChartColorFormat**)qax_result.constData();
}
这样应该就可以正常编译了。
3.例子
我在QtCreator中新建了一个工程testExcel2作为测试之用。注意要在.pro文件中添加
CONFIG += qaxcontainer。该工程中所有文件均为
uft8 格式。
//testExcel2.pro
#-------------------------------------------------
#
# Project created by QtCreator 2013-04-02T21:09:33
#
#-------------------------------------------------
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = testExcel2
TEMPLATE = app
CONFIG += qaxcontainer
SOURCES += main.cpp\
mainwindow.cpp \
office.cpp \
excel.cpp
HEADERS += mainwindow.h \
excel.h \
office.h
//mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QPushButton>
#include "excel.h"
#include <QAxObject>
#include <QFileDialog>
#include <QFileinfo>
#include <QDir>
#include <qmath.h>
#include <QDebug>
class MainWindow : public QPushButton
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void click_pushbutton();
};
#endif // MAINWINDOW_H
//main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
//mainwindow.cpp
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QPushButton(parent)
{
connect(this,SIGNAL(clicked()),this,SLOT(click_pushbutton()));
this->setText(tr("Push Me"));
}
MainWindow::~MainWindow()
{
}
void MainWindow::click_pushbutton(){
Excel::Application *excel = new Excel::Application(this);
excel->SetVisible(false);//不显示Excel窗体
excel->SetDisplayAlerts(false);//不显示警告
Excel::Workbooks *workbooks = excel->Workbooks();//工作簿集合
Excel::Workbook *workbook = workbooks->Add();//添加工作簿
Excel::Sheets *worksheets = workbook->Sheets();//工作表集合
//这里若写Excel::Worksheets *worksheets = workbook->Sheets();
//会出现cannot convert 'Excel::Sheets*' to 'Excel::Worksheets*' in initialization这样的错误
Excel::Worksheet* worksheet = new Excel::Worksheet(new Excel::_Worksheet(worksheets->Item(1)));//获取工作表
//这里若写Excel::Worksheet *worksheet = worksheets->Item(1);
//会出现cannot convert 'IDispatch*' to 'Excel::Worksheet*' in initialization这样的错误
//因为worksheets->Item(1)返回的是IDispatch*类型。要将IDispatch*转换为Excel的对象,就得用上面的
//new的方法。若Excel空间中包含_XXXX这样的类(如Excel::_Worksheet),则生成XXXX对象
//(如Excel::Worksheet)时,就用new Excel::XXXX(new Excel_XXXX(IDispatch*));这样的方法,例如
//Excel::Worksheet* worksheet = new Excel::Worksheet(new Excel::_Worksheet(worksheets->Item(1)));
//如果某对象XXXX没有_XXXX这样的类,那么将IDispatch*转换为该对象的指针时只用new XXXX(IDispatch*)
//这样的方法就行了,例如下面的
//Excel::SeriesCollection *seriesCollection = new Excel::SeriesCollection(chart->SeriesCollection());
//因为源码是utf8格式的,所以这里用QString::fromUtf8(...),否则Excel中会出现乱码
worksheet->SetName(QString::fromUtf8("【测试】"));//设置中文表名
QString picpath = QFileDialog::getOpenFileName(this,tr("Open"),".",tr("picture file (*.jpg | *.png)"));
if(!picpath.isEmpty()){
worksheet->SetBackgroundPicture(QDir::toNativeSeparators(picpath));//为工作表设置背景图片
//有关字体的Background属性,参见
//http://msdn.microsoft.com/en-us/library/office/bb220875(v=office.12).aspx
worksheet->Cells()->Font()->SetBackground(Excel::xlBackgroundAutomatic);//设置背景图片和文字重叠时的显示效果
worksheet->Cells()->Font()->SetBold(false);//不要粗体
}
Excel::Range *cell;
Excel::Range *cells = worksheet->Cells();//获取工作表中的所有单元格
for(int i=0;i<10;i++){
cells->SetItem(i+1,i+1,(i+1)*(i+1));//设置第i+1行,第i+1列的单元格的值
QString range = QString('A'+i)+QString::number(i+1);
cell = worksheet->Range(range);获取第i+1行,第i+1列的单元格
cell->Font()->SetItalic(true);//设置字体为斜体
cell->Font()->SetSize(25);//设置字号
cell->Font()->SetColor(QRgb("#FF0000")+i*30);//设置字体颜色,每次循环颜色都不同
}
//测试中文输入
cells->SetItem(3,5,QString::fromUtf8("输入中文"));//在第3行,第5列的单元格中输入中文字符
//测试Excel的函数1
cells->SetItem(2,1,5);
cell = worksheet->Range("A3");//获取A3单元格
cell->SetFormula("=sum(A1:A2)");//求取A1:A2单元格中值的和
//测试Excel的函数1
cells->SetItem(3,2,10);
cells->SetItem(4,2,"=sum(B1:B3)");//求取B1:B3单元格中值的和
//以上两种方式说明用setFormula和setItem来设置函数,效果是一样的
//Qt的dumpcpp工具没有生成Cells(int,int)这种用法,所以用以下方法
//间接调用Cells(int,int)函数
QAxObject* range = worksheet->querySubObject("Cells(int,int)",4,6);
cell = (Excel::Range*)range;
cell->SetValue(QString::fromUtf8("因为dumcpp没有自动生成的函数Cells(int,int),故用querySubObject(\"Cells(int,int),\",4,6)的方式调用"));
//由于Cells(int,int)十分常用,所以我在excel.h的Worksheet类中添加了Excel::Range* Worksheet::Cells(int,int)函数
//在excel.h中搜索class EXCEL_EXPORT Worksheet就可以找到Worksheet类
//然后在其中添加函数Cells(int rowIndex,int colIndex)即可。如:
//inline Excel::Range* Cells(int rowIndex,int colIndex){
// QAxObject* range = this->querySubObject("Cells(int,int)",rowIndex,colIndex);
// return (Excel::Range*)range;
//}
cell = worksheet->Cells(5,6);
cell->SetValue(QString::fromUtf8("手动在类Excel::Worksheet中添加函数Cells(int,int)后,可直接调用"));
//测试图表
//生成数据
Excel::Range* cellX;
Excel::Range* cellY;
double y;
for(int x=0;x<37;x++){
y=sin(2*3.141592653/36*x);
cellX = worksheet->Cells(x+1,12);
cellY = worksheet->Cells(x+1,13);
cellX->SetValue(x);
cellY->SetValue(y);
}
Excel::Range *xValues = worksheet->Range("L1:L37");
Excel::Range *yValues = worksheet->Range("M1:M37");
//先选择一列,则待会儿在Chart中会创建一个Series,否则会创建多个Series
xValues->Select();
//用workbook->Charts()->Add()添加一个图表工作表。由于返回的是IDispatch*类型
//所以需用如下方法,转为Excel::Chart*类型。这与前面获取Worksheet*时类似。
Excel::Chart *chart = new Excel::Chart(new Excel::_Chart(workbook->Charts()->Add()));
chart->SetChartType(Excel::xlXYScatterSmooth);//设置图表类型为带点迹的平滑散点图
chart->SetName(QString::fromUtf8("【测试图表】"));//设置表名
//获取Series集合。由于chart->SeriesCollection()返回的是IDispatch*类型,
//故用如下方法转换为Excel::SeriesCollection*
Excel::SeriesCollection *seriesCollection = new Excel::SeriesCollection(chart->SeriesCollection());
Excel::Series *series = seriesCollection->Item(1);//获取第1条曲线
series->SetName(QString::fromUtf8("正弦"));
series->SetXValues(xValues->asVariant());//设置横坐标
series->SetValues(yValues->asVariant());//设置对应的纵坐标
//在工作表内插入图表
//获取图表对象集合。同样,由于worksheet->ChartObjects()返回的是IDispatch*,
//故要用new的方式来转换为Excel::ChartObjects*
Excel::ChartObjects *chartObjects = new Excel::ChartObjects(worksheet->ChartObjects());
//添加图表,Left=800,Top=10,Height=500,Width=500,单位磅
Excel::ChartObject *chartObject = chartObjects->Add(800,10,500,500);
Excel::Chart *innerChart = chartObject->Chart();
//使用xValues作为数据源,Excel::xlColumns表示每列都为一组数据,即1个Series
innerChart->SetSourceData(xValues,Excel::xlColumns);
innerChart->SetHasTitle(true);//显示标题
innerChart->SetChartType(Excel::xlXYScatterSmoothNoMarkers);//图表为不包含数据点的散点图
//获取散点图的系列(Series)集合
Excel::SeriesCollection *innerSeriesCollection = new Excel::SeriesCollection(innerChart->SeriesCollection());
//选取系列1,因为我只有1个系列,即一条曲线
Excel::Series *innerSeries = innerSeriesCollection->Item(1);
innerSeries->SetName(QString::fromUtf8("正弦"));//设置系列名
innerSeries->SetXValues(xValues->asVariant());//设置横坐标
innerSeries->SetValues(yValues->asVariant());//设置对应的纵坐标
delete innerSeriesCollection;innerSeriesCollection=NULL;
delete chartObjects;chartObjects=NULL;
delete seriesCollection;seriesCollection=NULL;
delete chart;chart=NULL;
//QFileDialog::DontConfirmOverwrite表示如果遇到文件已存在,不提示,直接覆盖
QString filepath = QFileDialog::getSaveFileName(this,tr("Save"),".",tr("Microsoft Office 2007 (*.xlsx)\n"
"Microsoft Office 2003 (*.xls)"),0,QFileDialog::DontConfirmOverwrite);
//Excel::xlLocalSessionChanges表示遇到冲突时覆盖,且不提示
//SaveAs的详细用法参见http://msdn.microsoft.com/zh-cn/library/ff195820(v=office.15).aspx
if(QFileInfo(filepath).suffix() == "xls"){
workbook->SaveAs(QDir::toNativeSeparators(filepath),Excel::xlExcel8,"","",false,false,Excel::xlNoChange,Excel::xlLocalSessionChanges);
}
else{
workbook->SaveAs(QDir::toNativeSeparators(filepath),Excel::xlOpenXMLWorkbook,"","",false,false,Excel::xlNoChange,Excel::xlLocalSessionChanges);
}
workbook->Close();
excel->Quit();
delete worksheet;worksheet=NULL;
delete excel;excel=NULL;
}
在上例中,我测试了单元格的使用、函数的使用、图表的使用。还添加了Cells(int,int)函数,插入表格背景图片、中文输入无乱码,xls和xlsx两种版本的保存。
4.其他问题
运用过程中可能还会出现其他问题:
1)在
Excel::Application *excel = new Excel::Application(this);这句出现
undefined reference to `vtable for Excel::Application' 或 undefined reference to `Excel::Sheets::staticMetaObject'这样的错误。
解决办法:将外部引用的*.h文件在头文件中包含,不要包含在*.cpp中。在Office.h中包含<QMetaObject>。
2)出现
QAxBase::setControl: requested control {00020813-0000-0000-C000-000000000046} could not be instantiated
QAxBase::qt_metacall: Object is not initialized, or initialization failed
QAxBase::qt_metacall: Object is not initialized, or initialization failed
QAxBase::qt_metacall: Object is not initialized, or initialization failed这样的错误
解决办法:在整个工程中查找
{00020813-0000-0000-C000-000000000046},将其替换为
Excel.Application
3)出现
'Excel::XlFileFormat' is not a class or namespace这样的错误
解决办法:xlFileFormat是枚举类型,如果要用其中的某个枚举值,直接写成
Excel::xlOpenXMLWorkbook,不要写成
Excel::XlFileFormat::xlOpenXMLWorkbook
5.样例图片
6.源码下载
百度网盘:
3个压缩包均在: http://pan.baidu.com/share/link?shareid=2465785956&uk=4128141257
新浪网盘(最近把网盘整理了一下,不知这个失效没):
1) testExcel2_仅包含修改后的excel,h、excel.cpp、office.h和office.cpp
2) testExcel2_包含所有源码,不包含Qt运行库
3) testExcel2_包含所有源码和Qt运行库
7.其他类
在Qt的天池项目里也有处理Excel文件的类,不过我还没用过。大家有兴趣可以看看。地址: http://www.qtcn.org/bbs/thread-htm-fid-105.html