Создание собственного виджета в PyQt5

PyQt5 имеет богатый набор готовых виджетов. Тем не менее, нет в мире инструментария, способного предоставить все виджеты, в которых программисты могут нуждаться для своих приложений. Библиотеки обычно предоставляют только самые распространенные виджеты, такие как кнопки, текстовые виджеты или ползунки. Если есть необходимость в более специализированных виджетах, мы должны создать их сами.

Собственные виджеты создаются путём использования инструментов рисования, предоставляемых PyQt5. Существует две основных возможности: программист может изменить или дополнить существующий виджет, или он может создать собственный виджет с нуля.

Виджет записи дисков

Это виджет, который мы можем увидеть в Nero, K3B и в других программах записи CD/DVD.

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import sys
from PyQt5.QtWidgets import (QWidget, QSlider, QApplication,
    QHBoxLayout, QVBoxLayout)
from PyQt5.QtCore import QObject, Qt, pyqtSignal
from PyQt5.QtGui import QPainter, QFont, QColor, QPen


class Communicate(QObject):

    updateBW = pyqtSignal(int)


class BurningWidget(QWidget):

    def __init__(self):
        super().__init__()

        self.initUI()


    def initUI(self):

        self.setMinimumSize(1, 30)
        self.value = 75
        self.num = [75, 150, 225, 300, 375, 450, 525, 600, 675]


    def setValue(self, value):

        self.value = value


    def paintEvent(self, e):

        qp = QPainter()
        qp.begin(self)
        self.drawWidget(qp)
        qp.end()


    def drawWidget(self, qp):

        font = QFont('Serif', 7, QFont.Light)
        qp.setFont(font)

        size = self.size()
        w = size.width()
        h = size.height()

        step = int(round(w / 10))


        till = int(((w / 750) * self.value))
        full = int(((w / 750) * 700))

        if self.value >= 700:

            qp.setPen(QColor(255, 255, 255))
            qp.setBrush(QColor(255, 255, 184))
            qp.drawRect(0, 0, full, h)
            qp.setPen(QColor(255, 175, 175))
            qp.setBrush(QColor(255, 175, 175))
            qp.drawRect(full, 0, till-full, h)

        else:

            qp.setPen(QColor(255, 255, 255))
            qp.setBrush(QColor(255, 255, 184))
            qp.drawRect(0, 0, till, h)


        pen = QPen(QColor(20, 20, 20), 1,
            Qt.SolidLine)

        qp.setPen(pen)
        qp.setBrush(Qt.NoBrush)
        qp.drawRect(0, 0, w-1, h-1)

        j = 0

        for i in range(step, 10*step, step):

            qp.drawLine(i, 0, i, 5)
            metrics = qp.fontMetrics()
            fw = metrics.width(str(self.num[j]))
            qp.drawText(i-fw/2, h/2, str(self.num[j]))
            j = j + 1


class Example(QWidget):

    def __init__(self):
        super().__init__()

        self.initUI()


    def initUI(self):

        sld = QSlider(Qt.Horizontal, self)
        sld.setFocusPolicy(Qt.NoFocus)
        sld.setRange(1, 750)
        sld.setValue(75)
        sld.setGeometry(30, 40, 150, 30)

        self.c = Communicate()
        self.wid = BurningWidget()
        self.c.updateBW[int].connect(self.wid.setValue)

        sld.valueChanged[int].connect(self.changeValue)
        hbox = QHBoxLayout()
        hbox.addWidget(self.wid)
        vbox = QVBoxLayout()
        vbox.addStretch(1)
        vbox.addLayout(hbox)
        self.setLayout(vbox)

        self.setGeometry(300, 300, 390, 210)
        self.setWindowTitle('Burning widget')
        self.show()


    def changeValue(self, value):

        self.c.updateBW.emit(value)
        self.wid.repaint()


if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

В нашем примере, у нас есть QSlider и собственный виджет. Ползунок контролирует наш виджет. Этот виджет показывает графически общий объём носителя и свободное место, доступное для нас. Минимальное значение нашего виджета – 1, максимальное – 750. Если мы достигаем значения 700, мы начинаем рисование красным цветом. Это обычно означает переполнение. Виджет записи размещается внизу окна путём использования одного QHBoxLayout и одного QVBoxLayout.

class BurningWidget(QWidget):

    def __init__(self):
        super().__init__()

Виджет записи основывается на виджете QWidget.

self.setMinimumSize(1, 30)

Мы меняем минимальный размер (высоту) виджета. Значение по умолчанию немного маловато для нас.

font = QFont('Serif', 7, QFont.Light)
qp.setFont(font)

Мы используем более мелкий шрифт, чем по умолчанию. Это лучше подходит для наших нужд.

size = self.size()
w = size.width()
h = size.height()

step = int(round(w / 10))


till = int(((w / 750) * self.value))
full = int(((w / 750) * 700))

Мы рисуем виджет динамически. Чем больше окно, тем больше виджет записи, и наоборот. Вот почему мы должны вычислять размер виджета.

Параметр till - общий размер. Это значение известно нам из виджета ползунка. Это доля от полного пространства.

Параметр full определяет точку, где мы начинаем рисовать красным цветом.

Фактически рисование содержит три шага. Мы рисуем жёлтый или красный и жёлтый прямоугольник. Затем мы рисуем вертикальные линии, которые делят виджет на несколько частей. Наконец, мы рисуем числа, которые показывают ёмкость носителя.

metrics = qp.fontMetrics()
fw = metrics.width(str(self.num[j]))
qp.drawText(i-fw/2, h/2, str(self.num[j]))

Мы используем метрики шрифтов, чтобы рисовать текст. Мы должны знать ширину текста, для того чтобы центрировать его рядом с вертикальной линией.

def changeValue(self, value):
    self.c.updateBW.emit(value)
    self.wid.repaint()

Когда мы двигаем ползунок, вызывается метод changeValue(). Внутри метода, мы отправляем пользовательский сигнал updateBW с параметром. Параметр – это текущее значение ползунка. Значение позднее используется, чтобы вычислить ёмкость рисуемого виджета записи. Затем, наш виджет перерисовывается.

Виджет прожига CD/DVD

В этой части руководства PyQt5, мы создали свой виджет.

Для вставки кода на Python в комментарий заключайте его в теги <pre><code class="python3">Ваш код</code></pre>
Опечатка в тексте:
Послать сообщение об ошибке автору?