想要完成一个应用程序的开发,通常还需要一个可视化界面,QT就是可以帮助我们快速开发出可视化界面的一款工具,而利用QT实现页面布局,一般有两种方式,即通过qtdesigner和通过纯代码构建,第一种方式简单易见,可以通过拉取模块的方式快速实现页面的布局,而第二种方式需要自己心中首先构建好页面布局再进行编码,根据自己的需求进行选择。安装配置:pycharm+PyQt5+python最新开发环境配置,踩坑过程详解_Rose资源库-CSDN博客_pycharm和pyqt
0、安裝配置
安裝
pip install PyQt5 -i https://pypi.douban.com/simple
pip install PyQt5-tools -i https://pypi.douban.com/simple
如果想要基於pycharm進行開發,需要首先進行簡單配置
步骤:
File–>setting–>Tools–>External Tools
点击 + 号
>添加QtDesigner
- Name就是要添加的工具名称可以取
- Group就是要把这个工具添加到哪个工具箱里面,类是于文件夹
- Program是工具的位置,这里是安装QtDesigner,自然就是选QtDesigner的位置,这里可以填
Program 填 : /usr/lib/x86_64-linux-gnu/qt5/bin/designer
Arguments 填 : $FileName$
Working directory 填 : $ProjectFileDir$
>添加PyUIC
- Name就是要添加的工具名称可以自己取
- Group就是要把这个工具添加到哪个工具箱里面,类是于文件夹
Program 填 : /usr/bin/python3
Arguments 填 : -m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py
Working directory 填 : $FileDir$
即配置完成。
1、认识QtDesigner
打开QtDesigner,可以看到如下的界面,左侧是控件等,中间是页面的原始画布,右侧是其对应的属性,通过左侧拉取目标控件到画布上,然后在右侧属性中对其进行修改,可以实现界面的初始布局。
在使用Qtdesigner建立GUI程序时,会有QMainWindow, QWidget以及QDialog三种类型可供选择
其区别如下:
QmainWindow主窗口为用户提供一个应用程序框架,它有自己的布局,可以在布局中添加控件。在主窗口中可以添加控件,比如将工具栏、菜单栏、状态栏等添加到布局管理器中,是最常见的窗口形式,也可以说是GUI程序的主窗口。
QDialog是对话框窗口的基类。对话框主要用来执行短期任务,或者与用户进行互动,它可以是模态的,也可以是非模态的。他没有菜单栏、工具栏、状态栏等。
如果是主窗口,就使用QMainWindow类;
如果是对话框,就使用QDialog类;
如果不确定,有可能作为顶层窗口,也有可能嵌入到其他窗口,就使用QWidget类。
下面对其进行详细介绍,
(1)控件
布局(layouts)
![]() | 布局:
|
空间间隔组(Spacers):
![]() |
|
按钮组(Buttons)中各个按钮的名称依次解释如下:
![]() |
|
项目视图组(Item Views):
![]() |
|
项目组控件组(item widgets)
![]() |
容器组(Containers):
![]() |
|
输入部件组(Input Widgets):
![]() |
|
展示控件组(display widgets)
![]() |
还可以自定义一些控件,这里就不在赘述,详细可参考飞扬青云 (feiyangqingyun) - Gitee.com
有时候需要对整个页面进行弹性布局,这时候需要注意的是
同时还可以结合设置参数和空间间隔组来对整个界面进行一个合理的弹性布局,具体的可以参考Qt第二十一章:Qt Designer 之 布局_qt 信息输入界面 编辑框长短不一 怎么布局-CSDN博客额和
Qt Desginer布局方法_qt designer增加布局.
(2)信号与槽
信号就是页面中的一些操作事件,传递给槽函数进行后续的处理,如点击信号,双击信号等,而槽函数就是对接收到的信号进行相应的函数。在QtDesigner中实现信号与槽,可以通过如下操作
2、一些小知识点
》信号与槽之间的传参
具体实例
基于lambda表达式:widget.signal.connect( lambda: slot(para) )
基于[]:widget.signal[par].connect(slot)
基于partial函数:widget.signal.connect( partial(slot,para) )
》多线程
实例(实现一个简单的视频播放器):
#!/usr/bin/env python
#-*- coding:utf-8 -*-
# author: albert time: 2020/6/20
import sys
from PyQt5.QtWidgets import QApplication,QWidget,QPushButton,QLabel,QVBoxLayout,QFrame,QFileDialog
from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtGui import QPixmap, QImage
from PyQt5 import QtGui
import cv2,time
class my_form(QWidget):
def __init__(self):
super(my_form,self).__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("load_video")
self.setGeometry(50,50,800,600)
###添加控件
self.bt1=QPushButton("load_video")
self.lab=QLabel()
self.lab.setFrameShape(QFrame.StyledPanel)
###添加布局
ly=QVBoxLayout(self)
ly.addWidget(self.lab)
ly.addWidget(self.bt1)
###添加槽函数
self.bt1.clicked.connect(self.play_video)
self.show()
def play_video(self):
videoName,videoType= QFileDialog.getOpenFileName(self,
"打开视频",
"",
" *.mp4;;*.avi;;All Files (*)")
self.th = Thread(videoName,self.lab.width(),self.lab.height()) ###变成类的属性,否则会出现线程错误
self.th.changePixmap.connect(self.setImage)
self.th.start()
def setImage(self, image): ###这是在label控件上显示图像或视频
self.lab.setPixmap(QPixmap.fromImage(image))
class Thread(QThread):#采用线程来播放视频
changePixmap = pyqtSignal(QImage)
def __init__(self,videoName,width,height):
super(Thread,self).__init__()
self.videoName=videoName
self.width=width
self.height=height
def run(self):
cap = cv2.VideoCapture(self.videoName)
while (cap.isOpened()==True):
ret, frame = cap.read()
if ret:
rgbImage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
convertToQtFormat = QImage(rgbImage.data, rgbImage.shape[1], rgbImage.shape[0], QImage.Format_RGB888)#在这里可以对每帧图像进行处理,
p = convertToQtFormat.scaled(self.width, self.height, Qt.IgnoreAspectRatio)
self.changePixmap.emit(p)
time.sleep(0.01) #控制视频播放的速度
else:
break
def main():
app=QApplication(sys.argv)
window=my_form()
window.show()
app.exec_()
if __name__=="__main__":
main()
3、使用demo
通过上面对各控件的介绍,结合自己的需求进行拖拽布局,首先生成目标布局界面的ui文件,然后再利用pycharm里设计好的pyuic进行转换成可识别的py文件,最后再调用生成的布局py文件并显示,具体操作如下:
》ui布局
》转换布局py文件
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(526, 349)
self.pushButton = QtWidgets.QPushButton(Form)
self.pushButton.setGeometry(QtCore.QRect(370, 120, 51, 31))
self.pushButton.setObjectName("pushButton")
self.pushButton_2 = QtWidgets.QPushButton(Form)
self.pushButton_2.setGeometry(QtCore.QRect(370, 180, 51, 31))
self.pushButton_2.setObjectName("pushButton_2")
self.pushButton_3 = QtWidgets.QPushButton(Form)
self.pushButton_3.setGeometry(QtCore.QRect(370, 240, 51, 31))
self.pushButton_3.setObjectName("pushButton_3")
self.label = QtWidgets.QLabel(Form)
self.label.setGeometry(QtCore.QRect(160, 50, 241, 20))
font = QtGui.QFont()
font.setFamily("宋体")
font.setPointSize(20)
self.label.setFont(font)
self.label.setObjectName("label")
self.label_2 = QtWidgets.QLabel(Form)
self.label_2.setGeometry(QtCore.QRect(80, 100, 261, 191))
self.label_2.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.label_2.setLineWidth(1)
self.label_2.setText("")
self.label_2.setObjectName("label_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", "载入"))
self.pushButton_2.setText(_translate("Form", "检测"))
self.pushButton_3.setText(_translate("Form", "保存"))
self.label.setText(_translate("Form", "基于深度学习安全帽检测"))
》调用并显示
import sys
from PyQt5.QtWidgets import QApplication,QWidget,QMainWindow
from load_test import Ui_Form
from PyQt5 import QtCore
class my_form(QMainWindow,Ui_Form):
def __init__(self):
super(my_form,self).__init__()
self.setupUi(self)
if __name__=="__main__":
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) ###保证按原来布局正常显示
app=QApplication(sys.argv)
t=my_form()
t.show()
app.exec_()
对于界面如何适应不同分辨率显示问题,可以参考链接
(2)窗口跳转:当窗口A调用窗口B的时候,两个窗口不能是同一类型。如把A写成QWidget类型,把窗口B改成Dialog类型,通过触发A上事件,就可以成功调用B。
class A(QWidget):
def __init__(self):
...省略...
self.btn = QPushButton('跳转按钮')
def initUI(self):
pass
class B(QDialog):
def __init__(self):
pass
def initUI(self):
pass
if __name__ == '__main__':
app = QApplication(sys.argv)
a = A()
b = B()
a.show()
a.btn.clicked.connect(b.show) # 注意show,不要写成show()
app.exec_()
如果想要在两个窗口之间进行信息传递,这时候需要借助通过建立信号pyqtSignal 然后通过Connect 相应的槽函数来完成界面之间的数据传递。具体如下
class Form1():
signal_1 = pyqtSignal(str) #主界面的信号用来绑定子界面类的函数方法
def __init__(self, num=10):
#创建form2的对象
self.num = num
self.form2 = Form2() #实例化子界面类
#将自己的信号和Form2的接受函数绑定
self.signal_1.connect(self.form2.get_data)
#通过按键打开from2
self.pushButton.clicked.connect(self.open_form2())
def open_form2(self): #传递信号
self.form2.show() #通过pushbutton 展示子界面 如果只是做数据的传递,不打开第二个界面,可以 不调用self.form2.show() 方法
#向form2发送信号,将self.num发了出去
self.signal_1.emit(self.num) #通过自己的信号向子界面传递数据。要想多传递几个值,就在emit(值1,值2) 对应到子界面get_data接受就是2个参数,即get_data(值1,值2)
#将form2的信号_singal_2和主界面的getData_F2连接
self.form2.signal_2.connect(self.getData_F2)
def getData_F2(self,num2):
#用于接收Form2发过来的数据
self.num2 = num2 # num2 就是子界面传递过来的数据
class Form2(): #子界面类
signal_2 = pyqtSignal(str) #子界面类创建信号用来绑定主界面类的函数方法
def __init__(self,num2=20):
self.num2 = num2
def get_data(self,num):
#接受Form1传过来的num,保存到自己的变量里面。
self.num = num
#子界面通过signal_2 向主界面传递数据
signal_2.emit(self.num2)
(3)垂直布局和水平布局,槽函数
import sys
from PyQt5.QtWidgets import QApplication,QWidget,QPushButton,QFrame,QVBoxLayout,QHBoxLayout
from PyQt5.QtGui import QColor
class my_class(QWidget):
def __init__(self):
super(my_class,self).__init__()
self.initUI()
def initUI(self):
##窗体初始化
self.setWindowTitle("test3")
self.color=QColor(0,0,0)
dk=app.desktop()
self.setGeometry(dk.width()/2-self.width()/2,50,400,300)
##添加控件
btn1=QPushButton("red")
btn1.setCheckable(True)
btn1.clicked[bool].connect(self.setColor)
btn2=QPushButton("green")
btn2.setCheckable(True)
btn2.clicked[bool].connect(self.setColor)
btn3=QPushButton("blue")
btn3.setCheckable(True)
btn3.clicked[bool].connect(self.setColor)
self.frame=QFrame()
self.frame.setStyleSheet("QWidget{background-color:%s}"%self.color.name())
###控件布局
vly=QVBoxLayout()
vly.addWidget(btn1)
vly.addWidget(btn2)
vly.addWidget(btn3)
hly=QHBoxLayout(self) ###父容器种的声明需要带上self
hly.addLayout(vly)
hly.addWidget(self.frame)
self.show()
def setColor(self,p):
b=self.sender() ###接受信号,槽函数传参
v=255 if p else 0
print(p,b.text(),b)
if b.text() =="red":
self.color.setRed(v)
elif b.text() =="blue":
self.color.setBlue(v)
elif b.text()=="green":
self.color.setGreen(v)
print(self.color.name())
self.frame.setStyleSheet("QWidget{background-color:%s}"%self.color.name())
if __name__=="__main__":
app=QApplication(sys.argv)
t=my_class()
app.exec_()
对PyQt5的界面进行布局管理主要有两种方法,即采用绝对位置和布局类。在PyQt5中有四种布局方式,即水平布局、垂直布局、网格布局、表单布局,以及两种布局方法,即addLayout()和addWidget(),其中addLayout()用于在布局中插入子布局,addWidget()用于在布局中插入控件。有时候针对一些比较复制的界面,可能还会涉及到嵌套布局,具体可以参考博客:PyQt5中的布局管理-嵌套布局 - Laney_Sun - 博客园,需要注意的一点就是,如果想要将写好的gui程序移植到另一台电脑上,为了避免因为分辨率等原因导致界面混乱,最好使用弹性布局,即常用的网格布局等。
4、打包
一般而言,当我们开发好了应用程序后,应该将其打包再进行发布,这里推荐使用Pyinstaller将其打包成.exe文件,其中pyinstaller使用方法可以参考《使用Pyinstaller转换.py文件为.exe可执行程序》。打包具体过程如下:
pyinstaller.exe -F call_login.py -w
成功的话,然后会在当前目录下生成三个文件,在dist目录下即可发现打包好的exe格式文件,双击即可运行。
最后的最后安利一下自己的个人公众号,有兴趣的可以关注一下
补: 如果在运行脚本时,出现“Could not load the Qt platform plugin “xcb” in…”问题,常见的解决方式有两种:
- 可能导致的原因是opencv冲突,解决方法如下
pip uninstall opencv-python
pip install opencv-python-headless
- 还有一种解决方式就是终端下运行
export QT_QPA_PLATFORM=offscreen
或者通過設置(加在代碼前或加入環境變量)
envpath = '/home/uto/anaconda3/lib/python3.9/site-packages/cv2/qt/plugins/platforms'
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = envpath
参考链接:
Qt学习之Qt控件的介绍_tsvico的博客-CSDN博客_qt的控件(Qt学习之Qt控件的介绍)
[ PyQt入门教程 ] Qt Designer工具的使用 - 锅边糊 - 博客园([ PyQt入门教程 ] Qt Designer工具的使用)
PyQt5实现多窗口切换的框架_shangxiaqiusuo1的博客-CSDN博客_pyqt5实现多窗口切换(PyQt5实现多窗口切换的框架)