PyQt6 custom widgets
PyQt6 already has rich components, but no toolkit can provide all the components developers need to develop applications. Toolkits usually only provide the most common widgets, such as buttons, text widgets, or sliders. If we need a widget for a specific need, we have to create it ourselves.
Custom widgets are created using the drawing tools provided by the toolkit. Basically there are two ways: a programmer can modify or enhance an existing widget, or he can create a custom widget from scratch.
PyQt6 burning parts
This component can be seen in Nero, K3B or other CD/DVD burning software.
# file: burning_widget.py
#!/usr/bin/python
"""
ZetCode PyQt6 tutorial
In this example, we create a custom widget.
Author: Jan Bodnar
Website: zetcode.com
"""
from PyQt6.QtWidgets import (QWidget, QSlider, QApplication,
QHBoxLayout, QVBoxLayout)
from PyQt6.QtCore import QObject, Qt, pyqtSignal
from PyQt6.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.Weight.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.PenStyle.SolidLine)
qp.setPen(pen)
qp.setBrush(Qt.BrushStyle.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.horizontalAdvance(str(self.num[j]))
x, y = int(i - fw/2), int(h / 2)
qp.drawText(x, y, 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.Orientation.Horizontal, self)
sld.setFocusPolicy(Qt.FocusPolicy.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()
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec())
if __name__ == '__main__':
main()
In this example, there is a QSlider
and a custom widget - the slider controls the custom widget. This widget graphically displays the total capacity of the media and the available free space. Custom widgets have a minimum value of 1 and a maximum value of OVER_CAPACITY
. If the value is reached MAX_CAPACITY
, it will turn red, indicating that the data to be burned is greater than the capacity of the medium.
The programming part is located at the bottom of the window. Implemented with QHBoxLayout
and QVBoxLayout
.
class BurningWidget(QWidget):
def __init__(self):
super().__init__()
The burning component is based on QWidget
.
self.setMinimumSize(1, 30)
Set the height of the widget, the default height is a bit small.
font = QFont('Serif', 7, QFont.Weight.Light)
qp.setFont(font)
A smaller font size is used here, which looks 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))
Widgets are rendered dynamically. The larger the window, the larger the widget, and vice versa. So we need to dynamically calculate the size of the widget has to calculate the size of the widget on which the custom widget is drawn. The parameter till
determines the total size of the widget, this value comes from the slider widget, it is a ratio relative to the whole area. The parameter full
is the starting point of the red patch.
Drawing consists of three steps, first drawing a rectangle with yellow or red and yellow, then drawing a vertical line to divide the widget into parts, and finally drawing a number representing the capacity of the media.
metrics = qp.fontMetrics()
fw = metrics.horizontalAdvance(str(self.num[j]))
x, y = int(i - fw/2), int(h / 2)
qp.drawText(x, y, str(self.num[j]))
We use a font material to draw the text, so the width of the text must be known in order to center it vertically.
def changeValue(self, value):
self.c.updateBW.emit(value)
self.wid.repaint()
When the slider is moved, changeValue
the method is called. Inside the method, trigger a custom signal with a parameter updateBW
, the parameter is the current value of the slider, and this value is also used to calculate the capacity of the widget to be drawn Burning
, so that the widget is drawn.
Figure: Burning Components