目录
摘要
本篇文章对一下几点进行了总结
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文件,否则报错。
3.在项目属性->链接器 ->常规->附加库目录 添加 $(QTDIR)\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数据的读取并绘制曲线,可以看到除了第一次读取数据较慢,随后读取数据时间都很短。