本篇文章对一下几点进行了总结 1.利用QCustomPlot绘制曲线的基本操作 2.利用QAxObject对excel文件进行读取、写入操作 3.比较了QT对Excel文件快速和慢速读写方法的异同 4.总结了一些常见坑爹地方
QCustomPlot的安装和配置方法这里不赘述,直接看连接。链接: VS2012 使用QCustomPlot等三方库如何配置。
需要注意几点: 1.如果你用VS+QT联合开发不需要在pro中需要添加以下代码
QT += printsupport2.一定要记得配置动态链接库Qt5PrintSupport.lib/Qt5PrintSupportd.lib文件,否则报错。
打开Qt Designer 进入图形化设计界面,向主窗口中添加一个widget区域,对着所添加的widget区域点击右键,选择“提升为”按钮。提升的类名称中输入“QCustomPlot”,注意QCustomPlot的大小写一定不要拼错。然后点击添加。在之后的界面中选中QCustomPlot,点击提升按钮,我们创建的widget就被提升为QCustomPlot类了。
利用QCustomPlot绘制曲线需要将X、Y轴的数据存储在QVector中,具体代码如下。
.h文件
#include <QtWidgets/QMainWindow> #include "ui_qt_excel_test.h" class Qt_excel_test : public QMainWindow { Q_OBJECT public: Qt_excel_test(QWidget *parent = Q_NULLPTR); ~Qt_excel_test(); private: Ui::Qt_excel_testClass ui; QVector<double> x; QVector<double> y; }.cpp文件
#include "qt_excel_test.h" #include "qcustomplot.h" Qt_excel_test::Qt_excel_test(QWidget *parent) : QMainWindow(parent) { ui.setupUi(this); /*************绘图模块***************/ //设置坐标轴参数 ui.widget_my->xAxis->setLabel("x轴"); ui.widget_my->xAxis->setRange(0, 200); ui.widget_my->yAxis->setLabel("y轴"); ui.widget_my->yAxis->setRange(0, 40000); //设定右上角图形标注可见 ui.widget_my->legend->setVisible(true); //设定右上角图形标注的字体 ui.widget_my->legend->setFont(QFont("Helvetica", 9)); //添加图形 可以添加多条曲线 ui.widget_my->addGraph(); ui.widget_my->addGraph(); //设置画笔 ui.widget_my->graph(0)->setPen(QPen(Qt::blue)); ui.widget_my->graph(1)->setPen(QPen(Qt::red)); //设置右上角图形标注名称 ui.widget_my->graph(0)->setName("目标曲线"); ui.widget_my->graph(1)->setName("接收曲线"); //绘制第一条曲线 ui.widget_my->graph(0)->setData(x, y); ui.widget_my->replot(); }如果中文汉字乱码的话在 属性页->C/C+±>命令行 添加 /execution-charset:utf-8 即可解决。
这里我将对EXCEL的操作封装成了一个类。将慢速读取和快速读取封装成类中的两个成员函数。因为我项目需要,我所要读取的excel文件只有两列,即X和Y两列。如您要读取未知列数的excel,可在我的代码基础上修改。
#pragma once #include <QAxObject> #include <QDir> class qtexcel { public: qtexcel(); ~qtexcel(); /*读取*/ void readExcelFast(QString fileName, QVector<double> &x, QVector<double> &y);//快速读取函数 void castVariant2ListListVariant(QVariant &var, QList<QList<QVariant> > &x_y);//把QVariant转为QList<QList<QVariant> >,用于快速读出的 void castVariant2ListListVariant(QVariant &var, QVector<double> &x, QVector<double> &y);//函数的重载 void readExcelSlow(QString fileName, QVector<double> &x, QVector<double> &y);//慢速读取函数 /*写入*/ void writeExcelFast(QString fileName, QList<QList<QVariant> > &x_y);//快速写入 void castListListVariant2Variant(QList<QList<QVariant> > &cells, QVariant &res);//把QList<QList<QVariant> > 转为QVariant,用于快速写入的 void Excel_SetCell(QAxObject *worksheet, int row, int column, QString text);//按单元写入 void convert2ColName(int data, QString &res);//把列数转换为excel的字母列号 QString to26AlphabetString(int data);//数字转换为26字母 private: QAxObject * excel; QAxObject * workbooks; QAxObject * workbook; QAxObject * worksheets; QAxObject * worksheet; QAxObject * usedrange_Read;//读取数据矩形区域 QAxObject * usedrange_Write;//写入数据矩形区域 QAxObject * rows;//行数 QAxObject * columns;//列数 int WorkSheetCount;//Excel文件中表的个数 int RowsCount;//行总数 int ColumnsCount;//列总数 int StartRow;//数据的起始行 int StartColumn;//数据的起始列 QVariant var; };qtexcel类的构造函数及析构函数的实现
qtexcel::qtexcel() { excel = new QAxObject("Excel.Application");//加载Excel驱动 excel->setProperty("Visible", false); //不显示Excel界面,如果为true会看到启动的Excel界面 workbooks = excel->querySubObject("WorkBooks"); } qtexcel::~qtexcel() { delete excel; excel = NULL; }注意,一定要把加载Excel驱动这句话写在构造函数里,否则快速读取时速度也会很慢。
excel = new QAxObject(“Excel.Application”);//加载Excel驱动
慢速读取就是一般的读取方法,也就是对表中的cell一个一个的读取,每读取一个数据都要调用一次
worksheet->querySubObject(“Cells(int, int)”, i, j);
这就是导致这种方法读取速度慢的根本原因。
具体实现代码如下
void qtexcel::readExcelSlow(QString fileName, QVector<double> &x, QVector<double> &y) { workbooks->querySubObject("Open(QString&)", fileName);//按文件路径打开文件 workbook = excel->querySubObject("ActiveWorkBook");// 激活当前工作簿 worksheets = workbook->querySubObject("WorkSheets");// 获取打开的excel文件中所有的工作sheet WorkSheetCount = worksheets->property("Count").toInt();//Excel文件中表的个数: worksheet = worksheets->querySubObject("Item(int)", 1);//获取第一个工作表,最后参数填1 usedrange_Read = worksheet->querySubObject("UsedRange");//获取该sheet的数据范围(可以理解为有数据的矩形区域) //获取行数 rows = usedrange_Read->querySubObject("Rows"); RowsCount = rows->property("Count").toInt(); //获取列数 columns = usedrange_Read->querySubObject("Columns"); ColumnsCount = columns->property("Count").toInt(); //数据的起始行 StartRow = rows->property("Row").toInt(); //数据的起始列 StartColumn = columns->property("Column").toInt(); //——————————————读出数据(慢速)————————————— QAxObject *cell_x; // 用于定位的指针 QAxObject *cell_y; QVariant cell_value_x; // 存储值信息 QVariant cell_value_y; int j = StartColumn; for (int i = StartRow; i <= RowsCount; ++i) { cell_x = worksheet->querySubObject("Cells(int, int)", i, j); cell_y = worksheet->querySubObject("Cells(int, int)", i, j+1); cell_value_x = cell_x->property("Value"); // 获取单元格内容 cell_value_y = cell_y->property("Value"); //一定要注意,如果cell_value的值是无效的话,检查电脑DCOM配置中有没有Microsoft Excel应用程序,没有的话添加 x.push_back(cell_value_x.toDouble()); y.push_back(cell_value_y.toDouble()); } //一定要记得close,不然系统进程里会出现n个EXCEL.EXE进程 workbook->dynamicCall("Close(bool)", false); //关闭文件 excel->dynamicCall("Quit()"); }这里有一个大坑!因为我的电脑里同时安装了WPS和OFFICE,在对cell进行操作时总是提示我cell的值无效,这里要检查电脑DCOM配置中有没有Microsoft Excel应用程序,没有的话添加。 添加方法可以参考该链接: WIN7中组件服务中的DCOM配置找不到Microsoft Excel应用程序的解决办法和.
快速读取不同于慢速读取,不利用cell读取数据。而是将excel中有数据的矩形区域直接赋值给QVariant类型的变量,这样我们再利用 castVariant2ListListVariant 函数将 QVariant 转化为 QList<QList< QVariant >>类型,方便之后的使用。因为项目需要,这里我直接将QVariant 转换为两个 QVector 类型的数据。
void qtexcel::readExcelFast(QString fileName, QVector<double> &x, QVector<double> &y) { workbooks->querySubObject("Open(QString&)", fileName);//按文件路径打开已存在的工作簿 workbook = excel->querySubObject("ActiveWorkBook");// 获取活动工作簿 worksheets = workbook->querySubObject("WorkSheets");// 获取打开的excel文件中所有的工作sheet WorkSheetCount = worksheets->property("Count").toInt();//Excel文件中表的个数: worksheet = worksheets->querySubObject("Item(int)", 1);//获取第一个工作表,最后参数填1 usedrange_Read = worksheet->querySubObject("UsedRange");//获取该sheet的数据范围(可以理解为有数据的矩形区域) //获取行数 rows = usedrange_Read->querySubObject("Rows"); RowsCount = rows->property("Count").toInt(); //获取列数 columns = usedrange_Read->querySubObject("Columns"); ColumnsCount = columns->property("Count").toInt(); //数据的起始行 StartRow = rows->property("Row").toInt(); //数据的起始列 StartColumn = columns->property("Column").toInt(); if (worksheet != NULL && !worksheet->isNull()) { if (NULL == usedrange_Read || usedrange_Read->isNull()) { return; } var = usedrange_Read->dynamicCall("Value"); castVariant2ListListVariant(var,x,y); // 此函数将var转换成我们需要的格式 } //一定要记得close,不然系统进程里会出现n个EXCEL.EXE进程 workbook->dynamicCall("Close(bool)", false); //关闭文件 excel->dynamicCall("Quit()"); } void qtexcel::castVariant2ListListVariant(QVariant &var, QVector<double> &x, QVector<double> &y) { QVariantList varRows; varRows = var.toList(); if (varRows.isEmpty()) { return; } const int rowCount = varRows.size(); QVariantList rowData; for (int i = 0; i < rowCount; ++i) { rowData = varRows[i].toList(); x.push_back(rowData.value(0).toDouble()); y.push_back(rowData.value(1).toDouble()); } }excel的写入和读取思路差不多,这里我直接给出快速写入的代码,慢速写入可以在我上传的项目中查看,
fileName 是保存数据的路径 x_y 是要写入的数据,类型是QList<QList< QVariant >>
void qtexcel::writeExcelFast(QString fileName, QList<QList<QVariant>> & x_y) { workbooks->dynamicCall("Add");//新建一个工作表。 新工作表将成为活动工作表 workbook = excel->querySubObject("ActiveWorkBook");// 获取活动工作簿 worksheet = workbook->querySubObject("Sheets(int)", 1); //获取第一个工作表,最后参数填1 int row = x_y.size();//行数 int col = x_y.at(0).size();//列数 /*将列数转换成EXCEL中列的字母形式*/ QString rangStr; convert2ColName(col, rangStr); rangStr += QString::number(row); rangStr = "A1:" + rangStr; usedrange_Write = worksheet->querySubObject("Range(const QString&)", rangStr); QVariant var; castListListVariant2Variant(x_y, var); usedrange_Write->setProperty("Value", var); workbook->dynamicCall("SaveCopyAs(QString)", QDir::toNativeSeparators(fileName)); workbook->dynamicCall("Close(bool)", false); //关闭文件 excel->dynamicCall("Quit()");//关闭excel } void qtexcel::castListListVariant2Variant(QList<QList<QVariant>>& cells, QVariant & res) { QVariantList vars; const int rowCount = cells.size(); for (int i = 0; i < rowCount; ++i) { vars.append(QVariant(cells[i])); } res = QVariant(vars); } // brief 把列数转换为excel的字母列号 // param data 大于0的数 // return 字母列号,如1->A 26->Z 27 AA void qtexcel::convert2ColName(int data, QString &res) { Q_ASSERT(data > 0 && data < 65535); int tempData = data / 26; if (tempData > 0) { int mode = data % 26; convert2ColName(mode, res); convert2ColName(tempData, res); } else { res = (to26AlphabetString(data) + res); } } // brief 数字转换为26字母 // 1->A 26->Z QString qtexcel::to26AlphabetString(int data) { QChar ch = data + 0x40;//A对应0x41 return QString(ch); }下图演示的是使用快速读取方法进行excel数据的读取并绘制曲线,可以看到除了第一次读取数据较慢,随后读取数据时间都很短。
链接: QT5+VS2017 对EXCEL文件的快速读取及写入,并绘制曲线.