QT5+VS2017 绘制曲线(QCustomPlot)及EXCEL的快速读取和写入(QAxObject)教程

    科技2024-01-09  79

    目录

    摘要1. 利用QCustomPlot绘制曲线1.1 QCustomPlot环境配置1.2 添加绘图窗口1.3 绘制曲线 2. EXCEL文件的读取2.1 慢速读取2.2 快速读取 3. EXCEL文件的写入3.1快速写入 4. 效果展示4.1 读取Excel数据及绘图4.2 接收数据、绘图及保存为Excel 5. 项目下载

    摘要

    本篇文章对一下几点进行了总结 1.利用QCustomPlot绘制曲线的基本操作 2.利用QAxObject对excel文件进行读取、写入操作 3.比较了QT对Excel文件快速和慢速读写方法的异同 4.总结了一些常见坑爹地方

    1. 利用QCustomPlot绘制曲线

    1.1 QCustomPlot环境配置

      QCustomPlot的安装和配置方法这里不赘述,直接看连接。链接: VS2012 使用QCustomPlot等三方库如何配置。

    需要注意几点: 1.如果你用VS+QT联合开发不需要在pro中需要添加以下代码

    QT += printsupport

    2.一定要记得配置动态链接库Qt5PrintSupport.lib/Qt5PrintSupportd.lib文件,否则报错。

    1.2 添加绘图窗口

      打开Qt Designer 进入图形化设计界面,向主窗口中添加一个widget区域,对着所添加的widget区域点击右键,选择“提升为”按钮。提升的类名称中输入“QCustomPlot”,注意QCustomPlot的大小写一定不要拼错。然后点击添加。在之后的界面中选中QCustomPlot,点击提升按钮,我们创建的widget就被提升为QCustomPlot类了。

    1.3 绘制曲线

    利用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 即可解决。

    2. EXCEL文件的读取

    这里我将对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驱动

    2.1 慢速读取

    慢速读取就是一般的读取方法,也就是对表中的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应用程序的解决办法和.

    2.2 快速读取

    快速读取不同于慢速读取,不利用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()); } }

    3. EXCEL文件的写入

    excel的写入和读取思路差不多,这里我直接给出快速写入的代码,慢速写入可以在我上传的项目中查看,

    3.1快速写入

    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); }

    4. 效果展示

    4.1 读取Excel数据及绘图

    下图演示的是使用快速读取方法进行excel数据的读取并绘制曲线,可以看到除了第一次读取数据较慢,随后读取数据时间都很短。

    4.2 接收数据、绘图及保存为Excel

    5. 项目下载

    链接: QT5+VS2017 对EXCEL文件的快速读取及写入,并绘制曲线.

    Processed: 0.011, SQL: 8