Qt Custom Paint Event Progressbar

Я хочу сделать собственный индикатор выполнения на Qt.

Дизайн индикатора выполнения (это PNG):

Qt Custom Paint Event Progressbar

Вот результат на Qt:

Qt Custom Paint Event Progressbar

Код 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())

Это выглядит хорошо но, когда я устанавливаю значение индикатора выполнения в 0, результат выглядит следующим образом:

Qt Custom Paint Event Progressbar

Примечания:

  1. Мне нужно использовать pt.setClipPath(path2, Qt.ClipOperation.ReplaceClip) иначе Если вы внимательно посмотрите, многоугольник в правом верхнем углу пересек индикатор выполнения.

Qt Custom Paint Event Progressbar

Так что я думаю, все вещи рисования должны быть? в том же QPainterPath? Когда я пробую все рисунки по одному и тому же пути (например, OnePathCode), результат выглядит следующим образом:

Qt Custom Paint Event Progressbar

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
0
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема в ширине прямоугольника, который становится слишком узким из-за уменьшенной ширины и закругленной границы.

Лучшим подходом было бы обрезать путь к внешней границе и объединить этот клип с прямоугольником со скругленными углами, расширенным влево (так, чтобы его ширины всегда было достаточно.

Обратите внимание, что я решил радикально изменить большинство аспектов вашего кода, в том числе для улучшения читабельности. Для немного лучшей производительности я установил шрифт для виджета (что лучше, чем создавать его каждый раз заново) и игнорировал отрисовку полосы всякий раз, когда значение было равно 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()))

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.

Другие вопросы по теме