table of Contents
Custom controls in PyQt5
PyQt5 has a rich set of widgets. However, no toolkit can provide all the widgets programmers might need in their applications. Toolkits usually only provide the most common widgets, such as buttons, text widgets or sliders. If a more professional widget is needed, we must create it ourselves.
Create custom widgets by using the drawing tools provided in the toolkit. There are two basic possibilities: programmers can modify or enhance existing widgets, or they can create custom widgets from scratch.
Burning widget
This is the widget we can see in Nero, K3B or other CD / DVD burning software.
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from PyQt5.QtWidgets import (QWidget, QSlider, QApplication,
QHBoxLayout, QVBoxLayout)
from PyQt5.QtCore import QObject, Qt, pyqtSignal
from PyQt5.QtGui import QPainter, QFont, QColor, QPen
import sys
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):
MAX_CAPACITY = 700
OVER_CAPACITY = 750
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 / OVER_CAPACITY) * self.value))
full = int(((w / OVER_CAPACITY) * MAX_CAPACITY))
if self.value >= MAX_CAPACITY:
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):
OVER_CAPACITY = 750
sld = QSlider(Qt.Horizontal, self)
sld.setFocusPolicy(Qt.NoFocus)
sld.setRange(1, OVER_CAPACITY)
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_())
In our example, we have QSlider
a custom widget. The slider controls custom widgets. This widget graphically displays the total capacity and available space of the media. The minimum value of our custom widget is 1 and the maximum value is OVER_CAPACITY. If we reach the MAX_CAPACITY value, we start drawing red. This usually indicates excessive burning.
The burning widget is located at the bottom of the window. This is QHBoxLayout
achieved using one and one QVBoxLayout
.
class BurningWidget(QWidget):
def __init__(self):
super().__init__()
Widget-based burning QWidget
widget.
self.setMinimumSize(1,30)
We change the minimum size (height) of the widget. Our default value is a bit small.
font = QFont('Serif', 7, QFont.Light)
qp.setFont(font)
We use a smaller font than the default font. This is more suitable for our needs.
size = self.size()
w = size.width()
h = size.height()
step = int(round(w / 10))
till = int(((w / OVER_CAPACITY) * self.value))
full = int(((w / OVER_CAPACITY) * MAX_CAPACITY))
We draw widgets dynamically. The larger the window, the larger the burning widget, and vice versa. This is why we have to calculate the size of the widget we draw the custom widget. This till
parameter determines the total size to be drawn. This value comes from the slider widget. It accounts for the entire region. This full
parameter determines the point where we start drawing in red.
The actual drawing includes three steps. We draw yellow or red and yellow rectangles. Then we draw a vertical line and divide the widget into several parts. Finally, we plotted the number indicating the capacity of the medium.
metrics = qp.fontMetrics()
fw = metrics.width(str(self.num[j]))
qp.drawText(i-fw/2, h/2, str(self.num[j]))
We use font indicators to draw text. We must know the width of the text in order to center around the vertical line.
def changeValue(self, value):
self.c.updateBW.emit(value)
self.wid.repaint()
When we move the slider, changeValue()
this method is called. Inside the method, we send updateBW
a custom signal with parameters. The parameter is the current value of the slider. This value is later used to calculate the capacity of the "burning" widget to be drawn. Then redraw the custom widget.
Figure: Burn widget