一、前言
1.1Qt与PyQt
PyQt是Qt在python中的封装库,其功能在于快速实现可视化界面软件在python环境下的部署。
PyQt的基本思想诸如封装继承、信号与槽的机制等等与Qt一致,只是实现是通过python语言。
1.2C++与Python
C++为编译型语言,接近硬件底层,合理利用指针可节约硬件资源,执行速度较快,适合嵌入式开发或大型服务器开发。
Python为解释型语言,语法简单,有大量现成的开源算法库诸如Numpy、Matplotlib、Tensorflow、Opencv-python等,其在人工智能算法开发领域有着得天独厚的优势。
python与C++在定义类时的区别:
(1)C++定义一个具体的类时往往有.h文件与.cpp文件两个文件存在,其中.h文件负责类的声明,包含声明其相关访问权限、相关类型的变量及方法,且在C++中可定义各种类型的指针变量,可通过动态分配内存的方式将某些变量分配至堆区。而.cpp文件负责该类的具体实现,某个文件引用其他文件时,通过#include "xxxx.h"
的形式包含。
(2)Python定义一个具体的类直接在.py文件中实现,声明与定义同时实现,将另外某个文件引入本文件通过import xxx
,或者from xxx import xxx
实现。
本博客记录通过PyQt创建界面程序的一些常规性的操作,操作环境为
win10
,pythonIDE为pycharm
,通过Anaconda3
管理python虚拟环境。
二、效果
主要实现的窗口操作为以下4点内容:
(1)在“灰色”区域,即自定义标题栏区域内,最左侧为logo图标,往右为软件名称标题,最右边分别外三个按钮,依次为“最小化”、“关闭”按钮;
(2)标题栏区域(包含logo以及名称标题上)拖动鼠标,实现整个窗口的拖动功能,拖拽其他区域,不会拖动整个窗口;
(3)中间区域为显示区域,鼠标移入、移出,相应位置颜色发生变化;
(4)点击左边第一块绿色区域后,切换显示界面进入下一页,下一页点击返回按钮后再返回主页面。
下图为鼠标移入、移出,拖动自定义标题栏的效果。
下图为点击具体区域后实现最小化界面、关闭界面、切换显示区域内容的效果。
三、代码
3.1 编程思想
对一个PyQt项目进行编程,思路与Qt项目类似:
Step1:首先设计一个主窗体类(这里可以是QMainWindow类,QWidget类,或是它们的派生类),令其在主函数中循环阻塞,进行GUI展示,这样即一个可视化程序的大框架;
Step2:再该窗体类上进行相关逻辑操作,可以设计多个窗体类进行嵌套、切换等操作,通常需通过QtDesigner直接进行界面布局设计,设置布局关系、尺寸、styleSheet
以及资源文件等,也可以直接代码实现(对于简单布局窗体可以,复杂布局窗体推荐还是利用QtDesigner);
Step3:内部逻辑关系的实现,合理利用信号与槽的机制,重写相关函数如mousePressEvent等,合理利用定时器等;
Step4:合理利用PyQt多线程(QThread)
、串口通信(QSerialPort)
、多媒体(QMovie、QSound等)
、图表模块(QChart)
等多个模块进行相关操作。
3.2 创建主窗体类
python工程文件夹下内容如下图所示,最上层包含image、sound、UI三个文件夹及多个.py,src.qrc文件,其中image、sound两个文件夹内分别放置图像、音效文件,UI文件夹内为.ui文件及其生成的界面类py文件。
工程文件夹内容的设置时,本人习惯将所有UI设计相关的.ui及其生成的.py文件统统放置于同一文件夹UI内,使得整个工程文件夹排列较为整洁。
PyQt工程没有Qt中的.pro文件,且也不谈debug或者release版本及其编译文件夹路径的配置问题。
3.2.1 main.py
在python项目中通常以main.py作为整个程序的入口。
通过if __name__ == '__main__':
语句提示python解释器此处开始运行,在页面上右击run
(shift+F10)快速运行该脚本
首先application = QApplication(sys.argv) # 窗口通讯
创建一个QApplication对象。然后再root = Widget() # 创建对象
创建窗体对象,调用该窗体对象的show()
方法,并通过sys.exit(application.exec_()) # 消息循环
阻塞进程。这样便创建了一个主窗体,后面要做的就是不断往主窗体里加各种对象以实现相应的效果。
import sys
from widget import *
if __name__ == '__main__':
application = QApplication(sys.argv) # 窗口通讯
application.setStyle('windows') # 设置 windows 风格
root = Widget() # 创建对象
root.show() # 展示窗口
sys.exit(application.exec_()) # 消息循环
3.2.2 GUI窗体类结构
3.2.2.1 PyQT界面类的定义及设计
一个PyQt界面类主要包含一个.ui文件,一个由.ui文件生成的界面py文件以及一个逻辑实现py文件,若.ui文件需要图片、字体或者音效等资源,额外需要一个.qrc文件及其生成的py文件。
命名规则,将.ui格式文件统一加ui_
前缀,生成的.py文件与.ui文件同名,逻辑实现py文件与该类同名。如定义一个名称为Widget
的类,则其ui文件名为ui_Widget.ui
,ui_Widget.py
,逻辑实现py为Widget.py
。
Step1:根据需求输入设计并绘制.ui文件
通过QtDesigner实现。
(1)绘制UI界面;
(2)有时需要导入.qrc资源文件;
(3)绘制完毕后通过UIC工具将.ui文件转换为.py文件;
(4)有qrc文件的通过RCC工具将.qrc文件转换为src_**.py文件。
Step2:根据逻辑关系编写逻辑py文件
通过Pycharm(或其它编辑器)实现。
3.2.2.2 class Widget
的定义及设计
在
main.py
中的Widget
类由Ui_Widget
以及Qwidget
派生,Ui_Widget
类涉及UI布局、QWidget
涉及窗口对象的一般性操作。
class Widget
结构如下图所示。
Widget
类自身有两个属性,即Title
以及WelcomePage
,Title
为自定义标题栏类WelcomePage
为欢迎页面类,这两个类均为PyQT界面类,有.ui文件及其生成的.py文件,也包括一个各自的逻辑实现py文件。
(1) ui_Widget.ui
Ui_Widget
由ui_widget.ui
文件生成,ui_widget.ui
如下图所示。
在右上方对象查看器中可以看到,在最顶层的QWidget
下面自由放置了一个QLabel
以及一个QStackedWidget
,Qwidget
类对象名称为Widget
,Widget
此时不设置布局,布局通过后续代码实现。
(2) ui_Widget.py
以下为ui_Widget.py文件代码,每次对UI界面重新布局,即修改ui_Widget.ui后,均需要通过UIC工具重新生成该代码文件,因此对建议在该文件上编写代码。
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'ui_widget.ui'
#
# Created by: PyQt5 UI code generator 5.9.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Widget(object):
def setupUi(self, Widget):
Widget.setObjectName("Widget")
Widget.resize(1600, 1000)
Widget.setMinimumSize(QtCore.QSize(1600, 1000))
Widget.setMaximumSize(QtCore.QSize(1600, 1000))
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(":/image/icon.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
Widget.setWindowIcon(icon)
Widget.setStyleSheet("QLabel#labelinfo\n"
"{\n"
" \n"
" background-color: rgb(255, 255, 255);\n"
" border:1px solid;\n"
"}")
self.stackedWidget = QtWidgets.QStackedWidget(Widget)
self.stackedWidget.setGeometry(QtCore.QRect(30, 90, 821, 561))
self.stackedWidget.setStyleSheet("")
self.stackedWidget.setObjectName("stackedWidget")
self.labelinfo = QtWidgets.QLabel(Widget)
self.labelinfo.setGeometry(QtCore.QRect(30, 680, 821, 40))
self.labelinfo.setMinimumSize(QtCore.QSize(0, 40))
self.labelinfo.setMaximumSize(QtCore.QSize(16777215, 40))
self.labelinfo.setObjectName("labelinfo")
self.retranslateUi(Widget)
QtCore.QMetaObject.connectSlotsByName(Widget)
def retranslateUi(self, Widget):
_translate = QtCore.QCoreApplication.translate
Widget.setWindowTitle(_translate("Widget", "Widget"))
self.labelinfo.setText(_translate("Widget", "TextLabel"))
import src_rc
(3) Widget.py
class Widget
即Widget类的实现,是在Widget.py文件中
class Widget(QWidget, Ui_Widget):
通过关键字class 定义类名称,后面括号中放父类的类名称。
def __init__(self, parent=None):
该类的构造方法,括号内self指代本实例自身,约定使用“self”,最后一个参数为“parent”,这是python内部会处理的参数,没有parent的窗口会被认定是顶层窗口,此处按默认写即可。
super(Widget, self).__init__(parent)
调用父类构造函数,这句必须有,不调用父类的构造函数的话无法显示Ui_Widget中的内容。
self.t1 = Title()
实例化一个自定义标题栏界面类。
init(self)
对界面的逻辑操作,如设置布局,设置信号与槽的连接情况等等操作。
self.setWindowFlags(Qt.FramelessWindowHint)
设置窗口的无边框属性,需设置该属性后自定义标题栏才能生效。
changepage(self)
切换页面至各自页面的操作。
backtowelPage(self)
返回主页面的操作。
from UI.ui_widget import *
from welcomepage import *
from title import *
from visualslampage import *
# 定义Widget类继承QWidget以及Ui_Widget,缺一不可,继承QWidget一般性窗口操作方法,继承Ui_Widget的Ui设计
class Widget(QWidget, Ui_Widget):
# 构造方法
def __init__(self, parent=None):
# 调用父类的构造方法
super(Widget, self).__init__(parent)
# 实例化标题栏
self.t1 = Title()
# 实例化欢迎页面
self.p1 = WelcomePage()
# 初始化窗体内的逻辑操作
self.init()
def init(self):
# 初始化Ui界面,此处调用的是Ui_Widget中的setupUi函数
self.setupUi(self)
# 设置无边框属性,该属性继承于QWidget
self.setWindowFlags(Qt.FramelessWindowHint)
# 新建一个QVBoxLayout实例,用于垂直布局
layout = QVBoxLayout(self)
layout.addWidget(self.t1)
layout.addWidget(self.stackedWidget)
layout.addWidget(self.labelinfo)
layout.setSpacing(0)
layout.setContentsMargins(0, 0, 0, 0)
self.stackedWidget.addWidget(self.p1)
self.stackedWidget.setCurrentIndex(0)
self.labelinfo.setText("欢迎使用近地告警地面数据记录系统!上海航空电器有限公司荣誉出品!")
#信号与槽连接,label按下后调用changepage函数
self.p1.label.label_pressed.connect(self.changepage)
#label按下信号对应的槽函数
def changepage(self):
if self.sender() == self.p1.label:
self.labelinfo.setText("进入视觉辅助模块!")
#新建visualslamPage实例
self.p4 = visualslamPage()
#连接visulslamPage中的按钮按下信号与backtowelPage函数
self.p4.pushButton.clicked.connect(self.backtowelPage)
self.stackedWidget.addWidget(self.p4)
self.stackedWidget.setCurrentIndex(1)
#visulslamPage中按钮按下信号所对应的槽函数
def backtowelPage(self):
self.stackedWidget.setCurrentIndex(0)
self.labelinfo.setText("主页")
if self.sender() == self.p4:
#删除visulslamPage对象
del self.p4
3.2.2.2 class Title
的定义及设计
Widget
类中属性self.t1 = Title()
实例化一个Title
类,在该类中实现了自定义标题栏的各个功能,如显示软件标题、logo、最小化、关闭,拖拽移动窗口等功能。
Title
类实现方法与Widget
类实现方法步骤一致,即设计.ui文件,生成其.py,编写逻辑py。
在实现逻辑功能的py文件中重写以下三个鼠标事件函数。
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.m_leftButtonPressed = True
self.m_start = event.globalPos()
def mouseMoveEvent(self, event):
if self.m_leftButtonPressed:
self.window().move(self.window().geometry().topLeft() + event.globalPos()-self.m_start)
self.m_start = event.globalPos()
def mouseReleaseEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.m_leftButtonPressed = False
3.2.2.2 class WelcomePage
的定义及设计
Widget
类中属性self.p1 = WelcomePage()
实例化一个WelcomePage
类,在该类中实现了多个模块的选择功能,即鼠标移入、移出、点击事件的设计。
WelcomePage
类实现方法与Widget
类实现方法步骤一致,即设计.ui文件,生成其.py,编写逻辑py。
在
WelcomePage
类中需要对普通的QLabel类做提升操作,将鼠标操作变化QLabel样式的效果封装起来。
(1) WelcomePage.py
此处的WelcomePage.py内暂未开展相关逻辑操作,仅作一个初始化父类Ui_WelcomePage类的功能。
(2) WelcomePage.ui
WelcomePage.ui的设计如下图,将各个Qlabel按照需求进行布局,对绿色区域的4个QLabel右键提升为welLabel
填写提升的类的类名称为"welLabel"
,头文件自动生成为"wellabel.h"
,这里由于是在python环境中,没有.h文件,忽略即可,这样编译器会在工程目录下自动寻找"wellabel.py"
文件引入"ui_WelcomePage.py"
。
现在仅需在"wellabel.py"
中实现对QLabel的封装效果即可。
(3) wellabel.py
from PyQt5.Qt import *
class welLabel(QLabel):
label_pressed = pyqtSignal()
def __init__(self, parent=None):
super(welLabel, self).__init__(parent)
def enterEvent(self, event):
self.setStyleSheet("border:6px solid;"
"border-color:rgb(255,0,0);"
"background-color: rgb(0, 0, 255);")
def leaveEvent(self, event):
self.setStyleSheet("border:0px")
self.setText(" ")
self.setAlignment(Qt.AlignCenter)
def mousePressEvent(self, event):
self.setStyleSheet("border:2px solid;"
"background-color: rgb(255, 0, 0);")
self.setFont(QFont("Microsoft YaHei", 30, 75))
self.setText("被点击了")
self.setAlignment(Qt.AlignCenter)
self.label_pressed.emit()
label_pressed = pyqtSignal()
为定义pyqt信号的操作,与Qt中需要在头文件中声明信号类型以及数据类型不同,python中直接在类定义中定义即可。
在
mousePressEvent(self, event)
中self.label_pressed.emit()
发出了上述信号,在Widget
类中将该信号与changepage(self)
连接,实现点击QLabel实现页面切换的操作。
页面切换效果如下图所示。
四、代码资源
代码
项目代码至https://download.csdn.net/download/wang_chao118/86511095处下载。
项目环境
本项目在win10平台,Anaconda3虚拟环境下运行,
通过cmd配置以下内容
conda create -n py37_pyqt5 python==3.7
pip install pyqt5
pip install pyqt5-tools
在Pycharm编辑器中设置项目的虚拟环境为上述环境py37_pyqt5