信号与槽
由于Qt的性质,QObjects需要一种通信方式,这也是这种机制成为Qt的核心特征的原因。
简单地说,你可以理解信号和插槽,就像你和你家里的灯互动一样。当您移动灯光开关(信号)时,结果可能是您的灯泡打开/关闭(插槽)。
在开发界面时,您可以通过点击按钮的效果获得一个真实的示例:“click”将是信号,而插槽将是点击按钮时发生的情况,如关闭窗口、保存文档等。
注意:
如果您有使用其他框架或工具包的经验,您可能会看到一个名为“回调”的概念。撇开实现细节不谈,回调将与通知函数相关,传递一个指向函数的指针,以防由于程序中发生的事件而需要它。这种方法听起来可能类似,但有一些本质的区别使它成为一种非直观的方法,比如确保回调参数的类型正确性,以及其他一些方法。
所有继承自QObject或其子类(如QWidget)的类都可以包含信号和插槽。当对象以其他对象可能感兴趣的方式改变其状态时,会发出信号。这就是对象进行通信所做的一切。它不知道或不关心是否有任何东西正在接收它发出的信号。这是真正的信息封装,并确保对象可以用作软件组件。
插槽可用于接收信号,但它们也是正常的成员功能。正如对象不知道是否有任何东西接收到它的信号一样,插槽也不知道它是否有任何信号连接到它。这确保了可以使用Qt创建真正独立的组件。
您可以将任意数量的信号连接到单个插槽,信号可以连接到任意数量的插槽。甚至可以将一个信号直接连接到另一个信号。(当第一个信号发出时,这将立即发出第二个信号。)
Qt的小部件有许多预定义的信号和插槽。例如,QAbstractButton(Qt中按钮的基类)有一个clicked()信号,QLineEdit(单行输入字段)有一条名为“clear()”的槽。因此,可以通过将QToolButton放置在QLineEdit的右侧并将其clicked()信号连接到槽“clear()”来实现带有清除文本按钮的文本输入字段。这是使用信号的connect()方法完成的:
button = QToolButton()
line_edit = QLineEdit()
button.clicked.connect(line_edit.clear)
connect()返回一个QMetaObject连接对象,可与disconnect()方法一起使用以断开连接。
信号也可以连接到自由功能:
import sys
from PySide6.QtWidgets import QApplication, QPushButton
def function():
print("The 'function' has been called!")
app = QApplication()
button = QPushButton("Call function")
button.clicked.connect(func)
button.show()
sys.exit(app.exec())
连接可以在代码中详细说明,对于小部件形式,可以在Qt Designer的信号槽编辑器中设计。
信号类
在Python中编写类时,信号被声明为类QtCore.Signal()的类级变量。发出clicked()信号的基于QWidget的按钮可以如下所示:
from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import QWidget
class Button(QWidget):
clicked = Signal(Qt.MouseButton)
...
def mousePressEvent(self, event):
self.clicked.emit(event.button())
Signal的构造函数采用一个元组或Python类型和C类型的列表:
signal1 = Signal(int) # Python types
signal2 = Signal(QUrl) # Qt Types
signal3 = Signal(int, str, int) # more than one type
signal4 = Signal((float,), (QDate,)) # optional types
除此之外,它还可以接收定义信号名称的命名参数名称。如果没有传递任何信息,则新信号将具有与分配给它的变量相同的名称。
信号的另一个有用选项是参数名称,用于QML应用程序通过名称引用发出的值:
sumResult = Signal(int, arguments=['sum'])
Connections {
target: ...
function onSumResult(sum) {
// do something with 'sum'
}
插槽类
QObject派生类中的槽应该由@QtCore.Slot()
表示。同样,要定义签名,只需传递类似于QtCore.Signal()
类。
@Slot(str)
def slot_function(self, s):
...
Slot()还接受名称和结果关键字。result关键字定义将返回的类型,可以是C或Python类型。name关键字的行为方式与Signal()中的相同。如果没有传递任何名称,则新插槽将具有与正在修饰的函数相同的名称。
不同类型的过载信号和插槽
实际上,可以使用具有不同参数类型列表的同名信号和插槽。这是Qt 5的遗留版本,不建议用于新代码。在Qt 6中,信号具有不同类型的不同名称。
以下示例使用信号和插槽的两个处理程序来展示不同的功能。
import sys
from PySide6.QtWidgets import QApplication, QPushButton
from PySide6.QtCore import QObject, Signal, Slot
class Communicate(QObject):
# create two new signals on the fly: one will handle
# int type, the other will handle strings
speak = Signal((int,), (str,))
def __init__(self, parent=None):
super().__init__(self, parent)
self.speak[int].connect(self.say_something)
self.speak[str].connect(self.say_something)
# define a new slot that receives a C 'int' or a 'str'
# and has 'say_something' as its name
@Slot(int)
@Slot(str)
def say_something(self, arg):
if isinstance(arg, int):
print("This is a number:", arg)
elif isinstance(arg, str):
print("This is a string:", arg)
if __name__ == "__main__":
app = QApplication(sys.argv)
someone = Communicate()
# emit 'speak' signal with different arguments.
# we have to specify the str as int is the default
someone.speak.emit(10)
someone.speak[str].emit("Hello everybody!")
通过方法签名字符串指定信号和时隙
信号和时隙也可以指定为通过SIGNAL()和/或SLOT()函数传递的C++方法签名字符串:
from PySide6.QtCore import SIGNAL, SLOT
button.connect(SIGNAL("clicked(Qt::MouseButton)"),
action_handler, SLOT("action1(Qt::MouseButton)"))
这不建议用于连接信号,它主要用于为QWizardPage::registerField()等方法指定信号:
wizard.registerField("text", line_edit, "text",
SIGNAL("textChanged(QString)"))