系统地讲解信号与槽(✘),系统过一遍信号与槽(✔)(狗头)
这可以说是Qt中最核心的概念之一了。
一个小的Demo
信号与槽是Qt中的核心概念,有了信号与槽,我们就可以通过在UI上点击、输入等操作触发程序内部的函数发挥作用,比如在控制台上打印一段文本,比如将用户在QTextEdit中输入的文本读入程序做后续处理。因此想要真正做到通过UI让用户和内部程序进行交互,信号与槽是必不可少的(在Qt的框架下)。
信号(signal)是由事件(event)发生,进而引起控件发出,只要信号发出,那么与信号绑定(connect)的槽函数(slot)就会被执行。比如最简单的信号就是clicked,如果用户在UI上点击了按钮之类的控件,那么这个点击事件就会使得这些被点击的控件发出clicked信号,这些信号会被与控件绑定的若干个槽函数捕获,然后这些槽函数就会被执行。大致的逻辑如此。
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class SignalSlotDemo(QWidget):
def __init__(self):
super(SignalSlotDemo, self).__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("")
self.resize(400, 300)
self.btn = QPushButton("我的按钮", self)
self.btn.clicked.connect(self.onClick)
def onClick(self):
print("信号已发出")
if __name__ == '__main__':
app = QApplication(sys.argv)
main = SignalSlotDemo()
main.show()
sys.exit(app.exec_())
运行效果:
需要说明的是,一个控件发出的信号可以与多个槽函数绑定,多个控件发出的信号也可以与同一个槽函数绑定。
自定义信号
之前用到过了自定义信号(内嵌信号),PyQt5提供了信号对象pyqtSignal来允许用户创建自定义信号,从而使得系统处理事件更加灵活。
import sys
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
# 自定义信号
class MyTypeSignal(QObject):
# 定义一个信号
sendmsg = pyqtSignal(object)
# 发出信号的函数
def run(self):
self.sendmsg.emit("Hello PyQt5")
# 自定义的槽
class MySlot(QObject):
def get(self, msg):
print("信息" + msg)
if __name__ == '__main__':
send = MyTypeSignal()
slot = MySlot()
# 直接把信号和槽绑定
send.sendmsg.connect(slot.get)
# 发出信号
send.run()
运行效果:
信息Hello PyQt5
需要注意自定义的信号和槽都是继承自QObject对象。将信号和槽绑定后,只要信号发出(即我们使用了send.run()来发送信号),那么我们绑定的槽的函数就会被触发。
我们还可以通过disconnect()来让原本已经绑定的信号和槽解除绑定:
import sys
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class MyTypeSignal(QObject):
# 定义一个信号
sendmsg = pyqtSignal(object)
# 发出信号的函数,这个信号只传递一个参数,即字符串"Hello PyQt5"
def run(self):
self.sendmsg.emit("Hello PyQt5")
class MySlot(QObject):
def get(self, msg):
print("信息" + msg)
if __name__ == '__main__':
send = MyTypeSignal()
slot = MySlot()
# 直接把信号和槽绑定
send.sendmsg.connect(slot.get)
send.sendmsg.disconnect(slot.get)
# 发送信号
send.run()
运行结果:
不打印任何信息,也就是说槽函数没有被触发
可以传递多个参数的信号
在使用pyqtSignal()时,可以通过设置参数来制定该信号所能传送的参数的个数
import sys
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class MyTypeSignal(QObject):
# 定义一个信号
sendmsg = pyqtSignal(object)
# 发送3个参数的信号,参数类型分别为str,int,int
sendmsg1 = pyqtSignal(str, int, int)
# 发出信号的函数
def run(self):
self.sendmsg.emit("Hello PyQt5")
# 发送传送三个参数的信号
def run1(self):
self.sendmsg1.emit("Hello", 3, 4)
class MySlot(QObject):
def get(self, msg):
print("信息" + msg)
def get1(self, msg, a, b):
print(msg)
print(a + b)
if __name__ == '__main__':
send = MyTypeSignal()
slot = MySlot()
# 直接把信号和槽绑定
send.sendmsg.connect(slot.get)
send.sendmsg1.connect(slot.get1)
# 发送信号
send.run()
send.run1()
运行结果:
信息Hello PyQt5
Hello
7
为类添加多个信号
可以往类中添加任意数量的信号,而且信号传递的参数的类型可以是任意类型
import sys
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class MultiSignal(QObject):
signal1 = pyqtSignal()
signal2 = pyqtSignal(int)
signal3 = pyqtSignal(int, str)
signal4 = pyqtSignal(list)
signal5 = pyqtSignal(dict)
# 申明一个重载版本的信号
# 也就是说槽函数接受的参数可以是int和str类型的,也可以是只有一个str类型的参数
# 具体是哪一个视传入的参数情况而定
signal6 = pyqtSignal([int, str], [str])
def __init__(self):
super(MultiSignal, self).__init__()
self.signal1.connect(self.signalCall1)
self.signal2.connect(self.signalCall2)
self.signal3.connect(self.signalCall3)
self.signal4.connect(self.signalCall4)
self.signal5.connect(self.signalCall5)
self.signal6[int, str].connect(self.signalCall6)
self.signal6[str].connect(self.signalCall6Overload)
# 让所有信号都发送
self.signal1.emit()
self.signal2.emit(10)
self.signal3.emit(1, "hello world")
self.signal4.emit([1,2,3,4,5,6])
self.signal5.emit({"name":"Bill", "age":30})
self.signal6[int, str].emit(100, "test")
self.signal6[str].emit("test")
# 我们直接在这个类里面定义槽函数
def signalCall1(self):
print("信号1被触发")
def signalCall2(self, num):
print("信号2被触发,值为:{}".format(num))
def signalCall3(self, num, string):
print("信号3被触发,值为:{} {}".format(num, string))
def signalCall4(self, List):
print("信号4被触发,值为:{}".format(List))
def signalCall5(self, Dict):
print("信号5被触发,值为:{}".format(Dict))
def signalCall6(self, num, string):
print("信号6被触发,值为:{} {}".format(num, string))
def signalCall6Overload(self, string):
print("信号6被触发,值为:{}".format(string))
if __name__ == '__main__':
signal = MultiSignal()
运行结果:
信号1被触发
信号2被触发,值为:10
信号3被触发,值为:1 hello world
信号4被触发,值为:[1, 2, 3, 4, 5, 6]
信号5被触发,值为:{'name': 'Bill', 'age': 30}
信号6被触发,值为:100 test
信号6被触发,值为:test
其中1-5都好理解,signal6看起来很特殊,这是因为,我们使用了信号的重载(其实我感觉这更像是信号数组)。大致用法如上,在创建信号对象时,以列表包装多组参数,这几组参数之间形成了或与或的关系。需要绑定槽函数和发送信号时,需要在signal6后面加上[]操作符,里面填上定义时的某一组参数(这就有点类似索引了),来指定这个信号传递的参数到底是哪一组。
信号与槽多对多连接与断开连接
from PyQt5.QtCore import *
class NNSignal(QObject):
signal1 = pyqtSignal()
signal2 = pyqtSignal(int)
def __init__(self):
super(NNSignal, self).__init__()
self.signal1.connect(self.call1)
self.signal1.connect(self.call2)
print("signal1 发送信号")
self.signal1.emit() # 发送信号
self.signal2.connect(self.call1)
self.signal2.connect(self.call2)
print("signal2 发送信号")
self.signal2.emit(2) # 发送信号
self.signal1.disconnect(self.call1)
self.signal1.disconnect(self.call2)
self.signal2.disconnect(self.call1)
self.signal2.disconnect(self.call2)
print("再次发送信号")
# 再次发送信号,就应该不会再触发任何槽函数了
self.signal1.emit()
self.signal2.emit(2)
def call1(self):
print("call1 emit")
def call2(self):
print("call2 emit")
if __name__ == '__main__':
signal = NNSignal()
运行结果:
signal1 发送信号
call1 emit
call2 emit
signal2 发送信号
call1 emit
call2 emit
再次发送信号
我们还可以将信号与信号绑定,比如我们使用信号二的connect()将信号二和信号一绑定,那么信号二发送信号时,与信号一连接的所有槽函数都会被触发
from PyQt5.QtCore import *
class NNSignal(QObject):
signal1 = pyqtSignal()
signal2 = pyqtSignal(int)
def __init__(self):
super(NNSignal, self).__init__()
self.signal1.connect(self.call1)
self.signal1.connect(self.call2)
self.signal2.connect(self.signal1)
print("signal2 发送信号")
self.signal2.emit(2) # 发送信号
def call1(self):
print("call1 emit")
def call2(self):
print("call2 emit")
if __name__ == '__main__':
signal = NNSignal()
运行结果:
signal2 发送信号
call1 emit
call2 emit
为窗口添加信号
为窗口添加信号本质上和为类添加信号是一样的,因为窗口本质就是一个窗口类。
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class WinSignal(QWidget):
button_clicked_signal = pyqtSignal()
def __init__(self):
super().__init__()
self.setWindowTitle("为窗口类添加信号")
self.resize(300, 100)
btn = QPushButton("关闭窗口", self)
btn.clicked.connect(self.btn_clicked)
self.button_clicked_signal.connect(self.close)
def btn_clicked(self):
self.button_clicked_signal.emit()
def btn_close(self):
self.close()
if __name__ == '__main__':
app = QApplication(sys.argv)
main = WinSignal()
main.show()
sys.exit(app.exec_())
运行结果:
这里有点小绕,请务必理清逻辑关系:用户点击按钮->clicked信号发送->btn_clicked()槽函数被触发,函数执行内容为让button_clicked_signal信号发送->btn_close()槽函数被触发,函数执行内容为让窗体关闭->窗口关闭。只是单纯地演示窗口中自定义信号的使用,禁止套娃
多线程更新UI数据
PyQt5中最常用、最好的在子线程中完成工作并返回值的方式就是使用信号和槽。要做的很简单,就是将子线程(我们定义和创建的)中的信号和主线程中的槽函数绑定。那么我们需要完成的工作就交给子线程完成,待完成后,子线程中的数据再通过信号传递到主线程的槽函数中。
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
# 定义一个线程类
class BackendThread(QThread):
update_date = pyqtSignal(str)
# 该线程做的事就是每隔一秒就让update_date信号发送一次,信号传递的参数是当地时间的格式化字符串
def run(self):
while True:
data = QDateTime.currentDateTime()
currentTime = data.toString("yyyy-MM-dd hh:mm:ss")
self.update_date.emit(str(currentTime))
self.sleep(1)
class ThreadUpdateUI(QDialog):
def __init__(self):
super(ThreadUpdateUI, self).__init__()
self.setWindowTitle("多线程更新UI数据")
self.resize(400, 100)
self.input = QLineEdit(self)
self.input.resize(400, 100)
self.initUI()
def initUI(self):
# 创建线程对象
self.backend = BackendThread()
# 将线程对象中的update_date对象和下面要写得handleDisplay函数绑定
self.backend.update_date.connect(self.handleDisplay)
# 启动线程
self.backend.start()
def handleDisplay(self, data):
self.input.setText(data)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = ThreadUpdateUI()
main.show()
sys.exit(app.exec_())
运行效果:
总结:子线程中的信号和主线程中的槽函数绑定。
还有就是。。。看不懂BackendThread这个我们定义的线程类的同学,不妨去看看之前多线程那一章的内容。
信号与槽自动连接
我们之前连接信号和槽都是使用connect()函数一个一个手动连接的,但是如果需要连接的信号和槽很多的话,那么使用connect()去一个一个连接会显得很麻烦。
我们可以通过装饰器和设置控件的内部引用名的方式来让控件和被装饰器增强的槽函数自动绑定
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5 import QtCore
class AutoSignalSlot(QWidget):
def __init__(self):
super(AutoSignalSlot, self).__init__()
self.setWindowTitle("信号与槽自动连接")
self.resize(400, 100)
layout = QHBoxLayout()
self.okButton = QPushButton("OK")
self.cancelButton = QPushButton("CANCEL")
# 设置button的内部引用名
self.okButton.setObjectName("okButton")
self.cancelButton.setObjectName("cancelButton")
layout.addWidget(self.okButton)
layout.addWidget(self.cancelButton)
self.setLayout(layout)
# 规定按照内部引用名字(ObjectName)自动绑定信号和槽
QtCore.QMetaObject.connectSlotsByName(self)
@QtCore.pyqtSlot()
def on_okButton_clicked(self): # 槽函数的名字必须是这个,不能改成别的,下面的也一样
print("点击了ok按钮")
@QtCore.pyqtSlot()
def on_cancelButton_clicked(self):
print("点击了cancel按钮")
if __name__ == '__main__':
app = QApplication(sys.argv)
main = AutoSignalSlot()
main.show()
sys.exit(app.exec_())
运行结果:
QtCore.QMetaObject.connectSlotsByName(self)的意思就是在主窗口下开启信号和槽的自动匹配。
如希望信号和槽自动匹配,除了写下QtCore.QMetaObject.connectSlotsByName(self)这一句以外还需要做两件事:通过控件对象的setObjectName()方法设置控件的内部引用名。
在槽函数上使用@QtCore.pyqtSlot()进行功能增强。
需要说明的是自动匹配的规则。之所以能自动匹配,关键在于你希望自动匹配在一起的控件的内部引用名、发送的信号名和槽函数之间的名字关系。
除了使用装饰器增强外,槽函数的名字在控件的内部引用名设置完成后就是固定的了,规则为:
on_ObjectName-signal
比如我们将一个QPushButton通过setObjectName()方法将内部引用名设置为open,希望将这个按钮的clicked信号和一个槽函数自动绑定,那么这个槽函数的名字必须是on_open_clicked
使用lambda表达式为槽函数传递参数
在讲使用lambda表达式为槽函数传递参数之前,有必要学习或者复习一下lambda表达式(熟悉lambda表达式的同学可以直接跳过)
lambda表达式实际上就是匿名函数,也就是没有名字的函数。分为无参数的lambda表达式和带参数的lambda表达式,被lambda表达式赋予的那个变量就可以当做函数来使用,例如:
fun1 = lambda :print("hello") # 无参数的lambda表达式
fun1()
fun2 = lambda x,y:print(x,y) # 带参数的lambda表达式
fun2("a", "b")
运行结果:
hello
a b
容易观察到,无参数的lambda表达式应该为lambda : 函数体这样的形式;带参数的lambda表达式应该为lambda 参数1,参数2,... : 函数体这样的形式。
函数体不一定得是像print一样的内置函数,也可以是表达式,例如:
plus = lambda a,b : a + b
print(plus(3, 4))
运行结果:
7
有了lambda表达式,我们就可以干预信号往槽函数传递参数的过程,来让信号传递的参数以我们自定义的组织结构传入槽函数中。
from PyQt5.QtWidgets import *
import sys
class LambdaSlotArg(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("使用Lambda表达式为槽函数传递参数")
self.resize(400, 200)
layout = QHBoxLayout()
button1 = QPushButton("按钮1")
button2 = QPushButton("按钮2")
this_is_a_param = 100
button1.clicked.connect(lambda : self.onButtonClick(10, 20))
button2.clicked.connect(lambda : self.onButtonClick(this_is_a_param, -20))
layout.addWidget(button1)
layout.addWidget(button2)
self.setLayout(layout)
def onButtonClick(self, m, n):
print("m + n = {}".format(m + n))
QMessageBox.information(self, "结果", str(m + n))
if __name__ == '__main__':
app = QApplication(sys.argv)
main = LambdaSlotArg()
main.show()
sys.exit(app.exec_())
运行结果:
使用Partial对象为槽函数传递参数
除了使用lambda表达式之外,还可以通过Python的functool库中的partial方法来直接封装。效果和lambda是一样的:
from PyQt5.QtWidgets import *
from functools import partial
import sys
class PartialSlotArg(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("使用Partial对象为槽函数传递参数")
self.resize(400, 200)
layout = QHBoxLayout()
button1 = QPushButton("按钮1")
button2 = QPushButton("按钮2")
button1.clicked.connect(partial(self.onButtonClick, 10, 20))
button2.clicked.connect(partial(self.onButtonClick, 123, 20))
layout.addWidget(button1)
layout.addWidget(button2)
self.setLayout(layout)
def onButtonClick(self, m, n):
print("m + n = {}".format(m + n))
QMessageBox.information(self, "结果", str(m + n))
if __name__ == '__main__':
app = QApplication(sys.argv)
main = PartialSlotArg()
main.show()
sys.exit(app.exec_())
运行效果:
Override(覆盖)槽函数
或许系统或者我们自己已经写好了许多信号,在PyQt5中,我们可以通过直接定义同名信号来覆盖这些已经定义好的信号,来改变信号原本的行为。
比如PyQt5中有一个信号是keyPressEvent,当键盘上的按键按下时,这个信号会发送,传递的参数是代表按下按键的一个对象。我们可以直接def 它,让keyPressEvent发送信号时还能做一些别的事情,比如改变窗口的标题:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from functools import partial
import sys
class OverrideSlot(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Override(覆盖)槽函数")
self.resize(400, 200)
def keyPressEvent(self, e):
# 按下Esc键就退出窗口
if e.key() == Qt.Key_Escape:
self.close()
# 按下Alt键就改变窗口的标题
elif e.key() == Qt.Key_Alt:
self.setWindowTitle("按下Alt键")
if __name__ == '__main__':
app = QApplication(sys.argv)
main = OverrideSlot()
main.show()
sys.exit(app.exec_())
运行效果:按下Esc键窗口退出;按下Alt键,标题改为“按下Alt键”
多窗口交互(1):不使用信号与槽
所谓多窗口交互就是让多个窗口进行数据传递。我们下面通过强耦合的方式让两个窗口实现交互。
下面这个例子写成了两个文件:DateDialog.py和draft.py其中可能有一些很绕,但是这不是重点,重点在(2)
DateDialog.py:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
class DateDialog(QDialog):
def __init__(self, parent=None):
super(DateDialog, self).__init__(parent)
self.setWindowTitle("DateDialog")
layout = QVBoxLayout(self)
self.datetime = QDateTimeEdit(self)
# 使得QDateTimeEdit控件可以下拉出一个日历
self.datetime.setCalendarPopup(True)
# 展示类型为当地的日期和时间
self.datetime.setDateTime(QDateTime.currentDateTime())
layout.addWidget(self.datetime)
# 创建按钮盒子
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self)
# 下面的都是内置的信号和槽,直接绑定就行
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
def dateTime(self):
return self.datetime.dateTime()
@staticmethod
def getDateTime(parent=None):
dialog = DateDialog(parent)
# 执行该窗体
result = dialog.exec_()
date = dialog.dateTime()
return (date.date(), date.time(), result == QDialog.Accepted)
draft.py:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from DateDialog import DateDialog # 我们自己定义的类
import sys
class MultiWindow1(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("多窗口交互(1):不使用信号与槽")
self.resize(400, 200)
self.lineEdit = QLineEdit(self)
self.button1 = QPushButton("弹出对话框1")
self.button2 = QPushButton("弹出对话框2")
self.button1.clicked.connect(self.onButton1Click)
self.button2.clicked.connect(self.onButton2Click)
gridLayout = QGridLayout()
gridLayout.addWidget(self.lineEdit)
gridLayout.addWidget(self.button1)
gridLayout.addWidget(self.button2)
self.setLayout(gridLayout)
def onButton1Click(self):
dialog = DateDialog(self)
# 执行窗体二,也就是DateDialog
result = dialog.exec_()
date = dialog.dateTime()
self.lineEdit.setText(date.date().toString())
dialog.destroy()
def onButton2Click(self):
date, time, result = DateDialog.getDateTime()
self.lineEdit.setText(date.toString())
if result == QDialog.Accepted:
print("点击确定按钮")
else:
print("点击取消按钮")
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MultiWindow1()
main.show()
sys.exit(app.exec_())
运行draft.py结果:
需要注意的是,onButton1Click和onButton2Click的效果一样,只不过一个是在draft.py运行的窗体一中运行窗体二,使用DateDialog里的方法获得对话框;一个是直接使用DateDialog里我们自己定义的静态方法,这个静态方法中包含了执行窗体二的语句。
多窗口交互(2):使用信号与槽
强耦合虽然简短,但是缺点很多,我们可以使用信号和槽来降低多个窗口之间的耦合度。即将控件和数据分离,这样我们设计好的窗体对象就可以在别的场合使用。
下面我们尝试两种方式来将子窗口的数据传递到主窗口:使用子窗口的内嵌信号和自定义信号:
还是分为了两个文件:DateDialog.py和draft.py
DateDialog.py:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
class DateDialog(QDialog):
# 自定义信号
Signal_OneParameter = pyqtSignal(str)
def __init__(self, parent=None):
super(DateDialog, self).__init__(parent)
self.setWindowTitle("子窗口:用来发射信号")
# 设置布局,并在布局中添加控件
layout = QVBoxLayout(self)
self.label = QLabel("前者发射内置信号\n后者发射自定义信号")
# 设置一个显示时间的文本编辑框,这个时间编辑框通过内置信号传递数据
self.datetime_inner = QDateTimeEdit()
self.datetime_inner.setCalendarPopup(True)
self.datetime_inner.setDateTime(QDateTime.currentDateTime())
# 设置一个显示时间的文本编辑框,这个时间编辑框通过自定义信号传递数据
self.datetime_emit = QDateTimeEdit()
self.datetime_emit.setCalendarPopup(True)
self.datetime_emit.setDateTime(QDateTime.currentDateTime())
# 将控件放入布局中
layout.addWidget(self.label)
layout.addWidget(self.datetime_inner)
layout.addWidget(self.datetime_emit)
# 创建按钮盒子
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self)
# 盒子中的两个button(ok和cancel)分别连接accept()和reject()槽函数
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
# 将datetime_emit所选时间改变的信号和槽函数绑定
self.datetime_emit.dateTimeChanged.connect(self.emit_signal)
def emit_signal(self):
# 获取datetime_emit显示时间的默认格式化字符串
date_str = self.datetime_emit.dateTime().toString()
# 发送我们的自定义信号Signal_OneParameter,传递的参数就是date_str
self.Signal_OneParameter.emit(date_str)
draft.py:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from DateDialog import DateDialog # 我们自己定义的类
import sys
class MultiWindow2(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("多窗口交互(2):使用信号与槽")
self.resize(400, 200)
self.open_btn = QPushButton("获取时间")
self.lineEdit_inner = QLineEdit("接受子窗口内置信号的时间")
self.lineEdit_emit = QLineEdit("接受子窗口自定义信号的时间")
self.open_btn.clicked.connect(self.openDialog)
gridLayout = QGridLayout()
gridLayout.addWidget(self.lineEdit_inner)
gridLayout.addWidget(self.lineEdit_emit)
gridLayout.addWidget(self.open_btn)
self.setLayout(gridLayout)
def openDialog(self):
dialog = DateDialog(self)
# 连接子窗口的内置信号与主窗口的槽函数
dialog.datetime_inner.dateTimeChanged.connect(self.deal_inner_slot)
# 连接子窗口的自定义信号与主窗口的槽函数
dialog.Signal_OneParameter.connect(self.deal_emit_slot)
# 显示子窗口
dialog.show()
def deal_inner_slot(self, date):
self.lineEdit_inner.setText(date.toString())
def deal_emit_slot(self, date_str):
self.lineEdit_emit.setText(date_str)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MultiWindow2()
main.show()
sys.exit(app.exec_())
运行draft.py结果:
可以发现我们使用两种方法传递信号,一种通过
dialog.datetime_inner.dateTimeChanged.connect(self.deal_inner_slot)
在主窗口中访问子窗口中的控件datetime_inner,继而将这个控件的信号和主窗口中的槽函数deal_inner_slot函数绑定;
第二种,通过在子窗口中创建自定义信号Signal_OneParameter,这个信号我设置它传递我们需要传递的数据(格式化的时间字符串),然后我们在主窗口中直接访问这个自定义信号,而不是任何控件。
相比两种方法,更加推荐第二种,因为第二种方法中两个窗口的耦合度更低。灵活性更强,而且我们绑定的是自定义信号和槽,与任何控件都无关,如果我们需要改变子窗口中的控件,那么第一种方法就有可能需要修改程序,而第二种方法由于绑定的是信号,所以不需要修改。