Qt 自定义 Paint 事件进度条
Qt Custom Paint Event Progressbar
我想在 Qt 上制作自定义进度条。
进度条设计(PNG):
这是 Qt 上的结果:
图2代码:
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())
这个看起来不错但是 当我将进度条值设置为 0 时,结果如下:
备注:
- 我需要用pt.setClipPath(path2, Qt.ClipOperation.ReplaceClip) else 仔细一看,右上角的多边形已经越过进度条了。
所以我觉得所有画的东西一定是?在同一个 QPainterPath 中?当我尝试在同一路径(如 OnePathCode)中绘制所有图形时,结果如下:
问题出在矩形的宽度上,由于宽度减小和边框变圆,它变得太窄了。
更好的方法是将路径剪裁到外部边框并将该剪裁与向左延伸的圆角矩形合并(这样它的宽度将始终足够。
请注意,我选择从根本上更改代码中的大部分内容,也是为了提高可读性。为了获得更好的性能,我为小部件设置了字体(这比每次都重新创建它要好)并且在值为 0 时忽略条形图绘制。
最后,由于您将值颜色绘制为白色,因此当值小于 50% 时,您还必须将其绘制为另一种颜色,否则在达到该值之前用户将无法看到它点.
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()))
顺便提一下,请注意您的代码存在很多可读性问题;例如,你不应该使用分号来分隔函数:这样做没有任何好处,只会让代码阅读起来不必要地烦人;函数参数之间的空格也很重要;在官方 Style Guide for Python Code.
中阅读更多关于这些 极其 重要方面的信息
我想在 Qt 上制作自定义进度条。
进度条设计(PNG):
这是 Qt 上的结果:
图2代码:
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())
这个看起来不错但是 当我将进度条值设置为 0 时,结果如下:
备注:
- 我需要用pt.setClipPath(path2, Qt.ClipOperation.ReplaceClip) else 仔细一看,右上角的多边形已经越过进度条了。
所以我觉得所有画的东西一定是?在同一个 QPainterPath 中?当我尝试在同一路径(如 OnePathCode)中绘制所有图形时,结果如下:
问题出在矩形的宽度上,由于宽度减小和边框变圆,它变得太窄了。
更好的方法是将路径剪裁到外部边框并将该剪裁与向左延伸的圆角矩形合并(这样它的宽度将始终足够。
请注意,我选择从根本上更改代码中的大部分内容,也是为了提高可读性。为了获得更好的性能,我为小部件设置了字体(这比每次都重新创建它要好)并且在值为 0 时忽略条形图绘制。
最后,由于您将值颜色绘制为白色,因此当值小于 50% 时,您还必须将其绘制为另一种颜色,否则在达到该值之前用户将无法看到它点.
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()))
顺便提一下,请注意您的代码存在很多可读性问题;例如,你不应该使用分号来分隔函数:这样做没有任何好处,只会让代码阅读起来不必要地烦人;函数参数之间的空格也很重要;在官方 Style Guide for Python Code.
中阅读更多关于这些 极其 重要方面的信息