I want to make custom progressbar on Qt.
Design of progressbar (It's PNG):
Here is the result on Qt:
Code of Pic2:
import sys, os, time
from PySide6 import QtCore, QtWidgets, QtGui
from PySide6.QtWidgets import *
from PySide6.QtCore import *
from PySide6.QtGui import *
class EProgressbar(QProgressBar):
valueChanged = QtCore.Signal(int)
_val = 0
def __init__(self):
super(EProgressbar, self).__init__(None)
self.r = 15
self.setFixedHeight(40)
self._animation = QtCore.QPropertyAnimation(self, b"_vallll", duration=600)
self.valueChanged.connect(self.update)
def setValue(self, value:int) -> None:
self._animation.setStartValue(self.value())
self._animation.setEndValue(value)
self._val = value
self._animation.start()
def value(self) -> int:
return self._val
def ESetValue(self, value):
if self._val != value:
self._val = value
self.valueChanged.emit(value)
_vallll = QtCore.Property(int, fget=value, fset=ESetValue, notify=valueChanged)
def paintEvent(self, event: QPaintEvent) -> None:
pt = QPainter();pt.begin(self);pt.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing)
path = QPainterPath();path2 = QPainterPath(); path3 = QPainterPath()
font = QFont('Helvetica', 11, weight=QFont.Bold); font.setStyleHint(QFont.Times, QFont.PreferAntialias)
BRUSH_BASE_BACKGROUND, BRUSH_BASE_FOREGROUND, BRUSH_POLYGON, BRUSH_CORNER = QColor(247,247,250), QColor(255,152,91), QColor(255,191,153), QColor(203,203,205)
pt.setPen(QPen(BRUSH_CORNER,1.5));pt.setBrush(BRUSH_BASE_BACKGROUND)
rect = self.rect().adjusted(2,2,-2,-2)#QRect(1, 0, self.width()-2, self.height())
path.addRoundedRect(rect, self.r, self.r)
#pt.setBrush(BRUSH_BASE_FOREGROUND)
#path.addRect(self.rect())
path2.addRoundedRect(QRect(2,2, self._vallll/ 100 * self.width()-4, self.height()-4), self.r, self.r)
#path2.addRoundedRect(QRect(20,2,10, self.height()), self.r, self.r)
pt.drawPath(path)
pt.setBrush(BRUSH_BASE_FOREGROUND)
pt.drawPath(path2)
pt.setPen(Qt.NoPen)
pt.setBrush(BRUSH_POLYGON)
start_x = 20
y, dx = 3, 6
polygon_width = 14
polygon_space =18 #15#18
progress_filled_width = self.value()/self.maximum()*self.width()
pt.setClipPath(path2, Qt.ClipOperation.ReplaceClip) # bu olmazsa polygon taşıyor, clip yapılması lazım
for i in range(100):
x = start_x (i*polygon_width) (i*polygon_space)
if x >= progress_filled_width or (x polygon_width >= progress_filled_width):
break
path2.addPolygon(QPolygon([
QPoint(x, y),
QPoint(x polygon_width, y),
QPoint(x polygon_width/2, self.height()-y),
QPoint(x-polygon_width/2, self.height()-y)]))
pt.drawPath(path2)
pt.setFont(font)
pt.setPen(Qt.white)
pt.drawText(QRect(2,2,self.width()-4,self.height()-4), Qt.AlignCenter, f"%{self.value()}")
pt.end()
if __name__ == "__main__":
app = QApplication(sys.argv)
wind = QMainWindow();wind.setStyleSheet("QMainWindow{background-color:blue}");wind.setWindowTitle("EProgressBar")
wind.resize(221,150)
wid = QWidget();lay = QHBoxLayout(wid);lay.setAlignment(Qt.AlignCenter)
e = EProgressbar();e.setValue(80)
timer = QTimer(wind)
def lamb():
import random
e.setValue(random.randint(0,100))
timer.timeout.connect(lamb)
#timer.start(1000)
#e.setGeometry(QRect(10,10,170,250))
lay.addWidget(e)
wind.setCentralWidget(wid)
#e.setParent(wind)
wind.show()
sys.exit(app.exec())
This one looks good but when I set progressbar value to 0, result like this:
Notes:
- I need to use pt.setClipPath(path2, Qt.ClipOperation.ReplaceClip) else If you look closely, the polygon in the upper right has crossed the progressbar.
So I think all drawing things must be? in the same QPainterPath? When I try all the drawing in the same path (like
CodePudding user response:
The problem is in the width of the rectangle, which becomes too narrow due to the reduced width and the rounded border.
A better approach would be to clip the path to the external border and merge that clip with a rounded rectangle extended to the left (so that its width will always be enough.
Note that I choose to radically change most aspects in your code, also to improve readability. For slightly better performance, I've set the font for the widget (which is better than recreating it every time) and ignored the bar painting whenever the value was 0.
Finally, since you're painting the value color in white, you must also paint it with another color whenever the value is less than 50%, otherwise the user won't be able to see it until it reaches that point.
class EProgressbar(QProgressBar):
valueChanged = QtCore.pyqtSignal(int)
_val = 0
def __init__(self):
super(EProgressbar, self).__init__(None)
self.r = 15
self.setFixedHeight(40)
self._animation = QtCore.QPropertyAnimation(self, b"_vallll", duration=600)
self.valueChanged.connect(self.update)
font = QFont('Helvetica', 11, weight=QFont.Bold)
font.setStyleHint(QFont.Times, QFont.PreferAntialias)
self.setFont(font)
# ...
def paintEvent(self, event: QPaintEvent) -> None:
pt = QPainter(self)
pt.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing)
border = QPainterPath()
border.addRoundedRect(
QRectF(self.rect().adjusted(2, 2, -3, -3)),
self.r, self.r)
pt.setPen(QColor(203,203,205))
pt.setBrush(QColor(247,247,250))
pt.drawPath(border)
pt.setClipPath(border)
foreground = QColor(255,191,153)
pt.setPen(foreground.darker(110))
pt.drawText(self.rect(), Qt.AlignCenter, '{}%'.format(self.value()))
if self._vallll <= self.minimum():
return
polygon_width = 14
brush_polygon = QPolygonF([
QPoint(0, 3),
QPoint(polygon_width, 3),
QPoint(polygon_width / 2, self.height() - 3),
QPoint(-polygon_width / 2, self.height() - 3)
])
bar_width = (self.width() - 4) * self._vallll * .01
brush_size = brush_polygon.boundingRect().width() 4
bar_count = int(bar_width / brush_size) 1
value_clip = QPainterPath()
rect = QRectF(-20, 2, 20 bar_width, self.height() - 3)
value_clip.addRoundedRect(rect, self.r, self.r)
pt.setClipPath(value_clip, Qt.IntersectClip)
brush_path = QPainterPath()
for i in range(bar_count):
brush_path.addPolygon(brush_polygon.translated(brush_size * i, 0))
pt.setPen(Qt.NoPen)
pt.setBrush(QColor(255,152,91))
pt.drawPath(border)
pt.setBrush(foreground)
pt.drawPath(brush_path)
pt.setPen(Qt.white)
pt.setFont(self.font())
pt.drawText(self.rect(), Qt.AlignCenter, '{}%'.format(self.value()))
On an unrelated note, be aware that your code has lots of readability issues; for instance, you should not use semicolons to separate functions: doing it doesn't provide any benefit, and they only make the code unnecessarily annoying to read; spaces between function arguments are also very important; read more about these extremely important aspects in the official Style Guide for Python Code.