Qt第三方圓形進度條的改進
要實現一個圓形的進度條功能,在網上找到一個比較出名的第三方封裝類:QRoundProgressBar,地址:sourceforge 的 QRoundProgressBar
功能封裝的還是不錯,提供了3種模式,線形、圓環、餅狀。使用過程中發現圓環進度條對背景透明支持不夠完善,內圓的背景無法實現透明,為了解決此問題,下面對此控件進行了一些修訂,實現完整的圓形進度條。
QRoundProgressBar目前存在的不足

QRoundProgressBar在帶背景圖片widget下使用StyleDonut樣式時,內環背景無法透明

代碼如下:
頭文件:
class DRoundProgressBar; class QTimer; namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = 0); ~Widget(); public slots: void onTimeOut(); private: Ui::Widget *ui; QTimer* mTimer; DRoundProgressBar* mRoundBar; int mPresent; };
cpp:
#include "Widget.h" #include "ui_Widget.h" #include "DRoundProgressBar.h" #include <QTimer> Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); setAutoFillBackground(true); QPixmap img(":/img/BlueDialog_BK.png"); QPalette bkPalette; bkPalette.setBrush(QPalette::Window,QBrush(img)); setPalette(bkPalette); mRoundBar = new DRoundProgressBar(this); mRoundBar->setGeometry(150,100,500,500); mRoundBar->setBarStyle(DRoundProgressBar::StyleDonut); mRoundBar->setRange(0,100); QPalette palette; palette.setBrush(QPalette::Window,Qt::NoBrush); palette.setBrush(QPalette::AlternateBase,Qt::NoBrush); palette.setBrush(QPalette::Highlight,QBrush(QColor(0,140,255))); palette.setColor(QPalette::Text,QColor(0,0,0)); //palette.setBrush(QPalette::Base,Qt::white); mRoundBar->setPalette(palette); mTimer = new QTimer(this); mTimer->setInterval(200); connect(mTimer,SIGNAL(timeout()),this,SLOT(onTimeOut())); mPresent = 0; mTimer->start(); } Widget::~Widget() { delete ui; } void Widget::onTimeOut() { ++mPresent; if(mPresent >= 100) { mPresent = 0; } mRoundBar->setValue(mPresent); }
這里把QPalette::Window和QPalette::AlternateBase設置為透明,發現無法繪制圓環
原因
查看代碼,看看其繪制圓環的步驟
void QRoundProgressBar::drawValue(QPainter &p, const QRectF &baseRect, double value, double arcLength) { // nothing to draw if (value == m_min) return; // for Line style if (m_barStyle == StyleLine) { p.setPen(QPen(palette().highlight().color(), m_dataPenWidth)); p.setBrush(Qt::NoBrush); p.drawArc(baseRect.adjusted(m_outlinePenWidth/2, m_outlinePenWidth/2, -m_outlinePenWidth/2, -m_outlinePenWidth/2), m_nullPosition * 16, -arcLength * 16); return; } // for Pie and Donut styles QPainterPath dataPath; dataPath.setFillRule(Qt::WindingFill); // pie segment outer dataPath.moveTo(baseRect.center()); dataPath.arcTo(baseRect, m_nullPosition, -arcLength); dataPath.lineTo(baseRect.center()); p.setBrush(palette().highlight()); p.setPen(QPen(palette().shadow().color(), m_dataPenWidth)); p.drawPath(dataPath); }
發現繪制圓環和繪制pie是一個代碼,而實現圓環只不過是用一個背景覆蓋上去了,這樣,如果讓中間區域透明就會顯示畫的那個扇形的那一部分(原來作者是得多懶-_-#)。
因此。在繪制圓環時需要特殊對待,QPainterPath在畫圓環時把圓環填充。
改進
這里需要把原來的drawValue函數進行修正,原來drawValue函數對繪制pie和繪制Donut styles是一樣處理,這里修正為pie就畫pie,畫Donut styles就畫圓環:
為了繪制圓環,drawValue函數需添加兩個變量,是內環的對應的矩形和半徑
virtual void drawValue(QPainter& p, const QRectF& baseRect, double value, double arcLength, const QRectF & innerRect, double innerRadius);
改動后的cpp
void QRoundProgressBar::drawValue(QPainter &p , const QRectF &baseRect , double value , double arcLength , const QRectF & innerRect , double innerRadius) { // nothing to draw if (value == m_min) return; // for Line style if (m_barStyle == StyleLine) { p.setPen(QPen(palette().highlight().color(), m_dataPenWidth)); p.setBrush(Qt::NoBrush); p.drawArc(baseRect.adjusted(m_outlinePenWidth/2, m_outlinePenWidth/2, -m_outlinePenWidth/2, -m_outlinePenWidth/2), m_nullPosition * 16, -arcLength * 16); return; } // for Pie and Donut styles QPainterPath dataPath; dataPath.setFillRule(Qt::WindingFill); dataPath.moveTo(baseRect.center()); dataPath.arcTo(baseRect, m_nullPosition, -arcLength);//大家都是先繪制外圓的弧長 if(m_barStyle == StylePie) { // pie segment outer dataPath.lineTo(baseRect.center()); p.setPen(QPen(palette().shadow().color(), m_dataPenWidth)); } if(m_barStyle == StyleDonut) { // draw dount outer QPointF currentPoint = dataPath.currentPosition();//繪制完外圓弧長后,獲取繪制完的位置繪制一個直線到達內圓 currentPoint = baseRect.center() + ((currentPoint - baseRect.center()) * m_innerOuterRate;//計算內圓的坐標點,m_innerOuterRate替代了原作者寫的0.75,代表內圓是外圓的0.75倍 dataPath.lineTo(currentPoint);//繪制外圓到內圓的直線 dataPath.moveTo(baseRect.center());//坐標點回到中心准備繪制內圓弧形 dataPath.arcTo(innerRect, m_nullPosition-arcLength, arcLength);//繪制內圓的弧形 currentPoint = dataPath.currentPosition();//准備繪制內圓到外圓的直線,形成封閉區域 currentPoint = baseRect.center() + ((currentPoint - baseRect.center()) * (2-m_innerOuterRate));//繪制內圓到外圓的直線,這里2-m_innerOuterRate其實是對應(1 + (1 -m_innerOuterRate))的 dataPath.lineTo(currentPoint); p.setPen(Qt::NoPen);//這個很重要不然就會有繪制過程的一些輪廓了 } p.setBrush(palette().highlight()); p.drawPath(dataPath); }
具體過程見代碼的注釋。
這里作者把內圓直徑定死為外圓的0.75倍,我覺得這樣失去了靈活性,因此加入了一個float變量,m_innerOuterRate,默認為0.75,替代原來的0.75常數,並加入方法float innerOuterRate() const和void setInnerOuterRate(float r)進行設置
原來的paintEvent函數的函數順序也需要改變:
下面是原來的paintEvent函數:
void QRoundProgressBar::paintEvent(QPaintEvent* /*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 drawBase(p, baseRect); // data circle double arcStep = 360.0 / (m_max - m_min) * m_value; drawValue(p, baseRect, m_value, arcStep); // center circle double innerRadius(0); QRectF innerRect; calculateInnerRect(baseRect, outerRadius, innerRect, innerRadius); drawInnerBackground(p, innerRect); // text drawText(p, innerRect, innerRadius, m_value); // finally draw the bar p.end(); QPainter painter(this); painter.fillRect(baseRect, palette().background()); painter.drawImage(0,0, buffer); }
原來作者使用了QImage作為緩存,但qt自帶雙緩沖,這一步沒有必要(用QImage時在ubuntu還有小問題,在控件比較小時(200*200以下)會出現花屏現象,原因未知),修改為:
void QRoundProgressBar::paintEvent(QPaintEvent* /*event*/) { double outerRadius = qMin(width(), height()); QRectF baseRect(1, 1, outerRadius-2, outerRadius-2); QPainter p(this); p.setRenderHint(QPainter::Antialiasing); // data brush rebuildDataBrushIfNeeded(); // background drawBackground(p, rect()); double innerRadius(0); QRectF innerRect; calculateInnerRect(baseRect, outerRadius, innerRect, innerRadius); double arcStep = 360.0 / (m_max - m_min) * m_value; // base circle drawBase(p, baseRect,innerRect); // data circle drawValue(p, baseRect, m_value, arcStep,innerRect, innerRadius); // center circle drawInnerBackground(p, innerRect); // text drawText(p, innerRect, innerRadius, m_value); // finally draw the bar p.end(); }
主要是把calculateInnerRect(baseRect, outerRadius, innerRect, innerRadius);函數提前計算出內圓對應的參數。並傳入給新修改的drawValue函數。把多余的雙緩沖機制去掉.
此時效果還未達到需求的效果,發現drawBase函數還需要修改,原來的drawBase函數如下:
void QRoundProgressBar::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:; } }
上面的drawBase函數可見,由於原作者比較懶,對於Donut styles模式就直接畫了一個外圓,並不是一個空心圓環,改進如下:
void QRoundProgressBar::drawBase(QPainter &p, const QRectF &baseRect,const QRectF &innerRect) { switch (m_barStyle) { case StyleDonut: { QPainterPath dataPath; dataPath.setFillRule(Qt::OddEvenFill); dataPath.moveTo(baseRect.center()); dataPath.addEllipse(baseRect); dataPath.addEllipse(innerRect); p.setPen(QPen(palette().shadow().color(), m_outlinePenWidth)); p.setBrush(palette().base()); p.drawPath(dataPath); 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:; } }
最后運行效果:

代碼見:czyt1988的github
http://blog.csdn.net/czyt1988/article/details/53422274
