自定义信号
在之前的文章:PySide6 GUI 编程(3):信号槽机制中已经探讨过关于自定义信号的场景。在一些更追求灵活性的场景下,我们需要自定义信号,以此触发更多自定义的行为。
示例代码
代码语言:python代码运行次数:0复制from __future__ import annotations
from PySide6.QtCore import QObject, Signal
def show_signal_parameters(*args):
if len(args) == 0:
print('信号发送的参数为空')
return
if len(args) != 1:
print('信号发送的参数个数错误')
return
print('信号发送的参数:', args[0])
class MySignals(QObject):
"""
Signal只能在继承自QObject的类中使用
这是因为Signal和Slot机制是Qt的一个核心特性
而这个特性是通过QObject类实现的
"""
my_signal_1 = Signal(str)
my_signal_2 = Signal(int)
my_signal_3 = Signal(float)
my_signal_4 = Signal(bool)
my_signal_5 = Signal(list)
my_signal_6 = Signal(dict)
my_signal_7 = Signal(tuple)
my_signal_8 = Signal(set)
my_signal_9 = Signal(object)
def __init__(self):
super().__init__()
self.my_signal_1.connect(show_signal_parameters)
self.my_signal_2.connect(show_signal_parameters)
self.my_signal_3.connect(show_signal_parameters)
self.my_signal_4.connect(show_signal_parameters)
self.my_signal_5.connect(show_signal_parameters)
self.my_signal_6.connect(show_signal_parameters)
self.my_signal_7.connect(show_signal_parameters)
self.my_signal_8.connect(show_signal_parameters)
self.my_signal_9.connect(show_signal_parameters)
self.my_signal_1.emit('1')
self.my_signal_2.emit(2)
self.my_signal_3.emit(3.000)
self.my_signal_4.emit(4)
self.my_signal_5.emit([5, ])
self.my_signal_6.emit({'6': 6})
self.my_signal_7.emit((7,))
self.my_signal_8.emit({8, })
self.my_signal_9.emit([9, '9', ])
if __name__ == '__main__':
MySignals()
运行效果
使用匿名函数对信号进行拦截
在标准的 PySide6 信号中,信号与槽函数的入参总是固定的,这虽然可以在常见的信号使用上带来方便,但是也会限制一些更灵活的使用,比如对于按钮点击行为,当我希望获取更多的信息时,标准的信号与槽函数便不能满足要求。
因此有必要对原生的信号做拦截,并重新处理或打包信号的参数,并将其传递给自定义的函数做处理。
示例代码
代码语言:python代码运行次数:0复制from __future__ import annotations
from datetime import datetime
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget
def on_button_clicked(clicked: bool):
"""
标准的槽函数,只接收 clicked:bool 参数
"""
print('按钮被点击(标准槽函数on_button_clicked):', clicked, datetime.now().isoformat())
def on_button_clicked_2(clicked: bool, button: QPushButton):
"""
自定义中间槽函数
接收 clicked:bool 和 button:QPushButton 参数
其中 clicked:bool 是按钮被点击时发送的参数,是标准的槽函数参数
中间槽函数会额外接收 button:QPushButton 参数
"""
print('按钮被点击(中间槽函数on_button_clicked_2):', clicked, button.text(), datetime.now().isoformat())
# def on_button_clicked_4(clicked: bool, button: QPushButton):
# print('按钮被点击(中间槽函数on_button_clicked_4,结合 functools.partial 使用):', clicked, button.text(),
# datetime.now().isoformat())
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('信号拦截以及信号参数修改')
button1 = QPushButton('测试按钮1 (标准槽函数)')
button1.setCheckable(True) # 设置为checkable, 这样 slot 函数中接收到的参数就是 True
button1.clicked.connect(on_button_clicked)
button2 = QPushButton('测试按钮2 (lambda表达式作为中间槽函数)')
button2.setCheckable(True)
# 使用lambda 表达式作为中间槽函数
button2.clicked.connect(lambda button_clicked: on_button_clicked_2(clicked = button_clicked, button = button2))
button3 = QPushButton('测试按钮3 (纯 lambda表达式槽函数)')
button3.setCheckable(True)
# 使用lambda 表达式作为中间槽函数
# button=button3 是一个默认参数
# 它的值在 lambda 函数定义时就已经确定
# 因此即使在槽函数调用时 button 变量的值发生改变
# 传递给槽函数的 button 参数的值仍然是 lambda 函数定义时的值
button3.clicked.connect(
lambda clicked, button = button3: print('按钮被点击(纯 lambda 表达式槽函数):',
clicked, button.text(),
datetime.now().isoformat()))
# button4 = QPushButton('测试按钮4 (functools.partial 方式会报错)')
# button4.setCheckable(True)
# # functools.partial 可以生成一个新的函数,该新函数具有与原函数相同的行为,但某些参数已经预设了值
# # 在当前的场景下,信号连接到槽函数时,我们使用了 functools.partial 创建了一个新的函数,
# # 它将 button 作为预设参数传递给 on_button_clicked
# # 但是在当前这种场景下,使用 functools.partial 的方式并不适用,因为预设参数会被放在其他参数之前,导致参数传递出现问题
# # 运行时会报错:TypeError: on_button_clicked_4() missing 1 required positional argument: 'clicked'
# new_func = functools.partial(on_button_clicked_4, button = button4)
# button4.clicked.connect(new_func)
v_main_layout = QVBoxLayout()
v_main_layout.addWidget(button1)
v_main_layout.addWidget(button2)
v_main_layout.addWidget(button3)
# v_main_layout.addWidget(button4)
container = QWidget()
container.setLayout(v_main_layout)
self.setCentralWidget(container)
if __name__ == '__main__':
app = QApplication()
ins = MyWindow()
ins.show()
app.exec()
运行效果
使用匿名函数时的陷阱
匿名函数中变量引用问题是一个很常见的陷阱,尤其在循环中使用匿名函数时,容易形成一些错觉。
示例代码
代码语言:python代码运行次数:0复制from __future__ import annotations
from PySide6.QtGui import QFont
from PySide6.QtWidgets import QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget
class MyMainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('lambda 槽函数中使用参数传值的例子')
v_main_layout = QVBoxLayout()
for i in range(10):
button = QPushButton(str(i), parent = self)
# 由于 lambda 函数没有自己的 i 变量
# 它会捕获外部作用域中的 i 的引用
# 这意味着,当按钮被点击时
# self.button_clicked_1 方法将接收到循环结束时的 i 的值
# 而不是按钮创建时的值
button.clicked.connect(lambda clicked: self.button_clicked_1(i))
# 使用了默认参数 value = i 来捕获当前的 i 值
# 由于默认参数在 lambda 函数定义时就被评估
# 它会捕获每次循环迭代时的 i 的当前值
# 这样,当按钮被点击时,self.button_clicked_2 方法将接收到正确的值
# 即与该按钮相关联的值
button.clicked.connect(lambda clicked, value = i: self.button_clicked_2(value))
# 使用闭包确保每个按钮的点击事件都能正确地传递其对应的 i 值
button.clicked.connect(self.create_click_handler(i))
v_main_layout.addWidget(button)
self.label_1 = QLabel('label_1')
self.label_1.setFont(QFont('UbuntuMono NF', 20))
self.label_2 = QLabel('label_2')
self.label_2.setFont(QFont('UbuntuMono NF', 20))
self.label_3 = QLabel('label_3')
self.label_3.setFont(QFont('UbuntuMono NF', 20))
v_main_layout.addWidget(QLabel('-' * 16))
v_main_layout.addWidget(self.label_1)
v_main_layout.addWidget(QLabel('-' * 16))
v_main_layout.addWidget(self.label_2)
v_main_layout.addWidget(QLabel('-' * 16))
v_main_layout.addWidget(self.label_3)
container = QWidget()
container.setLayout(v_main_layout)
self.setCentralWidget(container)
def button_clicked_1(self, value: int):
self.label_1.setText('button_clicked_1, value={}'.format(str(value)))
def button_clicked_2(self, value: int):
self.label_2.setText('button_clicked_2, value={}'.format(str(value)))
# 闭包
def create_click_handler(self, value: int):
def on_click(_: bool):
self.label_3.setText('button_clicked_3, value={}'.format(str(value)))
return on_click
if __name__ == '__main__':
app = QApplication()
ins = MyMainWindow()
ins.show()
app.exec()
运行效果
通过匿名函数的方式设置槽函数的默认参数
基于匿名函数,我们可以对槽函数进行更多的改造,设置默认函数便是改造项之一。
示例代码
代码语言:python代码运行次数:0复制from __future__ import annotations
import time
from datetime import datetime
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget
def on_window_title_changed_1(title: str = 'default-title-1', time_str: str = datetime.now().isoformat()):
print('on_window_title_changed_1, title=', title, 'time_str=', time_str, )
def on_window_title_changed_2(title: str = 'default-title-2', time_str: str = datetime.now().isoformat()):
print('on_window_title_changed_2, title=', title, 'time_str=', time_str, )
def on_window_title_changed_3(title: str = 'default-title-3', time_str: str = datetime.now().isoformat()):
print('on_window_title_changed_3, title=', title, 'time_str=', time_str, )
def on_window_title_changed_4(title: str = 'default-title-3', time_str: str = datetime.now().isoformat()):
print('on_window_title_changed_4, title=', title, 'time_str=', time_str, )
class MyMainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('MyMainWindow')
self.windowTitleChanged.connect(on_window_title_changed_1)
self.windowTitleChanged.connect(lambda: on_window_title_changed_2())
self.windowTitleChanged.connect(lambda x: on_window_title_changed_3(title = x))
self.windowTitleChanged.connect(lambda x: on_window_title_changed_4(title = x, time_str = str(time.time_ns())))
button = QPushButton('更改标题')
button.clicked.connect(lambda: self.setWindowTitle('new-title-{}'.format(time.time_ns())))
v_main_layout = QVBoxLayout()
v_main_layout.addWidget(button)
container = QWidget()
container.setLayout(v_main_layout)
self.setCentralWidget(container)
if __name__ == '__main__':
app = QApplication()
ins = MyMainWindow()
ins.show()
app.exec()