背景:
之前项目中用到了动态图表QChart和动态仪表(QPainter、QLabel),其中QLabel实现了故障灯。
QtChart做个动态更新的曲线_qtchart坐标轴动态变化-CSDN博客
原本以为万事大吉。结果测试中发现,仪表和故障灯动态更新时,图表中的曲线闪烁严重。于是开始优化。
先说结果,最终使用画笔重做了故障灯,完美解决,很多朋友还用画笔模拟动画,性能也不错。
下面是分析过程。
图表:
之前做图表时,涉及到series动态更新,需要更新前后解绑和重新绑定坐标轴,亦即detach/attach。所以本次依然尝试在关键的地方重新绑定坐标轴。
这里直接说结果,无论如何还是不解决图表受影响的问题。
仪表优化:
网上看到很多人说QLabel呈现图像消耗cpu,性能不好,但我尝试的结果是,它真正最拉胯的应该是绘制的过程,也就是给它设定图像资源的这个过程,比如动态仪表的故障灯,当数值超过限度时需要设置QLabel加载报警图像。这就影响性能了。但QLabel的好处是使用简单,可以直接使用QMovie播放gif。
首先我用QPainter重做了一个故障灯,gif就没戏了,只能把故障灯亮起的状态单独做个静态图片,然后用定时器切换它,在paintEvent中使用画笔绘制图片。据说,画笔使用的是GPU。
先做一个widget子类,用于取代label显示图像。
guage_pic.h:
cpp
/**************************************************************************************************
** File name: guage_pic.h (Guage_Pic)
** Created by: Henrick.Nie at 2024-9-23
** Used for: Show the picture.
** Using method:
**
** Guage_Pic *wPic = new Guage_Pic;
** wPic->f_Init(...);
** wPic->f_SetWarning(...);
**
**************************************************************************************************/
#ifndef GUAGE_PIC_H
#define GUAGE_PIC_H
#include <QWidget>
#include <QPainter>
#include <QTimerEvent>
#include <QDebug>
class Guage_Pic : public QWidget
{
Q_OBJECT
public:
explicit Guage_Pic(QWidget *parent = nullptr);
inline void f_Init(const QString &sPicPath1, const QString &sPicPath2, const int &iMsec = 500)
{
m_sPicPath1 = sPicPath1;
m_sPicPath2 = sPicPath2;
m_iMsec = iMsec;
m_sPicPath = m_sPicPath1;
}
void f_SetWarning(const bool &bIsOn);
bool f_GetIsOn() const { return m_bIsOn; }
private:
QString m_sPicPath, m_sPicPath1, m_sPicPath2;
int m_iTimerID = 0;
int m_iMsec = 0;
bool m_bIsOn = false;
void paintEvent(QPaintEvent *event);
void f_Draw(QPainter *p);
void timerEvent(QTimerEvent *event);
};
#endif // GUAGE_PIC_H
guage_pic.cpp:
cpp
#include "guage_pic.h"
Guage_Pic::Guage_Pic(QWidget *parent) :
QWidget(parent)
{
}
void Guage_Pic::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
QPainter painter(this);
f_Draw(&painter);
}
void Guage_Pic::f_Draw(QPainter *p)
{
QPixmap pic(m_sPicPath);
int iPicWidth = pic.width(), iPicHeight = pic.height();
int iLeft = (width() - iPicWidth) / 2, iTop = (height() - iPicHeight) / 2;
p->save();
p->drawPixmap(iLeft, iTop, iPicWidth, iPicHeight, pic);
p->restore();
p->end();
}
void Guage_Pic::f_SetWarning(const bool &bIsOn)
{
if (bIsOn == m_bIsOn)
{
return;
}
m_bIsOn = bIsOn;
m_sPicPath.clear();
if (bIsOn)
{
m_iTimerID = this->startTimer(m_iMsec);
m_sPicPath = m_sPicPath2;
// qDebug() << this->objectName() << "timeerid = " << m_iTimerID;
}
else
{
if (0 != m_iTimerID)
{
// qDebug() << this->objectName() << "timeerid kill " << m_iTimerID;
this->killTimer(m_iTimerID);
}
m_sPicPath = m_sPicPath1;
}
this->update();
}
void Guage_Pic::timerEvent(QTimerEvent *event)
{
if (event->timerId() != m_iTimerID)
{
return;
}
// qDebug() << this->objectName() << m_iTimerID;
m_sPicPath = (m_sPicPath != m_sPicPath1) ? m_sPicPath1 : m_sPicPath2;
this->update();
}
然后重做一个故障灯面板。
guage_warning_pic.h:
cpp
/**************************************************************************************************
** File name: guage_warning_pic.h (Guage_Warning_Pic)
** Created by: Henrick.Nie at 2024-9-24
** Used for: Show the warning light by the painter.
** Using method:
**
** Firstly, a image resource path is required.
**
** obj->f_Init(":/images/");
**
**************************************************************************************************/
#ifndef GUAGE_WARNING_PIC_H
#define GUAGE_WARNING_PIC_H
#include <QWidget>
#include <QFrame>
namespace Ui {
class Guage_Warning_Pic;
}
class Guage_Warning_Pic : public QWidget
{
Q_OBJECT
public:
explicit Guage_Warning_Pic(QWidget *parent = nullptr);
~Guage_Warning_Pic();
enum EWarningType { eNone, eWater, eOil, eFuel, eEngine, eBattery, eBrake };
void f_Init(const QString &sRcPathPrefix);
void f_SetStatus(const EWarningType &eType, const bool &bIsWarning);
private:
Ui::Guage_Warning_Pic *ui;
QString m_sRcPathPrefix;
EWarningType m_eType = eNone;
};
#endif // GUAGE_WARNING_PIC_H
guage_warning_pic.cpp:
cpp
#include "guage_warning_pic.h"
#include "ui_guage_warning_pic.h"
Guage_Warning_Pic::Guage_Warning_Pic(QWidget *parent) :
QWidget(parent),
ui(new Ui::Guage_Warning_Pic)
{
ui->setupUi(this);
this->setAttribute(Qt::WA_StyledBackground, true);
this->setStyleSheet("background: black;");
ui->gridLayout->setSpacing(0);
}
Guage_Warning_Pic::~Guage_Warning_Pic()
{
delete ui;
}
void Guage_Warning_Pic::f_Init(const QString &sRcPathPrefix)
{
m_sRcPathPrefix = sRcPathPrefix;
ui->wWater ->f_Init(m_sRcPathPrefix + "warning_water.jpg", m_sRcPathPrefix + "warning_water_on.jpg");
ui->wOil ->f_Init(m_sRcPathPrefix + "warning_oil.jpg", m_sRcPathPrefix + "warning_oil_on.jpg");
ui->wFuel ->f_Init(m_sRcPathPrefix + "warning_fuel.jpg", m_sRcPathPrefix + "warning_fuel_on.jpg");
ui->wEngine ->f_Init(m_sRcPathPrefix + "warning_engine.jpg", m_sRcPathPrefix + "warning_engine_on.jpg");
ui->wBattery->f_Init(m_sRcPathPrefix + "warning_battery.jpg", m_sRcPathPrefix + "warning_battery_on.jpg");
ui->wBrake ->f_Init(m_sRcPathPrefix + "warning_brake.jpg", m_sRcPathPrefix + "warning_brake_on.jpg");
}
void Guage_Warning_Pic::f_SetStatus(const EWarningType &eType, const bool &bIsWarning)
{
Guage_Pic *wPic;
switch (eType) {
case eWater:
wPic = ui->wWater;
break;
case eOil:
wPic = ui->wOil;
break;
case eFuel:
wPic = ui->wFuel;
break;
case eEngine:
wPic = ui->wEngine;
break;
case eBattery:
wPic = ui->wBattery;
break;
case eBrake:
wPic = ui->wBrake;
break;
default:
wPic = nullptr;
break;
}
if (nullptr == wPic)
{
return;
}
if (wPic->f_GetIsOn() != bIsWarning)
{
wPic->f_SetWarning(bIsWarning);
}
}
guage_warning_pic.ui:
cpp
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Guage_Warning_Pic</class>
<widget class="QWidget" name="Guage_Warning_Pic">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>267</width>
<height>120</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2">
<widget class="Guage_Pic" name="wFuel" native="true"/>
</item>
<item row="1" column="1">
<widget class="Guage_Pic" name="wBattery" native="true"/>
</item>
<item row="0" column="1">
<widget class="Guage_Pic" name="wOil" native="true"/>
</item>
<item row="1" column="2">
<widget class="Guage_Pic" name="wBrake" native="true"/>
</item>
<item row="1" column="0">
<widget class="Guage_Pic" name="wEngine" native="true"/>
</item>
<item row="0" column="0">
<widget class="Guage_Pic" name="wWater" native="true"/>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Guage_Pic</class>
<extends>QWidget</extends>
<header>guage_pic.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
结合前篇动态仪表的故障灯用法,很容易就实现。
cpp
void MainWindow::on_sdrWaterTemp_valueChanged(int value)
{
if (m_iSbHandle != 2)
{
m_iSbHandle = 1;
ui->sdrWaterTemp_2->setValue(value);
// qDebug() << "1";
}
ui->labDegree_WaterTemp->setText(QString::number(value));
ui->labDegree_PoloWater->setText(QString::number(ui->lab_WaterTempPolo->f_GetDegrees()));
ui->labValueWaterTemp->setText(QString::number(value));
ui->lab_WaterTemp->f_SetTemp(value);
ui->lab_WaterTempPolo->f_SetValue(value);
ui->wWarning->f_SetStatus(Guage_Warning::eWater, (value > 120));
ui->wWarningPic->f_SetStatus(Guage_Warning_Pic::eWater, (value > 120));
m_iSbHandle = 0;
}
void MainWindow::on_sdrWaterTemp_2_valueChanged(int value)
{
if (m_iSbHandle != 1)
{
m_iSbHandle = 2;
ui->sdrWaterTemp->setValue(value);
// qDebug() << "2";
}
}
void MainWindow::on_sdrFuel_valueChanged(int value)
{
ui->labDegree_PoloFuel->setText(QString::number(ui->lab_FuelPolo->f_GetDegrees()));
ui->labValueFuel->setText(QString::number(value));
ui->lab_FuelPolo->f_SetValue(value);
ui->wWarning->f_SetStatus(Guage_Warning::eFuel, (value < 10));
ui->wWarningPic->f_SetStatus(Guage_Warning_Pic::eFuel, (value < 10));
}
void MainWindow::on_sdrTacho_valueChanged(int value)
{
ui->lab_Tacho->f_SetValue(value);
ui->labValueTacho->setText(QString::number(value));
ui->labDegree_Tacho->setText(QString::number(ui->lab_Tacho->f_GetDegrees()));
ui->wWarning->f_SetStatus(Guage_Warning::eEngine, (value > 6500));
ui->wWarningPic->f_SetStatus(Guage_Warning_Pic::eEngine, (value > 6500));
}
调用跟以前一样,一行搞定。
但上面的代码中,其实已经优化了另外一点。当仪表的数值连续变化时,总是不断调用更新故障灯代码,其实之前这样做很不好。于是都加了标记,有变化再更新设置,没变化就return。
当然,我也按照这个想法改善了之前的label。
Label优化:
guage_labelmovie.h:
cpp
/**************************************************************************************************
** File name: guage_labelmovie.h (Guage_LabelMovie)
** Created by: Henrick.Nie at 2024-9-10
** Used for: Show the picture (jpg/gifs).
**************************************************************************************************/
#ifndef GUAGE_LABELMOVIE_H
#define GUAGE_LABELMOVIE_H
#include <QLabel>
#include <QPainter>
#include <QMovie>
#include <QDebug>
class Guage_LabelMovie : public QLabel
{
Q_OBJECT
public:
explicit Guage_LabelMovie(QWidget *parent = nullptr);
void f_SetJpg(const QString &sFileName);
void f_SetGif(const QString &sFileName);
bool f_GetIsGif() const { return m_bIsGif; }
private:
QMovie m_movie;
bool m_bIsGif = false;
};
#endif // GUAGE_LABELMOVIE_H
guage_labelmovie.cpp:
cpp
#include "guage_labelmovie.h"
Guage_LabelMovie::Guage_LabelMovie(QWidget *parent) :
QLabel(parent)
{
}
void Guage_LabelMovie::f_SetJpg(const QString &sFileName)
{
if (m_bIsGif)
{
m_bIsGif = false;
this->setPixmap(QPixmap(sFileName));
}
}
void Guage_LabelMovie::f_SetGif(const QString &sFileName)
{
if (!m_bIsGif)
{
m_bIsGif = true;
m_movie.stop();
m_movie.setFileName(sFileName);
this->setMovie(&m_movie);
m_movie.start();
}
}
故障灯面板也加标记,更妥善。
guage_warning.cpp:
cpp
#include "guage_warning.h"
#include "ui_guage_warning.h"
Guage_Warning::Guage_Warning(QWidget *parent) :
QWidget(parent),
ui(new Ui::Guage_Warning)
{
ui->setupUi(this);
// this->setAttribute(Qt::WA_StyledBackground, true);
this->setStyleSheet("background: black;");
ui->gridLayout->setSpacing(0);
}
Guage_Warning::~Guage_Warning()
{
delete ui;
}
void Guage_Warning::f_Init(const QString &sRcPathPrefix)
{
m_sRcPathPrefix = sRcPathPrefix;
ui->labWater ->setPixmap(QPixmap(m_sRcPathPrefix + "warning_water.jpg"));
ui->labOil ->setPixmap(QPixmap(m_sRcPathPrefix + "warning_oil.jpg"));
ui->labFuel ->setPixmap(QPixmap(m_sRcPathPrefix + "warning_fuel.jpg"));
ui->labEngine ->setPixmap(QPixmap(m_sRcPathPrefix + "warning_engine.jpg"));
ui->labBattery->setPixmap(QPixmap(m_sRcPathPrefix + "warning_battery.jpg"));
ui->labBrake ->setPixmap(QPixmap(m_sRcPathPrefix + "warning_brake.jpg"));
}
void Guage_Warning::f_SetStatus(const EWarningType &eType, const bool &bIsWarning)
{
Guage_LabelMovie *lab;
QString sJpg, sGif;
switch (eType) {
case eWater:
lab = ui->labWater;
sJpg = "warning_water.jpg";
sGif = "warning_water.gif";
break;
case eOil:
lab = ui->labOil;
sJpg = "warning_oil.jpg";
sGif = "warning_oil.gif";
break;
case eFuel:
lab = ui->labFuel;
sJpg = "warning_fuel.jpg";
sGif = "warning_fuel.gif";
break;
case eEngine:
lab = ui->labEngine;
sJpg = "warning_engine.jpg";
sGif = "warning_engine.gif";
break;
case eBattery:
lab = ui->labBattery;
sJpg = "warning_battery.jpg";
sGif = "warning_battery.gif";
break;
case eBrake:
lab = ui->labBrake;
sJpg = "warning_brake.jpg";
sGif = "warning_brake.gif";
break;
default:
lab = nullptr;
break;
}
if ((nullptr == lab) || (lab->f_GetIsGif() == bIsWarning))//标记判断
{
return;
}
void (Guage_LabelMovie::*f_ptr)(const QString &sFileName)
= bIsWarning
? &Guage_LabelMovie::f_SetGif
: &Guage_LabelMovie::f_SetJpg;
QString sPic = bIsWarning ? sGif : sJpg;
(lab->*f_ptr)(m_sRcPathPrefix + sPic);
}
总结:
对于我的项目,即使优化了label,也还是会影响chart,所以最终使用了画笔实现故障灯。
可我还是保留了label的实现方法,从代码也能看出来,label的实现就是好用,很简洁。如果是不用频繁更新显示的情况,还是label更方便。
所以都整理了一下,以后做项目会用到。
本文完。