本文讲解信号与槽的连接机制,详细示范各种类型的信号/槽连接的实现方法,这是图形用户界面的核心内容。还将介绍面向对象的程序设计,这是图形用户界面的基本思想
目录
1. 信号与槽(Signals and slots)
信号与槽机制是 PyQt 的核心机制,用于对象之间的通信,也就是实现函数之间的自动调用。
1.1 信号与槽的原理
简单地说,将信号与槽函数连接后,当信号被触发时,槽函数将被自动调用。
分析这个过程,涉及到几个基本概念和关系:
信号:信号可以是一个动作,也可以是对象的一种状态,用于触发所连接的槽。
槽:槽就是一个函数,可以由连接的信号触发。
发射信号:信号被发射时,自动调用信号连接的槽函数。通常,在对象的状态改变时发射信号。
信号可以带有参数,但必须与槽函数的参数相对应。
一个信号可以连接多个槽函数,多个信号也可以连接到一个槽函数。
一个信号可以连接到另一个信号上。
很多窗口部件(控件)内置了一下信号和槽,可以直接调用。也可以按需求自定义信号和槽。
1.2 信号发送者与槽的接收者
信号的发送者通常是一个控件对象,在控件对象的状态发生变化时发送信号。常见的发送者是图形窗口中的各种控件对象,但也可以是动作对象。
槽的接收者通常也是控件对象。槽函数是一个自定义的槽函数,或控件内置的槽函数。一般地,槽函数也有一个对象作为主体,即对于接受者这个控件对象执行函数定义的操作。例如槽函数执行的功能是关闭,哪么究竟是关闭那个控件呢?关闭对象就是接受者。
为了方便讲解信号与槽的连接,我们用 QtDesigner 在上节设计的图形窗口 uiDemo3.ui 的基础上,增加几个按钮对象和文本行编辑对象:
打开 PyCharm,从 Tools -> ExternalTools -> QtDesigner 打开 QtDesigner,打开 uiDemo3.ui 文件。
鼠标点击选中 QtDesigner 左侧控件栏 Buttons 中的 PushButton,按住鼠标不放,将其拖动到中间的图形界面后松开鼠标,就在图形界面中创建了一个 PushButton 控件。
鼠标点击选中 QtDesigner 左侧控件栏 InputWidget 中的 LineEdit,按住鼠标不放,将其拖动到中间的图形界面后松开鼠标,就在图形界面中创建了一个 LineEdit 控件。
重复以上步骤,再建立几个 PushButton 控件和 LineEdit 控件。
注意在 QtDesigner 右侧 “对象查看器” 中所显示的控件名称和属性,多个 PushButton、LineEdit 被自动赋予不同的命名(objectName),如:LineEdit_1、LineEdit_2、LineEdit_3,这就如同桌子有几个抽屉分别标记为 “抽屉1”、“抽屉2”、“抽屉3” 以便识别。
控件的名称和其它属性都可以在 “属性编辑器” 中编辑修改。
鼠标选中在图形界面中创建的 PushButton 控件或 LineEdit 控件,可以拖动控件调整位置。
鼠标双击 PushButton 按钮,可以编辑按钮控件的标签即按钮上的显示内容。
为了便于讲解,本例将各 PushButton 控件的显示内容(text 属性)修改为:“1# 按钮” ~ “4# 按钮”,将各 LineEdit 控件的显示内容(text 属性)修改为:“文本编辑行-1” ~ “文本编辑行-3”。
将这个应用程序图形界面另存为 uiDemo4.ui,其预览效果如下:
2. QtDesigner 建立信号与槽的连接
2.1 信号与槽的连接:不同的发送者与接收者,槽函数为控件的内置函数
QtDesigner 提供了便捷和直观的信号/槽编辑方法。
本例介绍不同的发送者与接收者,槽函数为控件的内置函数的操作方法。不同类型的控件分别内置了若干方法,例如 QPushButton 控件内置的方法包括:点击、选中、状态变化、显示菜单等,而 QLineEdit 控件内置的方法包括:清空、复制、剪切、粘贴、全选、撤销操作等。使用控件内置的方法作为槽函数,可以直接调用,不需要对函数进行定义。
我们所设计的功能是:当点击按钮控件 “pushButton_1” 时,清空文本编辑控件 “lineEdit_1” 的显示内容。注意我们称按钮控件为 “lineEdit_1” 而不是 “文本编辑行-1”,这是因为 lineEdit_1 才是控件名称,在程序中是不变的,而 “文本编辑行-1” 只是显示内容,可以在程序中修改。
QtDesigner 设置信号/槽的连接的操作步骤如下:
(1)选择菜单项 “Edit” -> 编辑信号/槽,或者通过快捷键 F4 或在工具栏选择,进入信号/槽编辑模式。
(2)选中控件对象发送者,此处为按钮控件 “pushButton_1”,鼠标左键长按不放,该按钮控件变为浅红色。
鼠标左键继续长按不放,并移动鼠标,当鼠标移出控件对象区域后,出现一条带箭头的红线和一个红色的接地符号。
鼠标左键继续长按不放,并拖动鼠标到控件对象 “lineEdit_1”,松开鼠标左键,就建立了以控件对象 “pushButton_1” 为发送者、控件对象 “lineEdit_1” 为接收者的信号/槽连接。
此时控件对象 “pushButton_1” 和 “lineEdit_1” 都变为浅红色,带箭头的红线从 “pushButton_1” 出发,指向 “lineEdit_1” 结束。
(3)同时弹出对话框 “配置连接 - QtDesigner”,对话框的左侧显示发送者控件的信号选项,对话框的右侧显示接收者的控件选项。
根据功能要求,触发信号为按钮 “pushButton_1” 被点击,从对话框左侧选中 “clicked()”;
根据功能要求,收到信号后动作是清空文本编辑控件 “lineEdit_1”,从对话框右侧选中 “clear()”;
点击对话框下方的按钮 “OK”,完成该信号/槽连接的配置。
2.2 信号与槽的连接:不同的发送者与接收者,槽函数为自定义函数
本例介绍不同的发送者与接收者,槽函数为自定义函数的操作方法。
在 2.1 中介绍了使用控件内置的方法作为槽函数,可以直接调用,不需要对函数进行定义。程序设计中的核心功能通常是程序员根据需求开发的自定义函数。使用自定义函数作为槽函数,一方面当然是要编写自定义函数,另一方面要将自定义函数添加到槽函数配置连接表中。
我们所设计的功能是:
当点击按钮控件 “pushButton_2” 时,清空文本编辑控件 “lineEdit_2” 的显示内容,并显示文本信息 “current signal: click pushButton_2”。
在主程序中要编写一个自定义函数实现该功能,将该自定义函数命名为 click_pushButton_2()。
注意我们编写的自定义函数 click_pushButton_2(),虽然功能只是对文本编辑控件 “lineEdit_2” 进行操作,但对于自定义函数也可以完成任意的其它功能,对其它控件按照控件名称进行操作。因此该槽函数的接收者并不是文本编辑控件 “lineEdit_2”,而是主窗口控件 “MainWindow”。
QtDesigner 设置信号/槽的连接的操作步骤如下:
首先要在 QtDesigner 将自定义函数添加到槽函数配置连接表中——非常重要。
网上的很多文章都没有讲具体实现方法,这个操作的入口也很难找到。
(1)在 QtDesigner 右侧上方的 “对象查看器”,选中 MainWindow 或其它顶层对象,单击鼠标右键唤出下拉菜单,选择 “改变信号/槽”;
弹出 “MainWindow 的信号/槽” 对话框,对话框的上方显示槽的选项,下方显示信号选项。
点击对话框上方 “槽” 选项框下部的绿色 “+”,系统在 “槽” 选项表的最后自动增加了一行 “slot1()”。这就是新增的自定义槽函数。
点击选中 “slot1()”,再鼠标双击,就可以修改槽函数的函数名,例如修改为 click_pushButton_2()。
再点击对话框上方 “槽” 选项框下部的绿色 “+”,可以继续逐一添加自定义的槽函数。
然后设置信号/槽的连接:
(2)选择菜单项 “Edit” -> 编辑信号/槽,或者通过快捷键 F4 或在工具栏选择,进入信号/槽编辑模式。
选中控件对象发送者,此处为按钮控件 “pushButton_2”,长按鼠标左键并移动,当鼠标移出控件对象区域后,出现一条带箭头的红线和一个红色的接地符号。
松开鼠标左键,就建立了以控件对象 “pushButton_2” 为发送者、控件对象 “MainWindow” 为接收者的信号/槽连接。此时控件对象 “pushButton_2” 变为浅红色,带箭头的红线从 “pushButton_2” 出发,并不指向其它控件,而是以一个接地符号结束。
(3)同时弹出对话框 “配置连接 - QtDesigner”,对话框的左侧显示发送者控件 “pushButton_2” 的信号选项,对话框的右侧显示接收者 “MainWindow” 的控件选项。
根据功能要求,触发信号为按钮 “pushButton_2” 被点击,从对话框左侧选中 “clicked()”;
对话框右侧接收者 “MainWindow” 的控件选项列表中,显示了刚才添加的几个自定义函数,选择 “click_pushButton_2()”;
点击对话框下方的按钮 “OK”,完成该信号/槽连接的配置。
最后,别忘了要在主程序中编写自定义的函数。但这已不属于 QtDesigner 设计的内容了,在此不再详述。
类似地,我们设计:当点击按钮控件 “pushButton_3” 时,在文本编辑控件 "lineEdit_1"显示当前系统日期,在文本编辑控件 "lineEdit_2"显示当前系统时间,在文本编辑控件 “lineEdit_3” 显示提示信息。在主程序中编写一个自定义函数 click_pushButton_3(),并与 “pushButton_3” 建立信号/槽连接。
这表明:在自定义的子函数中,可以同时操作多个控件对象,进而可以实现用户定义的各种功能。
2.3 信号与槽的连接:相同的发送者与接收者,槽函数为控件的内置函数
本例介绍相同的发送者与接收者,槽函数为控件的内置函数的操作方法。
顾名思义,相同的发送者与接收者,就是说信号的发送者与槽函数的接收者是同一个控件对象。这是什么情况?例如,一个开关按钮有 “On/Off” 两种状态,每按一次则按钮状态发生翻转。类似地,选项框也有选中、未选中两种状态。特殊地,点击按钮后,关闭该按钮控件,也属于相同的发送者与接收者。
我们首先将控件对象 “pushButton_4” 从按钮控件 QPushButton 改变为 选项框控件 “QCheckBox”:
点击控件对象 “pushButton_4”,控件对象的周围边界显示几个蓝色小方块;
点击鼠标右键唤出下拉菜单,选择:“变型为” ->“QCheckBox”。
此时,设计界面窗口中的按钮控件,变成了一个选项框。同时,右侧 “对象查看器” 中的控件 “pushButton_4”,也自动变更为 “checkBox_4”。变更的控件 “checkBox_4” 继承了原来控件 “pushButton_4” 的一些属性,如:位置、尺寸、显示内容。
接下来设置信号/槽的连接:
(1)选择菜单项 “Edit” -> 编辑信号/槽,或者通过快捷键 F4 或在工具栏选择,进入信号/槽编辑模式。
(2)选中控件对象发送者,此处为按钮控件 “checkBox_4”,长按鼠标左键并移动,当鼠标移出控件对象区域后,出现一条带箭头的红线和一个红色的接地符号。
长按鼠标左键,拖动鼠标再回到控件对象 “checkBox_4” 区域后松开鼠标左键,就建立了发送者和接收者都是控件对象 “checkBox_4” 的信号/槽连接。
此时控件对象 “checkBox_4” 变为浅红色,带箭头的红线从 “checkBox_4” 出发,又返回到 “checkBox_4” 结束。
(3)同时弹出了对话框 “配置连接 - QtDesigner”,对话框的左侧和右侧分别是控件对象 “checkBox_4” 的信号和槽函数。
从对话框左侧选中 “clicked(bool)”;
从对话框右侧选中 “setChecked(bool)”;
点击对话框下方的按钮 “OK”,完成该信号/槽连接的配置。
2.4 信号与槽的连接:发送者是动作对象
常见的信号发送者是图形窗口中的各种控件对象,但也可以是动作对象。本例介绍对菜单栏和工具栏中控件对象建立信号与槽的连接。
信号的发送者是动作对象时,信号的接收者通常是顶层对象 “MainWindow”,而槽函数可以是对象 “MainWindow” 的内置函数,也可以是自定义函数。
在上一篇文章中我们曾为菜单栏和工具栏中的动作 “actionQuit” 建立信号/槽连接,就是发送者是动作对象、连接到对象 “MainWindow” 的内置函数的案例。其操作过程如下:
从在 QtDesigner 右侧下方窗口 “信号/槽编辑器”,点击绿色的 “+” 新建一个信号/槽连接;
点击 “<发送者>”,从菜单中选择对象 “actionQuit”;
点击 “<信号>”,从菜单中选择 “triggered()”;
点击 “<接收者>”,从菜单中选择 “MainWindow”;
点击 “<槽>”,从菜单中选择 “closed()”。
以上操作的作用是:发送者 对象 “actionQuit” 触发 “triggered()” 时,接收者 对象"MainWindow" 执行槽函数 “closed()”。
下面我们再为另一个动作 “actionHelp” 建立信号/槽连接,连接的槽函数为自定义函数 trigger_actHelp()。其操作过程如下:
在 QtDesigner 将自定义函数 trigger_actHelp() 添加到槽函数配置连接表中;
从在 QtDesigner 右侧下方窗口 “信号/槽编辑器”,点击绿色的 “+” 新建一个信号/槽连接;
点击 “<发送者>”,从菜单中选择对象 “actionHelp”;
点击 “<信号>”,从菜单中选择 “triggered()”;
点击 “<接收者>”,从菜单中选择 “MainWindow”;
点击 “<槽>”,从菜单中选择 “trigger_actHelp()”。
至此,本章介绍了用 QtDesigner 进行几种常见的信号/槽连接的编辑和设置方法。如下图所示,在 QtDesigner 中所添加的信号/槽连接都会在信号/槽编辑器窗口内显示。
3. 图形界面的主程序
上节在 QtDesigner 中完成了图形界面的设计,将该文件另存为 uiDemo4.ui。打开 PyCharm,选中文件 uiDemo4.ui,使用 PyUIC 可以将其转换为 uiDemo4.py。
接下来我们要编写图形界面的主程序,调用图形界面设计文件 uiDemo4.py。
3.1 从面向过程到面向对象
面向过程的程序设计
在上一篇文章中,我们在主程序中创建主窗口后,直接调用 UI 中的 Ui_MainWindow()。这是面向过程的程序设计方法。
1 2 3 4 5 6 7 8 9 | # GUIdemo3.py importuiDemo3 # 导入图像界面设计文件 if__name__ =='__main__': app =QApplication(sys.argv) # 创建应用程序对象 MainWindow =QMainWindow() # 创建主窗口 ui =uiDemo3.Ui_MainWindow() ui.setupUi(MainWindow) MainWindow.show() # 显示主窗口 sys.exit(app.exec_()) # 在主线程中退出 |
面向对象的程序设计
随着项目的不断深入,需要处理更加丰富的图形界面和实现更加复杂的软件功能,程序的规模越来越大,程序的结构越来越复杂。
面向对象的程序设计使程序的结构更加清晰,从而易于阅读、理解、开发和维护,非常适合开发大规模、多任务的图形界面应用软件。PyQt5 中的类、对象、控件和方法,都是面向对象的程序设计的概念。因此,从本文开始我们采用面向对象的程序设计方法,来编写图形界面的主程序。
例程 GUIdemo4.py 如下:
1 2 3 4 5 6 7 8 9 10 | fromuiDemo4 importUi_MainWindow # 导入 uiDemo4.py 中的 Ui_MainWindow 界面类 classMyMainWindow(QMainWindow, Ui_MainWindow): # 继承 QMainWindow类和 Ui_MainWindow界面类 def__init__(self, parent=None): super(MyMainWindow, self).__init__(parent) # 初始化父类 self.setupUi(self) # 继承 Ui_MainWindow 界面类 if__name__ =='__main__': app =QApplication(sys.argv) # 在 QApplication 方法中使用,创建应用程序对象 myWin =MyMainWindow() # 实例化 MyMainWindow 类,创建主窗口 myWin.show() # 在桌面显示控件 myWin sys.exit(app.exec_()) # 结束进程,退出程序 |
在上面这个例程中,我们创建了一个类 MyMainWindow(),它继承了 QtWidgets.QMainWindow 类方法和导入的 uiDemo4.py 中的 Ui_MainWindow 界面类。
super(MyMainWindow, self).init():初始化父类,把 MyMainWindow 的对象 self 转成父类 QMainWindow 对象,并调用 init 函数。
self.setupUi(self):继承 uiDemo4.py 中的 Ui_MainWindow 界面类,调用 Ui_MainWindow 类的setupUi() 方法。
不熟悉面向对象程序设计的小白,如果看不懂这段程序也没有关系,保存下来照猫画虎就可以了。
3.2 自定义槽函数
运行例程 GUIdemo4.py。咦,报错了:
AttributeError: ‘MyMainWindow' object has no attribute ‘click_pushButton_2'
系统提示找不到 ‘click_pushButton_2',这是因为在主程序中还没有编写 2.2 中自定义的槽函数。
将自定义的槽函数 click_pushButton_2()、click_pushButton_3、trigger_actHelp(self) 添加到主程序中。下面给出例程 GUIdemo4.py 完整的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | # GUIdemo4.py # Demo4 of GUI by PyQt5 # Copyright 2021 youcans, XUPT # Crated:2021-10-10
importsys fromPyQt5.QtWidgets importQApplication, QMainWindow, QMessageBox fromuiDemo4 importUi_MainWindow # 导入 uiDemo4.py 中的 Ui_MainWindow 界面类 classMyMainWindow(QMainWindow, Ui_MainWindow): # 继承 QMainWindow类和 Ui_MainWindow界面类 def__init__(self, parent=None): super(MyMainWindow, self).__init__(parent) # 初始化父类 self.setupUi(self) # 继承 Ui_MainWindow 界面类 defclick_pushButton_2(self): # 点击 pushButton_2 触发 self.lineEdit_2.setText("click_pushButton_2") return defclick_pushButton_3(self): # 点击 pushButton_3 触发 fromdatetime importdatetime # 导入 datetime 库 nowDate =datetime.now().strftime("%Y-%m-%d") # 获取当前日期 "2021-10-10" nowTime =datetime.now().strftime("%H:%M:%S") # 获取当前时间 "16:58:00" self.lineEdit_1.setText("Current date: {}".format(nowDate)) # 显示日期 self.lineEdit_2.setText("Current time: {}".format(nowTime)) # 显示时间 self.lineEdit_3.setText("Demo4 of GUI by PyQt5") # return deftrigger_actHelp(self): # 动作 actHelp 触发 QMessageBox.about(self, "About", """数字图像处理工具箱 v1.0\nCopyright YouCans, XUPT 2021""") return if__name__ =='__main__': app =QApplication(sys.argv) # 在 QApplication 方法中使用,创建应用程序对象 myWin =MyMainWindow() # 实例化 MyMainWindow 类,创建主窗口 myWin.show() # 在桌面显示控件 myWin sys.exit(app.exec_()) # 结束进程,退出程序 |
现在我们就得到了一个虽然简单但是很完整的面向对象的图形界面应用程序 GUIdemo4。
检查一下应用程序 GUIdemo4 的各项功能:
点击 “1# 按钮”,清空 “文本编辑行-1”;
点击 “2# 按钮”,在 “文本编辑行-2” 显示:“click_pushButton_2”;
点击 “3# 按钮”,在 “文本编辑行-1” 显示当前日期,在 “文本编辑行-2” 显示当前时间,在 “文本编辑行-3” 显示: “Demo4 of GUI by PyQt5”;
点击 “4# 按钮”, “4# 按钮” 的选项框被选中;
点击工具栏中的 “帮助”,弹出上图中的信息提示框,点击 “OK” 可以关闭信息框;
点击菜单栏或工具栏中的 “退出”,关闭图形窗口应用程序。
如果以上测试都成功了,那么恭喜小白同学,你已经掌握了用 QtDesigner 设计 PyQt5 图形界面的基本功能。
在下一篇文章中,我们将介绍 PyQt5 中的常用控件。