PyQt5 快速入门
一.Qt介绍
1.Qt
Qt是一个跨平台的C++开发库,主要用来开发图形用户界面(Graphical User Interface,GUI)程序,Qt是纯C++开发的,正常情况下需要先学习C语言,然后在学习C++然后才能使用Qt开发带界面的程序。
多亏了开源社区使得Qt还可以用Python,Ruby,Perl等脚本语言进行开发。
Qt支持的操作系统有很多,例如通用操作系统Windows,Linux,Unix,智能手机系统Android,iOS,嵌入式系统等等。可以说是跨平台的
2.PyQt
PyQt的开发者是英国的“Riverbank Computing”公司,它提供了GPL(简单来说,以GPL协议发布到网上的素材,你可以使用,也可以更改,但是经过你更改然后再次发布的素材必须也遵守GPL协议,主要要求是必须开源,而且不能删减原作者的声明信息等)与商业协议两种授权方式,因此它可以免费地用于自由软件的开发。
PyQt可以运行于Microsoft、Mac OS X、Linux以及Unix的多数变种上。
PyQt是Python语言的GUI(Graphical User Interface,简称 GUI,又称图形用户接口)编程解决方案之一,可以用来代替Python内置的Tkinter
。其它替代者还有PyGTK
、wxPython
等,与Qt一样,PyQt是一个自由软件
3.一句话概况
- Qt(C++语言 GUI)
- PyQt = Python + Qt技术
4.Python GUI开发热门选择
-
Tkinter
Python官方采用的标准库,优点是作为Python标准库、稳定、发布程序较小,缺点是控件相对较少。
-
wxPython
基于wxWidgets的Python库,优点是控件比较丰富,缺点是稳定性相对差点、文档少、用户少。
-
PySide2、PyQt5
基于Qt 的Python库,优点是控件比较丰富、跨平台体验好、文档完善、用户多。
缺点是 库比较大,发布出来的程序比较大。
PyQt5 的开发者是英国的“Riverbank Computing”公司 , 而 PySide2 则是 qt 针对python语言提供的专门
二.下载安装PyQt5
1.创建新的虚拟环境
因为我们后面需要对程序进行打包,所以为了减少对于打包没有帮助的库,我们创建一个干净的全新的虚拟环境进行开发。我这里是依赖于anaconda来创建虚拟环境,指令如下。
conda create -n pyqt5_dev python=3.8 #创建新的虚拟环境
conda activate pyqt5_dev # 激活
创建好之后,我们通过pip list指令来确认一下,发现确实是一个干净的环境。
2.安装pyqt5(命令行)
直接通过指令安装,这里是通过清华源进行加速,我这里也是很快就安装上了。
pip install pyqt5 -i https://pypi.tuna.tsinghua.edu.cn/simple
3.安装成功效果如下:
通过pip list指令:
在当前安装PyQt的虚拟环境中输入如下测试代码,我这里是一点问题都没有的。
# 如果执行成功,没有任何错误提示,则表明环境搭建成功
from PyQt5 import QtWidgets
# 当然也可以查看PyQt版本
from PyQt5.QtCore import *
print(QT_VERSION_STR)
三.PyQt基本UI
1.第一个PyQT程序
import sys
from PyQt5.QtWidgets import QApplication,QWidget
if __name__ =='__main__':
app=QApplication(sys.argv)
w=QWidget()
# 设置窗口标题
w.setWindowTitle("第一个PyQt")
# 展示窗口
w.show()
# 程序进行循环等待状态
app.exec()
上述的程序运行结果如下:
关于这段代码,有一些注解:
- 只要是Qt制作的app,必须有且只有1个QApplication对象
- sys.argv当作参数的目的是将运行时的命令参数传递给QApplication对象
- 创建了一个QWidget对象(这是一个白框),将它的标题设置为”第一个”PyQt“
- 然后调用show方法显示出来
- 开始运行程序,直到关闭了窗口,这里相当于是一个while(True)的循环等待机制,异步的
2.PyQt资料查询方式
PyQt中有非常多的功能模块,开发中最常用的功能模块主要有三个:
- QtCore:包含了核心的非GUI的功能。主要和时间、文件与文件夹、各种数据、流、URLs、mime类文件、进程与线程一起使用
- QtGui:包含了窗口系统、事件处理、2D图像、基本绘画、字体和文字类
- QtWidgets:包含了一些列创建桌面应用的UI元素
可以参考PyQt官网的所有模块,地址:https://www.riverbankcomputing.com/static/Docs/PyQt5/module_index.html#ref-module-index
C++具体实现的API文档,地址:https://doc.qt.io/qt-5/qtwidgets-module.html
用到什么功能就它相关的api或者别人分享的使用心得,是学习最快的方式
不建议在python的idea环境中直接跳转去看代码,因为python里面都是调用的C++的代码,无法查看!!!
3.控件
1.按钮
按钮对应的控件名称为QPushButton,位于PyQt5.QtWidgets里面:
import sys
from PyQt5.QtWidgets import QApplication,QWidget,QPushButton
if __name__ =='__main__':
app=QApplication(sys.argv)
w=QWidget()
# 设置窗口标题
w.setWindowTitle("第一个PyQt")
# 在窗口里面添加控件
btn = QPushButton("按钮")
# 设置按钮的父亲是当前窗口,等于是添加到窗口中显示
btn.setParent(w)
# 展示窗口
w.show()
# 程序进行循环等待状态
app.exec()
注意,按钮要在白板上展示,所以白板是按钮的父亲。代码运行效果如下:
2.文本
纯文本控件名称为QLabel,位于PyQt5.QtWidgets里面,纯文本控件仅仅作为标识显示而已,类似输入内容前的一段标签提示(账号、密码)
from PyQt5.QtWidgets import QLabel
# 下面创建一个Label,然后调用方法指定父类
label = QLabel("账号: ")
# 设置父对象
label.setParent(w)
每次这样设置是不是太过麻烦了点,能不能当这个控件出生的时候就给他指定一个父亲呢?
# 下面创建一个Label,在创建的时候指定了父对象
label = QLabel("账号: ",w)
我们如何指定这个文本控件的位置呢?
# 显示位置与大小:x,y,w,h
label.setGeometry(20,20,30,30)
设置完成之后,代码运行效果如下:
3.输入框
输入框的控件名称为QLineEdit,位于PyQt5.QtWidgets里面。
# 文本框
edit = QLineEdit(w)
edit.setPlaceholderText("请输入账号")
edit.setGeometry(55,20,200,20)
我们把学的这三个控件综合到一起,代码如下:
import sys
from PyQt5.QtWidgets import QApplication,QWidget,QPushButton,QLabel,QLineEdit
if __name__ =='__main__':
app=QApplication(sys.argv)
w=QWidget()
# 设置窗口标题
w.setWindowTitle("第一个PyQt")
# 纯文本
label = QLabel("账号",w)
label.setGeometry(20,20,30,20)
# 文本框
edit = QLineEdit(w)
edit.setPlaceholderText("请输入账号")
edit.setGeometry(55,20,200,20)
# 在窗口里面添加控件
btn = QPushButton("注册",w)
btn.setGeometry(50,80,70,30)
# 展示窗口
w.show()
# 程序进行循环等待状态
app.exec()
代码的运行效果如下,这里博主的电脑上会出现遮挡,我也不知道是为啥
4.调整窗口
设置窗口的大小、位置
w.resize(300,300)
然后需要将这个窗口移动到屏幕的中央:
# 调整窗口在屏幕的中央显示
center_pointer = QDesktopWidget().availableGeometry().center()
x = center_pointer.x()
y = center_pointer.y()
old_x, old_y, width, height = w.frameGeometry().getRect()
w.move(int(x-width/2),int(y-height/2))
运行效果,我们发现此时生成的窗口确实移动到屏幕的中央位置了。
接下来我们继续设置窗口的图标:
import sys
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QWidget
if __name__ == '__main__':
app = QApplication(sys.argv)
# 创建一个QWidget
w = QWidget()
# 设置标题
w.setWindowTitle("看看我图标帅吗")
# 设置图标
w.setWindowIcon(QIcon('panda.png'))
# 显示QWidget
w.show()
app.exec()
演示效果如下:
四.布局
在Qt里面布局分为四个大类:
- QBoxLayout
- QGridLayout
- QFormLayout
- QStackedLayout
1.QBoxLayout
直译为:盒子布局,一般使用它的两个子类QHBoxLayout和QVBoxLayout负责水平和垂直布局。
1.垂直布局实例
import sys
from PyQt5.QtWidgets import QApplication, QVBoxLayout, QWidget, QPushButton, QGroupBox, QMainWindow
from PyQt5.QtCore import Qt
class MyWindow(QWidget):
def __init__(self):
# 切记一定要调用父类的__init__方法,因为它里面有很多对UI空间的初始化操作
super().__init__()
# 设置大小
self.resize(300, 300)
# 设置标题
self.setWindowTitle("垂直布局")
# 垂直布局
layout = QVBoxLayout()
# 作用是在布局器中增加一个伸缩量,里面的参数表示QSpacerItem的个数,默认值为零
# 会将你放在layout中的空间压缩成默认的大小
# 下面的笔试1:1:1:2
layout.addStretch(1)
# 按钮1,这里没有设置父对象
btn1 = QPushButton("按钮1")
# 添加到布局器中
# 不设置父对象而是直接设置布局器
layout.addWidget(btn1)
layout.addStretch(1)
# 按钮2
btn2 = QPushButton("按钮2")
# 添加到布局器
layout.addWidget(btn2)
layout.addStretch(1)
# 按钮3
btn3 = QPushButton("按钮3")
# 添加到布局器
layout.addWidget(btn3)
layout.addStretch(2)
# 让当前的窗口使用这个布局器来排列
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
# 创建一个QWidget子类
w = MyWindow()
w.show()
app.exec()
运行结果如下:
如果伸缩量中的参数设置为0,会如何?答案是就消失了,0就是没有比例!!!
综上,我们可以通过设置弹簧以及弹簧的比例信息来调整各个按钮之间的位置关系!
2.水平布局的代码如下
这里我们将垂直布局与水平布局综合到一起,但是一个Widget只能设置一个布局器,如何解决呢?通过布局器的嵌套,一个大的布局器中嵌套两个小的布局器,一个垂直一个水平。
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QGroupBox, QVBoxLayout, QHBoxLayout, QRadioButton
class MyWindow(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
# 最外层的垂直布局,包含两部分:爱好和性别
container = QVBoxLayout()
# -----创建第1个组,添加多个组件-----
# hobby 主要是保证他们是一个组。
hobby_box = QGroupBox("爱好")
# v_layout 保证三个爱好是垂直摆放
v_layout = QVBoxLayout()
btn1 = QRadioButton("抽烟")
btn2 = QRadioButton("喝酒")
btn3 = QRadioButton("烫头")
# 添加到v_layout中
v_layout.addWidget(btn1)
v_layout.addWidget(btn2)
v_layout.addWidget(btn3)
# 把v_layout添加到hobby_box中
hobby_box.setLayout(v_layout)
# -----创建第2个组,添加多个组件-----
# 性别组
gender_box = QGroupBox("性别")
# 性别容器
h_layout = QHBoxLayout()
# 性别选项
btn4 = QRadioButton("男")
btn5 = QRadioButton("女")
# 追加到性别容器中
h_layout.addWidget(btn4)
h_layout.addWidget(btn5)
# 添加到 box中
gender_box.setLayout(h_layout)
# 把爱好的内容添加到容器中
container.addWidget(hobby_box)
# 把性别的内容添加到容器中
container.addWidget(gender_box)
# 设置窗口显示的内容是最外层容器
self.setLayout(container)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWindow()
w.show()
app.exec()
运行效果如下:
通过这个实例,我们可以发现水平布局器与垂直布局器是可以混合使用【通过分组这个功能】,即嵌套使用的。最外层是一个大的垂直布局器,这个布局器中存在两个组,第一个组里面是一个垂直布局器,第二个组里面是一个水平布局器。
2.QGridLayout
网格布局,有的人称之为九宫格布局:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QLineEdit, QGridLayout
class MyWindow(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
self.setWindowTitle("计算器")
# 准备数据
data = {
0: ["7", "8", "9", "+", "("],
1: ["4", "5", "6", "-", ")"],
2: ["1", "2", "3", "*", "<-"],
3: ["0", ".", "=", "/", "C"]
}
# 整体垂直布局
layout = QVBoxLayout()
# 输入框
edit = QLineEdit()
edit.setPlaceholderText("请输入内容")
# 把输入框添加到容器中
layout.addWidget(edit)
# 网格布局
grid = QGridLayout()
# 循环创建追加进去
for line_number, line_data in data.items():
# 此时line_number是第几行,line_data是当前行的数据
for col_number, number in enumerate(line_data):
# 此时col_number是第几列,number是要显示的符号
btn = QPushButton(number)
# grid.addWidget(btn)
grid.addWidget(btn, line_number, col_number)
# 把网格布局追加到容器中
layout.addLayout(grid)
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWindow()
w.show()
app.exec()
运行效果如下,上面的代码也没什么好说的,整体就是一个大的垂直布局,嵌套了一个输入框和一个网格布局,网格布局通过双重循环来增加按钮。注意向网格布局中插入按键的时候需要两个下标来表明位置。
3.QFormLayout
一般适用于提交数据form表单。比如:登录,注册类似的场景
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QVBoxLayout, QFormLayout, QLineEdit, QPushButton, QApplication, QWidget
class MyWindow(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
# 设定当前Widget的宽高(可以拉伸大小)
# self.resize(300, 200)
# 禁止改变宽高(不可以拉伸)
self.setFixedSize(300, 150)
# 外层容器
container = QVBoxLayout()
# 表单容器
form_layout = QFormLayout()
# 创建1个输入框
edit = QLineEdit()
edit.setPlaceholderText("请输入账号")
form_layout.addRow("账号:", edit)
# 创建另外1个输入框
edit2 = QLineEdit()
edit2.setPlaceholderText("请输入密码")
form_layout.addRow("密码:", edit2)
# 将from_layout添加到垂直布局器中
container.addLayout(form_layout)
# 按钮
login_btn = QPushButton("登录")
login_btn.setFixedSize(100, 30)
# 把按钮添加到容器中,并且指定它的对齐方式
container.addWidget(login_btn, alignment=Qt.AlignRight)
# 设置当前Widget的布局器,从而显示这个布局器中的子控件
self.setLayout(container)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWindow()
w.show()
app.exec()
运行效果如下:
4.QStackedLayout
提供了多页面切换的布局,一次只能看到一个界面。这个布局叫做抽屉布局。
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QStackedLayout, QLabel
class Window1(QWidget):
def __init__(self):
super().__init__()
QLabel("我是抽屉1要显示的内容", self)
self.setStyleSheet("background-color:green;")
class Window2(QWidget):
def __init__(self):
super().__init__()
QLabel("我是抽屉2要显示的内容", self)
self.setStyleSheet("background-color:red;")
class MyWindow(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.create_stacked_layout()
self.init_ui()
def create_stacked_layout(self):
# 创建堆叠(抽屉)布局
self.stacked_layout = QStackedLayout()
# 创建单独的Widget
win1 = Window1()
win2 = Window2()
# 将创建的2个Widget添加到抽屉布局器中
self.stacked_layout.addWidget(win1)
self.stacked_layout.addWidget(win2)
def init_ui(self):
# 设置Widget大小以及固定宽高
self.setFixedSize(300, 270)
# 1. 创建整体的布局器
container = QVBoxLayout()
# 2. 创建1个要显示具体内容的子Widget
widget = QWidget()
widget.setLayout(self.stacked_layout)
widget.setStyleSheet("background-color:grey;")
# 3. 创建2个按钮,用来点击进行切换抽屉布局器中的Widget
btn_press1 = QPushButton("抽屉1")
btn_press2 = QPushButton("抽屉2")
# 给按钮添加事件(即点击后要调用的函数)
btn_press1.clicked.connect(self.btn_press1_clicked)
btn_press2.clicked.connect(self.btn_press2_clicked)
# 4. 将需要显示的空间添加到布局器中
container.addWidget(widget)
container.addWidget(btn_press1)
container.addWidget(btn_press2)
# 5. 设置当前要显示的Widget,从而能够显示这个布局器中的控件
self.setLayout(container)
def btn_press1_clicked(self):
# 设置抽屉布局器的当前索引值,即可切换显示哪个Widget
self.stacked_layout.setCurrentIndex(0)
def btn_press2_clicked(self):
# 设置抽屉布局器的当前索引值,即可切换显示哪个Widget
self.stacked_layout.setCurrentIndex(1)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = MyWindow()
win.show()
app.exec()
这里的结构主要是一个垂直布局器,其中包含三个部分,第一个部分是一个白板(QWidget),第二部分和第三部分都是按钮,第一部分白板把它的布局器设置为可以切换的抽屉布局器,而这个抽屉布局器当作又包含了两部分的内容,分别为两个窗口。然后我们给这两个窗口添加响应机制,因为抽屉布局器中含有两个窗口就好比如列表中含有两个元素,所以他们都是有下标的,所以响应函数可以通过setCurrentIndex()
,根据下标的值来切换抽屉布局中需要显示的是哪个窗口。
代码实现效果如下:
五.窗口
在Qt中,生成窗口有三种方式,分别为:QWidget | QMainWindow | QDialog
-
QWidget
控件和窗口的父类,自由度高(什么东西都没有),没有划分菜单,工具栏,主窗口等区域
-
QMainWindow
是QWidget的子类,包含菜单栏,工具栏,状态栏,标题栏等,中间部分则为主窗口区域
-
QDialog
对话框窗口的基类
1.QWidget
import sys
from PyQt5.QtWidgets import QWidget, QLabel , QApplication
class mywnd(QWidget):
def __init__(self):
super(mywnd, self).__init__()
self.initUI()
def initUI(self):
label = QLabel("这是文字~~" )
label.setStyleSheet("font-size:30px;color:red")
label.setParent(self)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = mywnd()
#设置窗口标题
w.setWindowTitle("qwidget")
# 展示窗口
w.show()
# 程序进行循环等待状态
app.exec()
效果:
2.QMainWindow
import sys
from PyQt5.QtWidgets import QMainWindow, QLabel, QApplication
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
label = QLabel("这是文字~~")
label.setStyleSheet("font-size:30px;color:red")
# 调用父类中的menuBar,从而对菜单栏进行操作
menu = self.menuBar()
# 如果是Mac的话,菜单栏不会在Window中显示而是屏幕顶部系统菜单栏位置
# 下面这一行代码使得Mac也按照Windows的那种方式在Window中显示Menu
menu.setNativeMenuBar(False)
file_menu = menu.addMenu("文件")
file_menu.addAction("新建")
file_menu.addAction("打开")
file_menu.addAction("保存")
edit_menu = menu.addMenu("编辑")
edit_menu.addAction("复制")
edit_menu.addAction("粘贴")
edit_menu.addAction("剪切")
# 设置中心内容显示
self.setCentralWidget(label)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWindow()
# 设置窗口标题
w.setWindowTitle("我是窗口标题....")
# 展示窗口
w.show()
# 程序进行循环等待状态
app.exec()
效果:
3.QDialog
不过对话框一般不应该作为主窗口的存在,而是通过点击操作弹出,起到提示作用
import sys
from PyQt5.QtWidgets import QDialog, QPushButton, QApplication
class MyDialog(QDialog):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
ok_btn = QPushButton("确定", self)
ok_btn.setGeometry(50, 50, 100, 30)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyDialog()
# 设置窗口标题
w.setWindowTitle("对话框")
# 展示窗口
w.show()
# 程序进行循环等待状态
app.exec()
效果:
六.信号与槽
信号与槽是Qt的核心内容
1.信号(signal)
其实就是事件(按钮点击、内容发生改变、窗口的关闭事件)或者是状态(check选中了,togglebutton切换)。当程序触发了某种状态或者发生了某种事件(比如:按钮被点击了,内容改变等等),那么即可发射出来一个信号。
2.槽(slot)
若向捕获这个信号,然后执行相应的逻辑代码,那么就需要使用到槽,槽实际上是一个函数,当信号发射出来后,会执行与之绑定的槽函数。
3.将信号与槽链接
为了能够实现,当点击某个按钮时执行某个逻辑,需要把具体的信号和具体的槽函数绑定到一起。操作大体流程如下:
对象.信号.connect(槽函数)
需求:
当出现了某一种信号(某一种事件)的时候,我们需要执行一段代码(用函数来包装这份代码)
解决的办法:
信号和槽
4.案例1:接收信号
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
class MyWindow(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
# 更改当前Widge的宽高
self.resize(500, 300)
# 创建一个按钮
btn = QPushButton("点我点我", self)
# 设置窗口位置、宽高
btn.setGeometry(200, 200, 100, 30)
# 将按钮被点击时触发的信号与我们定义的函数(方法)进行绑定
# 注意:这里没有(),即写函数的名字,而不是名字()
btn.clicked.connect(self.click_my_btn)
def click_my_btn(self, arg):
# 槽函数,点击按钮则调用该函数
# 这里的参数正好是信号发出,传递的参数
print("点击按钮啦~", arg)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWindow()
w.show()
app.exec()
上面这段代码没什么好分析的,挺简单的,就是创建了一个窗口,连布局器都没有,然后在窗口上创建了一个按钮,按钮在创建的时候就指定了它的父亲是当前这个类(当前窗口),然后设置信号与槽进行链接即可,信号是我们点击按钮的动作,槽函数是我们的自定义函数click_my_btn
效果如下:
5.案例2:自定义信号【重点】
除了接收Qt自带的信号之外,我们也可以自行定义信号,在合适的时机,自行发射信息。自定义信号需要使用到 pyqtSignal 来声明信号,并且需要在类中的函数之外声明。如果会自定义信号,那么信号和槽基本就掌握了。否则永远只会接收别人发射出的信号。|
import sys
import time
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class MyWindow(QWidget):
# 声明一个信号 只能放在函数的外面
my_signal = pyqtSignal(str)
def __init__(self):
super().__init__()
self.init_ui()
self.msg_history = list() # 用来存放消息
def init_ui(self):
self.resize(500, 200)
# 创建一个整体布局器
container = QVBoxLayout()
# 用来显示检测到漏洞的信息
self.msg = QLabel("")
self.msg.resize(440, 15)
# print(self.msg.frameSize())
self.msg.setWordWrap(True) # 自动换行
self.msg.setAlignment(Qt.AlignTop) # 靠上
# self.msg.setStyleSheet("background-color: yellow; color: black;")
# 创建一个滚动对象
scroll = QScrollArea()
scroll.setWidget(self.msg)
# 创建垂直布局器,用来添加自动滚动条
v_layout = QVBoxLayout()
v_layout.addWidget(scroll)
# 创建水平布局器
h_layout = QHBoxLayout()
btn = QPushButton("开始检测", self)
# 绑定按钮的点击,点击按钮则开始检测
btn.clicked.connect(self.check)
h_layout.addStretch(1) # 伸缩器
h_layout.addWidget(btn)
h_layout.addStretch(1)
# 操作将要显示的控件以及子布局器添加到container
container.addLayout(v_layout)
container.addLayout(h_layout)
# 设置布局器
self.setLayout(container)
# 绑定信号和槽
self.my_signal.connect(self.my_slot)
def my_slot(self, msg):
# 更新内容
print(msg)
self.msg_history.append(msg)
self.msg.setText("<br>".join(self.msg_history))
self.msg.resize(440, self.msg.frameSize().height() + 15)
self.msg.repaint() # 更新内容,如果不更新可能没有显示新内容
def check(self):
for i, ip in enumerate(["192.168.1.%d" % x for x in range(1, 255)]):
msg = "模拟,正在检查 %s 上的漏洞...." % ip
# print(msg)
if i % 5 == 3:
# 表示发射信号 对象.信号.发射(参数)
self.my_signal.emit(msg + "【发现漏洞】")
time.sleep(0.01)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWindow()
w.show()
app.exec()
上面这段代码,通过自定义了一个自己的信号,在信号里面嵌套了信号,我们的按钮被点击是一个信号,当按钮被点击时,跳转到对应的槽函数中,在这个槽函数中,如果代码中变量的条件符合一定的要求后,会手动发送我们自定义的信号,这个时候我们自定义的槽函数2就被会触发,从而将满足条件的打印语句,输出到Qlable中。关于布局器,这里的结构是一个大的垂直布局器,嵌套了一个垂直布局器和一个水平布局器,在第一个垂直布局器中添加一个滚动条组件,在滚动条组件中添加我们的QLabel组件,这是第一个垂直布局器的内部结构,然后第二个水平布局器就中间是一个按钮,然后按钮的两边添加弹簧,比例是1:1,这样设计就可以让按钮居中显示。
效果:
七.QT Designer
我们如果纯靠代码来编写界面,效率属实是有点低,所以我们可以使用另外一个辅助设计图形化的软件QT Designer。大家可以自行去网上下载。
安装成功之后,运行的界面是这样的:
1.使用流程
- 创建一个基于QWidget的界面
- 此时会创建一个新的窗口,如下效果
- 拖动想要的控件
- 选中控件,看属性
- 修改属性
-
如果没有看到preperty等窗口怎么办?看下图
-
信号与槽
通过这个app左下序号的操作,可以将“点我”这个按钮点击触发的信号与LCDNumber进行关联,从而实现信号与槽的绑定。
我们在这里设置这样的信号与槽,信号的发送者是pushButton,触发条件是当按钮被点击时,信号的接受者为lcdNumber,接受者的执行动作为接受到信号后关闭。
-
预览效果
点击按钮之前:
点击按钮之后:
-
保存
我起名为test.ui。
-
python代码使用test.ui文件
若需要加载ui文件,则需要导入uic模块,它位于PyQt5当中
""" 动态加载ui文件 """ import sys from PyQt5.QtWidgets import QApplication from PyQt5 import uic if __name__ == '__main__': app = QApplication(sys.argv) ui = uic.loadUi("./test.ui") # 展示窗口 ui.show() app.exec()
将test.ui与上述代码文件放到同一个路径下,运行次.py文件后的效果如下:
我们可以看到,我们在qt designer中绘制的界面就成功的加载到python环境中了,为什么要加载到python中去做呢?因为qt designer虽然支持信号与槽,但是支持的仅仅只有系统内置的槽函数,不支持自定义的槽函数!!!所以我们还是需要在python代码中自己去编写符合我们的任务需求的槽函数才行!
2.练习
请使用QT Designer设计如下效果
3.进阶使用案例
目的:获取用户名、密码,在TextBrowser中显示一些登录的信息
使用的技术:python加载.ui文件获取了界面,对.ui文件中的控件操作,完成信号与槽的绑定等
-
看看.ui文件有什么属性,如下图:
在这里,我们看到加载后的.ui文件有7给对象属性,正好与在设计.ui文件时控件的数量一致,可见属性的个数正好对应.ui文件中的空间个数,所以想要操作哪个空间,就通过对象.属性的方式从.ui对象中提取即可。当然了不能盲目的提取,这些属性的名字就是在.ui文件中的空间的Object name,如下图:
-
编写代码如下:
import sys from PyQt5.QtWidgets import * from PyQt5 import uic class MyWindow(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.ui = uic.loadUi("./login.ui") # print(self.ui.__dict__) # 查看ui文件中有哪些控件 # 提取要操作的控件 self.user_name_qwidget = self.ui.lineEdit # 用户名输入框 self.password_qwidget = self.ui.lineEdit_2 # 密码输入框 self.login_btn = self.ui.pushButton # 登录按钮 self.forget_password_btn = self.ui.pushButton_2 # 忘记密码按钮 self.textBrowser = self.ui.textBrowser # 文本显示区域 # 绑定信号与槽函数 self.login_btn.clicked.connect(self.login) def login(self): """登录按钮的槽函数""" user_name = self.user_name_qwidget.text() password = self.password_qwidget.text() if user_name == "admin" and password == "123456": self.textBrowser.setText("欢迎%s" % user_name) self.textBrowser.repaint() else: self.textBrowser.setText("用户名或密码错误....请重试") self.textBrowser.repaint() if __name__ == '__main__': app = QApplication(sys.argv) w = MyWindow() # 展示窗口 w.ui.show() app.exec()
上面这段代码没什么好讲的,都是我们之前学习过的内容,我们将我们设计的ui内部的各个属性拿出来,保存为类的成员变量,然后进行信号与槽的绑定,当登录按钮被点击后,跳转到我们自己定义的login函数中,这个函数是我们自己定义的一个类的成员函数,在login函数中,会将我们的登录成功与否的结果返回到textBrowser这个控件上,效果如下:
八.PyQt多线程
1.引入
将上一节课讲解的最后一个FeiQQ登录的按钮,适当修改代码,详细代码如下:
import sys
import time
from PyQt5.QtWidgets import *
from PyQt5 import uic
class MyWindow(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
self.ui = uic.loadUi("./login.ui")
# print(self.ui.__dict__) # 查看ui文件中有哪些控件
# 提取要操作的控件
self.user_name_qwidget = self.ui.lineEdit # 用户名输入框
self.password_qwidget = self.ui.lineEdit_2 # 密码输入框
self.login_btn = self.ui.pushButton # 登录按钮
self.forget_password_btn = self.ui.pushButton_2 # 忘记密码按钮
self.textBrowser = self.ui.textBrowser # 文本显示区域
# 绑定信号与槽函数
self.login_btn.clicked.connect(self.login)
def login(self):
"""登录按钮的槽函数"""
user_name = self.user_name_qwidget.text()
password = self.password_qwidget.text()
for i in range(10):
print("正在登录服务器....%d" % (i + 1))
time.sleep(1)
if user_name == "admin" and password == "123456":
self.textBrowser.setText("欢迎%s" % user_name)
self.textBrowser.repaint()
else:
self.textBrowser.setText("用户名或密码错误....请重试")
self.textBrowser.repaint()
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyWindow()
# 展示窗口
w.ui.show()
app.exec()
展示效果如下,非常的卡!
可以很明显的看到,程序是卡顿的,why?大名鼎鼎的Qt应该不止于此吧!答案是:只要是带界面的程序,一般来说程序运行后会用当前线程进行事件的检查、按钮等图形界面的更新操作,如果在执行某个逻辑代码(例如登录)时耗时非常严重,此时就会出现界面卡顿。
怎样做更好?答:我们一般将界面的显示用主线程来操作,逻辑功能代码或者耗时的代码都用另外线程进行处理。这也就是为什么要研究PyQt中的多线程了,因为它能实现多任务,让界面用一个线程更新,让逻辑代码在另外一个线程中,互不影响。
2.PyQt使用线程
1.使用QT Designer设计如下效果的ui文件
2.参考代码
import sys
import time
from PyQt5 import uic
from PyQt5.Qt import QApplication, QWidget, QThread
class MyThread(QThread):
def __init__(self):
super().__init__()
def run(self):
for i in range(10):
print("是MyThread线程中执行....%d" % (i + 1))
time.sleep(1)
class MyWin(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
self.ui = uic.loadUi("./thread-1.ui")
# 从ui文件中加载控件
lineedit = self.ui.lineEdit
btn1 = self.ui.pushButton
btn2 = self.ui.pushButton_2
# 给2个按钮绑定槽函数
btn1.clicked.connect(self.click_1) # 绑定槽函数
btn2.clicked.connect(self.click_2) # 绑定槽函数
def click_1(self):
for i in range(10):
print("是UI线程中执行....%d" % (i + 1))
time.sleep(1)
def click_2(self):
self.my_thread = MyThread() # 创建线程
self.my_thread.start() # 开始线程
if __name__ == "__main__":
app = QApplication(sys.argv)
myshow = MyWin()
myshow.ui.show()
app.exec()
3.运行效果
运行起来之后会发现,我们点击按钮1之后,巨卡,根本就无法在输入框中输入内容,我们重新运行程序,然后点击按钮2,发现非常顺畅,可以很快的就看到我们在输入框中输入的内容。
九.打包为可执行程序
在Windows下,要将PyQt应用程序打包成一个独立的.exe文件,可以使用PyInstaller工具。以下是将PyQt程序打包成独立可执行文件的步骤:
-
确保你已经安装了PyQt5和PyInstaller。如果没有安装,可以使用pip安装:
pip install PyQt5 PyInstaller -i https://pypi.tuna.tsinghua.edu.cn/simple
-
使用PyInstaller创建.exe文件。打开命令行或终端,导航到你的PyQt应用程序的Python脚本所在目录,运行以下命令:
pyinstaller --onefile --windowed your_script.py
其中your_script.py 是你的主Python脚本文件
- -onefile
选项指示PyInstaller创建单个打包文件。- -windowed
选项指示PyInstaller创建一个没有控制台窗口的程序(对于GUI应用程序很有用)
-
PyInstaller会在当前目录下创建一个名为dist的文件夹,你的.exe文件就在这个文件夹里。
-
运行生成的.exe文件,确保它能够在没有Python环境的机器上运行。
按照这种方法打包完发现报错了,效果如下图:
解决方法之一可以将ui文件转为py文件,然后把这个py文件中定义窗口的类导入,具体可以看一看导出的py。我这里显示的定义窗口的类名是Ui_Form;
然后再把class MyWin(QWidget): 改成 class MyWin(QWidget, Ui_Form): 添加一下父类,最后在初始化函数里面写一下转化的py文件中的绘制窗口的函数就可以了,我的是self.setupUi(self)。 这个弄好后蛮方便的,然后再通过刚刚介绍的打包程序,即可将程序打包成功了。