在Qt自带的控件中,只有垂直进度条、水平进度条两种。在平时做页面开发时,有些时候会用到环形进度条。通常用于显示任务或操作的进度。它的使用场景包括但不限于以下几个方面:文件上传或下载进度:环形进度条可以显示文件上传或下载的进度,让用户清楚地知道任务完成的百分比,提供一个可视化的反馈;数据处理进度:在处理大量数据的任务中,环形进度条可以显示数据处理的进度,让用户了解任务的进行情况。总之,环形进度条可以在各种需要显示任务或操作进度的场景中使用,提供可视化的进度反馈,提升用户体验。
一、简述
本示例使用QT实现一个自定义环形进度条。实现这个功能主要两个重点:图形绘制和数值计算。
二、 设计思路
-
定义一个继承自QWidget的自定义控件,命名为RoundProgressBar。
-
在RoundProgressBar的构造函数中,初始化相关属性,如圆形的半径、进度条的宽度、进度条的颜色等。
-
重写RoundProgressBar的paintEvent函数,在该函数中绘制圆形背景和进度条。
-
定义一个public的成员函数,用于设置当前的进度值。该函数会自动调用update函数,触发重绘。
-
在paintEvent函数中,根据当前的进度值计算进度条的起始角度和结束角度,使用QPainter绘制出进度条。
-
可以添加一些额外的属性和函数,如设置进度条的最小值和最大值、设置进度条的文本显示等。
三、效果
四、核心代码
1、头文件
#ifndef ROUNDPROGRESSBAR_H
#define ROUNDPROGRESSBAR_H
#include <QWidget>
class RoundProgressBar : public QWidget
{
Q_OBJECT
public:
explicit RoundProgressBar(QWidget *parent = 0);
~RoundProgressBar();
//进度条显示类型
enum BarStyle
{
StyleDonut,//圆环
StylePie,//饼状
StyleLine,//线条
};
//起始角度
static const int PositionLeft = 180;
static const int PositionTop = 90;
static const int PositionRight = 0;
static const int PositionBottom = -90;
double getStartAngle() const { return m_startAngle; }
void setStartAngle(double angle);
BarStyle getBarStyle() const { return m_barStyle; }
void setBarStyle(BarStyle style);
double getValue() const { return m_value; }
void setOutlinePenWidth(double penWidth);
void setDataPenWidth(double penWidth);
void setDataColors(const QGradientStops& stopPoints);
void setFormat(const QString& format);
void setDecimals(int count);
void setClockwise(bool clockwise);
public slots:
void setRange(double min, double max);
void setValue(int val);
void setValue(double val);
void setBaseCircleVisible(bool visible);
void setDataCircleVisible(bool visible);
void setCenterCircleVisible(bool visible);
void setTextVisible(bool visible);
protected:
virtual void paintEvent(QPaintEvent *);
virtual void drawBackground(QPainter& p, const QRectF& baseRect);
virtual void drawBase(QPainter& p, const QRectF& baseRect);
virtual void drawValue(QPainter& p, const QRectF& baseRect, double value, double delta);
virtual void calculateInnerRect(const QRectF& baseRect, double outerRadius, QRectF& innerRect, double& innerRadius);
virtual void drawInnerBackground(QPainter& p, const QRectF& innerRect);
virtual void drawText(QPainter& p, const QRectF& innerRect, double innerRadius, double value);
virtual QString valueToText(double value) const;
virtual void valueFormatChanged();
virtual QSize minimumSizeHint() const { return QSize(32,32); }
void rebuildDataBrushIfNeeded();
private:
double m_min, m_max;//最小值,最大值
double m_value;//当前值
double m_startAngle;//起始角度
BarStyle m_barStyle;//显示类型
double m_outlinePenWidth, m_dataPenWidth;//外圆画笔宽度,数据圆画笔宽度(主要用在线条显示类型)
QGradientStops m_gradientData;//渐变颜色(主要用在圆环和饼状显示类型)
bool m_rebuildBrush;
QString m_format;//文本显示格式
int m_decimals;//小数点位数
bool m_clockwise;//顺时针
bool m_baseCircleVisible;//显示外圆
bool m_dataCircleVisible;//显示数据圆
bool m_centerCircleVisible;//显示内圆
bool m_textVisible;//显示文字
static const int UF_VALUE = 1;//文本格式-当前值
static const int UF_PERCENT = 2;//文本格式-当前值百分比
static const int UF_MAX = 4;//文本格式-最大值
int m_updateFlags;
};
#endif // ROUNDPROGRESSBAR_H
2、实现代码
#include "roundprogressbar.h"
#include <QPainter>
#include <QTransform>
RoundProgressBar::RoundProgressBar(QWidget *parent) :
QWidget(parent),
m_min(0),
m_max(100),
m_value(25),
m_startAngle(0),
m_barStyle(StyleDonut),
m_outlinePenWidth(1),
m_dataPenWidth(1),
m_rebuildBrush(false),
m_format("%p%"),
m_decimals(1),
m_clockwise(true),
m_baseCircleVisible(true),
m_dataCircleVisible(true),
m_centerCircleVisible(true),
m_textVisible(true),
m_updateFlags(UF_PERCENT)
{
}
RoundProgressBar::~RoundProgressBar()
{
}
void RoundProgressBar::setStartAngle(double angle)
{
if (angle != m_startAngle)
{
m_startAngle = angle;
update();
}
}
void RoundProgressBar::setBarStyle(RoundProgressBar::BarStyle style)
{
if (style != m_barStyle)
{
m_barStyle = style;
update();
}
}
void RoundProgressBar::setOutlinePenWidth(double penWidth)
{
if (penWidth != m_outlinePenWidth)
{
m_outlinePenWidth = penWidth;
update();
}
}
void RoundProgressBar::setDataPenWidth(double penWidth)
{
if (penWidth != m_dataPenWidth)
{
m_dataPenWidth = penWidth;
update();
}
}
void RoundProgressBar::setDataColors(const QGradientStops &stopPoints)
{
if (stopPoints != m_gradientData)
{
m_gradientData = stopPoints;
m_rebuildBrush = true;
update();
}
}
void RoundProgressBar::setFormat(const QString &format)
{
if (format != m_format)
{
m_format = format;
valueFormatChanged();
}
}
void RoundProgressBar::setDecimals(int count)
{
if (count >= 0 && count != m_decimals)
{
m_decimals = count;
valueFormatChanged();
}
}
void RoundProgressBar::setClockwise(bool clockwise)
{
if(clockwise != m_clockwise)
{
m_clockwise = clockwise;
update();
}
}
void RoundProgressBar::setRange(double min, double max)
{
m_min = min;
m_max = max;
if (m_max < m_min)
qSwap(m_max, m_min);
if (m_value < m_min)
m_value = m_min;
else if (m_value > m_max)
m_value = m_max;
update();
}
void RoundProgressBar::setValue(int val)
{
setValue((double)val);
}
void RoundProgressBar::setValue(double val)
{
if (m_value != val)
{
if (val < m_min)
m_value = m_min;
else if (val > m_max)
m_value = m_max;
else
m_value = val;
update();
}
}
void RoundProgressBar::setBaseCircleVisible(bool visible)
{
if(visible != m_baseCircleVisible)
{
m_baseCircleVisible = visible;
update();
}
}
void RoundProgressBar::setDataCircleVisible(bool visible)
{
if(visible != m_dataCircleVisible)
{
m_dataCircleVisible = visible;
update();
}
}
void RoundProgressBar::setCenterCircleVisible(bool visible)
{
if(visible != m_centerCircleVisible)
{
m_centerCircleVisible = visible;
update();
}
}
void RoundProgressBar::setTextVisible(bool visible)
{
if(visible != m_textVisible)
{
m_textVisible = visible;
update();
}
}
void RoundProgressBar::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
double outerRadius = qMin(width(), height());
QRectF baseRect(1, 1, outerRadius-2, outerRadius-2);
QImage buffer(outerRadius, outerRadius, QImage::Format_ARGB32_Premultiplied);
QPainter p(&buffer);
p.setRenderHint(QPainter::Antialiasing);
//data brush
rebuildDataBrushIfNeeded();
// background
drawBackground(p, buffer.rect());
// base circle
if(m_baseCircleVisible)
drawBase(p, baseRect);
// data circle
double delta = (m_max - m_min) / (m_value - m_min);
if(m_dataCircleVisible)
drawValue(p, baseRect, m_value, delta);
// center circle
double innerRadius(0);
QRectF innerRect;
calculateInnerRect(baseRect, outerRadius, innerRect, innerRadius);
if(m_centerCircleVisible)
drawInnerBackground(p, innerRect);
// text
if(m_textVisible)
drawText(p, innerRect, innerRadius, m_value);
// finally draw the bar
p.end();
QTransform transform;
transform.translate((width()-outerRadius)/2, (height()-outerRadius)/2);
QPainter painter(this);
painter.setTransform(transform);
painter.fillRect(baseRect, palette().background());
painter.drawImage(0,0, buffer);
}
void RoundProgressBar::drawBackground(QPainter &p, const QRectF &baseRect)
{
p.fillRect(baseRect, palette().background());
}
void RoundProgressBar::drawBase(QPainter &p, const QRectF &baseRect)
{
switch (m_barStyle)
{
case StyleDonut:
p.setPen(QPen(palette().shadow().color(), m_outlinePenWidth));
p.setBrush(palette().base());
p.drawEllipse(baseRect);
break;
case StylePie:
p.setPen(QPen(palette().base().color(), m_outlinePenWidth));
p.setBrush(palette().base());
p.drawEllipse(baseRect);
break;
case StyleLine:
p.setPen(QPen(palette().base().color(), m_outlinePenWidth));
p.setBrush(Qt::NoBrush);
p.drawEllipse(baseRect.adjusted(m_outlinePenWidth/2, m_outlinePenWidth/2, -m_outlinePenWidth/2, -m_outlinePenWidth/2));
break;
default:
break;
}
}
void RoundProgressBar::drawValue(QPainter &p, const QRectF &baseRect, double value, double delta)
{
if (value == m_min)
return;
// for Line style
if (m_barStyle == StyleLine)
{
p.setPen(QPen(palette().highlight().color(), m_dataPenWidth));
p.setBrush(Qt::NoBrush);
if (value == m_max)
{
p.drawEllipse(
baseRect.adjusted(m_outlinePenWidth/2, m_outlinePenWidth/2, -m_outlinePenWidth/2, -m_outlinePenWidth/2));
}
else
{
double arcLength = 360.0 / delta;
p.drawArc(
baseRect.adjusted(m_outlinePenWidth/2, m_outlinePenWidth/2, -m_outlinePenWidth/2, -m_outlinePenWidth/2),
m_startAngle * 16,
m_clockwise?-arcLength * 16:arcLength * 16);
}
return;
}
// for Pie and Donut styles
QPainterPath dataPath;
dataPath.setFillRule(Qt::WindingFill);
// pie segment outer
if (value == m_max)
{
dataPath.addEllipse(baseRect);
}
else
{
double arcLength = 360.0 / delta;
dataPath.moveTo(baseRect.center());
dataPath.arcTo(baseRect, m_startAngle, m_clockwise?-arcLength:arcLength);
dataPath.lineTo(baseRect.center());
}
p.setBrush(palette().highlight());
p.setPen(QPen(palette().shadow().color(), m_dataPenWidth));
p.drawPath(dataPath);
}
void RoundProgressBar::calculateInnerRect(const QRectF &/*baseRect*/, double outerRadius, QRectF &innerRect, double &innerRadius)
{
// for Line and Expand styles
if (m_barStyle == StyleLine)
{
innerRadius = outerRadius - m_outlinePenWidth;
}
else // for Pie and Donut styles
{
innerRadius = outerRadius * 0.75;
}
double delta = (outerRadius - innerRadius) / 2;
innerRect = QRectF(delta, delta, innerRadius, innerRadius);
}
void RoundProgressBar::drawInnerBackground(QPainter &p, const QRectF &innerRect)
{
if (m_barStyle == StyleDonut)
{
p.setBrush(palette().alternateBase());
p.drawEllipse(innerRect);
}
}
void RoundProgressBar::drawText(QPainter &p, const QRectF &innerRect, double innerRadius, double value)
{
if (m_format.isEmpty())
return;
// !!! to revise
QFont f(font());
f.setPixelSize(10);
QFontMetricsF fm(f);
double maxWidth = fm.width(valueToText(m_max));
double delta = innerRadius / maxWidth;
double fontSize = f.pixelSize() * delta * 0.75;
f.setPixelSize(fontSize);
p.setFont(f);
QRectF textRect(innerRect);
p.setPen(palette().text().color());
p.drawText(textRect, Qt::AlignCenter, valueToText(value));
}
QString RoundProgressBar::valueToText(double value) const
{
QString textToDraw(m_format);
if (m_updateFlags & UF_VALUE)
textToDraw.replace("%v", QString::number(value, 'f', m_decimals));
if (m_updateFlags & UF_PERCENT)
{
double procent = (value - m_min) / (m_max - m_min) * 100.0;
textToDraw.replace("%p", QString::number(procent, 'f', m_decimals));
}
if (m_updateFlags & UF_MAX)
textToDraw.replace("%m", QString::number(m_max - m_min + 1, 'f', m_decimals));
return textToDraw;
}
void RoundProgressBar::valueFormatChanged()
{
m_updateFlags = 0;
if (m_format.contains("%v"))
m_updateFlags |= UF_VALUE;
if (m_format.contains("%p"))
m_updateFlags |= UF_PERCENT;
if (m_format.contains("%m"))
m_updateFlags |= UF_MAX;
update();
}
void RoundProgressBar::rebuildDataBrushIfNeeded()
{
if (!m_rebuildBrush)
return;
if (m_gradientData.isEmpty())
return;
if (m_barStyle == StyleLine)
return;
m_rebuildBrush = false;
QPalette p(palette());
QConicalGradient dataBrush(QPointF(0.5,0.5), m_startAngle);
dataBrush.setCoordinateMode(QGradient::StretchToDeviceMode);
// invert colors
for (int i = 0; i < m_gradientData.count(); i++)
dataBrush.setColorAt(1.0 - m_gradientData.at(i).first, m_gradientData.at(i).second);
p.setBrush(QPalette::Highlight, dataBrush);
setPalette(p);
}
通过以上代码,可以实现一个QT环形进度条。可以通过设置其样式和属性来实现环形进度条的效果。可以根据项目需求进行进一步的扩展和优化。
五、使用示例
以下是一个简单的示例代码,演示了如何在Qt中使用此控件:
UI设计:
界面代码:
#include "roundprogressbartest.h"
#include "ui_roundprogressbartest.h"
RoundProgressBarTest::RoundProgressBarTest(QWidget *parent) :
QWidget(parent),
ui(new Ui::RoundProgressBarTest)
{
ui->setupUi(this);
//圆环
ui->roundBar1->setFormat("%v");
ui->roundBar1->setDecimals(0);
connectToSlider(ui->roundBar1);
connectToBaseCircleCheckBox(ui->roundBar1);
connectToDataCircleCheckBox(ui->roundBar1);
connectToCenterCircleCheckBox(ui->roundBar1);
connectToTextCheckBox(ui->roundBar1);
QGradientStops grandientPoints;
grandientPoints << QGradientStop(0, Qt::red) << QGradientStop(1, Qt::yellow);
ui->roundBar4->setStartAngle(RoundProgressBar::PositionLeft);
ui->roundBar4->setDecimals(0);
ui->roundBar4->setDataColors(grandientPoints);
connectToSlider(ui->roundBar4);
connectToBaseCircleCheckBox(ui->roundBar4);
connectToDataCircleCheckBox(ui->roundBar4);
connectToCenterCircleCheckBox(ui->roundBar4);
connectToTextCheckBox(ui->roundBar4);
//饼状
ui->roundBar2->setStartAngle(RoundProgressBar::PositionRight);
ui->roundBar2->setBarStyle(RoundProgressBar::StylePie);
ui->roundBar2->setDecimals(0);
connectToSlider(ui->roundBar2);
connectToBaseCircleCheckBox(ui->roundBar2);
connectToDataCircleCheckBox(ui->roundBar2);
connectToCenterCircleCheckBox(ui->roundBar2);
connectToTextCheckBox(ui->roundBar2);
ui->roundBar5->setStartAngle(RoundProgressBar::PositionLeft);
ui->roundBar5->setBarStyle(RoundProgressBar::StylePie);
ui->roundBar5->setDecimals(0);
connectToSlider(ui->roundBar5);
connectToBaseCircleCheckBox(ui->roundBar5);
connectToDataCircleCheckBox(ui->roundBar5);
connectToCenterCircleCheckBox(ui->roundBar5);
connectToTextCheckBox(ui->roundBar5);
//线条
ui->roundBar3->setStartAngle(RoundProgressBar::PositionTop);
ui->roundBar3->setBarStyle(RoundProgressBar::StyleLine);
ui->roundBar3->setOutlinePenWidth(4);
ui->roundBar3->setDataPenWidth(4);
ui->roundBar3->setDecimals(0);
connectToSlider(ui->roundBar3);
connectToBaseCircleCheckBox(ui->roundBar3);
connectToDataCircleCheckBox(ui->roundBar3);
connectToCenterCircleCheckBox(ui->roundBar3);
connectToTextCheckBox(ui->roundBar3);
ui->roundBar6->setStartAngle(RoundProgressBar::PositionTop);
ui->roundBar6->setBarStyle(RoundProgressBar::StyleLine);
ui->roundBar6->setDecimals(2);
ui->roundBar6->setClockwise(false);
ui->roundBar6->setOutlinePenWidth(18);
ui->roundBar6->setDataPenWidth(10);
connectToSlider(ui->roundBar6);
connectToBaseCircleCheckBox(ui->roundBar6);
connectToDataCircleCheckBox(ui->roundBar6);
connectToCenterCircleCheckBox(ui->roundBar6);
connectToTextCheckBox(ui->roundBar6);
}
RoundProgressBarTest::~RoundProgressBarTest()
{
delete ui;
}
void RoundProgressBarTest::connectToSlider(RoundProgressBar *bar)
{
bar->setRange(ui->valueSlider->minimum(), ui->valueSlider->maximum());
bar->setValue(ui->valueSlider->value());
connect(ui->valueSlider, SIGNAL(valueChanged(int)), bar, SLOT(setValue(int)));
}
void RoundProgressBarTest::connectToBaseCircleCheckBox(RoundProgressBar *bar)
{
connect(ui->base_circle_ckb, SIGNAL(toggled(bool)), bar, SLOT(setBaseCircleVisible(bool)));
}
void RoundProgressBarTest::connectToDataCircleCheckBox(RoundProgressBar *bar)
{
connect(ui->data_circle_ckb, SIGNAL(toggled(bool)), bar, SLOT(setDataCircleVisible(bool)));
}
void RoundProgressBarTest::connectToCenterCircleCheckBox(RoundProgressBar *bar)
{
connect(ui->center_circle_ckb, SIGNAL(toggled(bool)), bar, SLOT(setCenterCircleVisible(bool)));
}
void RoundProgressBarTest::connectToTextCheckBox(RoundProgressBar *bar)
{
connect(ui->text_ckb, SIGNAL(toggled(bool)), bar, SLOT(setTextVisible(bool)));
}
运行程序后,会显示不同风格环形进度条,拖动滑动条可逐渐填充直到达到100%。
QT环形进度条是一种常见的用户界面设计元素,在编写应用程序时经常用于显示任务进度或操作完成进度。在设计环形进度条时,需要考虑以下几个方面:
-
外观设计:环形进度条的外观设计应该符合应用程序的整体风格和主题。可以使用颜色、渐变、阴影等效果,使进度条看起来更加美观和吸引人。
-
进度显示:环形进度条通常由一个圆环组成,进度则通过改变圆环的长度来表示。可以使用动画效果来平滑地显示进度的变化,以提高用户体验。另外,可以在进度条上方或下方显示当前的进度百分比,方便用户了解任务的完成情况。
-
响应用户操作:环形进度条通常需要与用户进行交互,例如用户可以点击进度条来取消任务或查看详细信息。设计时应该考虑这些交互操作,并为用户提供相应的反馈。
-
可定制性:在设计环形进度条时,应该考虑到不同应用程序对进度条的需求可能不同。因此,应该提供一些可定制的参数,例如进度条的大小、颜色和显示方式等,以便开发人员能够根据具体需求进行调整。
总而言之,设计一个好的环形进度条需要综合考虑外观设计、进度显示、用户交互和定制性等因素,以提供更好的用户体验和满足不同应用程序的需求。
谢谢您的阅读,希望本文能为您带来一些帮助和启发。如果您有任何问题或意见,请随时与我联系。祝您度过美好的一天!