首页 > 解决方案 > 如何在 PySide 中创建带标签的 QProgressBar?

问题描述

这正是我想要重新创建的

我在 QProgressBar 下尝试了 QLabels 的 4x4 网格布局,但它看起来很糟糕,我想知道是否有任何可能的方法可以创建它?

标签: pythonpyside

解决方案


该小部件必须使用自定义绘画创建,如下所示:

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()

在此处输入图像描述

注意:要绘制复选图标,我使用了很棒的字体,因此必须从这里下载并放置在脚本旁边。

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()

推荐阅读