一、PyQt5简介
1、安装PyQt5
PyQt5是基于图形程序框架Qt5的Python语言实现,由一组Python模块构成。
由于Qt Designer已经在Python3.5版本从PyQt5转移到了tools,因此安装PyQt5后我们还需要安装pyqt5-tools。
安装pyqt5-tools就是为了用PyQt5Designer,为设计软件界面方便,也可以直接安装PyQt5Designer。
2、初识Qt Designer
Qt Designer的界面是全英文的,但是也有汉化方法。
用Win+S呼出Cornata主面板(搜索框)来启动各种应用,那么这里就是在搜索框中输入designer并敲回车,就能够启动Qt Designer了。
初次启动会弹出这个“New Form”窗口,一般来说选择“Main Window”然后点击“Create”就可以了。下方有个“Show this Dialogue on Startup”的checkbox,如果不想每次启动都看到这个“New Form”窗口,可以取消勾选。
创建“Main Window”之后,我们会看到如下画面
下面就来简单介绍下整个画面的构成:
-
左侧的“Widget Box”就是各种可以自由拖动的组件
-
中间的“MainWindow - untitled”窗体就是画布
-
右上方的"Object Inspector"可以查看当前ui的结构
-
右侧中部的"Property Editor"可以设置当前选中组件的属性
-
右下方的"Resource Browser"可以添加各种素材,比如图片,背景等等,目前可以不管
大致了解了每个板块之后,就可以正式开始编写第一个UI了
3、HelloWorld!
通常来说,编写GUI有两种方法:第一种就是直接使用方便快捷的Qt Designer,第二种就是写代码。在有Qt Designer的情况下,是完全不推荐费时费力去手写GUI代码的。Qt Designer可以所见即所得,并且可以方便的修改并做出各种调整。
按照惯例,我们先来实现一个能够显示HelloWorld的窗口。
1)添加文本
在左侧的“Widget Box”栏目中找到“Display Widgets”分类,将“Label”拖拽到屏幕中间的“MainWindow”画布上,你就获得了一个仅用于显示文字的文本框,如下图所示。
2)编辑文本
双击上图中的“TextLabel”,就可以对文本进行编辑,这里我们将其改成“HelloWorld!”,如下图所示。如果文字没有完全展示出来,可以自行拖拽空间改变尺寸。
特别提醒,编辑完文本之后记得敲击回车令其生效!
3)添加按钮
使用同样的方法添加一个按钮(PushButton)并将其显示的文本改成“HelloWorld!”,如下图所示。
4)修改窗口标题
下面修改窗口标题。选中右上方的"Object Inspector"中的“MainWindow”,然后在右侧中部的"Property Editor"中找到“windowTitle”这个属性,在Value这一栏进行修改,修改完记得敲击回车。
5)编辑菜单栏
注意到画布的左上方有个“Type Here”,双击它即可开始编辑菜单栏。菜单栏支持创建多级菜单以及分割线(separator)。我随意创建了一些菜单项目,如下图所示。
6)预览
使用快捷键Ctrl+R预览当前编写的GUI(或者从菜单栏的Form > Preview / Preview in进入)
7)保存
如果觉得完成了,那就可以保存成*.ui的文件,这里我们保存为HelloWorld.ui。为了方便演示,我将文件保存到D盘。
8)生成Python代码
使用cmd将目录切到D盘并执行下面的命令。请自行将下面命令中的name替换成文件名,比如本例中的“HelloWorld.ui”
pyuic5 -o name.py name.ui
生成的代码应该类似下图所示
9)运行Python代码
此时尝试运行刚刚生成的“HelloWorld.py”是没用的,因为生成的文件并没有程序入口。因此我们在同一个目录下另外创建一个程序叫做“main.py”,并输入如下内容。在本例中,gui_file_name就是HelloWorld,请自行替换。
import sys from PyQt5.QtWidgets import QApplication, QMainWindow import gui_file_name if __name__ == '__main__': app = QApplication(sys.argv) MainWindow = QMainWindow() ui = gui_file_name.Ui_MainWindow() ui.setupUi(MainWindow) MainWindow.show() sys.exit(app.exec_())
然后运行“main.py”,你就能看到刚刚编写的GUI了!
10)组件自适应
如果你刚刚尝试去缩放窗口,会发现组件并不会自适应缩放,因此我们需要回到Qt Designer中进行一些额外的设置。
点击画布空白处,然后在上方工具栏找到grid layout或者form layout,在本例中我们使用grid layout。两种layout的图标如下图所示。
上图中layout的左边有三条横线以及三条竖线的图标,这两个是用于对齐组件,非常实用。
设置grid layout后,我们使用Ctrl+R预览,这次组件可以自适应了!因为我们已经将UI(HelloWorld.py/HelloWorld.ui)跟逻辑(main.py)分离,因此直接重复步骤7-8即可完成UI的更新,无需改动逻辑(main.py)部分。
4、Interaction
刚刚写的HelloWorld中,我们设置的按钮(PushButton)是没有实际作用的,因为我们并没有告诉这个按钮应该做什么。实际上,要让这个按钮做点什么只需要增加一行代码就可以了。
1)获取按钮id
打开HelloWorld.ui,在designer中选中对应的按钮,从“Property Editor”中可以得知这个按钮的“objectName”叫做“pushButton”,如下图所示。
2)设置触发
Qt中有“信号和槽(signal and slot)”这个概念,不过目前无需深究,也无需在Designer中去设置对应按钮的“信号和槽”,直接在“main.py”中“MainWindow.show()”的后面加入下面这样的一行代码
ui.pushButton.clicked.connect(click_success)
下面简单解释下这行代码
- pushButton就是刚刚获取的按钮id
- clicked就是信号,因为是点击,所以我们这里用clicked
- click_success就是对应要调用的槽,注意这里函数并不写成click_success()
3)设置函数
既然刚刚设置了按钮的触发并绑定了一个函数click_success,我们就要在“main.py”中实现它。示例如下
def click_success(): print("啊哈哈哈我终于成功了!")
4)运行!
UI跟逻辑分离的好处就在这里,我们这次不用去管“HelloWorld.py”了,直接运行修改完的“main.py”。点击按钮,这次你会发现在控制台中有了我们预设的输出。
5、Conversion
这次我们来进行实战演练,编写一个带GUI的汇率转换器。
1)设计UI
通过上面的讲解,你应该能够毫无压力的设计上面这样的UI并获得对应的代码。如果不行,那么不建议继续往下阅读,应当回头复习。
2)传参
现在我们有了GUI的代码以及上一节中使用的“main.py”,我们可以开始编写这个汇率转换器的逻辑部分。
在上一节,我们介绍了如何让按钮响应点击操作,但是并没有接受任何参数,而且只是在控制台输出。但是,上一节中说明了并不能通过正常的方式进行传参。因此,对于传参,有两种解决方案,一种是使用lambda,还有一种是使用functool.partial。在接下来的环节中我们会使用partial。
partial的用法如下所示:
partial(function, arg1, arg2, ......)
既然使用partial传参,那么我们就要在程序(main.py)的头部加上下面这行。
ui.pushButton.clicked.connect(partial(convert, ui))
3)编写convert函数
首先,我们要获取用户输入的数字。为了使得教程简洁易懂,我们这次只讲解单向的汇率转换。既然是单项的转换,那么我们只需要获取左侧的文本框id。在本例中,左侧的文本框id为lineEdit。如果你对此感到一头雾水,请停下并回头复习。
获取文本使用的是text()方法,因此读取用户输入的代码如下
input = ui.lineEdit.text()
接着我们进行汇率转换,注意这里要进行类型转换
result = float(input) * 6.7
最后我们让右边的文本框显示结果
ui.lineEdit_2.setText(str(result))
下面是convert函数的代码
def convert(ui): input = ui.lineEdit.text() result = float(input) * 6.7 ui.lineEdit_2.setText(str(result))
一个简单的汇率转换器就这样诞生了!
那么,如何知道一个组件都有什么方法呢?直接去Qt官方文档查看就可以了。本节使用到的lineEdit的相关方法在这里
6、threading
1)前言
这几天在用PyQt5写东西的时候遇到这样一个问题,网上资料也特别少,我感觉值得拿出来说一说。
我的程序中使用了threading模块,GUI作为主线程去启动负责逻辑处理的子线程。其中,我设计的GUI里头有一个日志框,用来代替终端显示各种日志输出。既然子线程是负责逻辑处理,那么想当然的就会直接在子线程操作GUI的显示。
都说了想当然,那当然不行咯,在子线程对GUI操作的时候,终端会出现下面这个错误,但是程序又不会马上闪退。
QObject::connect: Cannot queue arguments of type 'QTextCursor' (Make sure 'QTextCursor' is registered using qRegisterMetaType().)
更让人摸不着头脑的是,过一阵子闪退的时候,会出现下面这句话:
段错误,核心已转储
这啥玩意儿?能说人话么?一番搜索之后,发现这个原来英语叫做“Segmentation fault (core dumped)”。
"Segmentation fault"用人话来说大概就是“你尝试访问你无法访问的内存”。
然后我把上面的报错信息搜索了下,发现之前有人在StackOverflow问过,但是答案牛头不对马嘴,不过倒是在评论区发现了大佬的留言。
It is likely that the asker was not actually directly using QTextCursor, but rather using GUI code from a thread that was not the GUI thread. Attempting this seems to result in this error arising from Qt-internal code, e.g. for QTextEdit.append()
简而言之,就是说虽然报错显示QTextCursor,但是实际上是在其它线程通过Qt内部的方法间接调用了这个东西。
热心大佬还留了个链接,我跟过去看了,收获不少。
It appears you're trying to access QtGui classes from a thread other than the main thread. Like in some other GUI toolkits (e.g. Java Swing), that's not allowed. Although QObject is reentrant, the GUI classes, notably QWidget and all its subclasses, are not reentrant. They can only be used from the main thread.
这个终于说到点子上了,一句话总结就是子线程不能调用主线程的QtGui类。
所以大佬给出的方案如下:
A solution is to use signals and slots for communication between the main thread (where the GUI objects live) and your secondary thread(s). Basically, you emit signals in one thread that get delivered to the QObjects via the other thread.
大概翻译下,就是说可以通过信号和槽来完成子线程跟GUI所在的主线程的通信,就是通过在子线程释放信号,传递到主线程的槽来完成。
可惜的是,大佬并没有给出示例代码,那接下来就是动手实践了。
2)实践
首先我们在子线程的代码中创建一个对象,并且继承QObject(因为需要释放信号)。
class UpdateLog(QObject): update_signal = pyqtSignal() def __init__(self): QObject.__init__(self) def update(self): self.update_signal.emit()
update_signal = pyqtSignal()
就是使用Signal类来创建一个自定义的信号。
self.update_signal.emit()
就是当条件满足的时候,子线程可以调用UpdateLog类的update方法,就会发出信号。
做完这些之后,主线程中别忘了连击信号和槽,比如self.afk.utils.logger.update_signal.connect(self.write_log)
。然后现在再尝试运行程序,就没有任何问题了。
不仅如此,其实其它需要共享的信息,也可以通过自定义信号和槽来传递。
那么,现在就可以愉快的在PyQt程序中使用threading模块了。
7、PyQt5包模块简介
PyQt5包括的主要模块如下。
- QtCore模块——涵盖了包的核心的非GUI功能,此模块被用于处理程序中涉及的时间、文件、目录、数据类型、文本流、链接、QMimeData、线程或进程等对象。
- QtGui模块——涵盖了多种基本图形功能的类,包括但不限于:窗口集、事件处理、2D图形、基本的图像和界面、字体和文本类。
- QtWidgets模块——包含了一整套UI元素控件,用于建立符合系统风格的Classic界面,非常方便,可以在安装时选择是否使用此功能。
- QtMultimedia模块——包含了一套类库,用于处理多媒体事件,通过调用API接口访问摄像头、语音设备、收发消息(Radio Functionality)等。
- QtBluetooth模块——包含了处理蓝牙活动的类库,其功能包括:扫描设备、连接、交互等行为。
- QtNetwork模块——包含了用于进行网络编程的类库,通过提供便捷的TCP/IP及UDP的C/S代码集合,使得基于Qt的网络编程更容易。
- QtPositioning模块——用于获取位置信息,此模块允许使用多种方式实现定位,包括但不限于:卫星、无线网、文字信息。此模块一般用在网络地图定位系统中。
- Enginio模块——用于构建客户端的应用程序库,在运行时访问Qt Cloud 服务器托管的应用程序。
QtWebSockets模块——包含了一组类程序,用于实现WebSocket协议。
QtWebKit模块——包含了用于实现基于WebKit2的网络浏览器的类库。 - QtWebKitWidgets模块——提供了一组类库,用于实现一种由Widgets包构建的、基于WebKit1的网络浏览器。
- QtXml模块——包含了用于处理XML的类库,此模块为SAX和DOM API 的实现提供了函数。
- QtSvg模块——通过一组类库,为显示矢量图形文件的内容提供了函数。
- QtSql模块——提供了数据库对象的接口以供使用。
- QtTest模块——包含了通过单元测试,调试PyQt5应用程序的功能。
- QtHelp模块——包含了用于创建和查看可查找的文档的类。
- QtOpenGL模块——使用OpenGL库来渲染3D和2D图形。该模块使得Qt GUI库和OpenGL库无缝集成。
- QtXmlPatterns模块——所包含的类实现了对XML和自定义数据模型的Xquery与XPath的支持。
- QtDesigner模块——所包含的类允许使用PyQt扩展Qt Designer。
- Qt模块——将上面模块中的类综合到一个单一的模块中。这样做的好处是你不用担心哪个模块包含了哪个特定的类;坏处是加载到整个Qt框架中,从而增加了应用程序的内存占用。
- uic模块——所包含的类用来处理.ui文件,该文件由Qt Designer创建,用于描述整个或者部分用户界面。它可以将.ui文件编译为.py文件,以便其他Python程序调用。
PyQt5增加了很多模块,可以去官方网站查看,基本上看模块名字就知道大概用处了。PyQt5已经没有phonon模块了,使用QtMultimedia来处理媒体。
另外,PyQt5新增的QtWebEngineWidgets模块替代了过时的QtWebKit,但是QtWebKit还在,而新模块更耗内存,具体使用哪个由读者自己决定。
.2 PyQt 5主要类介绍
PyQt5 API拥有620多个类和6000个函数。它是一个跨平台的工具包,可以运行在所有主流的操作系统上,包括Windows、Linux和Mac OS。
-
QObject类:在类层次结构中是顶部类(Top Class),它是所有PyQt对象的基类。
-
QPaintDevice类:所有可绘制的对象的基类。
-
QApplication类:用于管理图形用户界面应用程序的控制流和主要设置。它包含主事件循环,对来自窗口系统和其他资源的所有事件进行处理和调度;它也对应用程序的初始化和结束进行处理,并且提供对话管理;还对绝大多数系统范围和应用程序范围的设置进行处理。
-
QWidget类:所有用户界面对象的基类。QDialog类和QFrame类继承自QWidget类,这两个类有自己的子类系统(Sub-Class System)。
-
QFrame类:有框架的窗口控件的基类。它也被用来直接创建没有任何内容的简单框架,但是通常要用到QHBox或QVBox,因为它们可以自动布置放到框架中的窗口控件。
-
QMainWindow类:提供一个有菜单栏、锚接窗口(如工具栏)和状态栏的主应用程序窗口。
-
QDialog类:最普通的顶级窗口。如果一个窗口控件没有被嵌入到父窗口控件中,那么该窗口控件就被称为顶级窗口控件。在通常情况下,顶级窗口控件是有框架和标题栏的窗口。在Qt中,QMainWindow和不同的QDialog的子类是最普通的顶级窗口。
-
QLabel控件:用来显示文本或图像。
-
QLineEdit窗口控件:提供了一个单页面的单行文本编辑器。
-
QTextEdit窗口控件:提供了一个单页面的多行文本编辑器。
-
QPushButton窗口控件:提供了一个命令按钮。
-
QRadioButton控件:提供了一个单选钮和一个文本或像素映射标签。
-
QCheckBox窗口控件:提供了一个带文本标签的复选框。
-
QspinBox控件:允许用户选择一个值,要么通过按向上/向下键增加/减少当前显示值,要么直接将值输入到输入框中。
-
QScrollBar窗口控件:提供了一个水平的或垂直的滚动条。
-
QSlider控件:提供了一个垂直的或水平的滑动条。
-
QComboBox控件:一个组合按钮,用于弹出列表。
-
QMenuBar控件:提供了一个横向菜单栏。
-
QStatusBar控件:提供了一个适合呈现状态信息的水平条,通常放在QMainWindow的底部。
-
QToolBar控件:提供了一个工具栏,可以包含多个命令按钮,通常放在QMainWindow的顶部。
-
QListView控件:可以显示和控制可选的多选列表,可以设置ListMode或IconMode。
-
QPixmap控件:可以在绘图设备上显示图像,通常放在QLabel或QPushButton类中。
-
Qdialog控件:对话框窗口的基类。
QWidget是所有用户界面类的基类,它能接收所有的鼠标、键盘和其他系统窗口事件。没有被嵌入到父窗口中的Widget会被当作一个窗口来调用,当然,它也可以使用setWindowFlags(Qt.WindowFlags)函数来设置窗口的显示效果。QWidget的构造函数可以接收两个参数,其中第一个参数是该窗口的父窗口;第二个参数是该窗口的Flag,也就是Qt.WindowFlags。根据父窗口来决定Widget是嵌入到父窗口中还是被当作一个独立的窗口来调用,根据Flag来设置Widget窗口的一些属性。
QMainWindow(主窗口)一般是应用程序的框架,在主窗口中可以添加所需要的Widget,比如添加菜单栏、工具栏、状态栏等。主窗口通常用于提供一个大的中央窗口控件(如文本编辑或者绘制画布)以及周围的菜单栏、工具栏和状态栏。QMainWindow常常被继承,这使得封装中央控件、菜单栏,工具栏以及窗口状态变得更容易,也可以使用Qt Designer来创建主窗口。
-
3 QApplication类
QApplication类用于管理图形用户界面应用程序的控制流和主要设置,可以说QApplication是PyQt的整个后台管理的命脉。任何一个使用PyQt开发的图形用户界面应用程序,都存在一个QApplication对象。
在PyQt中,可以通过如下代码载入必需的模块,获得QApplication类。
from PyQt5.QtWidgets import QApplication
在PyQt的应用程序实例中包含了QApplication类的初始化,通常放在Python脚本的if name == "main": 语句后面,类似于放在C的main函数里,作为主程序的入口。因为QApplication对象做了很多初始化,所以它必须在创建窗口之前被创建。
QApplication类还可以处理命令行参数,在QApplication类初始化时,需要引入参数sys.argv。sys.argv是来自命令行的参数列表,Python脚本可以从shell运行,比如用鼠标双击qtSample.py,就启动了一个PyQt应用程序。引入sys.argv后就能让程序从命令行启动,比如在命令行中输入 python qtSample.py,也可以达到同样的效果。
QApplication类的初始化可以参考以下脚本引用。应用程序整体框架为: -
if name == "main": app = QApplication(sys.argv) # 界面生成代码 ... sys.exit(app.exec_())
sys.exit()函数可以结束一个应用程序,使应用程序在主循环中退出。
QApplication采用事件循环机制,当QApplication初始化后,就进入应用程序的主循环(Main Loop),开始进行事件处理,主循环从窗口系统接收事件,并将这些事件分配到应用程序的控件中。当调用sys.exit()函数时,主循环就会结束。
PyQt 5的应用程序是事件驱动的,比如键盘事件、鼠标事件等。在没有任何事件的情况下,应用程序处于睡眠状态。主循环控制应用程序什么时候进入睡眠状态,什么时候被唤醒。