一、背景
QT中没有操作Excel的官方库,本文章介绍的是Windows系统的ActiveX对象(QAxObject)操作Excel。
当然还有一些开源库可以用来操作Excel,详见:Qt处理Excel的一些库
本文只对QAxObject读取Excel作说明。
二、介绍
首先,我们先来解释一些概念,方面我们去理解Qt操作excel的基本流程。
Qt中,QAxObject对COM对象进行封装,QAObject派生自QAxBase,QAxBase提供了一组API通过IUnknown指针直接访问COM对象。具体结构如下图。
我们要操作的Excel也是一个COM对象,因此,可以采用上述方式实现对Excel的操作。
在具体操作之前,我们先了解一下excel的层次结构,如图所示,Application对象–>Workbook对象–>Worksheet对象–>Range对象。
1个excel有一个Application对象,1个Application对象有多个workbook对象组成,这些workbook对象由workbooks对象统一管理,workbook对象下包含若干个worksheet,这些worksheet对象由worksheets对象统一管理,worksheet下面的range对象,对应这worksheet里面的表格单元了。
基本操作方法
获取对象
上述对象获取某个子对象一般通过 QAxObject
的 querySubObject()
方法,比如:
QAxObject *excel = new QAxObject("Excel.Application");
QAxObject *workbooks = excel->querySubObject("WorkBooks");
QAxObject *workbook = workbooks->querySubObject("Open(QString&)", path);
QAxObject *sheets = workbook->querySubObject("Sheets");
QAxObject *sheet = sheets->querySubObject("Item(int)", 1);
QAxObject *range = sheet->querySubObject("Cells(int,int)", row, col);
这其中依次得到的对象分别是:
- Excel 的
Application
对象。 - 管理
Workbook
对象的Workbooks
对象。 - 路径为
path
的 Excel 文件对应的Workbook
对象。 - 管理其中工作表
Sheet
的Sheets
对象。 - 第一张工作表对应的
Sheet
对象。 - 其中第
row
行,第col
列的表格单元的range
对象。
调用动态方法
还可以通过 dynamicCall()
方法调用一些动作,比如:
workbook->dynamicCall("SaveAs(const QString &)", QDir::toNativeSeparators(path));
这是调用另存为 path
路径。
workbook->dynamicCall("Save()");
这是调用保存。
range->dynamicCall("Value", value);
这时设置单元格的值。
excel->dynamicCall("SetVisible(bool)", false);
这是设置打开 Excel 时不可见(也就是后台进行)。
workbooks->dynamicCall("Add");
这是新建一个 Excel 文件。
workbooks->dynamicCall("Close()");
excel->dynamicCall("Quit()");
这是关闭 Excel 应用。
除此之外,还有很多类似的方法。
设置和获取属性
一般通过 setProperty()
方法设置属性,比如:
range->setProperty("VerticalAlignment", -4108);
range->setProperty("HorizontalAlignment", -4108);
range->setProperty("WrapText", true);
range->setProperty("MergeCells", true);
range->setProperty("Bold", isBold);
分别为设置单元格:
- 竖直居中。
- 水平居中。
- 文本自动换行。
- 单元格合并。
- 字体加粗。
而如果想获取属性就可以通过 property()
方法,会返回一个 QVariant
对象,可以根据需求通过 toString()
、toInt()
等方法转为 Qt 的基本类型。
更多相关
除了上面提到的,更多的方法可以直接到微软官网查看文档。
三、使用要求
添加模块
在Qt Creator中使用QAxObject需要先在pro中添加:QT += axcontainer
包含头文件ActiveQt/QAxObject
#include <QAxObject>
与excel com连接的方法
QAxObject *excel = new QAxObject("Excel.Application"); //!建立excel操作对象,并连接Excel控件
excel->dynamicCall("SetVisible (bool Visible)", "false"); //! 设置为不显示窗体
excel->setProperty("DisplayAlerts", false); //! 不显示任何警告信息, 如关闭时的是否保存提示
excel->dynamicCall("Quit(void)"); //! 关闭excel程序,操作完后记着关闭,由于是隐藏在后台进程中,不关闭进程会有很多excel.exe。
workbook->dynamicCall("Close(Boolean)", false); //! 关闭exce程序先关闭.xls文件
Excel基本操作
只介绍简单的读写操作,需要修改单元格格式等操作,请"Excel VBA参考手册.chm"
excel文件操作
获取当前工作簿的集合,这里需要注意,工作簿就是Excel文件。
QAxObject *workbooks = excel->querySubObject("Workbooks"); //! 获取工作簿(excel文件)集合
新建一个工作簿,新建一个工作簿就是新建一个Excel文件
workbooks->synamicCall("Add"); //新建一个工作簿
QAxObject *workbook = excel->querySubObject("ActiveWorkBook"); //! 获取当前工作簿
打开一个已有的工作簿,就是打开一个Excel文件
QString filename = "e:/123.xlsx";
QAxObject* workbook = workbooks->querySubObject("Open(const QString&)", filename);
保存工作簿
workbook->dynamicCall("Save()"); //!保存文件
workbook->dynamicCall("Close(Boolean)", false); //! 关闭文件
excel->dynamicCall("Quit()"); //! 关闭excel
另存为工作簿
QDir::toNativeSeparators,将路径中的"/"转换为"\",否则无法保存,"/"只是qt中可以识别
workbook->dynamiCall("SaveAs(const QString&)", QDit::toNativeSeparators(filename));
workbook->synamicCall("Close(Boolean)", false); //! 关闭文件
excel->dynamicCall("Quit()"); //! 关闭excel
Sheet工作表操作
获取所有工作表
QAxObject *worksheets = workbook->querySubObject("Sheets"):
根据序号获取某个工作表,序号顺序就是excel 打开后下方的排序
QAxObject *worksheet = worksheets->querySubObejct("Item(int)", 1);
获取表中的行数列数
QAxObject* usedrange = worksheet->querySubObject("UsedRange"); //! sheet 范围
int intRowStart = usedrange->property("Row").toInt(); //! 起始行数
int intColStart = usedrange->property("Column").toInt(); //! 起始列数
QAxObject *rows, *columns;
rows = usedrange->querySubObject("Rows"): //! 行
columns = usedrange->querySubObject("Columns"); //! 列
int intRow = rows->property("Count").toInt(); //! 行数
int intCol = columns->property("Count").toInt(); //! 列数
内容操作
数据内容操作–获取单元格–基于坐标
QAxObject *cell = worksheet->querySubObject("Cells(int, int)", i, j);
数据内容操作–获取单元格–基于行列名称
QAxObject *cell = worksheet->querySubObject("Range(QVariant, QVariant)", "A1");
数据内容操作–读单元格内容
QVariant cell_value = cell->property("Value");
数据内容操作-- 写单元格内容
cell->setProperty("Value", "内容");
大数据量读取
读取所有单元格内容-数据量大,只需要进行一次操作即可读取所有内容,避免重复对每个单元格进行QAxObect操作
QVariant var;
QAxObject * usedRange = sheet->querySubObject("UseRange"); //! 获取用户区域范围
if(NULL == usedRange || usedRange->isNull())
{
return var;
}
var = usedRange->dynamicCall("Value"); // 读取区域内所有值
delete usedRange;
此时结果以QVariant保存,需要自行转化为QList<QList>
QList<QList<QVariant>> excel_list;
auto rows = var.toList();
for(auto row:rows)
{
excel_list.append(row.toList());
}
大数据写入
以QList<QList>存储,需要限定范围
QAxObject *user_rang = this->sheet->querySubObject("Rang(const QString&)", "A1:D100");
写入数据
rang->setProperty("Value", var);
四、具体使用说明
一般我们使用QAxObject操作Excel分为以下的步骤:
- 连接控件Excel
- 打开工作簿(新建或打开Excel文件)
- 打开sheet
- 获取行数,列数
- 读和写
- 设置样式
- 保存文件
- 另存为
- 关闭文件
下面我们就具体的说明一下怎么完成上面的操作。
1:连接控件Excel
2: 打开工作簿(新建或打开Excel文件)
3: 打开sheet
4: 获取行数,列数
5: 读和写
6:设置样式
7: 保存文件
8:另存为
9:关闭文件
1:连接控件Excel
QAxObject excel("Excel.Application");//连接Excel控件
excel.setProperty("Visible", false);// 不显示窗体
excel->setProperty("DisplayAlerts", false); // 不显示任何警告信息。如果为true, 那么关闭时会出现类似"文件已修改,是否保存"的提示
2: 打开工作簿(新建或打开Excel文件)
QAxObject* workbooks = excel->querySubObject("WorkBooks"); // 获取工作簿集合
2.1新建
workbooks->dynamicCall("Add"); // 新建一个工作簿
QAxObject* workbook = excel->querySubObject("ActiveWorkBook"); // 获取当前工作簿
2.2打开
QAxObject* workbook = workbooks->querySubObject("Open(const QString&)", ("C:/Users/lixc/Desktop/tt2.xlsx"));//Excel文件地址
3: 打开sheet
QAxObject* worksheet = workbook->querySubObject("WorkSheets(int)", 1); // 获取工作表集合的工作表1, 即sheet1
4: 获取行数,列数
QAxObject* usedrange = worksheet->querySubObject("UsedRange"); // sheet范围
int intRowStart = usedrange->property("Row").toInt(); // 起始行数 为1
int intColStart = usedrange->property("Column").toInt(); // 起始列数 为1
QAxObject *rows, *columns;
rows = usedrange->querySubObject("Rows"); // 行
columns = usedrange->querySubObject("Columns"); // 列
int intRow = rows->property("Count").toInt(); // 行数
int intCol = columns->property("Count").toInt(); // 列数
qDebug()<<"intRowStart:"<<intRowStart<<"\t intColStart"<<intColStart;
qDebug()<<"intRow"<<intRow<<"\t intCol"<<intCol;
5: 读和写
5.1读取单元格方式1
for(int i=intRowStart;i<intRow+intRowStart;i++)
{
for(int j=intColStart;j<intCol+intColStart;j++)
{
QAxObject* cell = worksheet->querySubObject("Cells(int, int)", i, j); //获单元格值
qDebug() << i << j << cell->dynamicCall("Value2()").toString();
}
}
5.2读取单元格方式2
QString X = "A2"; //设置要操作的单元格,A1
QAxObject* cellX = worksheet->querySubObject("Range(QVariant, QVariant)", X); //获取单元格
qDebug() << cellX->dynamicCall("Value2()").toString();
5.3写单元格方式1
cellX->dynamicCall("SetValue(conts QVariant&)", 100); // 设置单元格的值
5.4写单元格方式2
QAxObject *cell_5_6 = worksheet->querySubObject("Cells(int,int)", 5, 6);
cell_5_6->setProperty("Value2", "Java");
6:设置样式(未测试)
//获得单元格对象
QAxObject* cell = worksheet->querySubObject("Cells(int, int)", i, j);
6.1设置单元格内容的显示setProperty()
cell->setProperty("Value", "Java C++ C# PHP Perl Python Delphi Ruby"); //设置单元格值
cell->setProperty("RowHeight", 50); //设置单元格行高
cell->setProperty("ColumnWidth", 30); //设置单元格列宽
cell->setProperty("HorizontalAlignment", -4108); //左对齐(xlLeft):-4131 居中(xlCenter):-4108 右对齐(xlRight):-4152
cell->setProperty("VerticalAlignment", -4108); //上对齐(xlTop)-4160 居中(xlCenter):-4108 下对齐(xlBottom):-4107
cell->setProperty("WrapText", true); //内容过多,自动换行
cell->dynamicCall("ClearContents()"); //清空单元格内容
6.2设置单元格的样式 QAxObject* interior = cell->querySubObject("Interior");
QAxObject* interior = cell->querySubObject("Interior");
interior->setProperty("Color", QColor(0, 255, 0)); //设置单元格背景色(绿色)
QAxObject* border = cell->querySubObject("Borders");
border->setProperty("Color", QColor(0, 0, 255)); //设置单元格边框色(蓝色)
QAxObject *font = cell->querySubObject("Font"); //获取单元格字体
font->setProperty("Name", QStringLiteral("华文彩云")); //设置单元格字体
font->setProperty("Bold", true); //设置单元格字体加粗
font->setProperty("Size", 20); //设置单元格字体大小
font->setProperty("Italic", true); //设置单元格字体斜体
font->setProperty("Underline", 2); //设置单元格下划线
font->setProperty("Color", QColor(255, 0, 0)); //设置单元格字体颜色(红色)
6.3合并拆分单元格
QString merge_cell;
merge_cell.append(QChar(3 - 1 + 'A')); //初始列
merge_cell.append(QString::number(5)); //初始行
merge_cell.append(":");
merge_cell.append(QChar(5 - 1 + 'A')); //终止列
merge_cell.append(QString::number(8)); //终止行
QAxObject *merge_range = work_sheet->querySubObject("Range(const QString&)", merge_cell);
//merge_range 可以设置单元格属性
merge_range->setProperty("MergeCells", true); //合并单元格
//merge_range->setProperty("MergeCells", false); //拆分单元格
7: 保存文件
7.1保存当前文件(根据已经打开的工作簿,如果工作簿是新建的文件保存默认存储于Document文件夹中)
workbook->dynamicCall("Save()"); //保存文件
7.2指定目录存储
QString fileName = QFileDialog::getSaveFileName(NULL, QStringLiteral("保存文件"), QStringLiteral("excel名称"), QStringLiteral("EXCEL(*.xlsx)"));
//QString fileName=QStringLiteral("C:/Users/lixc/Desktop/excel名称.xlsx");
workbook->dynamicCall("SaveAs(const QString&)", QDir::toNativeSeparators(fileName)); //保存到filepath
// 注意一定要用QDir::toNativeSeparators, 将路径中的"/"转换为"\", 不然一定保存不了
9:关闭文件
workbook->dynamicCall("Close (Boolean)", false); //关闭文件
excel.dynamicCall("Quit(void)"); //退出
如果excel存在于堆上,注意手动释放
delete excel;
注意:调试过程中出现异常:需要打开任务管理器,结束Excle进程
//获得单元格对象
QAxObject* cell = worksheet->querySubObject("Cells(int, int)", i, j);
五、项目实战
在了解了大致的使用步骤后,我们可以通过实际项目来综合运用了。
实战项目1
项目代码如下:
//#include <QtGui/QApplication>
#include <QApplication>
#include "mainwindow.h"
#include <QAxObject>
#include <QAxWidget>
#include <QTextCodec>
#include <QtDebug>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// QTextCodec::setCodecForCStrings(QTextCodec::codecForName("GB18030"));
// QTextCodec::setCodecForLocale(QTextCodec::codecForName("GB18030"));
// QTextCodec::setCodecForTr(QTextCodec::codecForName("GB18030"));
QAxWidget excel("Excel.Application");
excel.setProperty("Visible", true);
QAxObject * workbooks = excel.querySubObject("WorkBooks");
if (!workbooks) return 1; //错误返回,用 if(!excel)则不行
workbooks->dynamicCall("Open (const QString&)", QString("E:\\wenjian\\cs\\code\\QT\\testExcel2\\test.xlsx"));
QAxObject * workbook = excel.querySubObject("ActiveWorkBook"); //5) 获取活动工作簿:
QAxObject * worksheets = workbook->querySubObject("WorkSheets"); //获取所有的工作表:
int intCount = worksheets->property("Count").toInt(); //获取工作表数量:
qDebug() << "工作表数量" << intCount;
for (int i = 1; i <= intCount; i++)
{
int intVal;
QAxObject * worksheet = workbook->querySubObject("Worksheets(int)", i); //获取第i个工作表:
qDebug() << i << worksheet->property("Name").toString();
QAxObject * range = worksheet->querySubObject("Cells(1,1)"); //获取cell的值:
intVal = range->property("Value").toInt();
range->setProperty("Value", QVariant(intVal+1));
QAxObject * range2 = worksheet->querySubObject("Range(C1)");
intVal = range2->property("Value").toInt();
range2->setProperty("Value", QVariant(intVal+1));
}
QAxObject * worksheet = workbook->querySubObject("Worksheets(int)", 1);
QAxObject * usedrange = worksheet->querySubObject("UsedRange");
QAxObject * rows = usedrange->querySubObject("Rows");
QAxObject * columns = usedrange->querySubObject("Columns");
int intRowStart = usedrange->property("Row").toInt();
int intColStart = usedrange->property("Column").toInt();
int intCols = columns->property("Count").toInt();
int intRows = rows->property("Count").toInt();
qDebug() << "表格行数" << intRows;
qDebug() << "表格列数" << intCols;
// for (int i = intRowStart; i < intRowStart + intRows; i++)
// {
// for (int j = intColStart; j <= intColStart + intCols; j++)
// {
// QAxObject * range = worksheet->querySubObject("Cells(int,int)", i, j );
// qDebug() << i << j << range->dynamicCall("Value2()").toString(); //property("Value");不行
// }
// }
for (int i = intRowStart; i < intRows; i++)
{
for (int j = intColStart; j < intCols; j++)
{
QAxObject * range = worksheet->querySubObject("Cells(int,int)", i, j );
qDebug() << i << j << range->dynamicCall("Value2()").toString(); //property("Value");不行
}
}
excel.setProperty("DisplayAlerts", 0);
//qDebug("here?");
workbook->dynamicCall("SaveAs (const QString&)", QString("E:\\wenjian\\cs\\code\\QT\\testExcel2\\temp.xls"));//这里只能用相对路径!!!用绝对路径就报下面的错误。
excel.setProperty("DisplayAlerts", 1);
workbook->dynamicCall("Close (Boolean)", false);
excel.dynamicCall("Quit (void)");
MainWindow w;
w.show();
return a.exec();
}
test.xlsx中内容如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VWR6lCEv-1677385787835)(C:\Users\10521\AppData\Roaming\Typora\typora-user-images\image-20230226101051423.png)]
执行代码会打印如下信息:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZMXaMuYv-1677385787836)(C:\Users\10521\AppData\Roaming\Typora\typora-user-images\image-20230226101140437.png)]
说明代码把Excel表格中的内容读取并打印出来了。
实战项目2
具体的操作流程如下:
QAxWidget excel(“Excel.Application”);
- 显示当前窗口:
excel.setProperty(“Visible”, true); - 更改 Excel 标题栏:
excel.setProperty(“Caption”, “Invoke Microsoft Excel”); - 添加新工作簿:
QAxObject * workbooks = excel.querySubObject(“WorkBooks”);
workbooks->dynamicCall(“Add”); - 打开已存在的工作簿:
workbooks->dynamicCall(“Open (const QString&)”, QString(“c:/test.xls”)); - 获取活动工作簿:
QAxObject * workbook = excel.querySubObject(“ActiveWorkBook”); - 获取所有的工作表:
QAxObject * worksheets = workbook->querySubObject(“WorkSheets”); - 获取工作表数量:
int intCount = worksheets->property(“Count”).toInt(); - 获取第一个工作表:
QAxObject * worksheet = workbook->querySubObject(“Worksheets(int)”, 1); - 获取cell的值:
QAxObject * range = worksheet->querySubObject(“Cells(int,int)”, 1, 1 );
项目代码如下:
#include <QApplication>
#include <QDir>
#include <QDebug>
//#include <QAxBase>
#include "mainwindow.h"
#include <QAxObject>
int main(int argc, char **argv)
{
QApplication a(argc, argv);
QAxObject excel("Excel.Application");
excel.setProperty("Visible", false);
QAxObject * workbooks = excel.querySubObject("WorkBooks");
workbooks->dynamicCall("Open (const QString&)", QString("E:\\wenjian\\cs\\code\\QT\\testExcel3\\test.xlsx"));
QAxObject * workbook = excel.querySubObject("ActiveWorkBook");
QAxObject * worksheets = workbook->querySubObject("WorkSheets");
int intCount = worksheets->property("Count").toInt();
QAxObject * worksheet = workbook->querySubObject("Worksheets(int)", 1);
QAxObject * usedrange = worksheet->querySubObject("UsedRange");
QAxObject * rows = usedrange->querySubObject("Rows");
QAxObject * columns = usedrange->querySubObject("Columns");
int intRowStart = usedrange->property("Row").toInt();
int intColStart = usedrange->property("Column").toInt();
int intCols = columns->property("Count").toInt();
int intRows = rows->property("Count").toInt();
qDebug() << "开始行数" << intRowStart;
qDebug() << "开始列数" << intColStart;
qDebug() << "总行数" << intRows;
qDebug() << "总列数" << intCols;
for (int i = intRowStart; i < intRowStart + intRows; i++)
{
for (int j = intColStart; j < intColStart + intCols; j++)
{
QAxObject * range = worksheet->querySubObject("Cells(int,int)", i, j );
qDebug() << i << j << range->property("Value");
}
}
excel.setProperty("DisplayAlerts", 0);
workbook->dynamicCall("SaveAs (const QString&)", QDir::toNativeSeparators("E:\\wenjian\\cs\\code\\QT\\testExcel3\\temp.xlsx"));
excel.setProperty("DisplayAlerts", 1);
workbook->dynamicCall("Close (Boolean)", false);
excel.dynamicCall("Quit (void)");
return a.exec();
}
被操作的Excel文件和实战项目1中的一致。
执行代码打印信息如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OIat4Y1r-1677385787836)(C:\Users\10521\AppData\Roaming\Typora\typora-user-images\image-20230226102643579.png)]
实战项目3
代码如下:
// 参考内容: https://blog.csdn.net/qq319923400/article/details/80149367
// https://www.cnblogs.com/lifexy/p/10743316.html
// https://www.cnblogs.com/lifexy/p/10743352.html
#include "widget.h"
#include <QApplication>
#include <QAxObject> // .pro需要添加: QT += axcontainer
#include <QFileDialog>
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
QAxObject excel("Excel.Application"); // 关联excel
excel.setProperty("Visible", true); // 运行程序时要不要通过excel打开当前编辑的表格
excel.setProperty("DisplayAlerts", false); // excel关闭时是否询问要不要保存
//excel.setProperty("Caption", "Qt Excel"); // 类似于文件类型吧,感觉这个名称意义不大
QAxObject* workbooks = excel.querySubObject("WorkBooks");
workbooks->dynamicCall("Add"); // 增加一个excel表格,写两遍会打开两个不同的excel表格
QAxObject* workbook = excel.querySubObject("ActiveWorkBook");
QAxObject* worksheet = workbook->querySubObject("WorkSheets(QString)", "Sheet1"); // 通过sheet名获取sheet1
// QAxObject* worksheet = workbook->querySubObject("WorkSheets(int)", "1"); // 通过编号获取sheet1, 结果和上面一样
worksheet->setProperty("Name", "Shadow3D_1"); // 修改sheet名
// 行列相关查询
QAxObject* usedrange = worksheet->querySubObject("UsedRange");
int intRowStart = usedrange->property("Row").toInt();
int intColStart = usedrange->property("Column").toInt();
// 注意: 这里的行和列都是从1开始计数的
qDebug() << "intRowStart: " << intRowStart << "\tintColStart: " << intColStart;
// 查看已经使用的最大行数和最大列数
int intRow = usedrange->querySubObject("Rows")->property("Count").toInt();
int intCol = usedrange->querySubObject("Columns")->property("Count").toInt();
qDebug() << "Rows: " << intRow << "\tColumns: " << intCol;
// 写单元格
for(int i = 1; i < 10; i++)
for(int j = 1; j < 10; j++)
worksheet->querySubObject("Cells(int, int)", i, j)->setProperty("Value2", i+j);
// 查看已经使用的最大行数和最大列数
usedrange = worksheet->querySubObject("UsedRange"); // 当重新修改sheet后,必须重新获取UsedRange值才能得到最新的行数和列数。
QAxObject* rows = usedrange->querySubObject("Rows");
QAxObject* columns = usedrange->querySubObject("Columns");
qDebug() << rows << columns; // 不知道这个rows和columns的值表示什么意义,每次运行都不一样。。。
intRow = rows->property("Count").toInt();
intCol = columns->property("Count").toInt();
qDebug() << "Rows: " << intRow << "\tColumns: " << intCol;
// 读单元格
QAxObject* cell = worksheet->querySubObject("Cells(int, int)", 5, 5); // 获取单元格对象
QString cellStr = cell->dynamicCall("Value2()").toString();
qDebug() << "cell: " << cellStr; // "10"
qDebug() << "cell: " << cellStr.toUtf8().data(); // 10 ( QString 类型转换为 QByteArray,再转换成Char* )
// 单元格格式
cell = worksheet->querySubObject("Cells(int, int)", 11, 11);
cell->setProperty("Value", "bool setProperty(const char *name, const QVariant &value");
cell->setProperty("RowHeight", 50);
cell->setProperty("ColumnWidth", 30);
cell->setProperty("HorizontalAlignment", -4108); // left:-4131 center:-4108 right:-4152
cell->setProperty("VerticalAlignment", -4108); // left:-4161 center:-4108 right:-4107
cell->setProperty("WrapText", true); // 单元格内容多时自动换行
// cell>dynamicCall("ClearContents()"); // 清空单元格内容
// 设置颜色,字体
cell = worksheet->querySubObject("Cells(int, int)", 12, 12); // 获取单元格对象
cell->setProperty("Value", "Text");
QAxObject* interior = cell->querySubObject("Interior");
interior->setProperty("Color", QColor(0, 255, 0)); // 背景颜色: Green
QAxObject* border = cell->querySubObject("Borders");
border->setProperty("Color", QColor(0, 0, 255)); // 边框颜色: Blue
QAxObject* font = cell->querySubObject("Font");
font->setProperty("Name", QStringLiteral("华文彩云"));
font->setProperty("Bold", true);
font->setProperty("Size", 20);
font->setProperty("Italic", true);
font->setProperty("Underline", 3); // 下划线:2 双下划线:3
font->setProperty("Color", QColor(255, 0, 0)); // 字体颜色: Red
// 合并拆分单元格
QString merge_cell;
merge_cell.append(QChar('A'+20)); // 从第(20+1)列开始,因为'A'表示第一列
merge_cell.append(QString::number(15)); // 从第15行开始
merge_cell.append(":");
merge_cell.append(QChar('A'+21)); // 到(21+1)列结束
merge_cell.append(QString::number(16)); // 到第16列结束
QAxObject* merge_range = worksheet->querySubObject("Range(const QString&)", merge_cell);
merge_range->setProperty("MergeCells", true); // 合并单元格
// merge_range->setProperty("MergeCells", false); // 拆分单元格
// 文件保存
QString fileName = QFileDialog::getSaveFileName(NULL, QStringLiteral("Save File As"), QStringLiteral("Shadow3D"), QStringLiteral("EXCEL(*.xlsx | *.xls)"));
workbook->dynamicCall("SaveAs(conse QString&)", QDir::toNativeSeparators(fileName));
// 关闭文件
workbook->dynamicCall("Close(Boolean)", false);
// 退出excel
excel.dynamicCall("Quit(void)");
// delete excel; // 如果excel是通过new方式建立在堆上,要记得释放。
w.show();
return a.exec();
}
实战项目4
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <ActiveQt/QAxObject>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void OpenExcel();
void AddNewExcel();
void SaveAndClose();
int GetRowsCount();
QString GetCell(int row, int column);
QString GetCell(QString numer);
void SetCell(int row, int column, QString value);
void SetCell(QString number, QString value);
void SetCellColor(int row, int column, QColor color);
private:
Ui::Widget *ui;
QAxObject *m_pExcel;
QAxObject *m_pWorkBooks;
QAxObject *m_pWorkBook;
QAxObject *m_pWorkSheets;
QAxObject *m_pWorkSheet;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QDir>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
, m_pExcel(nullptr)
, m_pWorkBooks(nullptr)
, m_pWorkBook(nullptr)
, m_pWorkSheets(nullptr)
, m_pWorkSheet(nullptr)
{
ui->setupUi(this);
// 连接excel 控件
m_pExcel = new QAxObject("Excel.Application");
// m_pExcel->setControl("Excel.Applicatio");
// 设置操作excel时不打开excel窗体
m_pExcel->dynamicCall("SetVisible(bool Visible)",false);
// 设置不显示任何警告信息
m_pExcel->setProperty("DisplayAlert",false);
}
// 打开已有的excel
void Widget::OpenExcel()
{
QString strExcelPath = "C:\\Users\\Qcx\\Desktop\\test.xlsx";
// 获取当前工作簿
m_pWorkBooks = m_pExcel->querySubObject("WorkBooks");
// 打开指定工作簿
m_pWorkBook = m_pWorkBooks->querySubObject("Open(const QString&)",strExcelPath);
if(m_pWorkBook)
{
qDebug()<<"Open Excel Success!";
}
// 获取sheets
m_pWorkSheets = m_pWorkBook->querySubObject("Sheets");
// 获取某个sheet
m_pWorkSheet = m_pWorkSheets->querySubObject("Item(int)",1);
}
// 创建新的Excel
void Widget::AddNewExcel()
{
// 获取当前工作簿
m_pWorkBooks = m_pExcel->querySubObject("WorkBooks");
m_pWorkBooks->dynamicCall("Add");
m_pWorkBook = m_pExcel->querySubObject("ActiveWorkBook");
m_pWorkSheets = m_pWorkBook->querySubObject("Sheets");
m_pWorkSheet = m_pWorkSheets->querySubObject("Item(int)",1);
}
// 保存并关闭Excel
void Widget::SaveAndClose()
{
QString strSavePath = "C:\\Users\\Qcx\\Desktop\\temp.xlsx";
// 保存文件,一定要将路径中的'/'转为'\\',前者只能被Qt识别
m_pWorkBook->dynamicCall("SaveAs(const QString&)", QDir::toNativeSeparators(strSavePath));
// 关闭文件
m_pWorkBook->dynamicCall("Close()");
// 关闭excel
m_pExcel->dynamicCall("Quit()");
delete m_pExcel;
m_pExcel = nullptr;
}
// 获取行数
int Widget::GetRowsCount()
{
int iRows = 0;
QAxObject *pRows = m_pWorkSheet->querySubObject("Rows");
iRows = pRows->property("Count").toInt();
return iRows;
}
// 获取单元格内容,行号+列号
QString Widget::GetCell(int row, int column)
{
QAxObject *pCell = m_pWorkSheet->querySubObject("Range(int, int)", row, column);
return pCell->property("Value").toString();
}
// 获取单元格内容,单元格标号如: A1,C5
QString Widget::GetCell(QString number)
{
QAxObject *pCell = m_pWorkSheet->querySubObject("Range(QString)", number);
return pCell->property("Value").toString();
}
// 设置单元格内容,行号+列号
void Widget::SetCell(int row, int column, QString value)
{
QAxObject *pCell = m_pWorkSheet->querySubObject("Range(int, int)", row, column);
pCell->setProperty("Value", value);
}
// 设置单元格内容,单元格标号如: A1,C5
void Widget::SetCell(QString number, QString value)
{
QAxObject *pCell = m_pWorkSheet->querySubObject("Range(QString)", number);
pCell->setProperty("Value", value);
}
// 设置单元格颜色的方式与设置值的方式一样,都是先获取到单元格,再设置属性
void Widget::SetCellColor(int row, int column, QColor color)
{
QAxObject *pCell = m_pWorkSheet->querySubObject("Range(int, int)", row, column);
QAxObject *pInterior = pCell->querySubObject("Interior");
pInterior->setProperty("Color", color);
}
Widget::~Widget()
{
delete ui;
}
可能遇到的坑
在进行关闭操作时,明明执行没有任何错误,指针也没有异常,但就是Excel进程没有被杀死,我遇到的情况是电脑安装了福昕阅读器,会占用Excel的com接口。有两种解决方案:
1、暴力简单,直接卸载福昕阅读器;
2、打开Excel --> 选项 --> 加载项 --> 最下方的管理 --> 选中COM加载项 --> 转到 --> 取消FoxitReader PDF Creator COM Add-in。
实战项目5-封装好的库
简单封装代码
头文件 excelmanager.h
#ifndef EXCELMANGER_H
#define EXCELMANGER_H
#include <QWidget>
#include <QString>
#include <QAxObject>
#include <QDialog>
class ExcelManger : public QWidget
{
Q_OBJECT
public:
explicit ExcelManger(QWidget *parent = nullptr);
~ExcelManger();
protected:
static QAxObject *excel;
static QAxObject *workbooks;
static QAxObject *workbook;
static int count;
void new_excel(const QString&);
void open_excel(const QString&);
QString get_cell_value(QAxObject*, int, int);
QVariant get_range(QAxObject*, const QString&);
void set_cell_value(QAxObject*, int, int, const QString&);
void merge_cells(QAxObject*, const QString&);
void set_cell_font_bold(QAxObject *sheet, const QString &cell, bool isBold = true);
void set_cell_font_center(QAxObject *sheet, const QString &cell);
void set_rows_autofit(QAxObject *sheet, const QString &rows);
void set_cols_autofit(QAxObject *sheet, const QString &cols);
void save_excel();
void save_excel_as(const QString&);
void close();
void free_excel();
signals:
};
#endif // EXCELMANGER_H
实现文件 excelmanager.cpp
#include "excelmanger.h"
#include <QDebug>
#include <QVariant>
#include <QFile>
#include <QDir>
#ifdef Q_OS_WIN
#include <windows.h>
#endif
QAxObject* ExcelManger::excel = nullptr;
QAxObject* ExcelManger::workbooks = nullptr;
QAxObject* ExcelManger::workbook = nullptr;
int ExcelManger::count = 0;
ExcelManger::ExcelManger(QWidget *parent) : QWidget(parent)
{
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if ((count++) == 0)
{
excel = new QAxObject("Excel.Application", this->parent());
excel->dynamicCall("SetVisible(bool)", false);
workbooks = excel->querySubObject("WorkBooks");
}
}
ExcelManger::~ExcelManger()
{
if ((--count) == 0)
free_excel();
}
void ExcelManger::new_excel(const QString &path)
{
workbooks->dynamicCall("Add");
workbook = excel->querySubObject("ActiveWorkBook");
save_excel_as(path);
}
void ExcelManger::open_excel(const QString &path)
{
close();
QFile file(path);
if (!file.exists())
new_excel(path);
else
workbook = workbooks->querySubObject("Open(QString&)", path);
file.close();
}
QString ExcelManger::get_cell_value(QAxObject *sheet, int row, int col)
{
QAxObject *range = sheet->querySubObject("Cells(int,int)", row, col);
return range->property("Value").toString();
}
QVariant ExcelManger::get_range(QAxObject *sheet, const QString &range)
{
return sheet->querySubObject("Range(const QString&)", range)->property("value");
}
void ExcelManger::set_cell_value(QAxObject *sheet, int row, int col, const QString& value)
{
QAxObject *range = sheet->querySubObject("Cells(int,int)", row, col);
range->dynamicCall("Value", value);
}
void ExcelManger::merge_cells(QAxObject *sheet, const QString &cell)
{
QAxObject *range = sheet->querySubObject("Range(const QString&)", cell);
range->setProperty("VerticalAlignment", -4108);
range->setProperty("WrapText", true);
range->setProperty("MergeCells", true);
}
void ExcelManger::set_cell_font_bold(QAxObject *sheet, const QString &cell, bool isBold)
{
QAxObject *range = sheet->querySubObject("Range(const QString&)", cell);
range = range->querySubObject("Font");
range->setProperty("Bold", isBold);
}
void ExcelManger::set_cell_font_center(QAxObject *sheet, const QString &cell)
{
QAxObject *range = sheet->querySubObject("Range(const QString&)", cell);
range->setProperty("HorizontalAlignment", -4108);
range->setProperty("VerticalAlignment", -4108);
}
void ExcelManger::set_rows_autofit(QAxObject *sheet, const QString &rows)
{
QAxObject *Rows = sheet->querySubObject("Rows(const QString &)", rows);
Rows->dynamicCall("AutoFit()");
}
void ExcelManger::set_cols_autofit(QAxObject *sheet, const QString &cols)
{
QAxObject *Cols = sheet->querySubObject("Columns(const QString &)", cols);
Cols->dynamicCall("AutoFit()");
}
void ExcelManger::save_excel_as(const QString &path)
{
if (workbook)
workbook->dynamicCall("SaveAs(const QString &)", QDir::toNativeSeparators(path));
}
void ExcelManger::save_excel()
{
if (workbook)
workbook->dynamicCall("Save()");
}
void ExcelManger::close()
{
if (workbook)
workbook->dynamicCall("Close()");
}
void ExcelManger::free_excel()
{
if (excel != nullptr)
{
workbooks->dynamicCall("Close()");
excel->dynamicCall("Quit()");
delete workbook;
delete workbooks;
delete excel;
excel = nullptr;
workbooks = nullptr;
workbook = nullptr;
}
}
其中现有的功能主要是:
-
new_excel()
:按照给定目录创建新表格文件。 -
open_excel()
:按照给定目录打开表格文件。 -
get_cell_value()
:获取指定工作表中某行某列的单元格值(返回类型为QString
)。 -
set_cell_value()
:设置指定工作表中某行某列的单元格值(设置类型为QString
)。 -
save_excel()
和save_excel_as()
:保存文件以及保存为路径。 -
close()
和free_excel()
:关闭文件和Application
。 - 以及一些基本的单元格样式设置,更多功能待补充。
其他
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
需要在使用 COM
接口前使用此函数初始化。
程序中使用了静态成员,是希望在有多个工作表被操纵的时候,保证后台只有一个 Excel 应用在运行。
当然为了实现这一目标,程序也需要保证在使用 ExcelManger
时每个对象能够得到正确的析构。
项目实战6-封装好的库
exceloperator.h
#ifndef EXCELOPERATOR_H
#define EXCELOPERATOR_H
#include <QObject>
#include <ActiveQt/QAxObject>
#include <QDebug>
#include <QDir>
class ExcelOperator : public QObject
{
Q_OBJECT
public:
explicit ExcelOperator(QObject *parent = nullptr);
~ExcelOperator();
//打开文件
bool open(QString path);
//关闭文件
bool close();
//获取工作表数量
int getSheetsCount();
//根据名称创建工作表
QAxObject* addSheet(QString name);
//根据名称删除工作表
bool delSheet(QString name);
//根据编号删除工作表
bool delSheet(int index);
//根据名称获取工作表
QAxObject* getSheet(QString name);
//根据编号获取工作表
QAxObject* getSheet(int index);
//获取行对象
QAxObject* getRows(QAxObject* pSheet);
//获取行数
int getRowsCount(QAxObject* pSheet);
//获取列对象
QAxObject* getColumns(QAxObject* pSheet);
//获取列数
int getColumnsCount(QAxObject* pSheet);
//根据行列值获取单元格值, 如: 3行,5列
QString getCell(QAxObject* pSheet, int row, int column);
//根据行列编号获取单元格值, 如: "F6"
QString getCell(QAxObject* pSheet, QString number);
//根据行列值设置单元格值
bool setCell(QAxObject* pSheet, int row, int column, QString value);
//根据行列编号设置单元格值
bool setCell(QAxObject* pSheet, QString number, QString value);
signals:
public slots:
private:
QAxObject* m_pExcel;
QAxObject* m_pWorksheets;
QAxObject* m_pWorkbook;
QString m_strPath;
};
#endif // EXCELOPERATOR_H
exceloperator.c
#include "exceloperator.h"
#include <objbase.h>
ExcelOperator::ExcelOperator(QObject *parent) : QObject(parent)
, m_pExcel(NULL)
, m_pWorksheets(NULL)
, m_pWorkbook(NULL)
{
}
ExcelOperator::~ExcelOperator()
{
close();
}
bool ExcelOperator::open(QString path)
{
m_strPath = path;
QAxObject *pWorkbooks = NULL;
CoInitializeEx(NULL, COINIT_MULTITHREADED);
m_pExcel = new(std::nothrow) QAxObject();
if (NULL == m_pExcel) {
qCritical()<<"创建Excel对象失败...";
return false;
}
try {
m_pExcel->setControl("Excel.Application");
m_pExcel->dynamicCall("SetVisible(bool)", false); //true 表示操作文件时可见,false表示为不可见
m_pExcel->setProperty("DisplayAlerts", false);
pWorkbooks = m_pExcel->querySubObject("WorkBooks");
pWorkbooks->dynamicCall("Add");
m_pWorkbook = m_pExcel->querySubObject("ActiveWorkBook");
qDebug()<<"excel path: "<<m_strPath;
// 获取打开的excel文件中所有的工作sheet
m_pWorksheets = m_pWorkbook->querySubObject("WorkSheets");
} catch (...) {
qCritical()<<"打开文件失败...";
return false;
}
return true;
}
bool ExcelOperator::close()
{
qDebug()<<"excel close...";
if (m_pExcel)
{
qDebug()<<"closing...";
m_pWorkbook->dynamicCall("SaveAs(const QString&)", QDir::toNativeSeparators(m_strPath));
m_pWorkbook->dynamicCall("Close()");
m_pExcel->dynamicCall("Quit()");
delete m_pExcel;
m_pExcel = NULL;
}
return true;
}
int ExcelOperator::getSheetsCount()
{
int count = 0;
count = m_pWorksheets->property("Count").toInt();
return count;
}
QAxObject* ExcelOperator::addSheet(QString name)
{
QAxObject *pWorkSheet = NULL;
try {
int count = m_pWorksheets->property("Count").toInt(); //获取工作表数目
QAxObject *pLastSheet = m_pWorksheets->querySubObject("Item(int)", count);
pWorkSheet = m_pWorksheets->querySubObject("Add(QVariant)", pLastSheet->asVariant());
pLastSheet->dynamicCall("Move(QVariant)", pWorkSheet->asVariant());
pWorkSheet->setProperty("Name", name); //设置工作表名称
} catch (...) {
qCritical()<<"创建sheet失败...";
}
return pWorkSheet;
}
bool ExcelOperator::delSheet(QString name)
{
try {
QAxObject *pFirstSheet = m_pWorksheets->querySubObject("Item(QString)", name);
pFirstSheet->dynamicCall("delete");
} catch (...) {
qCritical()<<"删除sheet失败...";
return false;
}
return true;
}
bool ExcelOperator::delSheet(int index)
{
try {
QAxObject *pFirstSheet = m_pWorksheets->querySubObject("Item(int)", index);
pFirstSheet->dynamicCall("delete");
} catch (...) {
qCritical()<<"删除sheet失败...";
return false;
}
return true;
}
QAxObject* ExcelOperator::getSheet(QString name)
{
QAxObject* pWorkSheet = NULL;
try {
pWorkSheet = m_pWorksheets->querySubObject("Item(QString)", name);
} catch (...) {
qCritical()<<"获取sheet失败...";
}
return pWorkSheet;
}
QAxObject* ExcelOperator::getSheet(int index)
{
QAxObject* pWorkSheet = NULL;
try {
pWorkSheet = m_pWorksheets->querySubObject("Item(int)", index);
} catch (...) {
qCritical()<<"获取sheet失败...";
}
return pWorkSheet;
}
QAxObject* ExcelOperator::getRows(QAxObject* pSheet)
{
QAxObject* pRows = NULL;
try {
pRows = pSheet->querySubObject("Rows");
} catch (...) {
qCritical()<<"获取行失败...";
}
return pRows;
}
int ExcelOperator::getRowsCount(QAxObject* pSheet)
{
int rows = 0;
try {
QAxObject* pRows = getRows(pSheet);
rows = pRows->property("Count").toInt();
} catch (...) {
qCritical()<<"获取行数失败...";
}
return rows;
}
QAxObject* ExcelOperator::getColumns(QAxObject* pSheet)
{
QAxObject* pColumns = NULL;
try {
pColumns = pSheet->querySubObject("Columns");
} catch (...) {
qCritical()<<"获取列失败...";
}
return pColumns;
}
int ExcelOperator::getColumnsCount(QAxObject* pSheet)
{
int columns = 0;
try {
QAxObject* pColumns = getColumns(pSheet);
columns = pColumns->property("Count").toInt();
} catch (...) {
qCritical()<<"获取列数失败...";
}
return columns;
}
QString ExcelOperator::getCell(QAxObject* pSheet, int row, int column)
{
QString strCell = "";
try {
QAxObject* pCell = pSheet->querySubObject("Cells(int, int)", row, column);
strCell = pCell->property("Value").toString();
} catch (...) {
qCritical()<<"获取单元格信息失败...";
}
return strCell;
}
QString ExcelOperator::getCell(QAxObject* pSheet, QString number)
{
QString strCell = "";
try {
QAxObject* pCell = pSheet->querySubObject("Range(QString)", number);
strCell = pCell->property("Value").toString();
} catch (...) {
qCritical()<<"获取单元格信息失败...";
}
return strCell;
}
bool ExcelOperator::setCell(QAxObject* pSheet, int row, int column, QString value)
{
try {
QAxObject* pCell = pSheet->querySubObject("Cells(int, int)", row, column);
pCell->setProperty("Value", value);
} catch (...) {
qCritical()<<"写入单元格信息失败...";
return false;
}
return true;
}
bool ExcelOperator::setCell(QAxObject* pSheet, QString number, QString value)
{
try {
QAxObject* pCell = pSheet->querySubObject("Range(QString)", number);
pCell->setProperty("Value", value);
} catch (...) {
qCritical()<<"写入单元格信息失败...";
return false;
}
return true;
}
六、一些小技巧
读取excel中的数据
前面说过了怎么保存Qt中的数据到excel,现在再来说一说怎么读取excel中的数据到Qt(无论是读取到表格中,还是保存到数据模型中,核心的一点都是将excel中的数据读取到Qt的容器中,然后只要能读取容器中的数据,那么后面是保存到表格还是模型,都是很简单的事了)。
假设我们已经有这么一个由Qt保存好的excel表格,现在想要读取其中的数据
代码如下:
void Widget::on_readbtn_clicked()
{
QAxObject *excel = new QAxObject(this);//建立excel操作对象
excel->setControl("Excel.Application");//连接Excel控件
excel->setProperty("Visible", false);//显示窗体看效果,选择ture将会看到excel表格被打开
excel->setProperty("DisplayAlerts", true);//显示警告看效果
QAxObject *workbooks = excel->querySubObject("WorkBooks");//获取工作簿(excel文件)集合
QString str = QFileDialog::getOpenFileName(this,"打开",
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),"Excel 文件(*.xls *.xlsx)");
workbooks->dynamicCall("Open(const QString&)", str);//打开刚才选定的excel
QAxObject *workbook = excel->querySubObject("ActiveWorkBook");
QAxObject *worksheet = workbook->querySubObject("WorkSheets(int)",1);
QAxObject *usedRange = worksheet->querySubObject("UsedRange");//获取表格中的数据范围
QVariant var = usedRange->dynamicCall("Value");//将所有的数据读取刀QVariant容器中保存
QList<QList<QVariant>> excel_list;//用于将QVariant转换为Qlist的二维数组
QVariantList varRows=var.toList();
if(varRows.isEmpty())
{
return;
}
const int row_count = varRows.size();
QVariantList rowData;
for(int i=0;i<row_count;++i)
{
rowData = varRows[i].toList();
excel_list.push_back(rowData);
}//转换完毕
qDebug()<<excel_list.at(3).at(1).toInt();
//然后将二维数组Qlist<Qlist<QVariant>>中的数据读取出来,到表格或者数据模型中,具体在这里过程省略
workbook->dynamicCall( "Close(Boolean)", false );
excel->dynamicCall( "Quit(void)" );
delete excel;//因为前面选择了不显示excel窗口,如果这里不删除excel的话,excel依然是打开的,只是我们看不见(因为被隐藏起来了)
}
其中标红的那一行代码可以用来测试是否能成功读取出二维数组中的数据。
当输入 qDebug()<<excel_list.at(3);时,打印的结果为
因为excel中一共有24列,但是只有6行中有数据,因此可以看到这其实是把表格中第4行的数据(因为是从零开始0,1,2,3)全部读取出来了。
当输入 qDebug()<<excel_list.at(3).at(3);时,打印结果为
即打印出了第4行第4列的数据.。
当输入qDebug()<<excel_list.at(3).at(3).toInt();时,打印的结果就是数字3。
由此,读取excel工作基本完成,至于怎么保存到表格中或者模型中,就看自己喜好啦。文章来源:https://www.toymoban.com/news/detail-612870.html
*Tips: 由于excel中保存的数据读取到Qlist的数组中之后,这不一定是个规则的二维数组,因此在必要的情况下可以将excel的第一二排留出来用于保存信息,这些信息可以用来判断读取时按什么规则来读取。文章来源地址https://www.toymoban.com/news/detail-612870.html
到了这里,关于QT使用QAxObject读取Excel教程-全网最全的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!