在Qt自带的控件中,只有垂直进度条、水平进度条两种。在平时做页面开发时,有些时候会用到环形进度条。通常用于显示任务或操作的进度。它的使用场景包括但不限于以下几个方面:文件上传或下载进度:环形进度条可以显示文件上传或下载的进度,让用户清楚地知道任务完成的百分比,提供一个可视化的反馈;数据处理进度:在处理大量数据的任务中,环形进度条可以显示数据处理的进度,让用户了解任务的进行情况。总之,环形进度条可以在各种需要显示任务或操作进度的场景中使用,提供可视化的进度反馈,提升用户体验。

一、简述

         本示例使用QT实现一个自定义环形进度条。实现这个功能主要两个重点:图形绘制和数值计算。

二、 设计思路             
  1. 定义一个继承自QWidget的自定义控件,命名为RoundProgressBar。

  2. 在RoundProgressBar的构造函数中,初始化相关属性,如圆形的半径、进度条的宽度、进度条的颜色等。

  3. 重写RoundProgressBar的paintEvent函数,在该函数中绘制圆形背景和进度条。

  4. 定义一个public的成员函数,用于设置当前的进度值。该函数会自动调用update函数,触发重绘。

  5. 在paintEvent函数中,根据当前的进度值计算进度条的起始角度和结束角度,使用QPainter绘制出进度条。

  6. 可以添加一些额外的属性和函数,如设置进度条的最小值和最大值、设置进度条的文本显示等。

三、效果 

四、核心代码  
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环形进度条是一种常见的用户界面设计元素,在编写应用程序时经常用于显示任务进度或操作完成进度。在设计环形进度条时,需要考虑以下几个方面:

  1. 外观设计:环形进度条的外观设计应该符合应用程序的整体风格和主题。可以使用颜色、渐变、阴影等效果,使进度条看起来更加美观和吸引人。

  2. 进度显示:环形进度条通常由一个圆环组成,进度则通过改变圆环的长度来表示。可以使用动画效果来平滑地显示进度的变化,以提高用户体验。另外,可以在进度条上方或下方显示当前的进度百分比,方便用户了解任务的完成情况。

  3. 响应用户操作:环形进度条通常需要与用户进行交互,例如用户可以点击进度条来取消任务或查看详细信息。设计时应该考虑这些交互操作,并为用户提供相应的反馈。

  4. 可定制性:在设计环形进度条时,应该考虑到不同应用程序对进度条的需求可能不同。因此,应该提供一些可定制的参数,例如进度条的大小、颜色和显示方式等,以便开发人员能够根据具体需求进行调整。

        总而言之,设计一个好的环形进度条需要综合考虑外观设计、进度显示、用户交互和定制性等因素,以提供更好的用户体验和满足不同应用程序的需求。

        谢谢您的阅读,希望本文能为您带来一些帮助和启发。如果您有任何问题或意见,请随时与我联系。祝您度过美好的一天!

六、源代码下载

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐