PyQt5 学习笔记

时间:2024-03-30 18:34:53

1、初探Qt Designer 设计师

打开PyQt5的Qt Designer,会自动弹出新建窗体对话框,对于我们最常用的就是Widget通用窗口类,还有个MainWindows顾名思义主窗口。PyQt5的Widget被分离出来,似乎用来替代Dialog,并将Widget放入了QtWidget模块(库)中,PyQt4是QtGUI。


PyQt5 学习笔记

    这是一个Widget和MainWindows,MainWindows默认添加了菜单栏、工具栏和状态栏等。

PyQt5 学习笔记

默认左边是控件栏,提供了很多空间类,我们可以直接拖放到widget中看到效果,点窗体--预览(Ctrl+R)。

每个空间都有自己的名称,提供不同的功能,比如常用的按钮、输入框、单选、文本框等等。


        右边是对窗口及控件的各种调整、设置、添加资源(列如:图片)、动作。还可以直接编辑Qt引以为豪的信号槽(signal和slot)。


    有了Qt Designer使得我们在程序设计中更快的能开发设计出程序界面,避免了用纯代码来写一个窗口的繁琐,同时PyQt支持界面与逻辑分离,这对于新手来说无疑是个最大的福音,当然要做出华丽的界面还是要学代码的。至少Qt Designer为我们提供了一些解决方法,另外我们也可以通过Qt Designer生成的代码来学习一些窗口控件的用法。


Qt Designer Layouts窗口布局

PyQt5 学习笔记

Qt Designer窗口布局Layouts提供了四种布局方法,他们是:


Vertical Layout 纵向布局
Horizontal Layout 横向布局
Grid Layout  栅格布局
Form Layout  在窗体布局中布局


前三种是我们经常会用到的,我们将布局Layouts拖动到窗体上会有红色框来显示(中间窗体中的四个小红框就是),Layout的一些属性可以通过属性编辑器来控制,一般包括:上下左右边距间隔,空间之间间隔等。


在我们使用布局之前,我们得对层次要有个了解,在程序设计中一般用父子关系来表示。当然有过平面设计经验的童鞋对分层应该有所了解,这里我们还需要将层分成层次。其实就像python中规定的代码缩进量代表不同层次的道理差不多。

PyQt5 学习笔记

         从对象查看器中我们可以方便的看出窗体(Form)--布局(Layout)--控件(这里是PushButton按钮)之间的层次关系。Form窗口一般作为顶层显示,然后使用Layout将控件按照我们想要的方式规划开来。


小提示:

通常我们使用栅格布局作为顶层布局,将控件放置好之后可以通过右键--布局--栅格布局,将布局充满整个窗体。我们可以先放入控件,然后ctrl选中多个控件,然后点击工具栏上快速布局工具进行布局。




这里要注意一下,Qt Designer设计出来的文件默认为ui文件,里面包含的类css布局设计语言,如果想要查看代码我们还需要将它转换(编译)成py文件,我们可以使用一条DOS命令来完成D:\Python33\Lib\site-packages\PyQt5\pyuic5.bat main.ui -o frist.py。  我的pycharm经过PyQt5+python3+pycharm开发环境配置的配置。通过下图的操作可以便捷的对UI进行转化

PyQt5 学习笔记


更实用的转换命令可以将当前文件夹下所有ui转换成py文件:

for /f "delims=" %%i in ('dir /b /a-d /s *.ui') do D:\Python33\Lib\site-packages\PyQt5\pyuic5.bat %%i -o %%i.py

PyQt支持用LoadUi方法直接加载ui文件,当然我们通过转换后可以方便学习PyQt窗体控件的源代码。


2、Qt Designer生成源码

下面来分析一下Qt Designer生成的源码。


Qt Designer制作的图形界面为

PyQt5 学习笔记


生成的代码如下

[python] view plaincopy
  1. from PyQt5 import QtCore, QtGui, QtWidgets                                  #导入模块  
  2.   
  3.   
  4. class Ui_Form(object):                                                      #创建窗口类,继承object  
  5.     def setupUi(self, Form):  
  6.         Form.setObjectName("Form")                                          #设置窗口名  
  7.         Form.resize(400300)                                               #设置窗口大小  
  8.         self.quitButton = QtWidgets.QPushButton(Form)                       #创建一个按钮,并将按钮加入到窗口Form中  
  9.         self.quitButton.setGeometry(QtCore.QRect(2802407523))         #设置按钮大小与位置  
  10.         self.quitButton.setObjectName("quitButton")                         #设置按钮名  
  11.   
  12.   
  13.         self.retranslateUi(Form)  
  14.         QtCore.QMetaObject.connectSlotsByName(Form)                         #关联信号槽  
  15.   
  16.   
  17.     def retranslateUi(self, Form):  
  18.         _translate = QtCore.QCoreApplication.translate  
  19.         Form.setWindowTitle(_translate("Form""Test"))                     #设置窗口标题  
  20.         self.quitButton.setText(_translate("Form""Quit"))                 #设置按钮显示文字  


现在运行这段代码,窗口是不会出现的。如何使窗口出现呢?下面需要添加一段代码

[python] view plaincopy
  1. if __name__=="__main__":  
  2.     import sys  
  3.     app=QtWidgets.QApplication(sys.argv)  
  4.     widget=QtWidgets.QWidget()  
  5.     ui=Ui_Form()  
  6.     ui.setupUi(widget)  
  7.     widget.show()  
  8.     sys.exit(app.exec_())  
因为Qt Designer默认继承的object类,不提供show()显示方法,所以我们生成一个QWidget对象来重载我们设计的Ui_Form类,达到显示效果。


新建一个文件,导入我们设计的untitled .py文件,实现代码与界面分离。

[python] view plaincopy
  1. from PyQt5 import QtWidgets  
  2. from untitled import Ui_Form  
  3.   
  4. class mywindow(QtWidgets.QWidget):  
  5.     def __init__(self):  
  6.         super(mywindow,self).__init__()  
  7.         self.new=Ui_Form()  
  8.         self.new.setupUi(self)  
  9.   
  10. if __name__=="__main__":  
  11.     import sys  
  12.   
  13.     app=QtWidgets.QApplication(sys.argv)  
  14.     myshow=mywindow()  
  15.     myshow.show()  
  16.     sys.exit(app.exec_())  
直接继承界面类
[python] view plaincopy
  1. from PyQt5 import QtWidgets  
  2. from untitled import Ui_Form  
  3.   
  4. class mywindow(QtWidgets.QWidget,Ui_Form):  
  5.     def __init__(self):  
  6.         super(mywindow,self).__init__()  
  7.         self.setupUi(self)  
  8.   
  9. if __name__=="__main__":  
  10.     import sys  
  11.   
  12.     app=QtWidgets.QApplication(sys.argv)  
  13.     myshow=mywindow()  
  14.     myshow.show()  
  15.     sys.exit(app.exec_())  



3、Qt Designer信号槽

一些信号槽的基本介绍:
        信号和槽是一种高级接口,应用于对象之间的通信,它是 QT 的核心特性,也是 QT 区别于其它工具包的重要地方。它为高层次的事件处理自动生成所需要的附加代码。在我们所熟知的很多 GUI 工具包中,窗口小部件 (widget) 都有一个回调函数用于响应它们能触发的每个动作,这个回调函数通常是一个指向某个函数的指针。但是,在 QT 中信号和槽取代了这些凌乱的函数指针,使得我们编写这些通信程序更为简洁明了。
        所有从 QObject 或其子类 ( 例如 Qwidget) 派生的类都能够包含信号和槽。当对象改变其状态时,信号就由该对象发射 (emit) 出去,这就是对象所要做的全部事情,它不知道另一端是谁在接收这个信号。这就是真正的信息封装,它确保对象被当作一个真正的软件组件来使用。槽用于接收信号,但它们是普通的对象成员函数。一个槽并不知道是否有任何信号与自己相连接。而且,对象并不了解具体的通信机制。
        你可以将很多信号与单个的槽进行连接,也可以将单个的信号与很多的槽进行连接,甚至于将一个信号与另外一个信号相连接也是可能的,这时无论第一个信号什么时候发射系统都将立刻发射第二个信号。总之,信号与槽构造了一个强大的部件编程机制。


        说实话对于像我这样的新手来说看着就蛋疼,想学会它没办法,我们还是简化一下概念吧:
        所有QObject类都可以使用信号槽,换句话来说继承自pyqt中的类基本上都可以使用信号槽机制。当然非QObject也是可以通过其他一些办法来使用信号槽的。
仅仅有了信号和槽是不行的,我们还需要了解:
        信号(Signal)、槽(slot)、连接(connect)、动作事件(action)、发射(emit)、发送者、接受者等等一些列的知识。好吧,别搞的那么复杂行不行,我们还是学学该怎么用吧。

        在Qt Designer中为我们提供了一些基本的信号槽方法,我们来看看:

        点击工具栏上的“编辑信号/槽”,进入信号槽编辑模式,我们可以直接在发送者(button)上按住鼠标左键不放,拖动到接收者(Form窗体)上。这样就建立起了连接。

PyQt5 学习笔记

          接着,会弹出配置连接对话框。

PyQt5 学习笔记

      左边是发送者(按钮)的信号(动作事件),右边是接收者(窗体)的槽(动作事件)

      如图所示,我信号选择的是clicked,槽选择的是close

PyQt5 学习笔记

        我们看一下编译后生成的代码:
self.quitButton.clicked.connect(Form.close)

        实现的功能是:当按钮点击之后关闭窗体。
       流程:按钮是信号发送者,当点击按钮之后会发送一个信号出去,通过这段代码程序内部的通讯机制知道这个按钮的点击事情被连接到窗体的关闭事件上去了,然后通知接受者窗体,你该运行槽函数close了!

那么我们怎么能执行自己的“槽”呢?

[python] view plaincopy
  1. from PyQt5 import QtWidgets    
  2. from untitled import Ui_Form    
  3.     
  4. class mywindow(QtWidgets.QWidget,Ui_Form):    
  5.     def __init__(self):    
  6.         super(mywindow,self).__init__()    
  7.         self.setupUi(self)  
  8.         self.myButton.clicked.connect(self.myPrint)   #槽函数不用加括号  
  9.     def myPrint(self):                                #定义槽  
  10.         print("helloWorld")  
  11.     
  12. if __name__=="__main__":    
  13.     import sys    
  14.     
  15.     app=QtWidgets.QApplication(sys.argv)    
  16.     myshow=mywindow()    
  17.     myshow.show()    
  18.     sys.exit(app.exec_())    

小提示:

        槽其实就个函数(方法),Qt5中的槽函数不在限定必须是slot,可以是普通的函数、类的普通成员函数、lambda函数等。编译期间就会检查信号与槽是否存在!
        信号的connect连接最好放在__init__析构函数里面,这样只会声明一次连接,如果在类方法(函数中)使用的话,要记得disconnect,否则connect会连接多次,导致程序异常。
        信号槽函数不用加 (),否则可能会导致连接异常。


 

4、Qt Designer自定义信号emit及传参


[python] view plaincopy
  1. from PyQt5 import QtWidgets,QtCore  
  2. from untitled import Ui_Form  
  3. import  time  
  4.     
  5. class MyWindow(QtWidgets.QWidget,Ui_Form):  
  6.     _signal=QtCore.pyqtSignal(str)                         #定义信号,定义参数为str类型  
  7.     def __init__(self):    
  8.         super(MyWindow,self).__init__()  
  9.         self.setupUi(self)  
  10.         self.myButton.clicked.connect(self.myPrint)  
  11.         self._signal.connect(self.mySignal)               #将信号连接到函数mySignal  
  12.   
  13.     def myPrint(self):  
  14.         self.tb.setText("")  
  15.         self.tb.append("正在打印,请稍候")  
  16.         self._signal.emit("你妹,打印结束了吗,快回答!")  
  17.     def mySignal(self,string):  
  18.         print(string)  
  19.         self.tb.append("打印结束")  
  20.   
  21. if __name__=="__main__":    
  22.     import sys    
  23.     
  24.     app=QtWidgets.QApplication(sys.argv)    
  25.     myshow=MyWindow()  
  26.     myshow.show()    
  27.     sys.exit(app.exec_())    

运行结果

PyQt5 学习笔记PyQt5 学习笔记


注意:当信号与槽函数的参数数量相同时,它们参数类型要完全一致。信号与槽不能有缺省参数。 当信号的参数与槽函数的参数数量不同时,只能是信号的参数数量多于槽函数的参数数量,且前面相同数量的参数类型应一致,信号中多余的参数会被忽略。此外,在不进行参数传递时,信号槽绑定时也是要求信号的参数数量大于等于槽函数的参数数量。这种情况一般是一个带参数的信号去绑定一个无参数的槽函数。
       可以出传递的参数类型有很多种:str、int、list、object、float、tuple、dict等等


 

5、通用对话框QMessageBox

PyQt5中为我们提供了很多默认信息框QMessageBox,注意为方便使用需要导入模块。

QMessageBox对话框包含类型只是图标不同其他无太大差别:
     QMessageBox.information 信息框
     QMessageBox.question 问答框
     QMessageBox.warning 警告
     QMessageBox.ctitical危险
     QMessageBox.about 关于

[python] view plaincopy
  1. from PyQt5 import QtWidgets  
  2. from PyQt5.QtWidgets import QMessageBox  
  3.     
  4. class MyWindow(QtWidgets.QWidget):  
  5.     def __init__(self):  
  6.         super(MyWindow,self).__init__()  
  7.         self.myButton = QtWidgets.QPushButton(self)  
  8.         self.myButton.setObjectName("myButton")  
  9.         self.myButton.setText("Test")  
  10.         self.myButton.clicked.connect(self.msg)  
  11.   
  12.     def msg(self):  
  13.         reply = QMessageBox.information(self,                         #使用infomation信息框  
  14.                                     "标题",  
  15.                                     "消息",  
  16.                                     QMessageBox.Yes | QMessageBox.No)  
  17.   
  18. if __name__=="__main__":    
  19.     import sys    
  20.     
  21.     app=QtWidgets.QApplication(sys.argv)    
  22.     myshow=MyWindow()  
  23.     myshow.show()  
  24.     sys.exit(app.exec_())    

PyQt5 学习笔记

可以添加对话框的点击函数

if result == reply.Yes
print("Yes")
if result == reply.No
print("No")
 添加图片
有两个控件可以用label和Graphics view
PyQt5 学习笔记
用法差不多,label还可以带字PyQt5 学习笔记

PyQt5 学习笔记

PyQt5 学习笔记

点击笔新建资源
PyQt5 学习笔记
会生成photo2.qrc资源文件
PyQt5 学习笔记
新建前缀
PyQt5 学习笔记
添加图片PyQt5 学习笔记

选中控件点击右键选择改变样式或change stylesheet

PyQt5 学习笔记

选择border-image,图片随控件变化始终填满控件

background-image,图片作为背景图片

image,图片不改变长宽比,但随控件变化

PyQt5 学习笔记

之后选择图片点击OK

PyQt5 学习笔记

PyQt5 学习笔记

PyQt5 学习笔记

转换成py文件,图片以十六进制转义字符储存

PyQt5 学习笔记

导入文件运行

PyQt5 学习笔记

6、标准输入框QInputDialog

[python] view plaincopy
  1. from PyQt5 import QtWidgets  
  2. from PyQt5.QtWidgets import QLineEdit,QInputDialog  
  3.   
  4. class MyWindow(QtWidgets.QWidget):  
  5.     def __init__(self):  
  6.         super(MyWindow,self).__init__()  
  7.         self.myButton = QtWidgets.QPushButton(self)  
  8.         self.myButton.setObjectName("myButton")  
  9.         self.myButton.setText("Test")  
  10.         self.myButton.clicked.connect(self.msg)  
  11.   
  12.     def msg(self):  
  13.          #后面四个数字的作用依次是 初始值 最小值 最大值 小数点后位数  
  14.         doubleNum,ok1 = QInputDialog.getDouble(self"标题","计数:"37.56, -10000100002)     
  15.          #后面四个数字的作用依次是 初始值 最小值 最大值 步幅  
  16.         intNum,ok2 = QInputDialog.getInt(self"标题","计数:"37, -10000100002)      
  17.          #第三个参数可选 有一般显示 (QLineEdit.Normal)、密碼显示( QLineEdit. Password)与不回应文字输入( QLineEdit. NoEcho)  
  18.         stringNum,ok3 = QInputDialog.getText(self"标题","姓名:",QLineEdit.Normal, "王尼玛")     
  19.          #1为默认选中选项目,True/False  列表框是否可编辑。  
  20.         items = ["Spring""Summer""Fall""Winter"]  
  21.         item, ok4 = QInputDialog.getItem(self"标题","Season:", items, 1True)     
  22.         text, ok5 = QInputDialog.getMultiLineText(self"标题""Address:""John Doe\nFreedom Street")  
  23.   
  24. if __name__=="__main__":    
  25.     import sys    
  26.     
  27.     app=QtWidgets.QApplication(sys.argv)    
  28.     myshow=MyWindow()  
  29.     myshow.show()  
  30.     sys.exit(app.exec_())    
  31.  

7、标准文件打开保存框QFileDialog

单个文件打开 QFileDialog.getOpenFileName()

多个文件打开 QFileDialog.getOpenFileNames()

文件夹选取     QFileDialog.getExistingDirectory()

文件保存         QFileDialog.getSaveFileName()

[python] view plaincopy
  1. from PyQt5 import QtWidgets  
  2. from PyQt5.QtWidgets import QFileDialog  
  3.   
  4. class MyWindow(QtWidgets.QWidget):  
  5.     def __init__(self):  
  6.         super(MyWindow,self).__init__()  
  7.         self.myButton = QtWidgets.QPushButton(self)  
  8.         self.myButton.setObjectName("myButton")  
  9.         self.myButton.setText("Test")  
  10.         self.myButton.clicked.connect(self.msg)  
  11.   
  12.     def msg(self):  
  13.         directory1 = QFileDialog.getExistingDirectory(self,  
  14.                                     "选取文件夹",  
  15.                                     "C:/")                                 #起始路径  
  16.         print(directory1)  
  17.   
  18.         fileName1, filetype = QFileDialog.getOpenFileName(self,  
  19.                                     "选取文件",  
  20.                                     "C:/",  
  21.                                     "All Files (*);;Text Files (*.txt)")   #设置文件扩展名过滤,注意用双分号间隔  
  22.         print(fileName1,filetype)  
  23.   
  24.         files, ok1 = QFileDialog.getOpenFileNames(self,  
  25.                                     "多文件选择",  
  26.                                     "C:/",  
  27.                                     "All Files (*);;Text Files (*.txt)")  
  28.         print(files,ok1)  
  29.   
  30.         fileName2, ok2 = QFileDialog.getSaveFileName(self,  
  31.                                     "文件保存",  
  32.                                     "C:/",  
  33.                                     "All Files (*);;Text Files (*.txt)")  
  34.   
  35. if __name__=="__main__":    
  36.     import sys    
  37.     
  38.     app=QtWidgets.QApplication(sys.argv)    
  39.     myshow=MyWindow()  
  40.     myshow.show()  
  41.     sys.exit(app.exec_())      

8、QColorDialog与QFontDialog

QColorDialog颜色对话框

QFontDialog字体对话框

[python] view plaincopy
  1. from PyQt5 import QtWidgets  
  2. from PyQt5.QtWidgets import QColorDialog,QFontDialog  
  3. from PyQt5.QtCore import Qt  
  4. from PyQt5.QtGui import QPalette  
  5.   
  6. class MyWindow(QtWidgets.QWidget):  
  7.     def __init__(self):  
  8.         super(MyWindow,self).__init__()  
  9.         self.myButton = QtWidgets.QPushButton(self)  
  10.         self.myButton.setObjectName("myButton")  
  11.         self.myButton.setText("Test")  
  12.         self.myButton.clicked.connect(self.msg)  
  13.   
  14.     def msg(self):  
  15.         color = QColorDialog.getColor(Qt.blue, self"Select Color")  
  16.         if color.isValid():  
  17.             print(color.name(),color)  
  18.             self.myButton.setPalette(QPalette(color))                            #给按钮填充背景色  
  19.             self.myButton.setAutoFillBackground(True)  
  20.   
  21.         font, ok = QFontDialog.getFont()  
  22.         if ok:  
  23.             print(font.key)  
  24.             self.myButton.setFont(font)  
  25.   
  26. if __name__=="__main__":    
  27.     import sys    
  28.     
  29.     app=QtWidgets.QApplication(sys.argv)    
  30.     myshow=MyWindow()  
  31.     myshow.show()  
  32.     sys.exit(app.exec_())    
  33.  

9、Qt Designer 主窗口MainWindows

再次回到Qt Designer。

      MainWindows 即主窗口,主要包含了菜单栏、工具栏、任务栏等。双击菜单栏上的“在这里输入”,然后录入文字,回车即可。注意要用回车键确认

PyQt5 学习笔记

       对于一级菜单,我们可以通过输入 文件(&F)  编辑(&E) 来加入菜单的快捷按钮,在预览中按alt+m  alt+e 可以看到效果.

       而子菜单可以通过动作编辑器或者属性编辑器中的shortcut来添加快捷键.

PyQt5 学习笔记

        双击需要编辑的动作,可以对其进行设置并添加图标、快捷键等等。

PyQt5 学习笔记

         工具栏,默认在PyQt5中不显示工具栏,我们可以点击右键来添加工具栏,通过属性编辑器我们可以修改图标大小。而工具栏上面的图标,我们可以通过动作编辑器建立并拖入工具栏中

PyQt5 学习笔记

PyQt5 学习笔记

         将ui文件和qrc文件生成对应的python源码,给菜单中的打开添加个文件对话框

[python] view plaincopy
  1. from PyQt5 import QtWidgets  
  2. from untitled import Ui_MainWindow  
  3. from PyQt5.QtWidgets import QFileDialog  
  4.   
  5. class MyWindow(QtWidgets.QMainWindow,Ui_MainWindow):  
  6.     def __init__(self):  
  7.         super(MyWindow,self).__init__()  
  8.         self.setupUi(self)  
  9.         self.fileOpen.triggered.connect(self.openMsg)      #菜单的点击事件是triggered  
  10.   
  11.     def openMsg(self):  
  12.         file,ok=QFileDialog.getOpenFileName(self,"打开","C:/","All Files (*);;Text Files (*.txt)")  
  13.         self.statusbar.showMessage(file)                   #在状态栏显示文件地址  
  14.   
  15. if __name__=="__main__":  
  16.     import sys  
  17.   
  18.     app=QtWidgets.QApplication(sys.argv)  
  19.     myshow=MyWindow()  
  20.     myshow.show()  
  21.     sys.exit(app.exec_())  

PyQt5 学习笔记

10、主窗口动态加载Widget

我们通过Qt Designer设计两个窗口,命名为主窗口(MainForm)和子窗口(ChildrenForm)。我们在主窗口的空白*添加一个栅格布局并命名为MaingridLayout,等会需要将ChildrenForm放进去。

      PyQt5 学习笔记

编写代码

[python] view plaincopy
  1. from PyQt5 import QtWidgets  
  2. from MainForm import Ui_MainForm  
  3. from Children import Ui_Form  
  4.   
  5. from PyQt5.QtWidgets import QFileDialog  
  6.   
  7. class MainForm(QtWidgets.QMainWindow,Ui_MainForm):  
  8.     def __init__(self):  
  9.         super(MainForm,self).__init__()  
  10.         self.setupUi(self)  
  11.   
  12.         self.child=ChildrenForm()                          #self.child = children()生成子窗口实例self.child  
  13.   
  14.   
  15.         self.fileOpen.triggered.connect(self.openMsg)      #菜单的点击事件是triggered  
  16.         self.fileClose.triggered.connect(self.close)  
  17.         self.actionTst.triggered.connect(self.childShow)   #点击actionTst,子窗口就会显示在主窗口的MaingridLayout中  
  18.   
  19.     def childShow(self):  
  20.         self.MaingridLayout.addWidget(self.child)          #添加子窗口  
  21.         self.child.show()  
  22.   
  23.   
  24.     def openMsg(self):  
  25.         file,ok=QFileDialog.getOpenFileName(self,"打开","C:/","All Files (*);;Text Files (*.txt)")  
  26.         self.statusbar.showMessage(file)                   #在状态栏显示文件地址  
  27.   
  28. class ChildrenForm(QtWidgets.QWidget,Ui_Form):  
  29.     def __init__(self):  
  30.         super(ChildrenForm,self).__init__()  
  31.         self.setupUi(self)  
  32.   
  33. if __name__=="__main__":  
  34.     import sys  
  35.   
  36.     app=QtWidgets.QApplication(sys.argv)  
  37.     myshow=MainForm()  
  38.     myshow.show()  
  39.     sys.exit(app.exec_()  

11、pyqt线程间通信

 信号(singal)与槽(slot)用于对象相互通信,信号:当某个对象的某个事件发生时,触发一个信号,槽:响应指定信号的所做的反应,其实信号槽类似于.NET里面的委托、事件,比如Repeater控件类,当行数据绑定后,触发一个ItemDataBound事件,不管使用者使用会监听该事件并做额外处理,其控件类内部都会触发该事件,这种机制很多程度提高了类的封装性和完整性。
  PyQt的窗体控件类已经有很多的内置信号,开发者也可以添加自己的自定义信号,信号槽有如下特点:
    - 一个信号可以连接到许多插槽。
    - 一个信号也可以连接到另一个信号。
    - 信号参数可以是任何Python类型。
    - 一个插槽可以连接到许多信号。
    - 连接可能会直接(即同步)或排队(即异步)。
    - 连接可能会跨线程。
    - 信号可能会断开

  (以上几条特点翻译于官方文档),接下来,我将以若干个实例,来体现以上几个特点。

(1)内置信号槽的使用

[python] view plaincopy
  1. from PyQt5.QtWidgets import *  
  2. from PyQt5.QtCore import *  
  3.     
  4. def sinTest():  
  5.     btn.setText("按钮文本改变")  
  6.     
  7. app = QApplication([])  
  8.     
  9. main = QWidget()  
  10. main.resize(200,100)  
  11. btn = QPushButton("按钮文本",main)  
  12. ##按钮btn的内置信号连接名为sinTest的槽  
  13. btn.clicked.connect(sinTest)  
  14. main.show()  
  15.     
  16. app.exec_()  
PyQt5 学习笔记

(2)自定义信号槽的使用

[python] view plaincopy
  1. class SinClass(QObject):  
  2.         
  3.     ##声明一个无参数的信号  
  4.     sin1 = pyqtSignal()  
  5.         
  6.     ##声明带一个int类型参数的信号  
  7.     sin2 = pyqtSignal(int)  
  8.         
  9.     ##声明带一个int和str类型参数的信号  
  10.     sin3 = pyqtSignal(int,str)  
  11.     
  12.     ##声明带一个列表类型参数的信号  
  13.     sin4 = pyqtSignal(list)  
  14.     
  15.     ##声明带一个字典类型参数的信号  
  16.     sin5 = pyqtSignal(dict)  
  17.     
  18.     ##声明一个多重载版本的信号,包括了一个带int和str类型参数的信号,以及带str参数的信号  
  19.     sin6 = pyqtSignal([int,str], [str])  
  20.         
  21.     def __init__(self,parent=None):  
  22.         super(SinClass,self).__init__(parent)  
  23.     
  24.         ##信号连接到指定槽  
  25.         self.sin1.connect(self.sin1Call)  
  26.         self.sin2.connect(self.sin2Call)  
  27.         self.sin3.connect(self.sin3Call)  
  28.         self.sin4.connect(self.sin4Call)  
  29.         self.sin5.connect(self.sin5Call)  
  30.         self.sin6[int,str].connect(self.sin6Call)  
  31.         self.sin6[str].connect(self.sin6OverLoad)  
  32.     
  33.         ##信号发射  
  34.         self.sin1.emit()  
  35.         self.sin2.emit(1)  
  36.         self.sin3.emit(1,"text")  
  37.         self.sin4.emit([1,2,3,4])  
  38.         self.sin5.emit({"name":"codeio","age":"25"})  
  39.         self.sin6[int,str].emit(1,"text")  
  40.         self.sin6[str].emit("text")  
  41.             
  42.     def sin1Call(self):  
  43.         print("sin1 emit")  
  44.     
  45.     def sin2Call(self,val):  
  46.         print("sin2 emit,value:",val)  
  47.     
  48.     def sin3Call(self,val,text):  
  49.         print("sin3 emit,value:",val,text)  
  50.     
  51.     def sin4Call(self,val):  
  52.         print("sin4 emit,value:",val)  
  53.             
  54.     def sin5Call(self,val):  
  55.         print("sin5 emit,value:",val)  
  56.     
  57.     def sin6Call(self,val,text):  
  58.         print("sin6 emit,value:",val,text)  
  59.     
  60.     def sin6OverLoad(self,val):  
  61.         print("sin6 overload emit,value:",val)  
  62.     
  63. sin = SinClass()  
运行结果:
sin1 emit
sin2 emit,value: 1
sin3 emit,value: 1 text
sin4 emit,value: [1, 2, 3, 4]
sin5 emit,value: {'age': '25', 'name': 'codeio'}
sin6 emit,value: 1 text
sin6 overload emit,value: text

(3)信号槽N对N连接、断开连接

[python] view plaincopy
  1. from PyQt5.QtWidgets import *  
  2. from PyQt5.QtCore import *  
  3.   
  4. class SinClass(QObject):  
  5.   
  6.     ##声明一个无参数的信号  
  7.     sin1 = pyqtSignal()  
  8.   
  9.     ##声明带一个int类型参数的信号  
  10.     sin2 = pyqtSignal(int)  
  11.   
  12.     def __init__(self,parent=None):  
  13.         super(SinClass,self).__init__(parent)  
  14.   
  15.         ##信号sin1连接到sin1Call和sin2Call这两个槽  
  16.         self.sin1.connect(self.sin1Call)  
  17.         self.sin1.connect(self.sin2Call)  
  18.   
  19.         ##信号sin2连接到信号sin1  
  20.         self.sin2.connect(self.sin1)  
  21.   
  22.         ##信号发射  
  23.         self.sin1.emit()  
  24.         self.sin2.emit(1)  
  25.   
  26.         ##断开sin1、sin2信号与各槽的连接  
  27.         self.sin1.disconnect(self.sin1Call)  
  28.         self.sin1.disconnect(self.sin2Call)  
  29.         self.sin2.disconnect(self.sin1)  
  30.   
  31.         ##信号sin1和sin2连接同一个槽sin1Call  
  32.         self.sin1.connect(self.sin1Call)  
  33.         self.sin2.connect(self.sin1Call)  
  34.   
  35.         ##信号再次发射  
  36.         self.sin1.emit()  
  37.         self.sin2.emit(1)  
  38.   
  39.     def sin1Call(self):  
  40.         print("sin1 emit")  
  41.   
  42.     def sin2Call(self):  
  43.         print("sin2 emit")  
  44.   
  45. sin = SinClass()  
运行结果:
sin1 emit
sin2 emit
sin1 emit
sin2 emit
sin1 emit
sin1 emit

(4)多线程信号槽通信

[python] view plaincopy
  1. from PyQt5.QtWidgets import *  
  2. from PyQt5.QtCore import *  
  3.   
  4. class Main(QWidget):  
  5.     def __init__(self, parent = None):  
  6.         super(Main,self).__init__(parent)  
  7.   
  8.         ##创建一个线程实例并设置名称、变量、信号槽  
  9.         self.thread = MyThread()  
  10.         self.thread.setIdentity("thread1")  
  11.         self.thread.sinOut.connect(self.outText)  
  12.         self.thread.setVal(6)  
  13.   
  14.     def outText(self,text):  
  15.         print(text)  
  16.   
  17. class MyThread(QThread):  
  18.   
  19.     sinOut = pyqtSignal(str)  
  20.   
  21.     def __init__(self,parent=None):  
  22.         super(MyThread,self).__init__(parent)  
  23.   
  24.         self.identity = None  
  25.   
  26.     def setIdentity(self,text):  
  27.         self.identity = text  
  28.   
  29.     def setVal(self,val):  
  30.         self.times = int(val)  
  31.   
  32.         ##执行线程的run方法  
  33.         self.start()  
  34.   
  35.     def run(self):  
  36.         while self.times > 0 and self.identity:  
  37.             ##发射信号  
  38.             self.sinOut.emit(self.identity+" "+str(self.times))  
  39.             self.times -= 1  
  40.   
  41. app = QApplication([])  
  42.   
  43. main = Main()  
  44. main.show()  
  45.   
  46. app.exec_()  
运行结果:
thread1 6
thread1 5
thread1 4
thread1 3
thread1 2
thread1 1

 

12、初识pyqt多线程操作

首先来看一个例子:

[python] view plaincopy
  1. # coding=utf-8  
  2. __author__ = 'a359680405'  
  3.   
  4. from PyQt5.QtCore import *  
  5. from PyQt5.QtGui import *  
  6. from PyQt5.QtWidgets import *  
  7.   
  8. global sec  
  9. sec=0  
  10. def setTime():  
  11.     global  sec  
  12.     sec+=1  
  13.     lcdNumber.display(sec)          #LED显示数字+1  
  14.   
  15. def work():  
  16.     timer.start(1000)               #计时器每秒计数  
  17.     for i in range(2000000000):  
  18.        pass  
  19.     timer.stop()  
  20.   
  21. app=QApplication([])  
  22. top=QWidget()  
  23. layout=QVBoxLayout(top)             #垂直布局类QVBoxLayout;  
  24. lcdNumber=QLCDNumber()              #加个显示屏  
  25. layout.addWidget(lcdNumber)  
  26. button=QPushButton("测试")  
  27. layout.addWidget(button)  
  28.   
  29. timer=QTimer()  
  30. timer.timeout.connect(setTime)      #每次计时结束,触发setTime  
  31. button.clicked.connect(work)  
  32.   
  33. top.show()  
  34. app.exec()  

PyQt5 学习笔记

      我们的主界面有一个用于显示时间的 LCD 数字面板还有一个用于启动任务的按钮。程序的目的是用户点击按钮,开始一个非常耗时的运算(程序中我们以一个 2000000000 次的循环来替代这个非常耗时的工作,在真实的程序中,这可能是一个网络访问,可能是需要复制一个很大的文件或者其它任务),同时 LCD 开始显示逝去的毫秒数。毫秒数通过一个计时器QTimer进行更新。计算完成后,计时器停止。这是一个很简单的应用,也看不出有任何问题。但是当我们开始运行程序时,问题就来了:点击按钮之后,程序界面直接停止响应,直到循环结束才开始重新更新,于是计时器使用显示0。


      有经验的开发者立即指出,这里需要使用线程。这是因为 Qt 中所有界面都是在 UI 线程中(也被称为主线程,就是执行了QApplication::exec()的线程),在这个线程中执行耗时的操作(比如那个循环),就会阻塞 UI 线程,从而让界面停止响应。界面停止响应,用户体验自然不好,不过更严重的是,有些窗口管理程序会检测到你的程序已经失去响应,可能会建议用户强制停止程序,这样一来你的程序可能就此终止,任务再也无法完成。所以,为了避免这一问题,我们要使用 QThread 开启一个新的线程:

[python] view plaincopy
  1. # coding=utf-8  
  2. __author__ = 'a359680405'  
  3.   
  4. from PyQt5.QtCore import *  
  5. from PyQt5.QtGui import *  
  6. from PyQt5.QtWidgets import *  
  7.   
  8. global sec  
  9. sec=0  
  10.   
  11. class WorkThread(QThread):  
  12.     trigger = pyqtSignal()  
  13.     def __int__(self):  
  14.         super(WorkThread,self).__init__()  
  15.   
  16.     def run(self):  
  17.         for i in range(203300030):  
  18.             pass  
  19.         self.trigger.emit()         #循环完毕后发出信号  
  20.   
  21. def countTime():  
  22.     global  sec  
  23.     sec+=1  
  24.     lcdNumber.display(sec)          #LED显示数字+1  
  25.   
  26. def work():  
  27.     timer.start(1000)               #计时器每秒计数  
  28.     workThread.start()              #计时开始  
  29.     workThread.trigger.connect(timeStop)   #当获得循环完毕的信号时,停止计数  
  30.   
  31. def timeStop():  
  32.     timer.stop()  
  33.     print("运行结束用时",lcdNumber.value())  
  34.     global sec  
  35.     sec=0  
  36.   
  37. app=QApplication([])  
  38. top=QWidget()  
  39. layout=QVBoxLayout(top)             #垂直布局类QVBoxLayout;  
  40. lcdNumber=QLCDNumber()              #加个显示屏  
  41. layout.addWidget(lcdNumber)  
  42. button=QPushButton("测试")  
  43. layout.addWidget(button)  
  44.   
  45. timer=QTimer()  
  46. workThread=WorkThread()  
  47.   
  48. button.clicked.connect(work)  
  49. timer.timeout.connect(countTime)      #每次计时结束,触发setTime  
  50.   
  51. top.show()  
  52. app.exec()  


        我增加了一个WorkerThread类。WorkerThread继承自QThread类,重写了其run()函数。可以认为,run()函数就是新的线程需要执行的代码。在这里就是要执行这个循环,然后发出计算完成的信号。而在按钮点击的槽函数中,使用work()中的workThread.start()函数启动一个线程(注意,这里不是run()函数)。再次运行程序,你会发现现在界面已经不会被阻塞了


 

13、PyQt 线程相关类

QThread是我们将要详细介绍的第一个类。它也是 Qt 线程类中最核心的底层类。由于 PyQt 的跨平台特性,QThread要隐藏掉所有平台相关的代码。


         正如前面所说,要使用QThread开始一个线程,我们可以创建它的一个子类,然后覆盖其QThread.run()函数:

[python] view plaincopy
  1. class Thread(QThread):  
  2.     def __init__(self):  
  3.         super(Thread,self).__init__()  
  4.     def run(self):  
  5.         pass               #线程相关代码  

         然后我们这样新建一个新的线程

[python] view plaincopy
  1. thread=Thread()  
  2. thread.start()  
        这个默认实现其实是简单地调用了QThread.exec()函数,而这个函数,按照我们前面所说的,其实是开始了一个事件循环
       QRunnable是我们要介绍的第二个类。这是一个轻量级的抽象类,用于开始一个另外线程的任务。这种任务是运行过后就丢弃的。由于这个类是抽象类,我们需要继承QRunnable,然后重写其纯虚函数QRunnable.run():

[python] view plaincopy
  1. class Task(QRunnable):  
  2.     def __init__(self):  
  3.         super(QRunnable,self).__init__()  
  4.     def run(self):  
  5.         pass               #线程相关代码  
        要真正执行一个QRunnable对象,我们需要使用QThreadPool类。顾名思义,这个类用于管理一个线程池。通过调用QThreadPool.start(runnable)函数,我们将一个QRunnable对象放入QThreadPool的执行队列。一旦有线程可用,线程池将会选择一个QRunnable对象,然后在那个线程开始执行。所有 PyQt 应用程序都有一个全局线程池,我们可以使用QThreadPool.globalInstance()获得这个全局线程池;与此同时,我们也可以自己创建私有的线程池,并进行手动管理。

[python] view plaincopy
  1. task=Task()  
  2. QThreadPool.globalInstance().start(task)  
        需要注意的是,QRunnable不是一个QObject,因此也就没有内建的与其它组件交互的机制。为了与其它组件进行交互,你必须自己编写低级线程原语,例如使用 mutex 守护来获取结果等。


       QtConcurrent是我们要介绍的最后一个对象。这是一个高级 API,构建于QThreadPool之上,用于处理大多数通用的并行计算模式:map、reduce 以及 filter。它还提供了QtConcurrent.run()函数,用于在另外的线程运行一个函数。注意,QtConcurrent是一个命名空间而不是一个类,因此其中的所有函数都是命名空间内的全局函数。


       不同于QThread和QRunnable,QtConcurrent不要求我们使用低级同步原语:所有的QtConcurrent都返回一个QFuture对象。这个对象可以用来查询当前的运算状态(也就是任务的进度),可以用来暂停/回复/取消任务,当然也可以用来获得运算结果。注意,并不是所有的QFuture对象都支持暂停或取消的操作。比如,由QtConcurrent.run()返回的QFuture对象不能取消,但是由QtConcurrent.mappedReduced()返回的是可以的。QFutureWatcher类则用来监视QFuture的进度,我们可以用信号槽与QFutureWatcher进行交互(注意,QFuture也没有继承QObject)。


下面我们可以对比一下上面介绍过的三种类:
特性 QThread QRunnable QtConcurrent
高级 API
面向任务
内建对暂停/恢复/取消的支持
具有优先级
可运行事件循环

 

14、pyqt事件循环

可重入的(Reentrant):如果多个线程可以在同一时刻调用一个类的所有函数,并且保证每一次函数调用都引用一个唯一的数据,就称这个类是可重入的(Reentrant means that all the functions in the referenced class can be called simultaneously by multiple threads, provided that each invocation of the functions reference unique data.)。类似的,一个函数被称为可重入的,如果该函数允许多个线程在同一时刻调用,而每一次的调用都只能使用其独有的数据。全局变量就不是函数独有的数据,而是共享的。换句话说,这意味着类或者函数的使用者必须使用某种额外的机制(比如锁)来控制对对象的实例或共享数据的序列化访问。
       线程安全(Thread-safe):如果多个线程可以在同一时刻调用一个类的所有函数,即使每一次函数调用都引用一个共享的数据,就说这个类是线程安全的(Threadsafe means that all the functions in the referenced class can be called simultaneously by multiple threads even when each invocation references shared data.)。如果多个线程可以在同一时刻访问函数的共享数据,就称这个函数是线程安全的。
       进一步说,对于一个类,如果不同的实例可以被不同线程同时使用而不受影响,就说这个类是可重入的;如果这个类的所有成员函数都可以被不同线程同时调用而不受影响,即使这些调用针对同一个对象,那么我们就说这个类是线程安全的。由此可以看出,线程安全的语义要强于可重入。接下来,我们从事件开始讨论。在 PyQt 中,事件由一个普通对象表示(QEvent或其子类)。这是事件与信号的一个很大区别:事件总是由某一种类型的对象表示,针对某一个特殊的对象,而信号则没有这种目标对象。所有QObject的子类都可以通过覆盖QObject::event()函数来控制事件的对象。

       事件可以由程序生成,也可以在程序外部生成。例如:

       QKeyEvent和QMouseEvent对象表示键盘或鼠标的交互,通常由系统的窗口管理器产生;
       QTimerEvent事件在定时器超时时发送给一个QObject,定时器事件通常由操作系统发出;
       QChildEvent在增加或删除子对象时发送给一个QObject,这是由 Qt 应用程序自己发出的。


       需要注意的是,与信号不同,事件并不是一产生就被分发。事件产生之后被加入到一个队列中(这里的队列含义同数据结构中的概念,先进先出),该队列即被称为事件队列。事件分发器遍历事件队列,如果发现事件队列中有事件,那么就把这个事件发送给它的目标对象。这个循环被称作事件循环。事件循环的伪代码描述大致如下所示:

[python] view plaincopy
  1. while (is_active)  
  2. {  
  3.     while (!event_queue_is_empty) {  
  4.         dispatch_next_event();  
  5.     }  
  6.     wait_for_more_events();  
  7. }  
PyQt程序在执行的时候调用QApplication.exec() 函数意味着进入了主循环。我们把事件循环理解为一个无限循环,直到QApplication.exit()或者QApplication.quit()被调用,事件循环才真正退出。

        伪代码里面的while会遍历整个事件队列,发送从队列中找到的事件;wait_for_more_events()函数则会阻塞事件循环,直到又有新的事件产生。我们仔细考虑这段代码,在wait_for_more_events()函数所得到的新的事件都应该是由程序外部产生的。因为所有内部事件都应该在事件队列中处理完毕了。因此,我们说事件循环在wait_for_more_events()函数进入休眠,并且可以被下面几种情况唤醒:

  • 窗口管理器的动作(键盘、鼠标按键按下、与窗口交互等);
  • 套接字动作(网络传来可读的数据,或者是套接字非阻塞写等);
  • 定时器;
  • 由其它线程发出的事件(我们会在后文详细解释这种情况)。

          在类 UNIX 系统中,窗口管理器(比如 X11)会通过套接字(Unix Domain 或 TCP/IP)向应用程序发出窗口活动的通知,因为客户端就是通过这种机制与 X 服务器交互的。如果我们决定要实现基于内部的socketpair(2)函数的跨线程事件的派发,那么窗口的管理活动需要唤醒的是:

  • 套接字 socket
  • 定时器 timer
        这也正是select(2)系统调用所做的:它监视窗口活动的一组描述符,如果在一定时间内没有活动,它会发出超时消息(这种超时是可配置的)。Qt 所要做的,就是把select()的返回值转换成一个合适的QEvent子类的对象,然后将其放入事件队列。好了,现在你已经知道事件循环的内部机制了。

         至于为什么需要事件循环,我们可以简单列出一个清单:

  • 组件的绘制与交互:QWidget.paintEvent()会在发出QPaintEvent事件时被调用。该事件可以通过内部QWidget.update()调用或者窗口管理器(例如显示一个隐藏的窗口)发出。所有交互事件(键盘、鼠标)也是类似的:这些事件都要求有一个事件循环才能发出。
  • 定时器:长话短说,它们会在select(2)或其他类似的调用超时时被发出,因此你需要允许 Qt 通过返回事件循环来实现这些调用。
  • 网络:所有低级网络类(QTcpSocket、QUdpSocket以及QTcpServer等)都是异步的。当你调用read()函数时,它们仅仅返回已可用的数据;当你调用write()函数时,它们仅仅将写入列入计划列表稍后执行。只有返回事件循环的时候,真正的读写才会执行。注意,这些类也有同步函数(以waitFor开头的函数),但是它们并不推荐使用,就是因为它们会阻塞事件循环。高级的类,例如QNetworkAccessManager则根本不提供同步 API,因此必须要求事件循环。
         有了事件循环,你就会想怎样阻塞它。阻塞它的理由可能有很多,例如我就想让QNetworkAccessManager同步执行。在解释为什么永远不要阻塞事件循环之前,我们要了解究竟什么是“阻塞”。假设我们有一个按钮Button,这个按钮在点击时会发出一个信号。这个信号会与一个Worker对象连接,这个Worker对象会执行很耗时的操作。当点击了按钮之后,我们观察从上到下的函数调用堆栈:
[python] view plaincopy
  1. QApplication.exec()  
  2. […]  
  3. QWidget.event()  
  4. Button.mousePressEvent()  
  5. Button.clicked()  
  6. […]  
  7. Worker.doWork()  
         开始事件循环,也就执行QApplication.exec()函数。窗口管理器侦测到鼠标点击后,PyQt 会发现并将其转换成QMouseEvent事件,发送给组件的event()函数。这一过程是通过QApplication.notify()函数实现的。注意我们的按钮并没有覆盖event()函数,因此其父类的实现将被执行,也就是QWidget.event()函数。这个函数发现这个事件是一个鼠标点击事件,于是调用了对应的事件处理函数,就是Button.mousePressEvent()函数。我们重写了这个函数,发出Button.clicked()信号,而正是这个信号会调用Worker.doWork()槽函数。

         在worker努力工作的时候,事件循环在干什么?或许你已经猜到了答案:什么都没做!事件循环发出了鼠标按下的事件,然后等着事件处理函数返回。此时,它一直是阻塞的,直到Worker.doWork()函数结束。注意,我们使用了“阻塞”一词,也就是说,所谓阻塞事件循环,意思是没有事件被派发处理。


         在事件就此卡住时,组件也不会更新自身(因为QPaintEvent对象还在队列中),也不会有其它什么交互发生(还是同样的原因),定时器也不会超时并且网络交互会越来越慢直到停止。也就是说,前面我们大费周折分析的各种依赖事件循环的活动都会停止。这时候,需要窗口管理器会检测到你的应用程序不再处理任何事件,于是告诉用户你的程序失去响应。这就是为什么我们需要快速地处理事件,并且尽可能快地返回事件循环。
         现在,重点来了:我们不可能避免业务逻辑中的耗时操作,那么怎样做才能既可以执行那些耗时的操作,又不会阻塞事件循环呢?一般会有三种解决方案:第一,我们将任务移到另外的线程(正如我们上一章看到的那样,不过现在我们暂时略过这部分内容);第二,我们手动强制运行事件循环。想要强制运行事件循环,我们需要在耗时的任务中一遍遍地调用Application.processEvents()函数。Application.processEvents()函数会发出事件队列中的所有事件,并且立即返回到调用者。仔细想一下,我们在这里所做的,就是模拟了一个事件循环;另外一种解决方案我们在前面的章节提到过:使用QEventLoop类重新进入新的事件循环。通过调用QEventLoop.exec()函数,我们重新进入新的事件循环,给QEventLoop.quit()槽函数发送信号则退出这个事件循环。

 

15、PyQt信号和槽传递额外参数

使用Pyqt编程过程中,经常会遇到给槽函数传递额外参数的情况。但是信号-槽机制只是指定信号如何连接到槽,信号定义的参数被传递给槽,而额外的参数(用户定义)不能直接传递。

而传递额外参数又是很有用处。你可能使用一个槽处理多个组件的信号,有时要传递额外的信息。


一种方法是使用lambda表达式。

[python] view plaincopyPyQt5 学习笔记
  1. from PyQt4.QtCore import *  
  2. from PyQt4.QtGui import *  
  3.   
  4. class MyForm(QMainWindow):  
  5.     def __init__(self, parent=None):  
  6.         super(MyForm, self).__init__(parent)  
  7.         button1 = QPushButton('Button 1')  
  8.         button2 = QPushButton('Button 1')  
  9.         button1.clicked.connect(lambdaself.on_button(1))  
  10.         button2.clicked.connect(lambdaself.on_button(2))  
  11.   
  12.         layout = QHBoxLayout()  
  13.         layout.addWidget(button1)  
  14.         layout.addWidget(button2)  
  15.   
  16.         main_frame = QWidget()  
  17.         main_frame.setLayout(layout)  
  18.   
  19.         self.setCentralWidget(main_frame)  
  20.   
  21.     def on_button(self, n):  
  22.         print('Button {0} clicked'.format(n))  
  23.   
  24. if __name__ == "__main__":  
  25.     import sys  
  26.     app = QApplication(sys.argv)  
  27.     form = MyForm()  
  28.     form.show()  
  29.     app.exec_()  



解释一下,on_button是怎样处理从两个按钮传来的信号。我们使用lambda传递按钮数字给槽,也可以传递任何其他东西---甚至是按钮组件本身(假如,槽打算把传递信号的按钮修改为不可用)


第2个方法是使用functools里的partial函数。

[python] view plaincopyPyQt5 学习笔记
  1. button1.clicked.connect(partial(self.on_button, 1))  
  2. button2.clicked.connect(partial(self.on_button, 2))  


哪个办法好一点?这个属于风格的问题。个人观点,喜欢lambda,条理清楚,而且灵活。


《Rapid GUI Program with Python and QT》 P143例子。

PyQt5 学习笔记


[python] view plaincopyPyQt5 学习笔记
  1. from PyQt4.QtCore import *  
  2. from PyQt4.QtGui import *  
  3. from functools import partial  
  4. import sys  
  5.   
  6. class Bu1(QWidget):  
  7.     
  8.     def __init__(self, parent=None):  
  9.         super(Bu1, self).__init__(parent)  
  10.         #水平盒式布局  
  11.         layout = QHBoxLayout()  
  12.         #显示  
  13.         self.lbl = QLabel('no button is pressed')  
  14.         #循环5个按钮  
  15.         for i in range(5):  
  16.             but = QPushButton(str(i))  
  17.             layout.addWidget(but)  
  18.             #信号和槽连接  
  19.             but.clicked.connect(self.cliked)  
  20.               
  21.         #使用封装,lambda  
  22.         but = QPushButton('5')  
  23.         layout.addWidget(but)  
  24.         but.clicked.connect(lambdaself.on_click('5'))  
  25.         #使用个who变量,结果不正常,显示 False is pressed  
  26.         #but.clicked.connect(lambda who="5": self.on_click(who))  
  27.           
  28.         #使用封装,partial函数  
  29.         but = QPushButton('6')  
  30.         layout.addWidget(but)  
  31.         but.clicked.connect(partial(self.on_click, '6'))  
  32.           
  33.         layout.addWidget(self.lbl)  
  34.         #设置布局  
  35.         self.setLayout(layout)  
  36.           
  37.     #传递额外参数     
  38.     def cliked(self):  
  39.         bu = self.sender()  
  40.         if isinstance(bu, QPushButton):  
  41.             self.lbl.setText('%s is pressed' % bu.text())  
  42.         else:  
  43.             self.lbl.setText('no effect')  
  44.     def on_click(self, n):  
  45.         self.lbl.setText('%s is pressed' % n)  
  46.               
  47. if __name__ == '__main__':          
  48.     app = QApplication(sys.argv)  
  49.     bu =Bu1()  
  50.     bu.show()  
  51.     app.exec_()  
     

16.QT Designer界面和逻辑分离  实例

通过Qt Designer生成的源码如下PtQt.py:界面里只有一个按钮和文本

# -*- coding: utf-8 -*-
 
# Form implementation generated from reading ui file 'untitled1.ui'
#
# Created by: PyQt5 UI code generator 5.5
#
# WARNING! All changes made in this file will be lost!
 
from PyQt5 import QtCore, QtGui, QtWidgets
 
class Ui_MainWindow(object): #创建窗体类MainWindow,继承object
def setupUi(self, MainWindow): #创建setupUi函数
MainWindow.setObjectName("MainWindow") #设置窗体名
MainWindow.resize(434, 390) #设置窗体的大小
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(80, 100, 54, 12))
self.label.setObjectName("label")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(70, 170, 75, 23))
self.pushButton.setObjectName("pushButton")
#MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 434, 23))
self.menubar.setObjectName("menubar")
#MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
#MainWindow.setStatusBar(self.statusbar)
 
self.retranslateUi(MainWindow) # 加载retranslateUi函数,只是做一系列的 setText、translate 等调用
 
QtCore.QMetaObject.connectSlotsByName(MainWindow) #关联信号槽
 
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label.setText(_translate("MainWindow", "文本测试"))
self.pushButton.setText(_translate("MainWindow", "按钮"))
在另一个文件中调用PtQt.py,并设置相应的逻辑

__author__ = 'zd'
from PyQt5 import QtWidgets,QtCore
from PtQt import Ui_MainWindow
import sys,time
class mydesignshow(QtWidgets.QWidget,Ui_MainWindow): #同时继承QWidget和Ui_MainWindow
_mysignal = QtCore.pyqtSignal() #定义信号
_mysignal1 = QtCore.pyqtSignal(str) #定义含参数信号
 
def __init__(self): # 析构函数,实例化后默认加载
super(mydesignshow,self).__init__() #超级加载
self.setupUi(self)
self.pushButton.clicked.connect(self.prn) #调用槽函数prn,注意槽函数不需要加()
self._mysignal.connect(self.mysignalslot) #将信号连接至槽
self._mysignal1.connect(self.mysignalslot1) #将信号连接至槽
 
def prn(self): #定义槽函数
print("按钮按下信号")
time.sleep(1)
print("延时1秒")
self._mysignal.emit() #发射信号
self._mysignal1.emit("有参信号") #发射带参信号
 
def mysignalslot(self): #定义自己的槽函数
print("无参信号")
 
 
def mysignalslot1(self,parameter): #定义自己的槽函数
print(parameter)
 
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv) #程序入口
myshow = mydesignshow() #实例化
myshow.show() #调用继承QWidget中的show方法
sys.exit(app.exec_())
注意:当信号和槽函数的参数相同时,他们的参数类型要完全一致,并且不能使用缺省参数。如果参数不同时,只能是信号的参数数量多于槽函数的参数数量,且前面的数量类型应一致。