'How to create a labelled QProgressBar in PySide?
This is exactly what I am trying to re-create
I've tried a 4x4 grid layout with QLabels underneath a QProgressBar but it looks awful and I am wondering if there are any possible ways I could approach creating this?
Solution 1:[1]
That widget must be created using a custom painting as shown below:
import os
from PySide2 import QtCore, QtGui, QtWidgets
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class CustomProgressBar(QtWidgets.QWidget):
stepsChanged = QtCore.Signal(list)
valueChanged = QtCore.Signal(int)
def __init__(self, parent=None):
super().__init__(parent)
self._labels = []
self._value = 0
self._animation = QtCore.QVariantAnimation(
startValue=0.0, endValue=1.0, duration=500
)
self._animation.valueChanged.connect(self.update)
def get_labels(self):
return self._labels
def set_labels(self, labels):
self._labels = labels[:]
self.stepsChanged.emit(self._labels)
labels = QtCore.Property(
list, fget=get_labels, fset=set_labels, notify=stepsChanged
)
def get_value(self):
return self._value
def set_value(self, value):
if 0 <= value < len(self.labels) + 1:
self._value = value
self.valueChanged.emit(value)
self.update()
if self.value < len(self.labels):
self._animation.start()
value = QtCore.Property(int, fget=get_value, fset=set_value, notify=valueChanged)
def sizeHint(self):
return QtCore.QSize(320, 120)
def paintEvent(self, event):
grey = QtGui.QColor("#777")
grey2 = QtGui.QColor("#dfe3e4")
blue = QtGui.QColor("#2183dd")
green = QtGui.QColor("#009900")
white = QtGui.QColor("#fff")
painter = QtGui.QPainter(self)
painter.setRenderHints(QtGui.QPainter.Antialiasing)
height = 5
offset = 10
painter.fillRect(self.rect(), white)
busy_rect = QtCore.QRect(0, 0, self.width(), height)
busy_rect.adjust(offset, 0, -offset, 0)
busy_rect.moveCenter(self.rect().center())
painter.fillRect(busy_rect, grey2)
number_of_steps = len(self.labels)
if number_of_steps == 0:
return
step_width = busy_rect.width() / number_of_steps
x = offset + step_width / 2
y = busy_rect.center().y()
radius = 10
font_text = painter.font()
font_icon = QtGui.QFont("Font Awesome 5 Free")
font_icon.setPixelSize(radius)
r = QtCore.QRect(0, 0, 1.5 * radius, 1.5 * radius)
fm = QtGui.QFontMetrics(font_text)
for i, text in enumerate(self.labels, 1):
r.moveCenter(QtCore.QPoint(x, y))
if i <= self.value:
w = (
step_width
if i < self.value
else self._animation.currentValue() * step_width
)
r_busy = QtCore.QRect(0, 0, w, height)
r_busy.moveCenter(busy_rect.center())
if i < number_of_steps:
r_busy.moveLeft(x)
painter.fillRect(r_busy, blue)
pen = QtGui.QPen(green)
pen.setWidth(3)
painter.setPen(pen)
painter.setBrush(green)
painter.drawEllipse(r)
painter.setFont(font_icon)
painter.setPen(white)
painter.drawText(r, QtCore.Qt.AlignCenter, chr(0xF00C))
painter.setPen(green)
else:
is_active = (self.value + 1) == i
pen = QtGui.QPen(grey if is_active else grey2)
pen.setWidth(3)
painter.setPen(pen)
painter.setBrush(white)
painter.drawEllipse(r)
painter.setPen(blue if is_active else QtGui.QColor("black"))
rect = fm.boundingRect(text)
rect.moveCenter(QtCore.QPoint(x, y + 2 * radius))
painter.setFont(font_text)
painter.drawText(rect, QtCore.Qt.AlignCenter, text)
x += step_width
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
_id = QtGui.QFontDatabase.addApplicationFont(
os.path.join(CURRENT_DIR, "fa-solid-900.ttf")
)
print(QtGui.QFontDatabase.applicationFontFamilies(_id))
progressbar = CustomProgressBar()
progressbar.labels = ["Step One", "Step Two", "Step Three", "Complete"]
button = QtWidgets.QPushButton("Next Step")
def on_clicked():
progressbar.value = (progressbar.value + 1) % (len(progressbar.labels) + 1)
button.clicked.connect(on_clicked)
w = QtWidgets.QWidget()
lay = QtWidgets.QVBoxLayout(w)
lay.addWidget(progressbar)
lay.addWidget(button, alignment=QtCore.Qt.AlignRight)
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Note: To paint the check icon I have used the awesome font so it must be downloaded from here and placed next to the script.
PySide:
import os
from PySide import QtCore, QtGui
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class CustomVariantAnimation(QtCore.QVariantAnimation):
def updateCurrentValue(self, value):
pass
class CustomProgressBar(QtGui.QWidget):
stepsChanged = QtCore.Signal(list)
valueChanged = QtCore.Signal(int)
def __init__(self, parent=None):
super().__init__(parent)
self._labels = []
self._value = 0
self._percentage_width = 0
self._animation = CustomVariantAnimation(startValue=0.0, endValue=1.0)
self._animation.setDuration(500)
self._animation.valueChanged.connect(self.update)
def get_labels(self):
return self._labels
def set_labels(self, labels):
self._labels = labels[:]
self.stepsChanged.emit(self._labels)
labels = QtCore.Property(
list, fget=get_labels, fset=set_labels, notify=stepsChanged
)
def get_value(self):
return self._value
def set_value(self, value):
if 0 <= value < len(self.labels) + 1:
self._value = value
self.valueChanged.emit(value)
self.update()
if self.value < len(self.labels):
self._animation.start()
value = QtCore.Property(int, fget=get_value, fset=set_value, notify=valueChanged)
def sizeHint(self):
return QtCore.QSize(320, 120)
def paintEvent(self, event):
grey = QtGui.QColor("#777")
grey2 = QtGui.QColor("#dfe3e4")
blue = QtGui.QColor("#2183dd")
green = QtGui.QColor("#009900")
white = QtGui.QColor("#fff")
painter = QtGui.QPainter(self)
painter.setRenderHints(QtGui.QPainter.Antialiasing)
height = 5
offset = 10
painter.fillRect(self.rect(), white)
busy_rect = QtCore.QRect(0, 0, self.width(), height)
busy_rect.adjust(offset, 0, -offset, 0)
busy_rect.moveCenter(self.rect().center())
painter.fillRect(busy_rect, grey2)
number_of_steps = len(self.labels)
if number_of_steps == 0:
return
step_width = busy_rect.width() / number_of_steps
x = offset + step_width / 2
y = busy_rect.center().y()
radius = 10
font_text = painter.font()
font_icon = QtGui.QFont("Font Awesome 5 Free")
font_icon.setPixelSize(radius)
r = QtCore.QRect(0, 0, 1.5 * radius, 1.5 * radius)
fm = QtGui.QFontMetrics(font_text)
for i, text in enumerate(self.labels, 1):
r.moveCenter(QtCore.QPoint(x, y))
if i <= self.value:
w = (
step_width
if i < self.value
else self._animation.currentValue() * step_width
)
r_busy = QtCore.QRect(0, 0, w, height)
r_busy.moveCenter(busy_rect.center())
if i < number_of_steps:
r_busy.moveLeft(x)
painter.fillRect(r_busy, blue)
pen = QtGui.QPen(green)
pen.setWidth(3)
painter.setPen(pen)
painter.setBrush(green)
painter.drawEllipse(r)
painter.setFont(font_icon)
painter.setPen(white)
painter.drawText(r, QtCore.Qt.AlignCenter, chr(0xF00C))
painter.setPen(green)
else:
is_active = (self.value + 1) == i
pen = QtGui.QPen(grey if is_active else grey2)
pen.setWidth(3)
painter.setPen(pen)
painter.setBrush(white)
painter.drawEllipse(r)
painter.setPen(blue if is_active else QtGui.QColor("black"))
rect = fm.boundingRect(text)
rect.moveCenter(QtCore.QPoint(x, y + 2 * radius))
painter.setFont(font_text)
painter.drawText(rect, QtCore.Qt.AlignCenter, text)
x += step_width
def main():
import sys
app = QtGui.QApplication(sys.argv)
_id = QtGui.QFontDatabase.addApplicationFont(
os.path.join(CURRENT_DIR, "fa-solid-900.ttf")
)
print(QtGui.QFontDatabase.applicationFontFamilies(_id))
progressbar = CustomProgressBar()
progressbar.labels = ["Step One", "Step Two", "Step Three", "Complete"]
button = QtGui.QPushButton("Next Step")
def on_clicked():
progressbar.value = (progressbar.value + 1) % (len(progressbar.labels) + 1)
button.clicked.connect(on_clicked)
w = QtGui.QWidget()
lay = QtGui.QVBoxLayout(w)
lay.addWidget(progressbar)
lay.addWidget(button, alignment=QtCore.Qt.AlignRight)
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Solution 2:[2]
For c++: https://i.stack.imgur.com/9iDlg.png
#ifndef NTUSTEPPROGRESSBAR_H
#define NTUSTEPPROGRESSBAR_H
#include <QObject>
#include <QWidget>
#include <QVariantAnimation>
class NTUStepProgressBar: public QWidget
{
Q_OBJECT
public:
NTUStepProgressBar(QWidget *widget =nullptr);
void setValue(int value);
void setLabel(QList<QString> label);
protected:
void paintEvent(QPaintEvent* paintEvent);
private:
QList<QString> mLabel;
int mValue;
QVariantAnimation mAnimation;
};
#endif // NTUSTEPPROGRESSBAR_H
#include "NTUStepProgressBar.h"
#include <QPainter>
NTUStepProgressBar::NTUStepProgressBar(QWidget* widget) : QWidget(widget)
{
this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
setWindowFlags(Qt::FramelessWindowHint);
this->mLabel << "Step One"
<< "Step Two"
<< "Step Three"
<< "Complete";
this->mValue = 0;
mAnimation.setStartValue(0.0);
mAnimation.setEndValue(1.0);
mAnimation.setDuration(500);
connect(&mAnimation, &QVariantAnimation::valueChanged, [this](const QVariant &value){
this->update();
});
}
void NTUStepProgressBar::setValue(int value)
{
if( 0 <= value < mLabel.size()+ 1)
{
mValue = value;
// self.valueChanged.emit(value)
this->update();
if (mValue < mLabel.size())
mAnimation.start();
}
}
void NTUStepProgressBar::setLabel(QList<QString> label)
{
mLabel = label;
}
void NTUStepProgressBar::paintEvent(QPaintEvent* paintEvent)
{
QPainter painter(this);
QColor grey = QColor("#777");
QColor grey2 = QColor("#dfe3e4");
QColor blue = QColor("#2183dd");
QColor green = QColor("#009900");
QColor white = QColor("#fff");
painter.setRenderHints(QPainter::Antialiasing);
int height = 5;
int offset = 10;
painter.fillRect(this->rect(), white);
QRect busy_rect = QRect(0, 0, this->width(), height);
busy_rect.adjust(offset, 0, -offset, 0);
busy_rect.moveCenter(this->rect().center());
painter.fillRect(busy_rect, grey2);
int number_of_steps = this->mLabel.size();
if (number_of_steps == 0)
return;
int step_width = busy_rect.width() / number_of_steps;
int x = offset + step_width / 2;
int y = busy_rect.center().y();
int radius = 10;
QFont font_text = painter.font();
QFont font_icon = QFont("Font Awesome 5 Free");
font_icon.setPixelSize(radius);
QRect r = QRect(0, 0, 1.5 * radius, 1.5 * radius);
QFontMetrics fm = QFontMetrics(font_text);
for (int i = 0; i < mLabel.size(); i++)
{
r.moveCenter(QPoint(x, y));
int w;
if (i <= mValue)
{
if (i < mValue)
w = step_width;
else
w = mAnimation.currentValue().toInt() * step_width;
QRect r_busy = QRect(0, 0, w, height);
r_busy.moveCenter(busy_rect.center());
if (i < number_of_steps)
{
r_busy.moveLeft(x);
painter.fillRect(r_busy, blue);
}
QPen pen = QPen(green);
pen.setWidth(3);
painter.setPen(pen);
painter.setBrush(green);
painter.drawEllipse(r);
painter.setFont(font_icon);
painter.setPen(white);
painter.drawText(r, Qt::AlignCenter, tr(""));
painter.setPen(green);
}
else
{
bool is_active = (this->mValue + 1) == i;
QPen pen = is_active ? QPen(grey) : QPen(grey2);
pen.setWidth(3);
painter.setPen(pen);
painter.setBrush(white);
painter.drawEllipse(r);
painter.setPen(is_active ? blue : QColor("black"));
}
QRect rect = fm.boundingRect(mLabel[i]);
rect.moveCenter(QPoint(x, y + 2 * radius));
painter.setFont(font_text);
painter.drawText(rect, Qt::AlignCenter, mLabel[i]);
x += step_width;
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | user1742740 |