效果
初始效果:
调整后:
代码
头文件
仪表头文件:
#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"))。