PyQt5基础学习

19 篇文章 3 订阅
12 篇文章 0 订阅

PyQt5基础学习

Pycharm配置External Tools: Qt Designer 和 pyUIC

pyUIC配置:

Qt Designer配置:

对pyQt界面的开发,建议先了解一下下面这几个章节

  • PyQt5中各大控件主要用来干什么
  • PyQt5如何对这些控件进行布局
  • PyQt如何为这些控件绑定事件

在开发过程中,一般步骤如下

  • Qt Designer绘制MainWindow界面图,完成控件的选择、属性的设置、以及布局的调整
  • 然后将生成的多个qt_mainwindow.ui转换成python代码,得到多个界面对象(这些界面对象继承的是object类,并不是QtWidget类)。
  • 我们只需要在创建页面时继承上一步生成的界面对象,并再继承一个QMainWidow,才能使用self.show() 实现界面的绘制(注意调用super()时传入的参数的顺序,还要记得在__init__中通过self.setupUi(self)创建父类窗体对象)。
  • 接着在该页面类中,定义槽函数(继承/自定义)完成界面对象中各个控件的事件绑定;通过增加弹出窗口控件(QMessageBox、QInputDialog、QFileDialog)来完善界面流程。

这里提醒一下:在使用pycharm进行pyqt5开发时,运行如果出现卡顿,说明运行有错误,但是控制台不会输出,需要打开调试运行项目才可以看到错误信息

PyQt5控件介绍

参考

主要包括:

  1. 按钮控件

    • QPushButton:仅仅用来被点击
    • QCommandLinkButton:加上详情
    • QRadioButton:单选按钮,可选择某一单独菜单
    • QCheckBox:复选框,可以同时选择多个。
  2. 输入控件

    • QLineEdit:单行输入框
    • QTextEdit:多行输入框,不仅可以输入文本,还可以输入超链接、图片等富文本
    • QPlainTextEdit:普通多行文本
    • QKeySequenceEdit:采集快捷方式(快捷键)
    • 步长调节(QAbstractSpinBox) (键盘+鼠标)
    • QDateTimeEdit:采集日期时间
    • QDateEdit:单独的日期。
    • QTimeEdit:单独的时间。
    • QSpinBox:输入整型数字。
    • QDoubleSpinBox:输入浮点型数字。
    • QComboBox:组合框(下拉选择输入),常用于填地址
    • QFontComboBox:组合框,Font 是字体的意思
    • QColorDialog:选择颜色的对话框
    • QFileDialog:选择文件的对话框。
    • QFontDialog:选择字体的详细设置的对话框
    • QInputDialog:弹出输入框的对话框(模态框)。
  3. 展示控件

    • QLabel:普通文本(仅有展示作用,一般不可编辑),数字富文本(带格式的文本、超链接),QLabel-超链接,图片,QLabel-动画(.gif)
    • QLCDNumber:像 LCD 灯一样
    • QProgressBar:进度条
    • QMessageBox:消息框
    • QErrorMessage:错误对话框
    • QProgressDialog:进程对话框
  4. 其他

PyQt5布局介绍(4大布局)

参考

1、QMainWindow和QWidget之间的关系

在了解Qt中基本布局之前,我们要先明白QMainWindow和QWidget之间的关系

如果一个窗口包含一个或多个窗口,那么这个窗口就是父窗口,被包含的窗口则是子窗口。没有父窗口的窗口就是顶层窗口,QMainWindow 就是一个顶层窗口,它可以包含很多界面元素,如菜单栏、工具栏、状态栏、子窗口等。

QWidget类

  • QWidget 类是所有用户界面对象的基类。它继承自QObject 和 QPaintDevice。

    在这里插入图片描述

  • 没有父类的 QWidget 叫做 Independent Window(独立窗口),可设置窗口标题,图标等;有父类的叫 Child Widgets(子部件),显示在父类中。

  • QWidget可内嵌很多子部件,这些子部件需要通过Layout(布局)来加入到该QWidget中。

QMainWindow类

QMainWindow从QWidget类继承:

在这里插入图片描述

  • QMainWindow是主窗体,有菜单栏、工具栏、状态栏,浮动窗体区,中心窗体区等。如果这个类需要作为主窗体,那么使用QMainWindow。
  • QMainWindow必须设置中心窗体,通过 setCentralWidget 函数设置。中心窗体是QWidget 类的实例。
  • QMainWindow 可使用 saveState() 存储布局的状态;稍后可使用 restoreState() 恢复。

QMainWindow继承自QWidget类,拥有它的所有派生方法和属性,比较重要的方法如下:

  • addToolBar():添加工具栏
  • centralWidget():返回窗口中心的一个控件,未设置时返回NULL
  • menuBar():返回主窗口的菜单栏
  • setCentralWidget()设置窗口中心的控件
  • setStatusBar():设置状态栏
  • statusBar():获得状态栏对象后,调用状态栏对象的
  • showMessage(message,int timeout=0):方法,显示状态栏信息。其中第一个参数是要显示的状态栏信息,第二个参数是信息停留的时间,单位是毫秒(ms),默认是0,表示一直显示状态栏信息。

NoteQMainWindow不能设置布局(使用setLayout()方法),因为它有自己的布局

QMainWindow类主要有两种写法:

  • 通过setCentralwidget,将组件放置在QMainWindows正中央(相对布局)

    该步骤往往是通过layout将各个组件布局好了,最后使用setCentralwidget,将layout嵌入widget中,再嵌入到QMainWindows的正中央。

    import sys
    from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout
    from PyQt5.QtGui import QPalette, QColor
    
    class Color(QWidget):
        def __init__(self,color):
            super(Color,self).__init__()
            self.setAutoFillBackground(True)
    
            palette = self.palette()
            palette.setColor(QPalette.Window, QColor(color))
            self.setPalette(palette)
    
    class MainWindow(QMainWindow):
    
        def __init__(self):
            super(MainWindow, self).__init__()
    
            self.setWindowTitle("My App")
    
            layout = QHBoxLayout()
    
            layout.addWidget(Color('red'))   #加入自定义的Widget控件(Color)
            layout.addWidget(Color('green'))   #加入自定义的Widget控件(Color)
            layout.addWidget(Color('blue'))   #加入自定义的Widget控件(Color)
            layout.addWidget(Color('yellow'))
    
            self.setObjectName("MainWindow")
            self.resize(100, 100)
    
            # 将widget组件放入到mainWindows中,使用相对位置布局
            widget = QWidget()
            widget.setLayout(layout)
            self.setCentralWidget(widget)
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        window = MainWindow()
        window.show()
        app.exec()
    

在这里插入图片描述

  • 将组件放入QMainWindow中(QWidget(self) ),接着使用绝对位置布局

    如果将上面的代码修改为

    #将widget组件放入QMainWindow中,使用绝对位置布局
    widget = QWidget(self)
    widget.setLayout(layout)
    

    效果如下:

    在这里插入图片描述

    其他代码参考如下:

    import sys
    from PyQt5.QtWidgets import QMainWindow,QApplication,QDesktopWidget,QPushButton
    
    class MainWindow(QMainWindow):
        def __init__(self):
            super(MainWindow,self).__init__()
    
            self.resize(400,400)
            self.setWindowTitle("第二个主窗口")
            button = QPushButton("关闭主窗口",self)
            button.move(100,200)
            button.clicked.connect(self.close)
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        form = MainWindow()
        form.show()
        sys.exit(app.exec_())
    

Qt中的4种基本布局(水平,垂直,栅格,网格,堆栈)

在这里插入图片描述

2、QHBoxLayout:水平布局,addWidget

QHBoxLayout:水平布局(widget.setLayout,setCentralwidget 和 QWidget(self) )

QHBoxLayout是相同的,除了水平移动。 添加小部件会将其添加到右侧。

在这里插入图片描述

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout
from PyQt5.QtGui import QPalette, QColor

class Color(QWidget):
    def __init__(self,color):
        super(Color,self).__init__()
        self.setAutoFillBackground(True)

        palette = self.palette()
        palette.setColor(QPalette.Window, QColor(color))
        self.setPalette(palette)

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")

        layout = QHBoxLayout()

        layout.addWidget(Color('red'))   #加入自定义的Widget控件(Color)
        layout.addWidget(Color('green'))   #加入自定义的Widget控件(Color)
        layout.addWidget(Color('blue'))   #加入自定义的Widget控件(Color)
        layout.addWidget(Color('yellow'))   

        widget = QWidget()
        widget.setLayout(layout)   #设置Widget布局
        self.setCentralWidget(widget)  #setCentralWidget可以把Qwidget设置为QMainWindow主窗口中的中心控件

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()  #执行widget的show方法
    app.exec()

3、QVBoxLayout:垂直布局,addWidget

使用QVBoxLayout,你可以将小部件线性排列。 添加小部件将把它添加到列的底部。

在这里插入图片描述

...
layout = QVBoxLayout()

layout.addWidget(Color('red'))   #加入自定义的Widget控件(Color)
layout.addWidget(Color('green'))   #加入自定义的Widget控件(Color)
layout.addWidget(Color('blue'))   #加入自定义的Widget控件(Color)
layout.addWidget(Color('yellow'))
...

4、QHBoxLayout和QVBoxLayout混合使用:嵌套布局(栅格布局),addLayout

在这里插入图片描述

...
super(MainWindow, self).__init__()

self.setWindowTitle("My App")

layout1 = QHBoxLayout()
layout2 = QVBoxLayout()
layout3 = QVBoxLayout()

layout2.addWidget(Color('red'))
layout2.addWidget(Color('yellow'))
layout2.addWidget(Color('purple'))

layout1.addLayout( layout2 )   #layout2为垂直布局,水平布局中嵌套垂直布局

layout1.addWidget(Color('green'))  #水平布局中直接嵌套租件

layout3.addWidget(Color('red'))
layout3.addWidget(Color('purple'))

layout1.addLayout(layout3)

widget = QWidget()
widget.setLayout(layout1)
self.setCentralWidget(widget)  #setCentralWidget可以把Qwidget设置为QMainWindow主窗口中的中心控件

设置布局周围的间隔:

layout1.setContentsMargins(15, 0, 15, 15)  #设置水平布局的周围间隔(左,上,右,下)

在这里插入图片描述

设置元素之间的间距:

layout1.setSpacing(20)   #设置水平布局元素之间的间距

在这里插入图片描述

5、QGridLayout:QGridLayout显示每个位置的网格位置,addWidget

QGridLayout允许您在网格中定位项目。为每个小部件指定行和列的位置。 您可以跳过元素,它们将被保留为空。有用的是,对于QGridLayout,你不需要填充网格中的所有位置

在这里插入图片描述

layout = QGridLayout()

layout.addWidget(Color('red'), 0, 3)
layout.addWidget(Color('green'), 1, 1)
layout.addWidget(Color('blue'), 2, 2)
layout.addWidget(Color('purple'), 3, 0)

6、QStackedLayout:堆栈布局,允许将第z个部件放置最上面

这种布局允许您将元素直接放置在另一个元素的前面。 然后可以选择要显示的小部件。 您可以使用它在图形应用程序中绘制层,或模仿类似选项卡的界面。 注意还有qStackedWidget,它是一个容器小部件,工作方式完全相同。 如果你想用setCentralwidget直接添加一个堆栈到QMainWindow,这是很有用的。

在这里插入图片描述

Qstackkedlayout -在使用中,只有最上面的小部件是可见的,默认情况下是第一个添加到布局的小部件。

layout = QStackedLayout()

layout.addWidget(Color("red"))
layout.addWidget(Color("green"))
layout.addWidget(Color("blue"))
layout.addWidget(Color("yellow"))

# layout.setCurrentIndex(3)  #yellow在最上面
layout.setCurrentIndex(2)  #blue在最上面

在这里插入图片描述

PyQt5事件处理

参考

事件处理离不开监听(信号)和回调(槽函数),其中槽函数包括自定义的槽函数和QWidget提供的槽函数,其中前者需要和指定的组件信号进行绑定,而后者不用。

1、窗口关闭-按钮事件(自定义槽函数)

  • textedit部件的textChanged信号 → 绑定 → on_textchanged_func槽函数
  • button部件的clicked信号 → 绑定 → on_clicked_func槽函数
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QTextEdit, QPushButton, QMessageBox, QVBoxLayout

class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()

        self.is_saved = True

        #实例化一个QTextEdit控件用于文本编辑,将其textChanged信号和自定义的槽函数连接起来
        self.textedit = QTextEdit(self)
        self.textedit.textChanged.connect(self.on_textchanged_func)  

        #实例化一个按钮用于保存操作,将clicked信号与自定义的槽函数进行连接
        self.button = QPushButton('Save', self)
        self.button.clicked.connect(self.on_clicked_func)

        self.v_layout = QVBoxLayout()
        self.v_layout.addWidget(self.textedit)
        self.v_layout.addWidget(self.button)
        self.setLayout(self.v_layout)

    def on_textchanged_func(self):
        if self.textedit.toPlainText():
            self.is_saved = False
        else:
            self.is_saved = True

    def on_clicked_func(self):
        self.save_func(self.textedit.toPlainText())  #如果文本框不空,则保存成文件
        self.is_saved = True

    def save_func(self, text):   
        with open('saved.txt', 'w') as f:
            f.write(text)

    def closeEvent(self, QCloseEvent):    # 窗口关闭事件
        if not self.is_saved:
            choice = QMessageBox.question(self, '', 'Do you want to save the text?',
                                          QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
            if choice == QMessageBox.Yes:
                self.save_func(self.textedit.toPlainText())
                QCloseEvent.accept()
            elif choice == QMessageBox.No:
                QCloseEvent.accept()
            else:
                QCloseEvent.ignore()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

在这里插入图片描述

  1. is_saved变量用于记录用户是否已经进行保存;
  2. 实例化一个QTextEdit控件用于文本编辑,将其textChanged信号和自定义的槽函数连接起来;
  3. 实例化一个按钮用于保存操作,将clicked信号与自定义的槽函数进行连接:
  4. 这里我们重新定义了QWidget的窗口关闭函数closeEvent(),如果用户还没有进行保存,则弹出一个QMessageBox窗口询问是否保存。

2、鼠标事件(QWidget内置槽函数)

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout

class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.button_label = QLabel('No Button Pressed', self)              # 1、button_label用来显示鼠标的点击和释放动作
        self.button_label.setStyleSheet("font-size:25px;")

        self.xy_label = QLabel('x:0, y:0', self)                           # 2、xy_label用于记录鼠标相对于QWidget窗口的坐标
        self.xy_label.setStyleSheet("font-size:25px;")

        self.global_xy_label = QLabel('global x:0, global y:0', self)      # 3、global_xy_label用于记录鼠标相对于显示屏屏幕的坐标
        self.global_xy_label.setStyleSheet("font-size:25px;")

        self.button_label.setAlignment(Qt.AlignCenter)  #button_label居中定位
        self.xy_label.setAlignment(Qt.AlignCenter)     #xy_label居中定位
        self.global_xy_label.setAlignment(Qt.AlignCenter)

        self.v_layout = QVBoxLayout()
        self.v_layout.addWidget(self.button_label)
        self.v_layout.addWidget(self.xy_label)
        self.v_layout.addWidget(self.global_xy_label)
        self.setLayout(self.v_layout)

        self.resize(300, 300)
        self.setMouseTracking(True)                                        # 4、setMouseTracking(True)方法可以让窗口始终追踪鼠标,否则只能每次等鼠标被点击后,窗口才会开始记录鼠标的动作变化;而鼠标释放后,窗口又会不进行记录了,这样比较麻烦

    def mouseMoveEvent(self, QMouseEvent):                                 # 5、mouseMoveEvent()为鼠标移动时所触发的响应函数
        x = QMouseEvent.x()
        y = QMouseEvent.y()
        global_x = QMouseEvent.globalX()
        global_y = QMouseEvent.globalY()

        self.xy_label.setText('x:{}, y:{}'.format(x, y))
        self.global_xy_label.setText('global x:{}, global y:{}'.format(global_x, global_y))

    def mousePressEvent(self, QMouseEvent):                                # 6、mousePressEvent()为鼠标被按下时所触发的响应函数
        if QMouseEvent.button() == Qt.LeftButton:
            self.button_label.setText('Left Button Pressed')
        elif QMouseEvent.button() == Qt.MidButton:
            self.button_label.setText('Middle Button Pressed')
        elif QMouseEvent.button() == Qt.RightButton:
            self.button_label.setText('Right Button Pressed')

    def mouseReleaseEvent(self, QMouseEvent):                              # 7、mouseReleaseEvent()为鼠标释放时所触发的响应函数
        if QMouseEvent.button() == Qt.LeftButton:
            self.button_label.setText('Left Button Released')
        elif QMouseEvent.button() == Qt.MidButton:
            self.button_label.setText('Middle Button Released')
        elif QMouseEvent.button() == Qt.RightButton:
            self.button_label.setText('Right Button Released')

    def mouseDoubleClickEvent(self, QMouseEvent):                          # 8、mouseDoubleClickEvent()为鼠标被双击时所触发的响应函数
        if QMouseEvent.button() == Qt.LeftButton:
            self.button_label.setText('Left Button Double Clikced')
        elif QMouseEvent.button() == Qt.MidButton:
            self.button_label.setText('Middle Button Double Clicked')
        elif QMouseEvent.button() == Qt.RightButton:
            self.button_label.setText('Right Button Double Clikced')

if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())
  1. button_label 用来显示鼠标的点击和释放动作
  2. xy_label 用于记录鼠标相对于QWidget窗口的坐标
  3. global_xy_label 用于记录鼠标相对于显示屏屏幕的坐标

在这里插入图片描述

3、键盘事件(setPixmap设置图片,内置槽函数)

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout

'''键盘事件'''
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout

class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.pic_label = QLabel(self)  # 1、pic_label用于设置图片,先将初始化的图片设为temp.png;
        self.pic_label.setPixmap(QPixmap('images/keyboard.png'))   
        self.pic_label.setAlignment(Qt.AlignCenter)

        self.key_label = QLabel('No Key Pressed', self)  # 2、key_label用于记录按键状态;
        self.key_label.setAlignment(Qt.AlignCenter)

        self.v_layout = QVBoxLayout()
        self.v_layout.addWidget(self.pic_label)
        self.v_layout.addWidget(self.key_label)
        self.setLayout(self.v_layout)

    def keyPressEvent(self, QKeyEvent):  # 3、keyPressEvent()为键盘某个键被按下时所触发的响应函数,在这个函数中我们判断被按下的键种类,
                                        # 并将pic_label设为相应的箭头图片,将key_label设为相应的文本;
        if QKeyEvent.key() == Qt.Key_Up:   #↑键
            self.pic_label.setPixmap(QPixmap('images/up.png'))
            self.key_label.setText('Key Up Pressed')
        elif QKeyEvent.key() == Qt.Key_Down:  #↓键
            self.pic_label.setPixmap(QPixmap('images/down.png'))
            self.key_label.setText('Key Down Pressed')
        elif QKeyEvent.key() == Qt.Key_Left:  #←键
            self.pic_label.setPixmap(QPixmap('images/left.png'))
            self.key_label.setText('Key Left Pressed')
        elif QKeyEvent.key() == Qt.Key_Right:   #→键
            self.pic_label.setPixmap(QPixmap('images/right.png'))
            self.key_label.setText('Key Right Pressed')

    def keyReleaseEvent(self, QKeyEvent):  # 4. keyReleasedEvent()在键盘上的任意键被释放时所触发的响应函数,在这个函数中,我们将pic_label设为初始图片keyboard.png,并将key_label文本设为‘Key Released'。
        self.pic_label.setPixmap(QPixmap('images/keyboard.png'))
        self.key_label.setText('Key Released')

if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())
  1. pic_label用于设置图片,先将初始化的图片设为keyboard.png;
  2. key_label用于记录按键状态
  3. keyPressEvent()为键盘某个键被按下时所触发的响应函数,在这个函数中我们判断被按下的键种类,并将pic_label设为相应的箭头图片,将key_label设为相应的文本;
  4. keyReleasedEvent()在键盘上的任意键被释放时所触发的响应函数,在这个函数中,我们将pic_label设为初始图片keyboard.png,并将key_label文本设为‘Key Released’。

在这里插入图片描述

常见控件使用

1、QImage,QPixmap绘制图片

参考

将cv2读取的图片加载到QImage控件中,代码如下:

img = cv2.imread('images/up.png')
height, width, bytesPerComponent = img.shape
bytesPerLine = 3 * width
cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img)

#利用QImage加载图片到组件中
QImg = QImage(img.data, width, height, bytesPerLine, QImage.Format_RGB888)  #PIL image

pixmap = QPixmap.fromImage(QImg)  #将QImage图片加载到QPixmap组件中,利用QPixmap组件绘制图片
self.lb.setPixmap(pixmap)

但是这样绘制的图片和Qlabel控件的大小不匹配,需要对图片进行缩放,代码如下(返回结果仍然是QImage组件):

qImg_scaled = qImg.scaled(draw_label.width(), draw_label.height(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)

pixmap = QtGui.QPixmap.fromImage(qImg_scaled)
draw_label.setPixmap(pixmap)   #利用QPixmap组件绘制图片

清除draw_label “绑定“ 的图片

draw_label.clear()

2、页面跳转事件基本逻辑

参考

假设登录页面如下:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'student_login.ui'
#
# Created by: PyQt5 UI code generator 5.15.6
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(260, 70, 331, 111))
        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(22)
        self.label.setFont(font)
        self.label.setObjectName("label")
        self.label_2 = QtWidgets.QLabel(self.centralwidget)
        self.label_2.setGeometry(QtCore.QRect(120, 210, 91, 51))
        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(18)
        self.label_2.setFont(font)
        self.label_2.setObjectName("label_2")
        self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
        self.textEdit.setGeometry(QtCore.QRect(220, 210, 371, 51))
        self.textEdit.setObjectName("textEdit")
        self.label_3 = QtWidgets.QLabel(self.centralwidget)
        self.label_3.setGeometry(QtCore.QRect(120, 290, 91, 51))
        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(18)
        self.label_3.setFont(font)
        self.label_3.setObjectName("label_3")
        self.textEdit_2 = QtWidgets.QTextEdit(self.centralwidget)
        self.textEdit_2.setGeometry(QtCore.QRect(220, 290, 371, 51))
        self.textEdit_2.setObjectName("textEdit_2")
        self.label_4 = QtWidgets.QLabel(self.centralwidget)
        self.label_4.setGeometry(QtCore.QRect(120, 370, 91, 51))
        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(18)
        self.label_4.setFont(font)
        self.label_4.setObjectName("label_4")
        self.textEdit_3 = QtWidgets.QTextEdit(self.centralwidget)
        self.textEdit_3.setGeometry(QtCore.QRect(220, 370, 371, 51))
        self.textEdit_3.setObjectName("textEdit_3")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(230, 480, 101, 41))
        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(18)
        self.pushButton.setFont(font)
        self.pushButton.setObjectName("pushButton")
        self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_2.setGeometry(QtCore.QRect(460, 480, 101, 41))
        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(18)
        self.pushButton_2.setFont(font)
        self.pushButton_2.setObjectName("pushButton_2")
        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.label.setText(_translate("MainWindow", "在线教育系统(学生端)"))
        self.label_2.setText(_translate("MainWindow", "用户名"))
        self.label_3.setText(_translate("MainWindow", "   密码"))
        self.label_4.setText(_translate("MainWindow", "房间号"))
        self.pushButton.setText(_translate("MainWindow", "登录"))
        self.pushButton_2.setText(_translate("MainWindow", "注册"))

页面效果如下:

关于登录按钮的事件绑定代码如下:

class LoginWindow(QtWidgets.QMainWindow, Ui_MainWindow):

    def __init__(self):
        self.pushButton.clicked.connect(lambda : self.login_clicked())   #为登录按钮绑定login_clicked 自定义槽函数
        self.pushButton_2.clicked.connect(lambda : self.register_redirect())    #为注册按钮绑定register_redirect 自定义槽函数

button在绑定事件时可能会报错:argument 1 has unexpected type ‘NoneType’,参考https://www.shuzhiduo.com/A/pRdBYpyP5n/

Note

  • 如果在点击按钮时,页面可以实现跳转,但是却一闪而过,可能存在的问题(参考【Pyqt5】窗口跳转闪退如何解决):
    #进入疲劳检测系统
    def enter(self):
        calibrateWindow = CalibrateWindow()
        calibrateWindow.show()  # 跳转至校准页面
        close()  # 关掉当前页面
        calibrateWindow.calibrate()  #出现校准弹窗
    
    可以通过如下方法解决(方法里创建属于类对象(self)的窗口对象):
    #进入疲劳检测系统
    def enter(self):
        self.calibrateWindow = CalibrateWindow()
        self.calibrateWindow.show()  # 跳转至校准页面
        self.close()  # 关掉当前页面
        self.calibrateWindow.calibrate()  #出现校准弹窗
    
  • 如果还不能解决,则给跳转的页面绑定按钮事件或者重写closeEvent方法等,比如在新调整的页面类中:
        def __init__(self,face_detector,landmark_detector):
            super(DetectWindow, self).__init__()
            self.setupUi(self)  # 创建窗体对象
            self.init()
    
    	def init(self):
        	self.pushButton.clicked.connect(lambda : self.fatigue_detect())
        
        #重写窗口关闭事件:关闭摄像头
    	def closeEvent(self,event):
            if(self.camera != None and self.camera.isOpened()):
                cv2.destroyAllWindows()
                self.camera.release()
            super().closeEvent(event)
    

3、为QPushButton设置图标

参考

4、消息弹出框按钮点击识别

参考

choice = QMessageBox.question(self, "提示", "你已创建过该房间,是否进入", QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
if choice == QMessageBox.Yes:
    print("Yes")
	self.join_channel()   #跳转页面
else:
	print("No")

注意,这里不用写choice.show()来显示弹出窗,而且还会报错。

这里要格外注意:QMessageBox.question中,question,warning,information是小写的,如果写成QMessageBox.Question,会报:TypeError: 'Icon' object is not callable

5、弹出下拉框

参考

# 1为默认选中选项目,True/False 列表框是否可编辑。 
items = ["Spring", "Summer", "Fall", "Winter"]
value, ok = QInputDialog.getItem(self, "输入框标题", "这是提示信息\n\n请选择季节:", items, 1, True)

注意:输入后有两个选项,点击OK则代码中value为输入内容,ok为True;点击Cancel则value为空,ok为False。

6、自适应窗口大小的控件

参考

自适应调整QLabel字体

  • 根据界面大小自适应调整Qlabel字体大小:将 Qlabel 的scaledContents 属性置为True

自适应调整lineEdit输入框的大小

  • 修改输入框字体大小,此时输入框的高度会发生改变

  • lineEdit放在表单布局或者网格布局中,通过缩放这个布局,实现输入框宽高的调整

在缩放窗口自适应调整控件时,要注意:

如果把控件放在MainWindow里面,由于MainWindow自带布局(centerWidget)MainWindow的缩放并不会带动MainWindow内布局的缩放,布局里的控件也无法实现缩放

效果如下:
在这里插入图片描述

在这里插入图片描述

所以对于QMainWindow控件比较好的方法就是固定窗口的大小,让它无法缩放。

7、在布局下调整/固定控件大小

参考PyQt在布局下调整控件大小

直接修改控件(QMainWindow,lineEdit等控件)的minimumSize和maximumSize中的长、宽,两者保持一致时,该控件的大小就不会随着布局的缩放发生改变。

如果对主页面(MainWindow)进行设置,如果max,min长宽一致,则该窗口不支持缩放

8、利用ScollArea动态增加的QLabel

参考Pyqt5 scroll 滑动(滚动)条对动态增加的控件进行控制(保姆级教程)

这里需要用到scrollArea容器,参考

如果scrollArea容器没有显示滚动条,主要检查如下两个步骤:

  1. scrollArea的widgetResizable是否置为False

  2. 撑大scrollArea内部区域的大小,即设置scrollAreaWidgetContentsgeometryheight,width,当大于一定值时,会存在横轴/纵轴滚动条

  3. 如果想横轴不出现滚动条,只想要纵轴显示滚动条,则可以设置:

    • HorizontalScrollBarPolicy:ScrollBarAlwaysOff
    • HorizontalScrollBarPolicy:ScrollBarAlwaysOn

小案例

1)错误做法:直接将QLabel和scrollAreaWidgetContents绑定

在scrollArea里面添加QLabel(先用Qt Designer完成上面scrollArea属性的设置)

     def add_button(self):
        print("add_button")
        self.num += 1
        button = QtWidgets.QPushButton(self.scrollAreaWidgetContents)
        button.setGeometry(QtCore.QRect(0,0,500,100))
        button.setMinimumSize(500,100)
        button.setMaximumSize(500,100)
        button.setStyleSheet("color:Red")
        button.setText("Hello World")
        button.setObjectName("button_" + str(self.num))
        self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 500, 100 * self.num))   #scrollAreaWidgetContents撑开
        return None

整体代码 见 《正确做法》

效果如下:虽然有布局,但是看不见按钮在哪里

在这里插入图片描述

2)正确做法:使用scrollAreaWidgetContents中的VertialLayout添加QLabel

这里用VertialLayoutaddWidget,实现QPushButton控件动态添加到scrollArea中, 整体代码如下:

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout
from PyQt5 import QtWidgets,QtCore,QtGui
from PyQt5.QtWidgets import QPushButton


class Ui_Display_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(500, 900)
        MainWindow.setMinimumSize(QtCore.QSize(500, 500))
        MainWindow.setMaximumSize(QtCore.QSize(500, 500))
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.scrollArea = QtWidgets.QScrollArea(self.centralwidget)
        self.scrollArea.setGeometry(QtCore.QRect(0, 0, 500, 500))
        self.scrollArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
        self.scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.scrollArea.setWidgetResizable(False)
        self.scrollArea.setObjectName("scrollArea")
        self.scrollAreaWidgetContents = QtWidgets.QWidget()
        self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 500, 500))
        self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")

        self.verticalLayoutWidget = QWidget(self.scrollAreaWidgetContents)
        self.verticalLayoutWidget.setGeometry(QtCore.QRect(0, 0, 500, 500))
        self.verticalLayoutWidget.setObjectName("verticalBoxLayoutWidget")
        self.verticalLayout = QVBoxLayout(self.verticalLayoutWidget)
        self.verticalLayout.setContentsMargins(0,0,0,0)
        self.verticalLayout.setObjectName("verticalLayout")

        self.scrollArea.setWidget(self.scrollAreaWidgetContents)
        self.addButton = QPushButton(self.scrollAreaWidgetContents)
        self.addButton.setGeometry(QtCore.QRect(0,0,50,50))
        self.addButton.setObjectName("addButton")
        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.addButton.setText(_translate("MainWindow", "添加"))


class MainWindow(QMainWindow,Ui_Display_MainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()
        self.setupUi(self)
        self.init()
        self.num = 0
        self.widget = QWidget()

    def init(self):
        self.addButton.clicked.connect(lambda : self.add_button())

    def add_button(self):
        print("clicked")
        self.num += 1
        button = QtWidgets.QPushButton()
        button.setGeometry(QtCore.QRect(0,0,500,100))
        button.setMinimumSize(500,100)
        button.setMaximumSize(500,100)
        button.setStyleSheet("color:Red")
        button.setText("Hello World")
        button.setObjectName("button_" + str(self.num))
        self.verticalLayout.addWidget(button)  
        self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0,0,500, 100 * self.num))   #scrollAreaWidgetContents撑开
        self.verticalLayoutWidget.setGeometry(QtCore.QRect(0, 0, 500, 100 * self.num))   #verticalLayoutWidget跟着撑开
        return None

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec()

这里要注意的是:

scrollAreaWidgetContents和verticalLayoutWidget要同时撑开,否则button即使设置了固定大小,也会被压缩在固定空间中。

效果如下:

在这里插入图片描述

9、删除布局中的所有控件

参考PyQt5入门——删除、清空layout布局中的所有对象(含常见问题详解)

代码和8是一起的

    def init(self):
        self.addButton.clicked.connect(lambda : self.add_button())
        self.deleteButton.clicked.connect(lambda : self.delete_button())

    def add_button(self):
       ...

    def delete_button(self):
        item_list = list(range(self.verticalLayout.count()))
        item_list.reverse()  # 倒序删除,避免影响布局顺序

        for i in item_list:
            item = self.verticalLayout.itemAt(i)
            self.verticalLayout.removeItem(item)
            if item.widget():
                item.widget().deleteLater()

        self.num = 0
        # self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 500, 500))  # scrollAreaWidgetContents撑开
        # self.verticalLayoutWidget.setGeometry(QtCore.QRect(0, 0, 500, 500))  # verticalLayoutWidget跟着撑开

实现效果:

在这里插入图片描述

10、textBrower控件的使用

参考

    text = text.encode("gbk").decode("gbk")  #用gbk编码成字节,再用gbk编码成字符,避免textBrower显示乱码
    self.textBrowser.append(text)  #追加文本
    self.textBrowser.moveCursor(self.textBrowser.textCursor().End)  # 文本框显示到底部

在使用textBrowser时,记得开启新的线程进行text绘制,否则主线程会卡死,直到界面结束之后才会显示文本。

11、QSlider控件的使用

参考PyQt5 组件之QSlider

12、关于MainWindow代码和Ui布局的对应理解

下面是简单的MainWindow代码,共5层结构,由于用pyUIC生成的代码没有换行,看起来比较费劲,这里特意换了行:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'untitled.ui'
#
# Created by: PyQt5 UI code generator 5.15.6
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        
        #第一层
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        
        #第二层
        self.centralwidget = QtWidgets.QWidget(MainWindow)   #隶属于第一层(通过构造函数建立关系,我顺从你)
        self.centralwidget.setObjectName("centralwidget")
        
        #第三层
        self.scrollArea = QtWidgets.QScrollArea(self.centralwidget)   #隶属于第二层(通过构造函数建立关系,我顺从你)
        self.scrollArea.setGeometry(QtCore.QRect(130, 50, 441, 431))  
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setObjectName("scrollArea")
        
        #第四层
        self.scrollAreaWidgetContents = QtWidgets.QWidget()
        self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 439, 429))
        self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
        
        #第五层
        self.horizontalLayoutWidget = QtWidgets.QWidget(self.scrollAreaWidgetContents)   #隶属于第四层(通过构造函数建立关系,我顺从你)
        self.horizontalLayoutWidget.setGeometry(QtCore.QRect(80, 90, 311, 171))
        self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)  #horizontalLayout布局隶属于horizontalLayoutWidget
        self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout.setObjectName("horizontalLayout")
        
        
        self.scrollArea.setWidget(self.scrollAreaWidgetContents)   #第三层强制第四层隶属于自己
        
        MainWindow.setCentralWidget(self.centralwidget)  #第一层强制第二层隶属于自己
        
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 26))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))

效果如下:

在这里插入图片描述

由此可以发现在设置布局关系时主要有两种方法:

  • 通过构造函数主动和控件或容器建立关系,选择依附于他
  • 通过set函数强制与其他控件建立关系,命令他服从

13、控件的隐藏和显示

参考PyQt5–控件的显示与隐藏、可用与不可用PyQt5 技术篇 - 按钮隐藏并保留位置

  • button.setVisible(False):不可见

  • button.setEnabled(False):可见但不可用(“不可用”半透明显示,“可用”全色显示)

  • 如果是绝对布局的话,隐藏不会影响控件的位置的变化

  • 如果在相对布局中(横向、纵向和网格布局),这种布局只要是里面的控件不可见,就相当于没有了,所以会重新分配各个组件的位置,这样我们的整体布局就会有所变化

14、关于控件生命周期的问题(以QLabel为例)

在使用pyqt5开发时,可能会出现莫名其妙的程序崩溃,这时通过设置计数器(count++),print打印输出,再使用断言定位count,看看能不能定位到bug的位置。

blink_detect finished
get_blinkSpeedDetect_perclos finished
putText50 finished 


Traceback (most recent call last):
  File "F:\fatigue_recognition\fatigue_detection_system\param_setting_window\blink_param_window.py", line 50, in <lambda>
    self.pushButton.clicked.connect(lambda : self.next())
  File "F:\fatigue_recognition\fatigue_detection_system\param_setting_window\blink_param_window.py", line 118, in next
    self.handler.detect()
  File "F:\fatigue_recognition\fatigue_detection_system\service\detect_handler.py", line 130, in detect
    qImg_scaled = QImg.scaled(self.qlabel.width(), self.qlabel.height(), Qt.IgnoreAspectRatio,
RuntimeError: wrapped C/C++ object of type QLabel has been deleted

可能原因
python的垃圾处理器真的挺迷的,在初始化自定义的handler时,我将Qt5界面(detectWindow)的qLabel作为handlerself.qLabel属性对象,但是到了一段时间之后Qt5界面中的控件对象应该会重新更新,导致handler里的原有的qLabel控件已经不可用了,导致如上的报错。
解决方法
直接将整个Qt5界面(detectWindow)传进去,作为handlerself.detectWindow属性对象,用detectWindow.label来调用qlabel

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值