文章目录

  • 一、Qt 的信号(signals)和槽(slots)
  • 二、信号槽实现
  • 1. 示例1:无参数信号
  • 2. 示例2:带参数信号
  • 三、信号槽的断开与重连
  • 四、控件之间的通信
  • 五、自定义信号
  • 1. 自定义信号(无参数)
  • 2. 自定义信号(带参数)

一、Qt 的信号(signals)和槽(slots)

在Qt中,不同的控件如果需要通信,通常通过信号和槽来实现的。比如,当一个按钮控件被按下的时候,会发送一个信号,此时我们就可以把这个信号和一个函数连接起来,这样,当按钮按下的时候,这个函数就被执行了,这个函数也被称为槽函数。Qt已经设计好了一系列的信号供程序员直接使用,比如按键的按下,单击(即按下又松开),双击,比如标题栏名字的改变,对象被销毁等等都会发送一个信号,只要我们将这些信号连接一个函数,就可以让我们的程序对这些事件作出预期的响应。注意到,Qt 的底层负责事件的派送,已经槽函数的调用。
信号和槽有以下几个知识点:

  • 信号可以连接信号
  • 一个槽可以监听多个信号
  • 一个信号可以连接多个槽
  • 信号槽连接以后,可以重复的断开和连接
  • 发射的信号可以带参数

注:本文使用的是pyQt5,在信号槽代码与pyQt4不同。

二、信号槽实现

1. 示例1:无参数信号

以下程序捕获一个对象销毁的信号,并打印出来。

import sys

from PyQt5.QtCore import QObject
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QIcon


class Example(QWidget):

    def __init__(self):
        super().__init__()
        self.init()
        # 0.运行一次QObject_Test()
        self.QObject_Test()

    # QObject_Create 创建一个 QObject 对象
    def QObject_Test(self):
        # 1.创建一个零时变量
        obj = QObject()
        # 2.定义个槽函数
        def destroy_slot(self):
            print('obj 被释放了');
        # 3.连接槽函数
        obj.destroyed.connect(destroy_slot)

    def init(self):
        self.setGeometry(300, 300, 300, 220)
        self.setWindowTitle('信号和槽实验')
        self.show()

if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

程序首先定一个了函数 == self.QObject_Test()== 并运行一次,这就使得在该函数内被定义的对象随着函数的运行而被创建,又随着函数的结束被销毁,在Qt中,一个对象被销毁,会发送一个信号,所以我们使用 obj.destroyed.connect(destroy_slot) 来将obj被销毁时发出的型号与destroy_slot()这个槽函数连接起来,程序运行以后,可以在控制台看到“obj 被释放了”的信息:

pychon里面的designer槽函数_自定义

2. 示例2:带参数信号

示例1我们只是捕获了一个信号,但是并没有使用到参数。正如前文提及,当窗口的标题被改变的时候,也会发送一个信号,此时我们通常关心,这个窗口被改变成什么名字了?以下程序演示了如何捕捉到窗口的标题改动时间,并获取到新的窗口名称。

import sys
from PyQt5.QtWidgets import QApplication, QWidget

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.init()

    # 定义槽函数
    def new_title_print(self,title):
        print("现在的标题是: " + title)
        
    def init(self):
        self.setGeometry(300, 300, 300, 220)
        self.setWindowTitle("原标题")
        self.windowTitleChanged.connect(self.new_title_print)  #连接窗口标题改变信号到槽函数 new_title_print()
        self.setWindowTitle("新的标题!")
        self.show()

if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

其运行结果如下:

pychon里面的designer槽函数_控件_02


可以看到,窗口标题为:新的标题。相关的事件信息也被打印出来,程序之所以可以打印出新标题,是因为标题改变以后,发送的信号带有标题信息,有了参数,我们可以更加灵活的设计程序。

三、信号槽的断开与重连

上文的程序示例了如何连接信号,但是,信号并不是连接以后,就不再断开的。考虑一种情况,如果我们需要设计一个给窗口标题自动加入前缀的槽函数,那么我们首先将窗口标题改变的型号连接到一个槽函数,接着在槽函数里将标题加上前缀,但是如果单纯这么实现,将会无限地修改窗口标题,因为每改一次,就触发一次修改标题,每修改一次标题,又触发一次修改标题,如此循环,直至程序错误退出。所以我们再修改标题前,先断开信号和槽的连接,当然,修改以后再连接回来即可。以下程序示例了这个功能的正确实现:

import sys
from PyQt5.QtWidgets import QApplication, QWidget

class Example(QWidget):

    def __init__(self):
        super().__init__()
        self.init()

    # 创建一个加入前缀的信号槽
    def add_prefix(self,title):
        # 1. 先断开
        self.windowTitleChanged.disconnect(self.add_prefix)
        # 2. 修改标题
        self.setWindowTitle('prefix ' + title)
        print("now titile is " + title)
        # 3. 再次连接
        self.windowTitleChanged.connect(self.add_prefix)


    def init(self):
        self.setGeometry(300, 300, 500, 300)
        self.setWindowTitle('信号和槽实验 NO.0')
        # 4. 连接信号槽
        self.windowTitleChanged.connect(self.add_prefix)
        self.setWindowTitle('信号和槽实验 NO.1')
        self.setWindowTitle('信号和槽实验 NO.2')
        self.setWindowTitle('信号和槽实验 NO.3')
        self.show()

if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

程序运行结果如下;

pychon里面的designer槽函数_自定义_03


程序可以正常运行,这是因为槽函数在修改标题的时候,先断开了信号,使得本身修改的时候不受理标题修改的型号,切记在修改以后,重新连接好信号和槽

四、控件之间的通信

上诉示例了3个程序,它们都是控件内部的交互,它们的连接也在类的内部。如果我们设计一个按钮,按下可以改变QWidght控件的背景颜色,则需要不同控件之间的通信,以下演示了该功能的实现:

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton

class Example(QWidget):

    def __init__(self):
        super().__init__()
        self.init()



    def init(self):
        self.setGeometry(300, 300, 200, 200)
        self.setWindowTitle("信号和槽")

        #1. 创建 wid 控件,并设置背景颜色等
        wid = QWidget(self);
        wid.setStyleSheet("background:red;")
        wid.resize(50,50)
        wid.move(75,40)

        #2. 创建 pushButton 控件
        btn = QPushButton("改变颜色",self)
        btn.move(65,110)

        #3. 定义槽函数
        def setColorBlue():
            wid.setStyleSheet("background:Blue;") 
        def setColorRed():
            wid.setStyleSheet("background:Red;")

        #4. 连接信号槽
        btn.pressed.connect(setColorBlue)          # 按下连接槽函数为:设置背景为蓝色
        btn.released.connect(setColorRed)          # 松开连接槽函数为:设置背景会红色

        self.show()

if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

其演示效果如下:

pychon里面的designer槽函数_自定义_04


可以看到,当我的鼠标按下的时候,widget变成蓝色,松开后又变成了红色。注意到,这个连接是在他们父类“层次”代码区间实现的。

五、自定义信号

上文的几个示例中,我们都只是自定义的槽函数,信号是由Qt给我没提供的。事实上,我们可以自己建立一个信号。接下来示例如何自定义一个不带参数的信号以及带参数的信号。

1. 自定义信号(无参数)

import sys
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtWidgets import QApplication, QWidget

#注:这里信号是必须封装为类的
class Communicate(QObject):
    #1. 定义一个无参数信号
    printSignal = pyqtSignal()

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        #2. 创建信号
        self.c = Communicate()
        #3. 连接信号
        self.c.printSignal.connect(self.print_val)

        self.setGeometry(300, 300, 290, 150)
        self.setWindowTitle('发送信号')
        self.show()
    #4. 实现槽函数
    def print_val(self):
        print('QWidget 被按下了')

    def mousePressEvent(self, event):
        #5. 发射信号
        self.c.printSignal.emit()


if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

其运行结果如下:

pychon里面的designer槽函数_自定义_05

注:这里,鼠标的左键,右键,甚至滑轮的点击事件,都可以触发打印。

至此,我们多接触了两个函数:

  1. pyqtSignal() :用来创建一个信号
  2. emit():用来发射一个信号

出现emit()函数是因为在自定义信号中,我们要告知系统何时发送该信号,而系统自带的信号它本身就知道何时被发送。

2. 自定义信号(带参数)

import sys
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtWidgets import QApplication, QWidget

class Communicate(QObject):
    #1. 在 pyqtSingal 后输入 int
    #    创建一个带 int 参数的信号
    printSignal = pyqtSignal(int)

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()


    def initUI(self):
        self.c = Communicate()
        self.c.printSignal.connect(self.print_val)

        self.setGeometry(300, 300, 290, 150)
        self.setWindowTitle('发送信号')
        self.show()

    #2. 定义槽函数,第二个参数为val,用来记录信号发送的值
    def print_val(self,val):
        #3. 打印信号的值
        print('para = ',val)

    def mousePressEvent(self, event):
        #2. 发送信号,并写入参数的值为:123
        self.c.printSignal.emit(123)


if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

其运行结果如下:

pychon里面的designer槽函数_自定义_06