VS2017+QT5.9.2中使用多媒体定时器+qcustomplot绘制动态曲线,从环境搭建到程序打包发布的一个相对完整的流程。定时器的精度实际没有测试,按我参考的博主的说法应该是ms级别的,我实际使用的时候是定时2ms。
也是初学QT的小白,下面的操作可能也不是很规范。都是按实际操作经验总结瞎掰出来,最后应该还是能用吧。
对于VS+QT的环境配置网上相关的教程比较多,这里就不再赘述。
对于这一部分,我也没用参考什么教程,就直接无脑安装,先安装QT5.9.2,好像是全部勾选(但可能有点组件并不需要),最终大小为12G左右;然后在安装VS2017社区版,我安装的时候也是无脑选择前面三个组件,或许QT开发不需要这么多,需要注意的是C++通用windows平台工具是必要的,默认似乎不会勾选这个选项,然后等待它安装成功,大概有20G吧。
然后在VS中配置QT环境,在 工具>扩展和更新>联机 中搜索QT,然后就可以看见QT Visual Studio Tools,点击下载即可,然后关闭VS2017后会弹出一个更新界面,点击 修改 就安装成功了,重新打开VS2017,界面的选项栏就会多一个 QT VS Tools 的选项,点击该选项,点击 Qt Options,在其中添加安装QT的路径对应的编译器。
我对应是这样的路径 C:\Qt\Qt5.9.6\5.9.6\msvc2017_64,然后点击确定就基本完成了。
在配置好环境后,在VS2017中新建项目时,就会在 Visual C++ 中多出一个 QT 的选项。
我们选择QT的窗体程序,命名为qt_demo,新建工程。之后还会弹出一些关于QT基本设置的窗体选项,主要在于下面这个选项配置。
我们选择debug,然后 QT Version 就现在之前配置的 msvc2017_64,然后对于Qt Modules 默认会帮你选上三个必要的module,点击那个表格框,就会弹出全部的选项。
对于我需要实现的动态绘图功能,QT官方有一个 Charts 的库,但是用过后感觉很一般吧,点数多的时候,刷新频率就不能太快,论坛上面说似乎只有50hz吧。所以这里没有勾选它。然后一路 Next Finish,QT工程就创建完毕了。
最后,需要留意的是工程对应的SDK版本,在项目上 右键>属性>配置属性>常规 中查看,SDK版本需要换为 SDK10。(另外,由于我电脑里面同时装了VS2013和VS2017,所以在平台工具集会默认选择2013,需要自己手动换成2017)。
由于甲方的需要,我需要一个大概2ms的定时器,虽然QT里面自带定时器控件,但是定时精度似乎只有20ms,然后解决方案是调用Windows的多媒体定时器来达到ms精度的定时功能。我参考这位博主的方法,十分感谢这位博主:
Qt之高精度多媒体定时器:https://blog.csdn.net/iliukunpeng/article/details/79211201
直接根据这个博文,建立相应的头文件和源文件,我命名为了performancetimer,将它们放入项目文件夹中,然后将它们添加到项目中即可。
经过个人的实践,在纯QT环境中,可以直接使用,但是需要在工程对应的 .pro 文件中添加 LIBS += -lwinmm ,添加这个库才可以编译成功使用。
LIBS += -lwinmm
在VS+QT的环境中,上述博文中头文件中的
friend WINAPI void CALLBACK PeriodCycle(uint,uint,DWORD_PTR,DWORD_PTR,DWORD_PTR);
// 需要替换为
friend void WINAPI CALLBACK PeriodCycle(uint,uint,DWORD_PTR,DWORD_PTR,DWORD_PTR);
然后在项目的头文件中需要添加 #pragma comment(lib,“Winmm.lib”),来添加对应的库。就可以正常使用了。
// qt_demo.h中
#include "performancetimer.h"
#pragma comment(lib,"Winmm.lib")
对于QT中动态绘图的功能,即我想要的示波器功能不像NI的一些仪器仪表软件封装的那么方便,QT中自带的绘图库QCharts确实不太行(也可能是我不太会使用),然后我选择了第三方绘图库QCustomplot,它的使用和配置都比较方便而且性能也能达到我的需求。
对于qcustomplot的基本配置网上有很多教程,这里不再赘述。不能说毫无难度,但可以说是非常简单。只需要去qcustomplot官网下载压缩包。好像里面就这么点东西。。。
然后将qcustomplot.h和qcustomplot.cpp放入工程文件夹,在将它们添加到工程中,然后还需要添加一个库,在项目上 右键>属性>链接器>输入>附加依赖项 中添加“Qt5PrintSupportd.lib”,然后对应 release 里面是“Qt5PrintSupport.lib”,这里可以顺手添加,不然以后发布程序的时候,忘记这茬事就很尴尬了。。。然后就可以用啦。
我们小试一波,打开QT界面文件(qt_demo.ui),然后在主界面中拖入一个Widget控件,缩放到合适的大小,点击 右键>提升为…,然后出现如下界面,然后在 提升类的名称:中输入 QCustomPlot(一定要注意大小写哈,不然会很尴尬的,,),点击 添加,然后选中刚刚添加的类,点击 提升。
在保存ui文件后,在debug下点击运行,就会出现如下效果了。
在纯QT环境中,操作也类似,就是对于 printsupport 库的添加方式不太一样,只需要在.pro文件中添加:
QT += printsupport
对于动态绘图,我的思路就很简单,就是创建两个定时器,一个用于往向量中更新数据,我设置了2ms一次;一个用于刷新绘图,包括更新坐标轴和更新曲线,我设置了100ms一次。然后我在界面中放置了两个示波器,都绘制一个简单的正弦函数,也放置了一些按钮,用了开启和停止定时器,增大和缩小正弦波幅值。最后运行结果大概是这个样子的。
我就直接上代码了,主要是qt_demo.h和qt_demo.cpp文件,关键的地方也做了一些必要的注释;剩下就是ui文件,按上图自己搭建就好。然后整个工程文件的下载链接在文末,跳转自行下载哈。
qt_demo.h
#pragma once
#include
#include "ui_qt_demo.h"
#include
#include "performancetimer.h"
#pragma comment(lib,"Winmm.lib")
class qt_demo : public QMainWindow
{
Q_OBJECT
public:
qt_demo(QWidget *parent = Q_NULLPTR);
private:
Ui::qt_demoClass ui;
PerformanceTimer* timer_chart;
PerformanceTimer* timer_data;
private slots:
void on_pushButton_clicked(); // 点击启动绘图
void on_pushButton_2_clicked(); // 点击停止绘图
void on_pushButton_3_clicked(); // 点击增大正弦函数幅值
void on_pushButton_4_clicked(); // 点击减小正弦函数幅值
void handleTimeout_chart(); // 绘图的定时回调函数
void handleTimeout_data(); // 控制的定时回调函数
};
qt_demo.cpp
#include "qt_demo.h"
#include "qcustomplot.h"
int timer_interval = 2; // 数据添加定时器的定时间隔,2ms
float timer_interval_f = timer_interval / 1000.0;
int timer_interval_chart = 100; // 绘图定时器的定时间隔,100ms
QVector data_x1, data_y1, data_x2, data_y2;
int axisTime = 40; // 横坐标显示时间长度
int dataSzie = axisTime * 1000 / timer_interval; // 图标显示点的个数
int flag_axis = 0; // 是否更新坐标轴的标志位
int counter = 0; // 数据添加时间的计数器
int counter_chart = 0; // 绘图计数器
float F = 1.0;
qt_demo::qt_demo(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
QFont font, font_tick;
font.setPointSize(12);
font.setWeight(50);
font_tick.setPointSize(10);
// 初始化绘图界面
ui.widget_1->addGraph(); // 加入一条曲线
ui.widget_1->yAxis->setRange(0, 10); // 设置横纵坐标轴范围
ui.widget_1->xAxis->setRange(0, axisTime);
ui.widget_1->xAxis->setLabel(QStringLiteral("时间/s")); // 设置横纵坐标轴标签
ui.widget_1->yAxis->setLabel(QStringLiteral("压力/MPa"));
ui.widget_1->xAxis->setLabelFont(font);
ui.widget_1->xAxis->setTickLabelFont(font_tick);
ui.widget_1->yAxis->setLabelFont(font);
ui.widget_1->yAxis->setTickLabelFont(font_tick);
ui.widget_2->addGraph();
ui.widget_2->yAxis->setRange(0, 10);
ui.widget_2->xAxis->setRange(0, axisTime);
ui.widget_2->xAxis->setLabel(QStringLiteral("时间/s"));
ui.widget_2->yAxis->setLabel(QStringLiteral("压力/MPa"));
ui.widget_2->xAxis->setLabelFont(font);
ui.widget_2->xAxis->setTickLabelFont(font_tick);
ui.widget_2->yAxis->setLabelFont(font);
ui.widget_2->yAxis->setTickLabelFont(font_tick);
// 给开始和停止按钮贴图
QIcon ico("./picture/start.png");
ui.pushButton->setIcon(ico);
ui.pushButton->setIconSize(QSize(50, 50));
ui.pushButton->setFlat(true);
QIcon ico_stop("./picture/stop.png");
ui.pushButton_2->setIcon(ico_stop);
ui.pushButton_2->setIconSize(QSize(50, 50));
ui.pushButton_2->setFlat(true);
// 创建多媒体定时器
timer_chart = new PerformanceTimer(this);
connect(timer_chart, SIGNAL(timeout()), this, SLOT(handleTimeout_chart())); // 绑定槽函数
timer_data = new PerformanceTimer(this);
connect(timer_data, SIGNAL(timeout()), this, SLOT(handleTimeout_data()));
}
void qt_demo::on_pushButton_clicked()
{
timer_data->start(timer_interval);
timer_chart->start(timer_interval_chart);
ui.pushButton->setEnabled(false);
}
void qt_demo::on_pushButton_2_clicked()
{
timer_data->stop();
timer_chart->stop();
ui.pushButton->setEnabled(true);
}
void qt_demo::on_pushButton_3_clicked()
{
F = F + 0.1;
}
void qt_demo::on_pushButton_4_clicked()
{
F = F - 0.1;
}
void qt_demo::handleTimeout_data()
{
float y1, y2;
float T;
T = timer_interval_f * counter;
y1 = F * sin(T) + 1;
y2 = F * sin(T) - 1;
// 添加新的数据进入数据向量
data_x1.append(T);
data_y1.append(y1);
data_x2.append(T);
data_y2.append(y2);
// 移除不在显示范围的数据
if (data_x1.size() > dataSzie)
{
data_x1.pop_front();
data_y1.pop_front();
data_x2.pop_front();
data_y2.pop_front();
flag_axis = 1;
}
counter++;
}
void qt_demo::handleTimeout_chart()
{
float axis_min;
float axis_max;
float y_max, y_min;
// 自动按照y值最大值、最小值更新y轴范围
y_max = (*(std::max_element(std::begin(data_y1), std::end(data_y1))));
y_max = y_max>0 ? y_max*1.05 : y_max*0.95;
y_min = (*(std::min_element(std::begin(data_y1), std::end(data_y1))));
y_min = y_min>0 ? y_min*0.95 : y_min*1.05;
ui.widget_1->yAxis->setRange(y_min, y_max);
y_max = (*(std::max_element(std::begin(data_y2), std::end(data_y2))));
y_max = y_max > 0 ? y_max * 1.05 : y_max * 0.95;
y_min = (*(std::min_element(std::begin(data_y2), std::end(data_y2))));
y_min = y_min > 0 ? y_min * 0.95 : y_min * 1.05;
ui.widget_2->yAxis->setRange(y_min, y_max);
// 更新x轴范围
if (flag_axis == 1)
{
axis_min = counter_chart * timer_interval_chart / 1000.0 - axisTime;
axis_max = counter_chart * timer_interval_chart / 1000.0;
ui.widget_1->xAxis->setRange(axis_min, axis_max);
ui.widget_2->xAxis->setRange(axis_min, axis_max);
}
// 刷新图表
ui.widget_1->graph(0)->setData(data_x1, data_y1);
ui.widget_1->replot();
ui.widget_2->graph(0)->setData(data_x2, data_y2);
ui.widget_2->replot();
counter_chart++;
}
对于VS+QT的程序的打包发布网上也有许多教程,可以参考这个博文:
VS+Qt应用开发-发布Release程序打包发布:https://blog.csdn.net/qq_36170958/article/details/108717691
1、首先把原先的程序使用 release 进行编译,注意!!!原先在debug中添加的库也需要在release里面添加一遍,这个demo里面就只有“Qt5PrintSupport.lib”了。其实使用debug发布也是可以的,可以参考这个博文:vs 项目发布Debug和Release区别。
2、打开cmd,进入Qt安装目录下对应编译器的bin文件路径,我的是 C:\Qt\Qt5.9.2\5.9.2\msvc2017_64\bin,
3、输入命令 windeployqt (工程的release路径),例如 windeployqt C:\Users\135\source\repos\qt_demo\x64\Release 回车,即生成成功了,然后工程的release路径中的.exe文件就可以双击运行了。
4、如果工程使用了一些额外的图片或者dll资源时,也需要把对应的资源复制到.exe所在的文件夹中。这个demo工程里面使用两个贴图,所以也需要将存放图片的picture文件夹复制到.exe文件夹中,不然图片无法正常显示。然后将这个文件夹压缩打包就算结束辽。
工程文件的百度云链接:https://pan.baidu.com/s/1aOUrMf93Lfouyl4twCLFcg
提取码:42ia