我在使用Qt开发应用程序的过程中,时不时的会使用到类似进度条的功能。时间紧迫时,便使用Qt自带的QProgressBar控件,配合样式美化一下,也能达到令人满意的效果。但是如果想使用别具一格的控件,可以使用第三方的控件,或者自己绘制。这里,我选择自己进行绘制的方式来实现。
本程序是根据之前看到的图片开发的,忘记来源是哪了,以后找到了再补上。
原理很简单,定义一个继承自QWidget的类,并重写paintEvent函数即可。
这里将列出本次的实现过程中涉及到的一点点的数学知识,如下:
已知圆心(x0, y0), 半径r, 圆心角θ, 求圆上对应点的坐标(x,y)。
x = x0 + r * cos(θ)
y = y0 + r * sin(θ)
三角函数基本性质
sin(x)、cos(x)都是周期函数,最小周期为2π。
sin(x)为奇函数,cos(x)为偶函数。
正余弦的两角和与差公式
sin(α + β) = sinαcosβ + cosαsinβ
sin(α - β) = sinαcosβ - cosαsinβ
cos(α + β) = cosαcosβ - sinαsinβ
cos(α - β) = cosαcosβ + sinαsinβ
已知过圆心O(0,0)且斜率为k的直线l1,过直线l1上同一点p1(x,y)[p1在圆外],并与直线l1的夹角为θ的两条直线l2, l3,求直线l2,l3上与点p(x,y)的距离为r’的点p2,p3的坐标。
这个问题,其实就是求以点p1(x,y)为圆心,r’为半径的圆,与过圆心的直线l2,l3的交点。【只取四个交点中的两个】
其本质还是求圆上点的坐标。
点在第二三象限时,即横坐标小于0时,α,β分别为θ+π/6,θ-π/6;
点在第一四象限时,即橫坐标大于0时,α,β分别为π+θ+π/6,π+θ-π/6。
全部代码如下,实现的并不完善,很多地方并没有进行数据的有效性验证,也可能存在bug,希望发现bug的朋友不吝指正。
mymeter.h
#ifndef MYMETER_H
#define MYMETER_H
#include
class MyMeter : public QWidget
{
Q_OBJECT
public:
explicit MyMeter(QWidget *parent = nullptr);
virtual ~MyMeter();
void set_start_and_span_angle(int start, int span);
void set_arc_radius(int radius);
void set_text(const QString & text);
void set_show_text(bool yes);
void set_protrude_tick_mark(bool yes);
/* TODO */
/* 设置颜色,最大值、最小值等的接口相对比较简单,这里不再提供了,自己实现一下即可 */
/* 其实是我偷懒啦,我也没有实现这部分 */
protected:
void resizeEvent(QResizeEvent * event) override;
void paintEvent(QPaintEvent * event) override;
void draw_background(QPainter & painter); /* 绘制部件背景 */
void draw_negative(QPainter & painter); /* 绘制底片 */
void draw_positive(QPainter & painter); /* 绘制当前内容 */
public slots:
void set_value(qint16 value);
signals:
private:
qint16 my_start_angle; /* 起始角度 */
qint16 my_span_angle; /* 跨越角度 */
qint16 my_stop_angle; /* 终止边角度 */
qreal my_angle; /* 每单位所跨角度 */
qreal my_cur_angle; /* 当前角度 */
qint16 my_minimum; /* 最小值 */
qint16 my_maximum; /* 最大值 */
qint16 my_value; /* 当前值 */
qint16 my_arc_width; /* 圆弧宽度 */
qint16 my_arc_radius; /* 圆弧半径 */
qint16 my_arc_circle_space; /* 圆弧内侧与中心圆间的间距 */
qint16 my_arc_tick_mark_space;/* 圆弧外侧与刻度内侧的间距 */
qint16 my_tick_mark_length; /* 刻度线度 */
qint16 my_tick_mark_count; /* 突出显示刻度线的数量 */
qint16 my_tick_mark_len_delta;/* 突出显示的相邻刻度线长度的差值 */
QString my_text; /* 提示文本 */
bool my_show_text; /* 是否显示功能提示文本 */
bool my_protrude_tick_mark; /* 是否突出显示当前刻度线 */
QColor my_background_color; /* 背景色 */
QColor my_color; /* 前景色 文本颜色 */
};
#endif // MYMETER_H
mymeter.cpp
#include
#include
#include
#include "mymeter.h"
MyMeter::MyMeter(QWidget *parent) :
QWidget(parent),
my_start_angle(315),
my_span_angle(270),
my_minimum(0),
my_maximum(100),
my_value(0),
my_arc_width(20),
my_arc_radius(100),
my_arc_circle_space(20),
my_arc_tick_mark_space(10),
my_tick_mark_length(10),
my_tick_mark_count(4),
my_tick_mark_len_delta(2),
my_show_text(true),
my_protrude_tick_mark(true),
my_background_color(QColor(198, 239, 206)),
my_color(QColor(66, 202, 107))
{
my_angle = (qreal)(my_span_angle)/(my_maximum - my_minimum);
my_stop_angle = my_start_angle > 180 ? 720 - (my_span_angle + my_start_angle) : 360 - (my_start_angle + my_span_angle);
my_cur_angle = my_stop_angle;
my_text = QString(QStringLiteral("进度"));
}
MyMeter::~MyMeter()
{
}
void MyMeter::set_start_and_span_angle(int start, int span)
{
my_start_angle = start;
my_span_angle = span;
my_stop_angle = my_start_angle > 180 ? 720 - (my_span_angle + my_start_angle) : 360 - (my_start_angle + my_span_angle);
}
void MyMeter::set_arc_radius(int radius)
{
my_arc_radius = radius;
}
void MyMeter::set_text(const QString &text)
{
my_text = text;
}
void MyMeter::set_show_text(bool yes)
{
my_show_text = yes;
}
void MyMeter::set_protrude_tick_mark(bool yes)
{
my_protrude_tick_mark = yes;
}
void MyMeter::resizeEvent(QResizeEvent *event)
{
int half_min_width = my_arc_radius
+ (my_arc_width>>1)
+ my_arc_tick_mark_space
+ my_tick_mark_length
+ my_tick_mark_count * my_tick_mark_len_delta; /* 突出显示刻度线时,多出来的最大长度 */
resize((half_min_width<<1), (half_min_width<<1));
}
void MyMeter::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.translate(width()>>1, height()>>1);
/* 1. 窗体背景 */
draw_background(painter);
/* 2. 静态部分 */
draw_negative(painter);
/* 3. 动态部分 */
draw_positive(painter);
}
void MyMeter::draw_background(QPainter &painter)
{
painter.save();
painter.restore();
}
void MyMeter::draw_negative(QPainter &painter)
{
painter.save();
/* 1. 刻度 */
painter.setPen(QPen(my_background_color, 2));
int tick_mark_inner_radius = my_arc_radius + (my_arc_width>>1) + my_arc_tick_mark_space ;
int tick_mark_outer_radius = my_arc_radius + (my_arc_width>>1) + my_arc_tick_mark_space + my_tick_mark_length;
int total = my_maximum - my_minimum + 1;
for (int i = 0; i < total; ++i) {
QPointF p_start(tick_mark_inner_radius * qCos((my_stop_angle + i*my_angle)*M_PI/180),
tick_mark_inner_radius * qSin((my_stop_angle + i*my_angle)*M_PI/180));
QPointF p_stop(tick_mark_outer_radius * qCos((my_stop_angle + i*my_angle)*M_PI/180),
tick_mark_outer_radius * qSin((my_stop_angle + i*my_angle)*M_PI/180));
painter.drawLine(p_start, p_stop);
}
/* 2. 背景进度条 */
painter.setPen(QPen(my_background_color, my_arc_width, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
painter.drawArc(-my_arc_radius, -my_arc_radius, my_arc_radius<<1, my_arc_radius<<1, my_start_angle<<4, my_span_angle<<4);
/* 3. 中间渐变圆 */
int circle_radius = my_arc_radius - my_arc_circle_space - (my_arc_width>>1);
QLinearGradient linear_ball(QPointF(0, circle_radius>>1), QPointF(0, -circle_radius>>1));
my_background_color.setAlpha(128);
my_color.setAlpha(128);
linear_ball.setColorAt(0.0, my_background_color);
linear_ball.setColorAt(1.0, my_color);
painter.setBrush(linear_ball);
painter.setPen(Qt::NoPen);
painter.drawEllipse(QPointF(0, 0), circle_radius, circle_radius);
my_background_color.setAlpha(255);
my_color.setAlpha(255);
painter.restore();
}
void MyMeter::draw_positive(QPainter &painter)
{
painter.save();
/* 1 刻度 */
painter.setPen(QPen(my_color, 2));
int tick_mark_inner_radius = my_arc_radius + (my_arc_width>>1) + my_arc_tick_mark_space ;
int tick_mark_outer_radius = my_arc_radius + (my_arc_width>>1) + my_arc_tick_mark_space + my_tick_mark_length;
int upper(0);
if (my_protrude_tick_mark) {
upper = my_value + 1 - my_tick_mark_count;
for (int i = my_value; i > my_value - my_tick_mark_count && i >= 0; --i) {
int tick_mark_different_outer_radius = tick_mark_outer_radius + (my_tick_mark_count + i - my_value) * my_tick_mark_len_delta;
QPointF p_start(tick_mark_inner_radius * qCos((my_stop_angle + i*my_angle)* M_PI/180),
tick_mark_inner_radius * qSin((my_stop_angle + i*my_angle) * M_PI/180));
QPointF p_stop(tick_mark_different_outer_radius * qCos((my_stop_angle + i*my_angle)*M_PI/180),
tick_mark_different_outer_radius * qSin((my_stop_angle + i*my_angle)*M_PI/180));
painter.drawLine(p_start, p_stop);
}
}
else
upper = my_value + 1 - my_tick_mark_count;
for (int i = 0; i < upper; ++i) {
QPointF p_start(tick_mark_inner_radius * qCos((my_stop_angle + i*my_angle)* M_PI/180),
tick_mark_inner_radius * qSin((my_stop_angle + i*my_angle) * M_PI/180));
QPointF p_stop(tick_mark_outer_radius * qCos((my_stop_angle + i*my_angle)*M_PI/180),
tick_mark_outer_radius * qSin((my_stop_angle + i*my_angle)*M_PI/180));
painter.drawLine(p_start, p_stop);
}
/* 2. 动态进度条 */
QLinearGradient gradient(QPointF(my_arc_radius * qCos(my_stop_angle*M_PI/180),
my_arc_radius * qSin(my_stop_angle*M_PI/180)),
QPointF(my_arc_radius * qCos(my_cur_angle*M_PI/180),
my_arc_radius * qSin(my_cur_angle*M_PI/180)));
gradient.setColorAt(0.0, my_background_color);
gradient.setColorAt(1.0, my_color);
painter.setPen(QPen(gradient, my_arc_width, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
painter.drawArc(-my_arc_radius, -my_arc_radius, my_arc_radius<<1, my_arc_radius<<1,
-my_stop_angle<<4, -(my_cur_angle-my_stop_angle)*16);
/* 3. 进度指针 */
/* 3.1 指示弧线 设置弧线所对圆心角为20度 */
int circle_angle(20);
int half_angle(circle_angle >> 1);
int pointer_arc_radius = my_arc_radius - ((my_arc_width + my_arc_circle_space)>>1);
gradient.setStart(QPointF(pointer_arc_radius * qCos((my_cur_angle - half_angle)*M_PI/180),
pointer_arc_radius * qSin((my_cur_angle - half_angle)*M_PI/180)));
gradient.setFinalStop(QPointF(pointer_arc_radius * qCos((my_cur_angle + half_angle)*M_PI/180),
pointer_arc_radius * qSin((my_cur_angle + half_angle)*M_PI/180)));
gradient.setColorAt(0.0, my_background_color);
gradient.setColorAt(0.4, my_color);
gradient.setColorAt(0.6, my_color);
gradient.setColorAt(1.0, my_background_color);
painter.setPen(QPen(gradient, 6, Qt::SolidLine, Qt::RoundCap));
painter.drawArc(-pointer_arc_radius, -pointer_arc_radius,
pointer_arc_radius<<1, pointer_arc_radius<<1,
-(my_cur_angle - half_angle)*16, -circle_angle<<4); /* 顺时针计算 */
/* 3.2 等边三角形 BEGIN */
painter.setPen(QPen(my_color, 1, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin));
painter.setBrush(my_color);
int space(2); /* 进度弧线内侧与顶点间的距离 建议值域为[0, 2] */
int vertex((my_arc_circle_space>>1) - space); /* 三角形靠近刻度线的顶点 与 指示弧线中心的距离 */
QPointF p1((pointer_arc_radius + vertex) * qCos(my_cur_angle*M_PI/180),
(pointer_arc_radius + vertex) * qSin(my_cur_angle*M_PI/180));
/* 根据斜率求出正切角度 */
qreal k0, theta;
if (-0.0000001 > p1.x() || 0.0000001 < p1.x()) {
k0 = p1.y()/p1.x();
theta = qAtan(k0); // (-PI/2, PI/2)
}
else
theta = M_PI/2;
qreal alpha = theta + M_PI/6;
qreal beta = theta - M_PI/6;
if (0 < p1.x()) {
alpha = M_PI + alpha;
beta = M_PI + beta;
}
QPointF p2(p1.x() + vertex * qCos(alpha), p1.y() + vertex * qSin(alpha));
QPointF p3(p1.x() + vertex * qCos(beta), p1.y() + vertex * qSin(beta));
QPointF pf[4] = {p1, p2, p3, p1};
painter.drawPolygon(pf, 4);
/* 三角形 END */
/* 4. 提示文本 */
QString text(QString::number(my_value) + "%");
int circle_radius = my_arc_radius - my_arc_circle_space - (my_arc_width>>1);
QFont ft = painter.font();
ft.setPointSize(circle_radius/2);
ft.setBold(true);
painter.setFont(ft);
QFontMetrics fm(ft);
painter.setPen(QPen(my_color, 2));
painter.drawText(-fm.horizontalAdvance(text)>>1, 0, text);
ft.setPointSize(circle_radius/3);
ft.setBold(true);
painter.setFont(ft);
QFontMetrics fm2(ft);
painter.drawText(-fm2.horizontalAdvance(my_text)>>1, fm2.height(), my_text);
painter.restore();
}
void MyMeter::set_value(qint16 value)
{
my_value = value;
int theta = my_start_angle > 180 ? 720 - (my_span_angle + my_start_angle) : 360 - (my_start_angle + my_span_angle);
my_cur_angle = theta + (my_value - my_minimum) * my_angle;
update();
}
testwidget.h
#ifndef TESTWIDGET_H
#define TESTWIDGET_H
#include
#include
#include
#include "mymeter.h"
class TestWidget : public QWidget
{
Q_OBJECT
public:
explicit TestWidget(QWidget *parent = nullptr);
virtual ~TestWidget();
signals:
private:
QTimer * my_timer;
MyMeter * my_process;
MyMeter * my_cpu;
MyMeter * my_disk;
MyMeter * my_hygrometer;
QGridLayout * my_gridlayout;
};
testwidget.cpp
#include
#include "testwidget.h"
TestWidget::TestWidget(QWidget *parent) : QWidget(parent)
{
setWindowTitle(QStringLiteral("测试仪表"));
setFixedSize(600, 600);
my_pro = new MyMeter;
my_pro->set_arc_radius(60);
my_cpu = new MyMeter;
my_cpu->set_text("CPU");
my_cpu->set_arc_radius(70);
my_disk = new MyMeter;
my_disk->set_text("磁盘");
my_disk->set_arc_radius(80);
my_hygrometer = new MyMeter;
my_hygrometer->set_text("湿度");
my_hygrometer->set_start_and_span_angle(0, 270);
my_gridlayout = new QGridLayout;
my_gridlayout->addWidget(my_pro, 0, 0);
my_gridlayout->addWidget(my_cpu, 0, 1);
my_gridlayout->addWidget(my_disk, 1, 0);
my_gridlayout->addWidget(my_hygrometer, 1, 1);
setLayout(my_gridlayout);
my_timer = new QTimer(this);
my_timer->setInterval(500);
connect(my_timer, &QTimer::timeout, [=]{
my_pro->set_value(QRandomGenerator::global()->generate() % 101);
my_cpu->set_value(QRandomGenerator::global()->generate() % 101);
my_disk->set_value(QRandomGenerator::global()->generate() % 101);
my_hygrometer->set_value(QRandomGenerator::global()->generate() % 101);
});
my_timer->start();
}
TestWidget::~TestWidget()
{
if (my_timer) {
delete my_timer;
my_timer = nullptr;
}
if (my_pro) {
delete my_pro;
my_pro = nullptr;
}
if (my_cpu) {
delete my_cpu;
my_cpu = nullptr;
}
if (my_disk) {
delete my_disk;
my_disk = nullptr;
}
if (my_gridlayout) {
delete my_gridlayout;
my_gridlayout = nullptr;
}
}
main文件主要只有两三行代码
#include "testwidget.h"
TestWidget tw;
tw.show();