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

摘要

本篇文章对一下几点进行了总结
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数据的读取并绘制曲线,可以看到除了第一次读取数据较慢,随后读取数据时间都很短。
读取Excel数据及绘图

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

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

5. 项目下载

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

评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天才小小傲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值