PyQt5 has a rich set of widgets. However, no toolkit can provide all widgets that programmers might need in their applications. Toolkits usually provide only the most common widgets like buttons, text widgets, or sliders. If there is a need for a more specialised widget, we must create it ourselves.
Custom widgets are created by using the drawing tools provided by the toolkit. There are two basic possibilities: a programmer can modify or enhance an existing widget or he can create a custom widget from scratch.
Burning widget:
This is a widget that we can see in Nero, K3B, or other CD/DVD burning software.
代码:
# coding='utf-8'
from PyQt5.QtWidgets import QWidget, QApplication,\
QSlider, QHBoxLayout, QVBoxLayout
from PyQt5.QtGui import QPainter, QFont, QColor, QPen
from PyQt5.QtCore import Qt, QObject, pyqtSignal
import sys
# 我们写一个QObject类的继承类,我们前面说过了,\
# QObject类/子类的实例化对象可以发出信号
class Communicate(QObject):
# 我们创建一个pyqtSignal类的实例化对象,这是一种用户自定义的信号
updateBW = pyqtSignal(int)
# 我们构造属于自己的/复杂的 组件, 称作custom widget,\
# 我们在这个子类里重写Paintevent方法来让它能表现得更复杂
class BurningWidget(QWidget):
def __init__(self):
super().__init__()
self.start()
def start(self):
# We change the minimum size (height) of the widget. The default value is a bit small for us.
# 我们设置的这个组件最小的宽是1,最小的高度是30(保证了高度不能太小)
self.setMinimumSize(1, 30)
# 这个类属性value用来在后面的Qslider和这个custom widget之间通信
self.value = 75
# 这些是待绘制的一些离散值
self.num = [75, 150, 225, 300, 375, 450, 525, 600, 675]
# 这个方法可以设置类间通信变量value的值
def set_value(self, value):
self.value = value
# 重写painEvent方法,每当组件size改变时会触发(组件从无到有也算size改变)
def paintEvent(self, e) -> None:
# QPainter类对象的常规用法,待绘制的操作放在begin和end中间
qp = QPainter()
qp.begin(self)
# 绘制函数主要在后面
self.draw_widget(qp)
qp.end()
def draw_widget(self, qp):
# The actual drawing consists of three steps.\
# We draw the yellow or the red and yellow rectangle.\
# Then we draw the vertical lines which divide the\
# widget into several parts. Finally,\
# we draw the numbers which indicate\
# the capacity of the medium.
# 创建黄色区域(普通区域最高值)
MAX_CAPACITY = 700
# 创建红色区域(最终最高值)
OVER_CAPACITY = 750
# 创建小一点的字体,能更好的满足我们的需求
font = QFont('Serif', 7, QFont.Light)
qp.setFont(font)
# 得到我们组件当前的大小(组件大小每次变化后我们都要改变组件绘制部分的大小)
size = self.size()
# w,h分别是用户自定义组件当前的宽,高
w = size.width()
h = size.height()
# 设置每一段的长度(一共10段)
step = int(round(w / 10))
# till表示我们需要画出来的所有长度,这个值从QSlider中获得,\
# 它是我们整个区域的一个比例部分
till = int(((w / OVER_CAPACITY) * self.value))
# full是表示我们从哪里开始绘制红色(高温)
full = int(((w / OVER_CAPACITY) * MAX_CAPACITY))
# 如果当前QSlider表示的value值超过了最大值(警戒线),要开始把警戒线后面绘制成红色了
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)
# QSlider当前的值没有超过警戒线的值,正常绘制黄色就行
else:
# 设置画笔颜色白色(边框设置成白色)
qp.setPen(QColor(255, 255, 255))
# 设置画刷是黄色(画刷是绘制图形填充区域,内部绘制成换黄色)
qp.setBrush(QColor(255, 255, 184))
# 利用设定好的画笔和画刷去绘制矩形,矩形的宽一直绘制到当前QSlider的值那里
qp.drawRect(0, 0, till, h)
# 设置画笔是连续的直线,颜色是黑色,填充度是1(用来绘制外面黑色的边框)
pen = QPen(QColor(20, 20, 20), 1,
Qt.SolidLine)
# 给qp对象设置上刚刚的画笔
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 Gui(QWidget):
def __init__(self):
super().__init__()
self.start()
# 程序从这里开始
def start(self):
# 这个参数设置QSlider的最高值(要保证和我们自定义的组件绘制的最高值一样)
OVER_CAPACITY = 750
# 实例化在QWidget主窗体上的水平拉条对象
sld = QSlider(Qt.Horizontal, self)
# 设置拉条对象不能接受焦点,不能用tab把焦点移动到上面去
sld.setFocusPolicy(Qt.NoFocus)
# 设置QSlider的全部范围,从1-->最大值(要保证和我们自定义的组件一样)
sld.setRange(1, OVER_CAPACITY)
# 设置初始值
sld.setValue(75)
# 设置sld滑条的绝对位置(30,40)和长(30)宽(150)
sld.setGeometry(30, 40, 150, 30)
# 实例化QObject子类对象,用来发出信号
self.c = Communicate()
self.wid = BurningWidget()
# 将实例化QObject子类对象同self.wid.set_value槽相联系,\
# 这样当我们手动控制它发出信号的时候就可以被捕捉到了
self.c.updateBW[int].connect(self.wid.set_value)
# 将滑条valueChanged[int]信号与self.changeValue槽相联系(随时改变value值)
sld.valueChanged[int].connect(self.changeValue)
# 设置水平布局组件hbox
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):
# 手动控制自己写的继承自QObject类发出信号(此时就会被slot捕捉)
self.c.updateBW.emit(value)
# 重新绘制我们自定义的组件wid(此时不改变组件大小也会绘制了)
self.wid.repaint()
app = QApplication(sys.argv)
gui = Gui()
sys.exit(app.exec_())
运行结果:
![](https://img.haomeiwen.com/i22578190/b4d93f1baa81a5cd.png)
网友评论