一、简介
pyqt5做为Python的一个模块,它有620多个类和6000个函数和方法。这是一个跨平台的工具包,它可以运行在所有主要的操作系统,包括UNIX,Windows,Mac OS。pyqt5是双重许可。开发者可以在GPL和商业许可之间进行选择。
pyqt5的类别分为几个模块,包括以下:
- QtCore
- QtGui
- QtWidgets
- QtMultimedia
- QtBluetooth
- QtNetwork
- QtPositioning
- Enginio
- QtWebSockets
- QtWebKit
- QtWebKitWidgets
- QtXml
- QtSvg
- QtSql
- QtTest
QtCore:包含了核心的非GUI功能。此模块用于处理时间、文件和目录、各种数据类型、流、URL、MIME类型、线程或进程。
QtGui包含类窗口系统集成、事件处理、二维图形、基本成像、字体和文本。
qtwidgets模块包含创造经典桌面风格的用户界面提供了一套UI元素的类。
QtMultimedia包含的类来处理多媒体内容和API来访问相机和收音机的功能。
Qtbluetooth模块包含类的扫描设备和连接并与他们互动。描述模块包含了网络编程的类。这些类便于TCP和IP和UDP客户端和服务器的编码,使网络编程更容易和更便携。
Qtpositioning包含类的利用各种可能的来源,确定位置,包括卫星、Wi-Fi、或一个文本文件。
Enginio模块实现了客户端库访问Qt云服务托管的应用程序运行时。
Qtwebsockets模块包含实现WebSocket协议类。
QtWebKit包含一个基于Webkit2图书馆Web浏览器实现类。
Qtwebkitwidgets包含的类的基础webkit1一用于qtwidgets应用Web浏览器的实现。
QtXml包含与XML文件的类。这个模块为SAX和DOM API提供了实现。
QtSvg模块提供了显示SVG文件内容的类。可伸缩矢量图形(SVG)是一种描述二维图形和图形应用的语言。
QtSql模块提供操作数据库的类。
QtTest包含的功能,使pyqt5应用程序的单元测试
1 安装PyQt5
直接pip安装PyQt5,可能是pip/pip3
pip install PyQt51
由于Qt Designer已经在Python3.5版本从PyQt5转移到了tools,因此我们还需要安装pyqt5-tools
pip install pyqt5-tools1
到此,PyQt5就安装完成了
也可使用国内源安装
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyqt5 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyqt5-tools
通过下面若干可选的操作来检查是否已经安装成功:
- Win+S呼出Cornata主面板(搜索框),输入designer,如果看到跟下图类似的结果说明PyQt Designer已经被安装
- 在cmd中输入pyuic5,如果返回“Error: one input ui-file must be specified”说明安装成功。
- 测试代码
import sys from PyQt5 import QtWidgets,QtCore app = QtWidgets.QApplication(sys.argv) widget = QtWidgets.QWidget() widget.resize(360,360) widget.setWindowTitle("hello,pyqt5") widget.show() sys.exit(app.exec_())
2 PyQt5汉化
下载地址:https://pan.baidu.com/s/1jzkumqjw-3VQiJxvo4V6Xw
把汉化包复制进去,就是下图的第一个文件就是汉化包,找到pyqt-tools/ Qt/ bin文件夹,例:D:\python\Lib\site-packages\pyqt5_tools\Qt\bin,在这个目录下建立一个名为translations的空文件夹。将汉化包文件(designer_zh_CN.qm)复制到建立的translations文件夹内,即可实现Qt Designer的汉化。
3 QT Designer+pycharm配合使用
QtDesigner:设计UI界面,但生成.ui文件,需要通过PyUIC转换成.py文件
3.1 配置 Qt Designer
Working directory:$FileDir$
3.2 配置PyUIC:
Program:python的安装目录下的python.exe文件
Arguments:-m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py
3.3 配置Pyrcc:
Program:python的安装目录下的Scripts文件夹的pyrcc5.exe文件
Arguments:$FileName$ -o $FileNameWithoutExtension$_rc.py
3.4 .ui转换成.py:
启动qt designer
先点击Designer设计ui界面,保存后如图生成了ui文件
记得选这个创建窗口
然后点击ui文件,点击PyUIC,就能将ui文件转换成py文件了
或者cmd运行
pyuic5 -o ui.py untitled.ui
3.5 使用转换后的程序
导入库
import sys
最后面写入程序入口:
if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) MainWindow = QtWidgets.QMainWindow() ui = Ui_Form() # ui_from是类名 ui.setupUi(MainWindow) MainWindow.show() sys.exit(app.exec_())
转换后的代码
# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'untitled.ui' # # Created by: PyQt5 UI code generator 5.11.3 # # WARNING! All changes made in this file will be lost! import sys from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(400, 300) self.pushButton = QtWidgets.QPushButton(Form) self.pushButton.setGeometry(QtCore.QRect(110, 130, 75, 23)) self.pushButton.setObjectName("pushButton") self.pushButton_2 = QtWidgets.QPushButton(Form) self.pushButton_2.setGeometry(QtCore.QRect(210, 210, 75, 23)) self.pushButton_2.setObjectName("pushButton_2") self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "Form")) self.pushButton.setText(_translate("Form", "PushButton")) self.pushButton_2.setText(_translate("Form", "PushButton")) if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) MainWindow = QtWidgets.QMainWindow() ui = Ui_Form() ui.setupUi(MainWindow) MainWindow.show() sys.exit(app.exec_())
运行:
二、Qt Designer使用教程
2.1 启动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"可以添加各种素材,比如图片,背景等等,目前可以不管
2.2 控件使用
通常来说,编写GUI有两种方法:第一种就是直接使用方便快捷的Qt Designer,第二种就是写代码。在有Qt Designer的情况下,是完全不推荐费时费力去手写GUI代码的。Qt Designer可以所见即所得,并且可以方便的修改并做出各种调整
添加文本
在左侧的“Widget Box”栏目中找到“Display Widgets”分类,将“Label”拖拽到屏幕中间的“MainWindow”画布上,你就获得了一个仅用于显示文字的文本框,如下图所示。
编辑文本
双击上图中的“TextLabel”,就可以对文本进行编辑,这里我们将其改成“HelloWorld!”,如下图所示。如果文字没有完全展示出来,可以自行拖拽空间改变尺寸。
特别提醒,编辑完文本之后记得敲击回车令其生效!
添加按钮
使用同样的方法添加一个按钮(PushButton)并将其显示的文本改成“HelloWorld!”,如下图所示。
修改窗口标题
下面修改窗口标题。选中右上方的"Object Inspector"中的“MainWindow”,然后在右侧中部的"Property Editor"中找到“windowTitle”这个属性,在Value这一栏进行修改,修改完记得敲击回车
编辑菜单栏
注意到画布的左上方有个“Type Here”,双击它即可开始编辑菜单栏。菜单栏支持创建多级菜单以及分割线(separator)。我随意创建了一些菜单项目,如下图所示。
预览
使用快捷键Ctrl+R预览当前编写的GUI(或者从菜单栏的Form > Preview / Preview in进入)
保存
如果觉得完成了,那就可以保存成*.ui的文件,这里我们保存为HelloWorld.ui。为了方便演示,我将文件保存到D盘
生成Python代码
使用cmd将目录切到D盘并执行下面的命令。请自行将下面命令中的name替换成文件名,比如本例中的“HelloWorld.ui”
pyuic5 -o name.py name.ui
运行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_())123456789101112
然后运行“main.py”,你就能看到刚刚编写的GUI了!
组件自适应
如果你刚刚尝试去缩放窗口,会发现组件并不会自适应缩放,因此我们需要回到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)部分。
2.3 控件函数
刚刚写的HelloWorld中,我们设置的按钮(PushButton)是没有实际作用的,因为我们并没有告诉这个按钮应该做什么。实际上,要让这个按钮做点什么只需要增加一行代码就可以了。
2.3.1 获取按钮id
打开HelloWorld.ui,在designer中选中对应的按钮,从“Property Editor”中可以得知这个按钮的“objectName”叫做“pushButton”,如下图所示。
2.3.2 设置触发
Qt中有“信号和槽(signal and slot)”这个概念,不过目前无需深究,也无需在Designer中去设置对应按钮的“信号和槽”,直接在“main.py”中“MainWindow.show()”的后面加入下面这样的一行代码
ui.pushButton.clicked.connect(click_success)
下面简单解释下这行代码
- pushButton就是刚刚获取的按钮id
- clicked就是信号,因为是点击,所以我们这里用clicked
- click_success就是对应要调用的槽,注意这里函数并不写成click_success()
2.3.2 设置函数
既然刚刚设置了按钮的触发并绑定了一个函数click_success,我们就要在“main.py”中实现它。示例如下
def click_success(): print("啊哈哈哈我终于成功了!")
运行!
UI跟逻辑分离的好处就在这里,我们这次不用去管“HelloWorld.py”了,直接运行修改完的“main.py”。点击按钮,这次你会发现在控制台中有了我们预设的输出。
三、实例开发
以一个简单的城市天气预报为例,演示使用PyQt5开发一个GUI程序的基本流程。
3.1 获取天气数据
主要逻辑是通过Http接口调用免费的API接口获取相关城市天气数据,详见天气API说明,如测试一下请求天津的天气,链接为:http://t.weather.sojson.com/api/weather/city/101030100。返回成功状态(status)为:200 ,失败为非200,返回数据为json数据,直接解析获取即可。
3.2 设计界面UI
打开Qt Designer,可参考下图设计Weather.ui:
我们主要用到的控件有Button, GroupBox, Label,ComboBox,TextEdit,同时定义了两个按钮queryBtn及clearBtn,分别用来查询及清空天气数据。我们需要绑定槽函数,方法如下
- 在Qt Designer右下角选择 信号/槽编辑器,点击+号新增
- 分别选择queryBtn及clearBtn,选择信号 clicked(), 接收者 Dialog 及槽 accept() (我没找到绑定自定义槽函数的方法...)
最后选择保存为 Weather.ui文件。
3.3 转换.ui文件为.py文件
在PyCharm中选中Weather.ui文件后,右键选择 External Tools - PyUIC,即可生成Weather.py,实际运行命令如下:
C:\Python38\python.exe -m PyQt5.uic.pyuic Weather.ui -o Weather.py
其中,我们需要把两个按钮绑定的槽函数:
self.queryBtn.clicked.connect(Dialog.accept)
self.clearBtn.clicked.connect(Dialog.accept)
修改为自定义函数:
self.queryBtn.clicked.connect(Dialog.queryWeather)
self.clearBtn.clicked.connect(Dialog.clearText)
最终Weather.py内容如下:
# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'Weather.ui' # # Created by: PyQt5 UI code generator 5.13.2 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName("Dialog") Dialog.resize(400, 300) self.groupBox = QtWidgets.QGroupBox(Dialog) self.groupBox.setGeometry(QtCore.QRect(0, 0, 391, 241)) self.groupBox.setObjectName("groupBox") self.textEdit = QtWidgets.QTextEdit(self.groupBox) self.textEdit.setGeometry(QtCore.QRect(20, 50, 351, 181)) self.textEdit.setObjectName("textEdit") self.comboBox = QtWidgets.QComboBox(self.groupBox) self.comboBox.setGeometry(QtCore.QRect(100, 20, 91, 20)) self.comboBox.setObjectName("comboBox") self.comboBox.addItem("") self.comboBox.addItem("") self.comboBox.addItem("") self.label = QtWidgets.QLabel(self.groupBox) self.label.setGeometry(QtCore.QRect(30, 20, 61, 21)) self.label.setObjectName("label") self.queryBtn = QtWidgets.QPushButton(Dialog) self.queryBtn.setGeometry(QtCore.QRect(40, 250, 75, 23)) self.queryBtn.setMaximumSize(QtCore.QSize(75, 16777215)) self.queryBtn.setObjectName("queryBtn") self.clearBtn = QtWidgets.QPushButton(Dialog) self.clearBtn.setGeometry(QtCore.QRect(250, 250, 75, 23)) self.clearBtn.setMaximumSize(QtCore.QSize(75, 16777215)) self.clearBtn.setObjectName("clearBtn") self.retranslateUi(Dialog) self.queryBtn.clicked.connect(Dialog.queryWeather) self.clearBtn.clicked.connect(Dialog.clearText) QtCore.QMetaObject.connectSlotsByName(Dialog) def retranslateUi(self, Dialog): _translate = QtCore.QCoreApplication.translate Dialog.setWindowTitle(_translate("Dialog", "Dialog")) self.groupBox.setTitle(_translate("Dialog", "城市天气预报")) self.comboBox.setItemText(0, _translate("Dialog", "北京")) self.comboBox.setItemText(1, _translate("Dialog", "上海")) self.comboBox.setItemText(2, _translate("Dialog", "天津")) self.label.setText(_translate("Dialog", "城市")) self.queryBtn.setText(_translate("Dialog", "查询")) self.clearBtn.setText(_translate("Dialog", "清空"))
3.4 调用MainDialog
在MainDialog中调用界面类Ui_Dialog,然后在其中中添加查询天气的业务逻辑代码,这样就做到了界面显示和业务逻辑的分离。
新增demo.py文件, 在MainDialog类中定义了两个槽函数queryWeather()和clearText(),以便在界面文件Weather.ui中定义的两个按钮(queryBtn 和clearBtn) 触发clicked 信号与这两个槽函数进行绑定。
完整代码如下:
# coding:utf-8 import sys import Weather from PyQt5.QtWidgets import QApplication, QDialog import requests class MainDialog(QDialog): def __init__(self, parent=None): super(QDialog, self).__init__(parent) self.ui = Weather.Ui_Dialog() self.ui.setupUi(self) def queryWeather(self): cityName = self.ui.comboBox.currentText() cityCode = self.getCode(cityName) r = requests.get("http://t.weather.sojson.com/api/weather/city/{}".format(cityCode)) print(r.json()) if r.json().get('status') == 200: weatherMsg = '城市:{}\n日期:{}\n天气:{}\nPM 2.5:{} {}\n温度:{}\n湿度:{}\n风力:{}\n\n{}'.format( r.json()['cityInfo']['city'], r.json()['data']['forecast'][0]['ymd'], r.json()['data']['forecast'][0]['type'], int(r.json()['data']['pm25']), r.json()['data']['quality'], r.json()['data']['wendu'], r.json()['data']['shidu'], r.json()['data']['forecast'][0]['fl'], r.json()['data']['forecast'][0]['notice'], ) else: weatherMsg = '天气查询失败,请稍后再试!' self.ui.textEdit.setText(weatherMsg) def getCode(self, cityName): cityDict = {"北京": "101010100", "上海": "101020100", "天津": "101030100"} return cityDict.get(cityName, '101010100') def clearText(self): self.ui.textEdit.clear() if __name__ == '__main__': myapp = QApplication(sys.argv) myDlg = MainDialog() myDlg.show() sys.exit(myapp.exec_())
最终运行显示效果如下:
四、PyQt5中多线程模块QThread使用方法
多线程模块QThread解决PyQt界面程序唉执行耗时操作时,程序卡顿出现的无响应以及界面输出无法实时显示的问题。用户使用工具过程中出现这些问题时会误以为程序出错,从而把程序关闭。这样,导致工具的用户使用体验不好。
使用PyQt界面程序,点击运行按钮后,程序在显示框中每秒打印1个数字。程序代码如下:
# -*- coding: utf-8 -*- import sys import time from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtWidgets import QApplication, QMainWindow from QThread_Example_UI import Ui_Form class MyMainForm(QMainWindow, Ui_Form): def __init__(self, parent=None): super(MyMainForm, self).__init__(parent) self.setupUi(self) self.runButton.clicked.connect(self.display) def display(self): for i in range(20): time.sleep(1) self.listWidget.addItem(str(i)) if __name__ == "__main__": app = QApplication(sys.argv) myWin = MyMainForm() myWin.show() sys.exit(app.exec_())
程序运行过程结果如下(点击Run按钮后界面出现未响应字样,同时程序也没有出现每隔1秒打印1个数字,实际结果是循环结束后20个数字一同展示)
问题分析
上述实现的GUI程序都是单线程运行,对于需要执行一个特别耗时的操作时就会出现该问题现象。要解决这种问题可以考虑使用多线程模块QThread。
多线程模块QThread基本原理
QThread是Qt的线程类中最核心的底层类。由于PyQt的的跨平台特性,QThread要隐藏所有与平台相关的代码 要使用的QThread开始一个线程,可以创建它的一个子类,然后覆盖其它QThread.run()函数。
class Thread(QThread): def __init__(self): super(Thread,self).__init__() def run(self): pass
接下来创建一个新的线程
thread = Thread()
thread.start()
可以看出,PyQt的线程使用非常简单,建立一个自定义的类(如Thread),自我继承自QThread ,并实现其run()方法即可。在使用线程时可以直接得到Thread实例,调用其start()函数即可启动线程,线程启动之后,会自动调用其实现的run()的函数,该方法就是线程的执行函数 。
业务的线程任务就写在run()函数中,当run()退出之后线程就基本结束了,QThread有started和finished信号,可以为这两个信号指定槽函数,在线程启动和结束之时执行一段代码进行资源的初始化和释放操作,更灵活的使用方法是,在自定义的QThread实例中自定义信号,并将信号连接到指定的槽函数,当满足一定的业务条件时发射此信号。
QThread类中的常用方法
start():启动线程
wait():阻止线程,直到满足如下条件之一
(1)与此QThread对象关联的线程已完成执行(即从run返回时),如果线程完成执行,此函数返回True,如果线程尚未启动,也返回True
(2)等待时间的单位是毫秒,如果时间是ULONG_MAX(默认值·),则等待,永远不会超时(线程必须从run返回),如果等待超时,此函数将会返回False
sleep():强制当前线程睡眠多少秒
QThread类中的常用信号
started:在开始执行run函数之前,从相关线程发射此信号
finished:当程序完成业务逻辑时,从相关线程发射此信号
使用QThread重新实现程序解决问题
先继承QThread类并重新实现其中的run()函数,也就是说把耗时的操作放入run()函数中。代码如下:
# -*- coding: utf-8 -*- import sys import time from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtWidgets import QApplication, QMainWindow from QThread_Example_UI import Ui_Form class MyMainForm(QMainWindow, Ui_Form): def __init__(self, parent=None): super(MyMainForm, self).__init__(parent) self.setupUi(self) # 实例化线程对象 self.work = WorkThread() self.runButton.clicked.connect(self.execute) def execute(self): # 启动线程 self.work.start() # 线程自定义信号连接的槽函数 self.work.trigger.connect(self.display) def display(self,str): # 由于自定义信号时自动传递一个字符串参数,所以在这个槽函数中要接受一个参数 self.listWidget.addItem(str) class WorkThread(QThread): # 自定义信号对象。参数str就代表这个信号可以传一个字符串 trigger = pyqtSignal(str) def __int__(self): # 初始化函数 super(WorkThread, self).__init__() def run(self): #重写线程执行的run函数 #触发自定义信号 for i in range(20): time.sleep(1) # 通过自定义信号把待显示的字符串传递给槽函数 self.trigger.emit(str(i)) if __name__ == "__main__": app = QApplication(sys.argv) myWin = MyMainForm() myWin.show() sys.exit(app.exec_())