Skip to content

信号与槽

信号和槽关联

信号(signal)和槽(slot)是Qt的核心机制,通过建立信号和槽的连接可以实现对象之间的通信,当信号发射(emit)时,连接的槽函数将会自动执行。

Qt中,每一个QObject对象和PyQt中所有继承自QWidget的控件都支持信号与槽机制。

信号和槽通过QObject.signal.connect()连接。

所以从QObject类或其子类(如QWidget)派生的类都能够包含信号和槽,当对象改变其状态时,信号就由该对象发射出去,槽用于接收信号,多个信号可以与单个槽进行连接,单个信号也可以与多个槽进行连接,一个信号可以连接另外一个信号,信号与槽的连接方式可以是同步的也可以是异步的,信号和槽构建了一种强大的控件机制。

Qt中,通过Qt信号槽机制对鼠标或键盘在界面上的操作进行响应处理,如鼠标单击按钮的处理。


定义信号

PyQt的内置信号是自动定义的,使用PyQt5.QtCore.pyqtSignal()函数可以为QObject创建一个信号,使用pyqtSingnal()函数可以把信号定义为类的属性。

QObject对象创建信号

使用pyqtSingnal()函数创建一个或多个重载的未绑定的信号作为类的属性,信号只能在QObject的子类中定义

python
class foo(QObject):
    valueChanged = pyqtSingnal([dict], [list])

信号必须在类创建时定义,不能在后续动态的添加。types参数表示定义信号时参数的类型,name参数表示信号的名字,该项缺少时使用类的属性名字。

使用pyqtSingnal()函数创建信号,信号可以传递多个参数,并指定信号传递参数的类型。

为控件创建信号

自定义控件WinForm创建一个btnClickedSignal信号:

python
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class WinForm(QMainWindow):
    btnClickedSignal = pyqtSingnal()

操作信号

  • 使用connect()函数将信号绑定到槽函数上。

  • 使用disconnect()函数解除信号与槽的绑定。

  • 使用emit()函数可以发射信号。


信号与槽的应用

内置信号与槽的使用

内置信号的使用,是指在发射信号时,使用窗口控件的函数,而不是使用自定义函数。

通过QObject.signal.connect()将一个的QObject的信号连接到另外一个QObject的槽函数。

内置信号和内置槽函数:

python
btn = QPushButton("关闭按钮")
bth.clicked.connect(self.close())   # 单击按钮触发内置信号clicked和内置槽函数self.close

将按钮对象内置的clicked信号连接到槽函数close()

内置信号与自定义槽函数

以按钮关闭为例:

python
# -*- coding: utf-8 -*-

from PyQt5.QtWidgets import *
import sys

class Winform(QWidget):
	def __init__(self,parent=None):
		super().__init__(parent)
		self.setWindowTitle('内置的信号和自定义槽函数示例')
		self.resize(330,  50 ) 
		btn = QPushButton('关闭', self)		
		btn.clicked.connect(self.btn_close)  # 内置信号连接自定义槽函数

	def btn_close(self):   # 自定义槽函数
		self.close()
		
if __name__ == '__main__':
	app = QApplication(sys.argv)
	win = Winform()
	win.show()
	sys.exit(app.exec_())

单击按钮时触发按钮内置信号clicked,绑定自定义的槽函数btn_close()

自定义信号和内置槽函数

以关闭按钮为例:

python
# -*- coding: utf-8 -*-

from PyQt5.QtWidgets import *
from PyQt5.QtCore import pyqtSignal
import sys

class Winform(QWidget):
	button_clicked_signal = pyqtSignal()  # 自定义信号,不带参数

	def __init__(self,parent=None):
		super().__init__(parent)
		self.setWindowTitle('自定义信号和内置槽函数示例')
		self.resize(330,  50 ) 
		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()  # 发送自定义信号,无参数
		                    		
if __name__ == '__main__':
	app = QApplication(sys.argv)
	win = Winform()
	win.show()
	sys.exit(app.exec_())

单击按钮触发自定义信号button_clicked_signal,连接内置槽函数self.close

自定义信号与槽函数的使用

在发射信号时,不使用窗口控件的函数,而是使用自定义函数(使用pyqtSingnal类实例发射信号)

自定义信号与槽函数可以灵活地实现一些业务逻辑。

按钮关闭案例:

python
# -*- coding: utf-8 -*-

from PyQt5.QtWidgets import *
from PyQt5.QtCore import pyqtSignal
import sys

class Winform(QWidget):
	button_clicked_signal = pyqtSignal()  # 自定义信号,不带参数

	def __init__(self,parent=None):
		super().__init__(parent)
		self.setWindowTitle('自定义信号和槽函数示例')
		self.resize(330,  50 ) 
		btn = QPushButton('关闭', self)
		btn.clicked.connect(self.btn_clicked)  # 连接信号和槽
		self.button_clicked_signal.connect(self.btn_close)   # 接收信号,连接到自定义槽函数

	def btn_clicked(self):
		self.button_clicked_signal.emit()   # 发送自定义信号,无参数

	def btn_close(self):  # 自定义槽函数
		self.close()
		
if __name__ == '__main__':
	app = QApplication(sys.argv)
	win = Winform()
	win.show()
	sys.exit(app.exec_())

单击按钮时触发自定义信号button_clicked_signal,绑定自定义的槽函数btn_close()

其他自定义信号和自定义槽函数案例:

python
# -*- coding: utf-8 -*-

from PyQt5.QtCore import QObject, pyqtSignal

# 信号对象
class QTypeSignal(QObject):
    sendmsg = pyqtSignal(object)  # 定义一个信号

    def __init__(self):
        super(QTypeSignal, self).__init__()

    def run(self):
        # 发射信号的实现
        self.sendmsg.emit('Hello Pyqt5')

# 槽对象          
class QTypeSlot(QObject):
    def __init__(self):
        super(QTypeSlot, self).__init__()
    # 槽对象里的槽函数
    def get(self, msg):   # 槽函数接收数据
        print("QSlot get msg => " + msg)

if __name__ == '__main__':
    send = QTypeSignal()
    slot = QTypeSlot()
    #1
    print('--- 把信号绑定到槽函数 ---')
    send.sendmsg.connect(slot.get)  # 将信号与槽函数get()绑定起来,槽函数能接收到所发射的信号'Hello Pyqt5'
    send.run()
    #2
    print('--- 把信号断开槽函数 ---')
    send.sendmsg.disconnect(slot.get) # 断开信号与槽函数get连接,槽函数是接收不到发射信号的
    send.run()

高级自定义信号与槽

我们通过自己喜欢的方式定义信号与槽函数,并传递参数。

自定义信号一般流程如下:(1)定义信号 (2)定义槽函数 (3)连接信号与槽函数 (4)发射信号

定义信号

通过类成员变量定义信号对象:

python
class MyWidget(QWidget):
    Signal_NoParameters = pyqtSignal()  # 无参数的信号
    Signal_OneParameter = pyqtSignal(int)   # 带一个参数(整数)的信号
    Signal_OneParameter = pyqtSignal(list)   # 带一个列表类型参数的信号
    Signal_OneParameter = pyqtSignal(dict)   # 带一个字典类型参数的信号
    Signal_OneParameter_Overload = pyqtSignal([int],[str])   # 带一个参数(整数或者字符串)的重载版本的信号
    Signal_TwoParameters = pyqtSignal(int, str)  # 带两个参数(整数,字符串)的信号
    # 带两个参数([整数,整数]或者[整数,字符串])的重载版本的信号
    Signal_TwoParameters_Overload = pyqtSignal([int,int],[int,str])

自定义信号需要在__init__()函数之前定义

自定义信号可以传递strintlistobjectfloattupledict等多类型的参数

定义槽函数

定义一个槽函数,它有多个不同的输入参数:

python
class MyWidget(QWidget):
    def setValue_NoParameters(self):
        ```无参数的槽函数```
        pass
    def setValue_OneParameter(self,nIndex):
        ```带一个参数(整数)的槽函数```
        pass
    def setValue_OneParameter_String(self,szIndex):
        ```带一个参数(字符串)的槽函数```
        pass
    def setValue_TwoParameters(self,x,y):
        ```带两个参数(整数,整数)的槽函数```
        pass
    def setValue_TwoParameters_String(self,x,szY):
        ```带两个参数(整数,字符串)的槽函数```
        pass
连接信号与槽函数

通过connect方法连接信号与槽函数或者可调用对象:

python
app = QApplication(sys.argv)
widget = MyWidget()
widget.Signal_NoParameters.connect(self.setValue_NoParameters)  # 连接无参数的信号
widget.Signal_OneParameter.connect(self.setValue_OneParameter)  # 连接一个带整数参数的信号
# 连接带一个整数参数,经过重载的信号
widget.Signal_OneParameter_Overload[int].connect(self.setValue_OneParameter)
# 连接带一个字符串参数,经过重载的信号
widget.Signal_OneParameter_Overload[str].connect(self.setValue_OneParameter_String)
# 连接带一个信号,它有两个整数参数
widget.Signal_TwoParameters.connect(self.setValue_TwoParameters)
# 连接带两个参数(整数,整数)的重载版本的信号
widget.Signal_TwoParameter_Overload[int, int].connect(self.setValue_TwoParameters)
# 连接带两个参数(整数,字符串)的重载版本的信号
widget.Signal_TwoParameters_Overload[int, str].connect(self.setValue_TwoParameters_String)
widget.show()
发射信号

通过emit方法发射信号:

python
class MyWidget(QWidget):
    def mousePressEvent(self, event):
        self.Signal_NoParameters.emit()   # 发射无参数的信号
        self.Signal_OneParameter.emit(1)   # 发射带一个参数(整数)的信号
        self.Signal_OneParameter_Overload.emit(1)   # 发射带一个参数(整数)的重载版本的信号
        self.Signal_OneParameter_Overload.emit("abc") # 发射带一个参数(字符串)的重载版本的信号
        self.Signal_TwoParameters.emit(1,"abc")   # 发射带两个参数(整数,字符串)的信号
        self.Signal_TwoParameters_Overload.emit(1,2)# 发射带两个参数(整数,整数)的重载版本的信号
        self.Signal_TwoParameters_Overload.emit(1,"abc")   # 发射带两个参数(整数,字符串)的重载版本的信号

使用自定义参数

信号发出的参数个数一定要大于槽函数接收的参数个数,否则会报错

对于信号clicked来说,是没有参数的,但是对于槽函数,我们希望可以接收参数,我们可以通过自定义参数的传递来解决,通常使用lambda表达式

python
button1.clicked.connect(lambda: self.onButtonClick(1))
# 槽函数设计,希望可以引入参数,使用lambda表达式传递按钮数字给槽函数
def onButtonClick(self, n):
    QMessageBox.information(self, "信息提示框", 'Button {0} clicked'.format{n})

信号与槽的断开

当我们想临时或永久断开某个信号与槽函数的连接,可以使用disconnect()方法

self.signal.disconnect(self.sinCall)


Qt Designer来设计信号与槽

实现功能:当单击关闭按钮后关闭窗口

Qt Designer窗口拖拽push Button控件到窗体中,在text属性中改为“关闭窗口”,将objectName属性值改为“closeWinBtn”

单击“Edit” --> “编辑信号/槽” --> 进入编辑模式,在发射者(关闭窗口按钮上)按住鼠标不放,拖到接收者(Form窗体上),就建立了连接,着会弹出“配置连接”的对话框,如下图所示:

在“配置连接”的对话框中勾选显示从QWidget 继承的信号和槽,左侧的closeWinBtn按钮的信号栏里选择clicked()信号,右侧的Form槽函数中选择close(),这就意味着,对“关闭窗口”按钮点击会发射clicked()信号,这个信号会被Form窗体的槽函数close()捕捉到,并触发该窗体close行为及关闭该窗体。

连接信号和槽成功后发现在编辑信号/槽模式下,其信号和槽的关系连线是红色的

button_close.ui文件通过PyUIC工具生成button_close.py文件,通过button_close.py文件可以发现是通过以下代码进行连接的:

python
self.cloesWinBtn.clicked.connect(Form.close)

使用QObject.signal.connect()连接的槽函数不要加括号,否则会报错。

可以在信号/槽编辑器中获取默认可用的信号与槽列表,还可以进行编辑调整。


多线程中信号与槽的使用

多线程使用方法是通过QThread函数

在开发程序时会执行一些耗时的操作,会导致界面卡顿,我们可以使用多线程来解决该问题,使用主线程更新界面,使用子线程实时处理数据,最终将结果显示到界面上。

以下是一个多线程更新跟新数据的案例:

python
# -*- coding: utf-8 -*-

from PyQt5.QtCore import QThread ,  pyqtSignal,  QDateTime 
from PyQt5.QtWidgets import QApplication,  QDialog,  QLineEdit
import time
import sys

class BackendThread(QThread):
    # 通过类成员对象定义信号对象  
	update_date = pyqtSignal(str)
	
    # 处理要做的业务逻辑
	def run(self):
		while True:
			data = QDateTime.currentDateTime()
			currTime = data.toString("yyyy-MM-dd hh:mm:ss")
			self.update_date.emit( str(currTime) )
			time.sleep(1)

class Window(QDialog):
	def __init__(self):
		QDialog.__init__(self)
		self.setWindowTitle('pyqt5界面实时更新例子')
		self.resize(400, 100)
		self.input = QLineEdit(self)
		self.input.resize(400, 100)
		self.initUI()

	def initUI(self):
        # 创建线程,来模拟后台的耗时操作,使用BackendThread线程类在后台处理数据,每秒发射一个自定义信号update_date
		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)
	win = Window()
	win.show() 
	sys.exit(app.exec_())

事件处理机制

相对于信号与槽用于解决窗口控件的某些特定行为,事件处理机制可以对窗口控件做更深层次的研究,如自定义窗口等。

当采用信号与槽机制处理不了时,可以考虑事件处理机制。信号与槽是对事件处理机制的高级封装

常见的事件类型:

事件类型描述
键盘事件按钮按下和松开
鼠标事件鼠标指针移动、鼠标按键按下和松开
拖放事件用鼠标进行拖放
滚轮事件鼠标滚轮滚动
绘屏事件重绘屏幕的某些部分
定时事件定时器到时
焦点事件键盘焦点移动
进入和离开事件鼠标指针移入Widget内,或者移出
移动事件Widget的位置改变
大小改变事件Widget的大小改变
显示和隐藏事件Widget显示和隐藏
窗口事件窗口是否为当前窗口

使用事件处理的方法

常用的方法有:重新实现事件函数(mousePressEvent()keyPressEvent()paintEvent())、重新实现QObject.event()

对于绘图事件,event函数会交给paintEvent函数处理;对于鼠标事件,event函数会交给mouseMoveEvent函数处理;对于键盘按下事件,event函数会交给keyPressEvent函数处理

案例--重新实现事件函数和重新实现event函数
python
import sys
from PyQt5.QtCore import (QEvent, QTimer, Qt)
from PyQt5.QtWidgets import (QApplication, QMenu, QWidget)
from PyQt5.QtGui import QPainter


class Widget(QWidget):
    def __init__(self, parent=None):
        super(Widget, self).__init__(parent)
        self.justDoubleClicked = False
        self.key = ""
        # 建立text和message两个变量,使用paintEvent函数把它们输出到窗口中
        self.text = ""
        self.message = ""
        self.resize(400, 300)
        self.move(100, 100)
        self.setWindowTitle("Events")
        # 避免窗口大小重绘事件的影响,可以把参数0改变成3000(3秒)
        QTimer.singleShot(0, self.giveHelp)  

    def giveHelp(self):
        self.text = "请点击这里触发追踪鼠标功能"
        self.update() # 重绘事件,也就是触发paintEvent函数。update函数的作用是更新窗口

    # 重新实现关闭事件
    def closeEvent(self, event):
        print("Closed")

    # 重新实现上下文菜单事件,对于上下文事件,主要影响message变量的结果
    def contextMenuEvent(self, event):
        menu = QMenu(self)
        oneAction = menu.addAction("&One")
        twoAction = menu.addAction("&Two")
        oneAction.triggered.connect(self.one)
        twoAction.triggered.connect(self.two)
        if not self.message:
            menu.addSeparator()
            threeAction = menu.addAction("Thre&e")
            threeAction.triggered.connect(self.three)
        menu.exec_(event.globalPos())

    # 上下文菜单槽函数
    def one(self):
        self.message = "Menu option One"
        self.update()

    def two(self):
        self.message = "Menu option Two"
        self.update()

    def three(self):
        self.message = "Menu option Three"
        self.update()

    # 重新实现绘制事件,是代码的核心事件,主要跟踪text和message这两个变量的信息
    def paintEvent(self, event):
        text = self.text
        i = text.find("\n\n")
        if i >= 0:
            text = text[0:i]
        if self.key:   # 若触发了键盘按钮,则在文本信息中记录这个按钮信息。
            text += "\n\n你按下了: {0}".format(self.key)
        painter = QPainter(self)
        painter.setRenderHint(QPainter.TextAntialiasing)
        painter.drawText(self.rect(), Qt.AlignCenter, text) # 绘制信息文本的内容
        if self.message:    # 若消息文本存在则在底部居中绘制消息,5秒钟后清空消息文本并重绘。
            painter.drawText(self.rect(), Qt.AlignBottom | Qt.AlignHCenter,
                             self.message)
            QTimer.singleShot(5000, self.clearMessage)
            QTimer.singleShot(5000, self.update)

    # 清空消息文本的槽函数
    def clearMessage(self):
        self.message = ""

    # 重新实现调整窗口大小事件
    def resizeEvent(self, event):
        self.text = "调整窗口大小为: QSize({0}, {1})".format(
            event.size().width(), event.size().height())
        self.update()

    # 重新实现鼠标释放事件
    def mouseReleaseEvent(self, event):
        # 若鼠标释放为双击释放,则不跟踪鼠标移动
        # 若鼠标释放为单击释放,则需要改变跟踪功能的状态,如果开启跟踪功能的话就跟踪,不开启跟踪功能就不跟踪
        if self.justDoubleClicked:
            self.justDoubleClicked = False
        else:
            self.setMouseTracking(not self.hasMouseTracking()) # 单击鼠标
            if self.hasMouseTracking():
                self.text = "开启鼠标跟踪功能.\n" + \
                            "请移动一下鼠标!\n" + \
                            "单击鼠标可以关闭这个功能"
            else:
                self.text = "关闭鼠标跟踪功能.\n" + \
                            "单击鼠标可以开启这个功能"
            self.update()

    # 重新实现鼠标移动事件
    def mouseMoveEvent(self, event):
        if not self.justDoubleClicked:
            globalPos = self.mapToGlobal(event.pos()) # 窗口坐标转换为屏幕坐标
            self.text = """鼠标位置:
            窗口坐标为:QPoint({0}, {1}) 
            屏幕坐标为:QPoint({2}, {3}) """.format(event.pos().x(), event.pos().y(), globalPos.x(), globalPos.y())
            self.update()

    # 重新实现鼠标双击事件
    def mouseDoubleClickEvent(self, event):
        self.justDoubleClicked = True
        self.text = "你双击了鼠标"
        self.update()

    # 重新实现键盘按下事件
    def keyPressEvent(self, event):
        self.key = ""
        if event.key() == Qt.Key_Home:
            self.key = "Home"
        elif event.key() == Qt.Key_End:
            self.key = "End"
        elif event.key() == Qt.Key_PageUp:
            if event.modifiers() & Qt.ControlModifier:
                self.key = "Ctrl+PageUp"
            else:
                self.key = "PageUp"
        elif event.key() == Qt.Key_PageDown:
            if event.modifiers() & Qt.ControlModifier:
                self.key = "Ctrl+PageDown"
            else:
                self.key = "PageDown"
        elif Qt.Key_A <= event.key() <= Qt.Key_Z:
            if event.modifiers() & Qt.ShiftModifier:
                self.key = "Shift+"
            self.key += event.text()
        if self.key:
            self.key = self.key
            self.update()
        else:
            QWidget.keyPressEvent(self, event)

    # 重新实现其他事件,适用于PyQt没有提供该事件的处理函数的情况,Tab键由于涉及焦点切换,不会传递给keyPressEvent,因此,需要在这里重新定义。
    def event(self, event):
        if (event.type() == QEvent.KeyPress and
                    event.key() == Qt.Key_Tab):
            self.key = "在event()中捕获Tab键"
            self.update()
            return True
        return QWidget.event(self, event)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    form = Widget()
    form.show()
    app.exec_()

窗口数据传递

一个窗口间的数据是如何在控件中传递的,多个窗口间不同的窗口是如何传递数据的

对于多个窗口传递数据,有两种方法:

  1. 主窗口获取子窗口中控件的属性
  2. 通过信号与槽机制,一般通过子窗口发射信号的形式传递数据,主窗口的槽函数获取这些数据
单一窗口的数据传递

对于单一窗口的数据传递,一般是一个控件的变化影响另一个控件的变化,可以通过信号和槽进行传递

python
# -*- coding: utf-8 -*-

import sys
from PyQt5.QtWidgets import QWidget,QLCDNumber,QSlider,QVBoxLayout,QApplication
from PyQt5.QtCore import Qt

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

    def initUI(self):
        # 先创建滑块和 LCD 部件
        lcd = QLCDNumber(self)
        slider = QSlider(Qt.Horizontal, self)
        
        # 通过QVboxLayout来设置布局
        vBox = QVBoxLayout()
        vBox.addWidget(lcd)
        vBox.addWidget(slider)
        self.setLayout(vBox)
        
        # valueChanged()是Qslider的一个信号函数,只要slider的值发生改变,它就会发射一个信号,然后通过connect连接信号的接收部件,也就是lcd。
        slider.valueChanged.connect(lcd.display)

        self.setGeometry(300,300,350,150)
        self.setWindowTitle("信号与槽:连接滑块LCD")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    form = WinForm()
    form.show()                      
    sys.exit(app.exec_())

程序运行结果:改变滑块的值,相应变化的结果会显示在LCD

多窗口数据传递

调用属性方法

将多个参数写到一个窗口中,会使主窗口很臃肿,所以一般添加一个按钮调用对话框,在对话框中进行参数的选择,关闭对话框时将参数值返回给主窗口。

自定义一个对话框作为一个子窗口,为后续主窗口来调用这个子窗口的属性

python
# -*- coding: utf-8 -*-

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class DateDialog(QDialog):
    def __init__(self, parent=None):
        super(DateDialog, self).__init__(parent)
        self.setWindowTitle('DateDialog')

        # 在布局中添加部件
        layout = QVBoxLayout(self)
        self.datetime = QDateTimeEdit(self)
        self.datetime.setCalendarPopup(True)
        self.datetime.setDateTime(QDateTime.currentDateTime())
        layout.addWidget(self.datetime)

        # 使用两个button(ok和cancel)分别连接accept()和reject()槽函数
        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()

    # 静态方法创建对话框并返回 (date, time, accepted)
    @staticmethod
    def getDateTime(parent=None):   # 在类中定义一个静态函数getDateTime(),来返回三个时间值
        dialog = DateDialog(parent)  # 静态函数中实例化DateDialog类
        result = dialog.exec_()  # 调用 dialog.exec_()来显示执行对话框,通过返回值来判断用户单击的是OK还是Cancel
        date = dialog.dateTime()
        return (date.date(), date.time(), result == QDialog.Accepted)

创建一个调用对话框的主窗口

python
# -*- coding: utf-8 -*-

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from DateDialog import DateDialog   # 调用DateDialog.py文件的DateDialog类

class WinForm(QWidget):
    def __init__(self, parent=None):
        super(WinForm, self).__init__(parent)
        self.resize(400, 90)
        self.setWindowTitle('对话框关闭时返回值给主窗口例子')
		# 创建相关控件,同时连接槽函数
        self.lineEdit = QLineEdit(self)
        self.button1 = QPushButton('弹出对话框1')
        self.button1.clicked.connect(self.onButton1Click)
        self.button2 = QPushButton('弹出对话框2')
        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.py文件的DateDialog类
        result = dialog.exec_()
        date = dialog.dateTime()
        self.lineEdit.setText(date.date().toString())
        print('\n日期对话框的返回值')
        print('date=%s' % str(date.date()))
        print('time=%s' % str(date.time()))
        print('result=%s' % result)
        dialog.destroy()

    def onButton2Click(self):  # 在主窗口程序中调用子窗口的静态函数,利用静态函数的特点,在子窗口的静态函数中创建实例化对象
        date, time, result = DateDialog.getDateTime()
        self.lineEdit.setText(date.toString())
        print('\n日期对话框的返回值')
        print('date=%s' % str(date))
        print('time=%s' % str(time))
        print('result=%s' % result)
        if result == QDialog.Accepted:
            print('点击确认按钮')
        else:
            print('点击取消按钮')

if __name__ == "__main__":
    app = QApplication(sys.argv)
    form = WinForm()
    form.show()
    sys.exit(app.exec_())

程序运行结果:在子窗口中选择相关时间后,点击OK,会显示在主窗口的lineEdit中

信号与槽方法

通过信号与槽进行多窗口的数据传递,一般是通过子窗口发射信号,主窗口通过槽函数捕获信号,然后获取信号里面的数据。

创建一个子窗口对话框文件

python
# -*- coding: utf-8 -*-

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

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(self)
        self.label.setText('前者发射内置信号\n后者发射自定义信号')  # 用于给用户提示

        self.datetime_inner = QDateTimeEdit(self)
        self.datetime_inner.setCalendarPopup(True)
        self.datetime_inner.setDateTime(QDateTime.currentDateTime())

        self.datetime_emit = QDateTimeEdit(self)
        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)

        # 使用两个button(ok和cancel)分别连接accept()和reject()槽函数
        buttons = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
            Qt.Horizontal, self)
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)
        layout.addWidget(buttons)
		# 控件datetime_emit的时间发生变化时,会触发子窗口的槽函数emit_signal
        self.datetime_emit.dateTimeChanged.connect(self.emit_signal)

    def emit_signal(self):  # 槽函数emit_signal会发发射自定义信号Signal_OneParameter,传递date_str参数给主函数的槽函数
        date_str = self.datetime_emit.dateTime().toString()
        self.Signal_OneParameter.emit(date_str)

主窗口代码:获取子窗口的信号,并将其绑定在自己的槽函数上,就实现了子窗口的控件与主窗口的控件的信号与槽的绑定

python
# -*- coding: utf-8 -*-

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from DateDialog2 import DateDialog

class WinForm(QWidget):
    def __init__(self, parent=None):
        super(WinForm, self).__init__(parent)
        self.resize(400, 90)
        self.setWindowTitle('信号与槽传递参数的示例')
		# 创建相关的控件
        self.open_btn = QPushButton('获取时间')
        self.lineEdit_inner = QLineEdit(self)
        self.lineEdit_emit = QLineEdit(self)
        self.open_btn.clicked.connect(self.openDialog)  # 连接槽函数
        self.lineEdit_inner.setText('接收子窗口内置信号的时间')
        self.lineEdit_emit.setText('接收子窗口自定义信号的时间')
		# 进行布局
        grid = QGridLayout()
        grid.addWidget(self.lineEdit_inner)
        grid.addWidget(self.lineEdit_emit)
        grid.addWidget(self.open_btn)
        self.setLayout(grid)

    def openDialog(self):
        dialog = DateDialog(self)  # 调用子窗口的DateDialog类
        # 连接子窗口的内置信号与主窗口的槽函数
        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, dateStr):
        self.lineEdit_emit.setText(dateStr)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    form = WinForm()
    form.show()
    sys.exit(app.exec_())

Released under the MIT License.