上节课我们通过自己编程实现了消息循环,更加深刻的理解了界面库运行的机制,大家还记得我们当时使用一个 list
变量来模拟消息队列,直接把消息 append
到消息队列里面,然后去处理该消息。但是,当我们使用
Qt 界面库时,我们没有办法去操作 Qt 的消息循环函数 exec_
,那我们应该如何投递消息呢?Qt
为了让我们可以投递和处理消息,给我们提供一个信号槽机制。
在此,大家可能有些疑惑,前面我们所讲的例子中,大家好像也没看到投递消息和处理消息,那是因为 Qt 把常用的行为默认替我们做好了消息投递和处理,比如关闭界面,最小化界面,最大化界面,拖动界面等等。 当然,我们也可以自己重新处理这些消息。
本节课,我们就来学习 Qt 的信号槽机制,同样,为了更深刻的了解信号槽,最后,本节课我们也学习,自己如何编程实现信号槽。
PyQt5 有一个独特的信号槽(signal&slot)机制来处理事件,信号槽用于对象间的通信。signal 在某一特定事件发生时被触发,slot 可以是任何 callable 对象,当 signal 触发时会调用与之相连的 slot。
import sys from PyQt5.QtWidgets import QWidget, QPushButton, QApplication, QMessageBox class SignalsSlotsOne(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.btn = QPushButton("老鸟button", self) # 定义一个按钮控件 self.btn.move(100, 100) # 移动按钮到窗口坐标(100, 100) self.btn.clicked.connect(self.buttonClicked) # 给 clicked 消息关联消息处理函数 self.setGeometry(600, 200, 800, 500) self.setWindowTitle('信号槽演示1') self.show() def buttonClicked(self): # 槽函数 QMessageBox.information(None, "弹框", self.btn.text()) app = QApplication(sys.argv) ex = SignalsSlotsOne() app.exec_()
在上面这个例子中,我们展示了对 QPushButton 控件的 clicked
消息进行处理,其中 self.btn.clicked.connect(self.buttonClicked)
函数将 QPushButton 控件的 clicked 信号连接到 SignalsSlotsOne 对象的 buttonClicked 槽函数。
大家可能注意到了,上面的例子中,我们只是做了信号(clicked 消息发出的信号)和槽的关联,以及槽函数的处理,而对于 clicked 消息的投递则是 Qt 默认处理的,同样,对于这种常见的消息(点击消息,移动鼠标消息,键盘输入消息等等), Qt 都有默认的投递行为。
显然,Qt 的默认的消息不能满足于我们开发复杂的界面程序,我们经常需要创建自己的消息,然后投递该信息。Qt 提供了一个 pyqtSignal 对象,使用该对象我们可以自己投递消息(发信号)。
import sys from PyQt5.QtCore import pyqtSignal from PyQt5.QtWidgets import QWidget, QApplication, QLineEdit class SignalsSlotsTwo(QWidget): sinOut = pyqtSignal(str) # 定义一个消息对象 def __init__(self): super().__init__() self.sinOut.connect(self.outText) # 关联槽函数 outText self.initUI() def initUI(self): self.edit = QLineEdit("编辑框", self) # 定义一个编辑框控件 self.edit.move(100, 100) # 移动编辑框到窗口坐标(100, 100) self.setGeometry(600, 200, 800, 500) self.setWindowTitle('信号槽演示2') self.show() self.sinOut.emit("老鸟python") # 往消息队列里面投递消息(发信号) def outText(self, text): # 槽函数 self.edit.setText(text) app = QApplication(sys.argv) ex = SignalsSlotsTwo() app.exec_()
在上面的例子中,我们定义一个消息对象 sinOut,然后我们给该消息关联一个槽函数 self.outText,在界面启动后,我们用 emit 函数进行了消息投递,在投递消息的同时,我们还传了个参数 "老鸟python",并且在消息处理函数 outText 中,我们在编辑框中显示了该参数。
使用 pyqtSignal 进行消息投递时,消息投递函数和消息处理函数可以是同步的也可以是异步的,如果消息投递和消息处理是在同一个线程中,默认是同步的。
''' 同线程中,消息投递函数和消息处理函数默认是同步的 ''' import sys from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtWidgets import QWidget, QApplication class SignalsSlotsThree(QWidget): sinOut = pyqtSignal() # 定义一个消息对象 def __init__(self): super().__init__() self.sinOut.connect(self.outText) # 同线程默认是同步的 self.init() def init(self): self.sinOut.emit() # 往消息队列里面投递消息(发信号) print("消息投递过了") # 该语句后于槽函数执行 def outText(self): # 槽函数 print("消息处理完毕") app = QApplication(sys.argv) ex = SignalsSlotsThree() app.exec_()
结果如下:
消息处理完毕 消息投递过了
在同一个线程中,可以通过给 connect 函数设置一个 Qt.QueuedConnection 参数来实现消息投递和消息处理成为异步。
''' 同线程中,通过给 connect 函数设置 Qt.QueuedConnection 参数,可以使消息投递函数和消息处理函数变为异步 ''' import sys from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtWidgets import QWidget, QApplication class SignalsSlotsThree(QWidget): sinOut = pyqtSignal() # 定义一个消息对象 def __init__(self): super().__init__() self.sinOut.connect(self.outText, Qt.QueuedConnection) # 同线程默认是同步的,在此改成异步 self.init() def init(self): self.sinOut.emit() # 往消息队列里面投递消息(发信号) print("消息投递过了") # 该语句先于槽函数执行 def outText(self): # 槽函数 print("消息处理完毕") app = QApplication(sys.argv) ex = SignalsSlotsThree() app.exec_()
结果如下:
消息投递过了 消息处理完毕
如果消息投递和消息处理不在同一个线程中,则默认是异步的。
import sys import threading from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtWidgets import QWidget, QApplication class SignalsSlotsFour(QWidget): sinOut = pyqtSignal() # 定义一个消息对象 def __init__(self): super().__init__() self.sinOut.connect(self.outText) # 不同线程默认是异步的 def outText(self): # 槽函数 print("消息处理完毕") def run(): # 消息投递函数 ex.sinOut.emit() print("消息投递过了") # 该语句先于槽函数执行 app = QApplication(sys.argv) ex = SignalsSlotsThree() t = threading.Thread(target=run) t.start() app.exec_()
结果如下:
消息投递过了 消息处理完毕
在不同线程中,可以通过给 connect 函数设置一个 Qt.DirectConnection 参数来实现消息投递和消息处理成为同步。
import sys import threading from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtWidgets import QWidget, QApplication class SignalsSlotsFive(QWidget): sinOut = pyqtSignal() # 定义一个消息对象 def __init__(self): super().__init__() self.sinOut.connect(self.outText, Qt.DirectConnection) # 不同线程默认是异步的,在此改成同步 def outText(self): # 槽函数 print("消息处理完毕") def run(): # 消息投递函数 ex.sinOut.emit() print("消息投递过了") # 该语句后于槽函数执行 app = QApplication(sys.argv) ex = SignalsSlotsFive() t = threading.Thread(target=run) t.start() app.exec_()
结果如下:
消息处理完毕 消息投递过了
为了弄明白 Qt 的信号槽机制,我们自己来实现信号槽,下面我以消息投递和消息处理是在同一个线程中举例。
''' 纯手工打造消息队列,消息循环,和消息投递,实现单线程同步和异步 ''' msg = [] # 消息队列 class mySignal(object): # 自定义消息对象 def __init__(self, argtype): # argtype 是消息对象传来的参数 self.argtype = argtype def connect(self, func, syn=True): # 默认是同步的 self.asyn = syn self.func = func def emit(self, arg): if type(arg) == self.argtype: if self.asyn == True: # 同步,直接调用槽函数 self.func(arg) else: # 异步,入消息队列 msg.append((self.func, arg)) else: print("type error") class Myapp(object): def __init__(self): super(Myapp, self).__init__() def getmessage(self): # 从消息队列获取一个消息 try: newmsg = msg[-1] msg.pop() return newmsg except: return None def exec_(self): while True: # 消息循环 newmsg = self.getmessage() # 取出一条消息 if newmsg: newmsg[0](newmsg[1]) # func(arg) else: pass def func(self, arg): # 槽函数 print("收到参数:%s" % arg) print("消息处理完毕") class MyMain(object): sinOut = mySignal(str) def __init__(self, main): super(MyMain, self).__init__() self.main = main self.sinOut.connect(main.func) # 默认同步 def run(self): self.sinOut.emit("老鸟") print("消息投递过了") # 该语句后于槽函数执行 app = Myapp() mymain = MyMain(app) mymain.run() app.exec_()
运行结果:
收到参数:老鸟 消息处理完毕 消息投递过了
上面例子可以通过给 connect 函数传入关键字参数 syn=False 来实现同线程异步,同学们可以试验一下。
熟练使用 Qt 的信号槽。
弄明白同线程或不同线程的信号槽处理机制。
会自己实现信号槽。
消息投递和消息处理在不同线程中,手工实现信号槽,实现同步和异步。
看了这篇文章才算对反射有个点了解