Qt自定义控件仪表盘

效果

初始效果:

调整后:

代码

 头文件

仪表头文件:

#pragma once
#pragma execution_character_set("utf-8")
#include <qpainter.h>
#include <qpen.h>
#include <qbrush.h>
#include <qcolor.h>
#include <qlabel.h>
#include <qtransform.h>
#include <qrectf>
#include <qdebug.h>
#include <qvector.h>
#include <qwheelevent>
#include <qmath.h>
#include <qtimer.h>
#include <qmessagebox.h>
#define _USE_MATH_DEFINES
#include "math.h"
#include <qwidget.h>
class Gauge :
    public QWidget
{
    Q_OBJECT

public:
    Gauge(QWidget* parent = nullptr);
    ~Gauge();
    void startPainting();
    void setVal(const float& val);
    bool setParts(const int& num);
    bool setOutlineWidth(const float& num);
    bool setSeparatorWidth(const float& num);
    bool setStartNum(const float& num);
    bool setStepNum(const float& num);
    bool setNumSize(const float& num);
    bool setTextSize(const float& num);
    bool setValSize(const float& num);
    bool setText(const QString& str);
private:
    int parts;//共有多少个分割区间
    int oulineWidth;//仪表轮廓曲线粗度
    int separatorWidth;//分隔符粗度
    float startNum;//轮廓处起始数值
    float stepNum;//每次递增的数值
    float numSize;//仪表轮廓数值尺寸
    float startAngle;//起始角度
    float textSize;//仪表中央文本尺寸
    float needleAngle;//由于绘制指针时的角度坐标与绘制轮廓处数值时不同,故须单独定义指针的起始角度
    float partAngle; //每个分割区间占用角度
    float radius;//仪表半径
    float stepVal;//指针每次移动所增加的数值
    float currentVal;//指针当前所指向的数值
    float valSize;//指针指向值尺寸
    float input;
    float count;
    QRectF map;//仪表所处控件的矩形
    QRectF textRect;//绘制文本时的矩形
    QPen pen;
    QString text;//需要在仪表中央绘制的文本
    QPainter* painter;
    QTimer* timer;
    void drawGauge();
    void drawText();
    void drawSeparator();
    void drawSperateNumber();
    void drawCenterPoint();
    void drawNeedle();
    void drawValue();
    float bounceFunc(float x);
    void paintEvent(QPaintEvent* event);
public slots:
    void onTimeOut();
};

主窗口头文件:

#pragma once

#include <QtWidgets/QWidget>
#include "ui_GuageDemo.h"
#include "Gauge.h"
#include <qrandom.h>
#include <qthread.h>
class GuageDemo : public QWidget
{
    Q_OBJECT

public:
    GuageDemo(QWidget *parent = nullptr);
    ~GuageDemo();
    Gauge* gauge;
private:
    Ui::GuageDemoClass ui;
public slots:
    void onSliderMoved(int);
};

源文件

仪表源文件:

#include "Gauge.h"

Gauge::Gauge(QWidget* parent)
	:QWidget(parent), parts(15), startAngle(40), startNum(0), stepNum(5), numSize(10), oulineWidth(12), separatorWidth(5), text("Gauge\nDemo"), textSize(16), valSize(16), count(0)
{
	this->timer = new QTimer(this);
    connect(this->timer, SIGNAL(timeout()), this, SLOT(onTimeOut()));
}

Gauge::~Gauge() {
    delete this->painter;
    delete this->timer;
}

float Gauge::bounceFunc(float x) {
	float res = 2 * (this->input - this->currentVal) / (qExp(-0.5 * x) + 1) - (this->input - this->currentVal);
	return res;
}

void Gauge::startPainting() {
    this->partAngle = (360 - 2 * this->startAngle) / this->parts;
    this->stepVal = this->stepNum / this->partAngle;
    this->currentVal = this->startNum;
    this->needleAngle = this->startAngle - 270;
    this->map = this->rect();
    this->radius = this->map.height() > this->map.width() ? this->map.width() : this->map.height() * 0.8 / 2;
    this->painter = new QPainter(this);
    update();
}

void Gauge::setVal(const float& val) {
    this->timer->stop();
    this->count = 0;
    this->input = val;
    if (this->input<this->startNum || this->input>this->startNum + this->stepNum * this->parts) {
        QMessageBox::warning(this, "", "输入错误");
        return;
    }
    this->timer->setInterval(1);
    this->timer->start();
}

bool Gauge::setParts(const int& num) {
    if (num > 0) {
        this->parts = num;
        startPainting();
        return true;
    }
    return false;
}

bool Gauge::setOutlineWidth(const float& num)
{
    if (num > 0) {
        this->oulineWidth = num;
        startPainting();
        return true;
    }
    return false;
}

bool Gauge::setSeparatorWidth(const float& num) {
    if (num > 0) {
        this->separatorWidth = num;
        startPainting();
        return true;
    }
    return false;
}

bool Gauge::setStartNum(const float& num) {
    if (num > 0) {
        this->startNum = num;
        startPainting();
        return true;
    }
    return false;
}

bool Gauge::setStepNum(const float& num) {
    this->stepNum = num;
    startPainting();
    return true;
}

bool Gauge::setNumSize(const float& num) {
    if (num > 0) {
        this->numSize = num;
        startPainting();
        return true;
    }
    return false;
}

bool Gauge::setTextSize(const float& num) {
    if (num > 0) {
        this->textSize = num;
        startPainting();
        return true;
    }
    return false;
}

bool Gauge::setValSize(const float& num) {
    if (num > 0) {
        this->valSize = num;
        startPainting();
        return true;
    }
    return false;
}

bool Gauge::setText(const QString& str) {
    if (str.size() > 1 && str.size() < 20) {
        this->text = str;
        startPainting();
        return true;
    }
    return false;
}

void Gauge::drawGauge() {//绘制边缘
    QRectF rect = this->rect();
    this->painter->begin(this);
    QRectF realScope(rect.topLeft() + QPointF(rect.height() * 0.1, rect.width() * 0.1), rect.size() * 0.8);
    this->painter->setRenderHint(QPainter::Antialiasing, true);
    this->pen = QPen(QBrush("#5599FF"), this->oulineWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
    this->painter->setPen(this->pen);
    this->painter->drawArc(realScope, -(this->startAngle + 10) * 16, (180 + (this->startAngle + 10) * 2) * 16);
    drawSeparator();
    drawSperateNumber();
    drawText();
    drawValue();
    drawCenterPoint();
    drawNeedle();
    this->painter->end();
}

void Gauge::paintEvent(QPaintEvent* event) {
    if (this->painter)
        drawGauge();
}

void Gauge::drawText() {//在中央绘制文字
    QStringList lis = this->text.split("\n");
    int row = lis.size();
    int maxStrSize = 0;
    for (int i = 0; i < row; i++) {
        if (lis[i].size() > maxStrSize)
            maxStrSize = lis[i].size();
    }
    textRect = QRectF(QPointF(-this->textSize * maxStrSize * 1.6, this->textSize) / 2, QSizeF(this->textSize * maxStrSize, this->textSize * row) * 1.6);
    pen.setColor("#99BBFF");
    this->painter->setPen(pen);
    this->painter->setFont(QFont("STHupo", this->textSize, QFont::Medium));
    this->painter->drawText(textRect, Qt::AlignHCenter, this->text);
}

void Gauge::drawSeparator() {//绘制分割符
    this->painter->translate(this->rect().center());
    this->painter->rotate(40 - partAngle);
    pen.setColor("#99BBFF");
    pen.setWidth(this->separatorWidth);
    this->painter->setPen(pen);
    for (int i = 0; i <= this->parts; i++) {
        this->painter->rotate(partAngle);
        this->painter->drawLine(QPointF(0, radius - 30), QPointF(0, radius - 15));
    }
}

void Gauge::drawSperateNumber() {//绘制分割数字
    pen.setColor("#5599FF");
    this->painter->setFont(QFont("FangSong", this->numSize, QFont::DemiBold));
    this->painter->rotate(-this->parts * partAngle - 40);
    this->painter->setPen(pen);
    for (int i = 0; i <= this->parts; i++) {
        float bias = 0;
        if (i < this->parts / 2)
            bias = this->numSize;
        QRectF numRect(QPointF(-this->numSize - bias, -this->numSize) / 2 + QPointF(sin(-(40 + i * partAngle) * M_PI / 180) * (radius - 45), cos(-(40 + i * partAngle) * M_PI / 180) * (radius - 45)), QSizeF(this->numSize * 3, this->numSize * 2));
        QString text = QString::number(this->startNum + i * this->stepNum);
        this->painter->drawText(numRect, Qt::AlignLeft, text, false);
    }
}

void Gauge::drawCenterPoint() {//绘制中央点
    pen.setColor("#5599FF");
    pen.setWidthF(radius * 0.1);
    this->painter->setPen(pen);
    this->painter->drawPoint(QPoint(0, 0));
}

void Gauge::drawNeedle() {//绘制指针
    pen.setColor("#5500DD");
    pen.setWidthF(1);
    this->painter->setPen(pen);
    QPainterPath path(QPoint(radius * 0.05, 0));
    path.cubicTo(QPointF(radius * 0.06, radius * 0.03), QPointF(radius * 0.15, radius * 0.07), QPointF(radius * 0.8, 0));
    path.cubicTo(QPointF(radius * 0.15, -radius * 0.07), QPointF(radius * 0.06, -radius * 0.03), QPointF(radius * 0.05, 0));
    this->painter->setOpacity(0.3);
    this->painter->rotate(this->needleAngle);
    this->painter->fillPath(path, QBrush("#0088A8"));
}

void Gauge::onTimeOut() {
    float val = bounceFunc(this->count);
    if (this->currentVal == this->input) {
        this->count = 0;
        this->timer->stop();
    }
    this->currentVal += val;
    float angle = this->partAngle / this->stepNum * val;
    this->needleAngle += angle;
    this->count += 0.001;
    update();
}

void Gauge::drawValue() {
    QString val = QString::number(this->currentVal, 'f', 2);
    QRectF valRect(QPointF(-4 * this->valSize / 2, this->textRect.bottom() + this->valSize), QSize(4 * this->valSize, this->valSize));
    pen.setColor("#9F88FF");
    this->painter->setPen(pen);
    this->painter->setFont(QFont("FangSong", this->valSize, QFont::DemiBold));
    this->painter->drawText(valRect, Qt::AlignCenter, val);
}

主窗口源文件:

#include "GuageDemo.h"

GuageDemo::GuageDemo(QWidget* parent)
    : QWidget(parent)
{
    ui.setupUi(this);
    QThread* thread = new QThread;
    this->gauge = new Gauge(this);
    this->gauge->setGeometry(QRect(10, 10, 300, 300));
    this->gauge->moveToThread(thread);
    this->gauge->startPainting();
    ui.horizontalSlider_input->setMaximum(75);
    connect(ui.pushButton, &QPushButton::clicked, this, [=]() {this->gauge->setText("Hello\nWorld"); });
    connect(ui.horizontalSlider_input, &QSlider::sliderMoved, this, [=]() {this->gauge->setVal(ui.horizontalSlider_input->value()); });
    connect(ui.horizontalSlider_textSize, &QSlider::sliderMoved, this, [=]() {this->gauge->setTextSize(ui.horizontalSlider_textSize->value()); });
    connect(ui.horizontalSlider_numSize, &QSlider::sliderMoved, this, [=]() {this->gauge->setNumSize(ui.horizontalSlider_numSize->value()); });
    connect(ui.horizontalSlider_parts, &QSlider::sliderMoved, this, [=]() {this->gauge->setParts(ui.horizontalSlider_parts->value()); });
    connect(ui.horizontalSlider_separatorSzie, &QSlider::sliderMoved, this, [=]() {this->gauge->setSeparatorWidth(ui.horizontalSlider_separatorSzie->value()); });
    connect(ui.horizontalSlider_startNum, &QSlider::sliderMoved, this, [=]() {this->gauge->setStartNum(ui.horizontalSlider_startNum->value()); });
    connect(ui.horizontalSlider_stepNum, &QSlider::sliderMoved, this, [=]() {this->gauge->setStepNum(ui.horizontalSlider_stepNum->value()); });
    connect(ui.horizontalSlider_outLineWidth, &QSlider::sliderMoved, this, [=]() {this->gauge->setOutlineWidth(ui.horizontalSlider_outLineWidth->value()); });
}

GuageDemo::~GuageDemo()
{}

void GuageDemo::onSliderMoved(int val) {
    this, gauge->setVal(val);
}

介绍

    QThread* thread = new QThread;
    this->gauge = new Gauge(this);
    this->gauge->setGeometry(QRect(10, 10, 300, 300));
    this->gauge->moveToThread(thread);
    this->gauge->startPainting();

将仪表控件扔进子线程运行,先new一个仪表,再定义它的几何大小,然后调用其startPainting函数开始绘制。

仪表的接口分别为 开始绘图 设置当前值 设置区间个数 设置轮廓大小 设置分隔符大小 设置起始值 设置数字大小 设置文本大小 设置当前值大小 设置文本。

    void startPainting();
    void setVal(const float& val);
    bool setParts(const int& num);
    bool setOutlineWidth(const float& num);
    bool setSeparatorWidth(const float& num);
    bool setStartNum(const float& num);
    bool setStepNum(const float& num);
    bool setNumSize(const float& num);
    bool setTextSize(const float& num);
    bool setValSize(const float& num);
    bool setText(const QString& str);

实现指针连续旋转

在输入一个数值时,要想让指针连续的移动到数值所在处,加入sigmod函数,实现如下。若要修改指针旋转的速度便修改qExp那个0.5即可。函数图像如下

float Gauge::bounceFunc(float x) {
	float res = 2 * (this->input - this->currentVal) / (qExp(-0.5 * x) + 1) - (this->input - this->currentVal);
	return res;
}
void Gauge::setVal(const float& val) {
    this->timer->stop();
    this->count = 0;
    this->input = val;
    if (this->input<this->startNum || this->input>this->startNum + this->stepNum * this->parts) {
        QMessageBox::warning(this, "", "输入错误");
        return;
    }
    this->timer->setInterval(1);
    this->timer->start();
}
void Gauge::onTimeOut() {
    float val = bounceFunc(this->count);
    if (this->currentVal == this->input) {
        this->count = 0;
        this->timer->stop();
    }
    this->currentVal += val;
    float angle = this->partAngle / this->stepNum * val;
    this->needleAngle += angle;
    this->count += 0.001;
    update();
}

在调用bounceFunc时,定义一个计时器,计时间隔为1ms。

关于指针形状的绘制

使用QPainterPath绘制贝塞尔曲线path,然后this->painter->fillPath(path, QBrush("#0088A8"))。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值