学习笔记

PyQt5 - Python GUI编程

by 猪皮怪, 2022-08-08


武沛齐的PYQT5教程

安装

pip install pyqt5

安装速度太慢的解决办法:

  1. 采用国内源,加速下载模块的速度
  2. 常用pip源:
    -- 豆瓣:https://pypi.douban.com/simple
    -- 阿里:https://mirrors.aliyun.com/pypi/simple
    -- 中科大:https://pypi.mirrors.ustc.edu.cn/simple
    -- 清华:https://pypi.tuna.tsinghua.edu.cn/simple
  3. 加速安装的命令:
    -- >: pip install 模块名 -i https://pypi.douban.com/simple

创建主类

import sys
from PyQt5.QtWidgets import QApplication,QWidget,QDesktopWidget

继承QWidget类,相当于继承PyQt的插件,我们开发的是一个桌面插件。

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        # 窗口标题
        self.setWindowTitle('测试程序')
        # 窗口尺寸,宽*高
        self.resize(980,450)
        # 窗口位置,显示在屏幕中间
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        # 显示窗体
        self.show()

启动程序

# 创建一个QApplication对象
app = QApplication(sys.argv)
# 实例化我们的主程序,执行程序内部init初始化方法。
window = MainWindow()
sys.exit(app.exec_())

窗体布局

该布局类似于css的flex布局,有垂直布局和水平布局两种,由外向内布局。
弹簧作用:撑开页面,挤压页面元素。
from PyQt5.QtWidgets import QHBoxLayout,QVBoxLayout

QHBoxLayout:水平布局
QVBoxLayout:垂直布局

    # 开始布局,创建整体的垂直布局(div的display:flex,flex-direction:column)
    layout = QVBoxLayout()

    # 1.创建内部区域布局(div的display:flex,flex-direction:row)
    header_layout = QHBoxLayout()
    # 把该区域加入到主layout中(加入主div中)
    layout.addLayout(header_layout)





    # 给窗体设置元素的排列方式,该布局是垂直布局,所以设置成垂直的排列方式。
        self.setLayout(layout)

创建按钮元素(QPushButton)

from PyQt5.QtWidgets import QPushButton
    # 创建一个 “开始” 按钮
    btn_start = QPushButton('开始')
    # 将按钮加入到布局中
    header_layout.addWidget(btn_start)

加入弹簧(addStretch)

    header_layout.addStretch()

创建单行输入框元素(QLineEdit)

from PyQt5.QtWidgets import QLineEdit
    # 创建输入框
    txt_asin = QLineEdit()
    txt_asin.setPlaceholderText('请输入内容')
    # 将输入框加入布局中
    form_layout.addWidget(txt_asin)

创建表格元素(QTableWidget)

from PyQt5.QtWidgets import QTableWidget
    # 创建2行8列的表格对象
    table_widget = QTableWidget(2,8)
    # 将表格对象加入到布局中
    table_layout.addWidget(table_widget)

修改表头(创建单元格对象:QTableWidgetItem)

修改方式是通过索引找到该表格,创建单元格对象,设置单元格内容

    item = QTableWidgetItem()
    item.setText('标题')
    # 通过索引设置该单元格对象
    table_widget.setHorizontalHeaderItem(0,item)
    # 通过索引设置列宽
    table_widget.setColumnWidth(0,160)

2022-08-27T02:33:56.png

优化修改表头方式

    table_header = [
        {'field':'asin','text':'ASIN','width':120},
        {'field':'title','text':'标题','width':150},
        {'field':'url','text':'URL','width':400},
        {'field':'price','text':'底价','width':100},
        {'field':'success','text':'成功次数','width':100},
        {'field':'error','text':'503次数','width':100},
        {'field':'status','text':'状态','width':100},
        {'field':'frequency','text':'频率(N秒/次)','width':100},
    ]
    for i,v in enumerate(table_header):
        item = QTableWidgetItem()
        item.setText(str(v['text']))
        table_widget.setHorizontalHeaderItem(i,item)
        table_widget.setColumnWidth(i,v['width'])

2022-08-27T02:46:31.png

其余内容

……
2022-08-27T02:54:22.png

优化代码

2022-08-27T07:27:16.png

初始化表格内单元格数据

    # 获取当前表格内总行数,无数据时current_row_count = 0
    current_row_count = table_widget.rowCount()
    # 循环加入数据,在列表内循环,row_list相当于数据的每一行
    for row_list in data_list:
        # 在第current_row_count行插入一行,此时表示在第0行插入一行
        table_widget.insertRow(current_row_count)
        # 通过在数据行内循环,把行内数据添加进单元格中。
        for i,ele in enumerate(row_list):
            cell = QTableWidgetItem(str(ele))
            # 这里用table_widget的setItem方法来设置单元格对象。三个参数分别表示在第几行,第几列,插入一个QTableWidgetItem对象。
            table_widget.setItem(current_row_count,i,cell)
        # 一行循环结束,行数+1
        current_row_count += 1

设置单元格是否可编辑

# 导入
from PyQt5.QtCore import Qt
    # 如果单元格索引到i列时,设置该单元格的属性为不可修改
    if i in [0,4,5,6]:
        cell.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)

获取输入框中的内容

由于初始化输入框和添加按钮所涉及的事件是在两个方法中,所以要在外层放置一个self.text_asin,并将其初始化为None,在初始化表单的时候,将对应对象赋值给self.text_asin,即可在该方法中的任何需要调用的地方用到该对象。
初始化:
2022-08-27T08:23:40.png
对象赋值:
2022-08-27T08:24:39.png
需要调用的地方:
2022-08-27T08:25:21.png

按钮单击事件

# 假设有一个登陆按钮
login = QPushButton('登陆')
    # 登录按钮需要触发该类自身的event_doLogin函数
    login.clicked.connect(self.event_doLogin)

在event_doLogin方法中编写如下代码:

# 登录按钮需要做的事
    def event_doLogin(self):
        # 1.获取账号密码
        account = self.input_account.text().strip()
        password = self.input_password.text().strip()
        if not account or not password:
            QMessageBox.warning(self,'警告','请输入用户名和密码')
            return
        # 2.登录操作,将该任务委派给线程完成,要向线程传递的参数写在前,“self”在最后。
        login_thread = loginTaskThread(account,password,self)
        # 线程处理完毕(触发信号)后的回调函数
        login_thread.success.connect(self.login_task_success_callback)
        login_thread.error.connect(self.login_task_error_callback)
        # 开始执行
        login_thread.start()
        pass

数据的更新(QThread、pyqtSignal)

创建一个单独的线程py文件,在其中引入QThread、pyqtSignal两类

from PyQt5.QtCore import QThread,pyqtSignal

开始线程类功能的编写:

# 继承QThread类
class loginTaskThread(QThread):
    # 创建两个变量用于传递信号,一个用于登录成功的信号传递,另一个用于失败时的信号传递。
    # pyqtSignal参数要填写待传递信息的type,如传递字符串,则写“str”。
    success = pyqtSignal(str)
    error = pyqtSignal(int)

    # 必写初始化函数,形参个数根据待接收的实参设定。
    def __init__(self,account,password,*args,**kwargs):
        super().__init__(*args,**kwargs)
        self.account = account
        self.password = password

    # 重构run方法,该方法内实现线程需要做的内容。
    def run(self):
        # 传递信息(成功/失败),参数类型要根前面设定的类型一致。
        # 线程执行结束获取结果后,将该结果传递回前台界面接收处理。
        self.success.emit(passport_name)
        self.error.emit(0)
        return

线程结束后的回调函数操作

# 登录成功或失败后的回调函数
def login_task_success_callback(self,passport_name):
    self.nickname.setText(passport_name)
    self.nickname.repaint()
# 线程执行失败()的回调函数
def login_task_error_callback(self,status):
    QMessageBox.warning(self,'警告','用户名或密码错误')

弹窗

from PyQt5.QtWidgets import QMessageBox
    # 第二个参数是弹窗标题,第三个参数是弹窗内容。
    QMessageBox.warning(self,'警告','输入不能为空')

数据删除

# 选中行的获取
current_row_list = self.table_widget.selectionModel().selectedRows()
# 做一次列表反转操作,必做操作!!!!!!
current_row_list.reverse()
for row in current_row:
    # 获取当前行的索引值
    index = row.row()
    # 通过索引值删除对应项
    self.table_widget.removeRow(index)

弹窗

创建弹窗

需要创建一个新的弹窗类,继承QDialog,然后对弹窗进行布局。

from PyQt5.QtWidgets import QDialog,QVBoxLayout,QLabel
from PyQt5.QtCore import Qt

class AlertDialog(QDialog):
    def __init__(self,*args, **kwargs):
        super().__init__(*args, **kwargs)
        # 创建弹窗界面,初始化ui
        self.init_ui()
    
    def init_ui(self):
        self.setWindowTitle('配置')
        self.resize(640,480)

        layout = QVBoxLayout()

        label = QLabel('弹窗界面')
        layout.addWidget(label)
        
        self.setLayout(layout)

调用弹窗

导入新建好的弹窗类

from utils.dialog import AlertDialog

在点击事件中对弹窗类进行实例化操作

def event_dialog(self):
    dialog = AlertDialog()
    dialog.setWindowModality(Qt.ApplicationModal)
    dialog.exec_()

单元格右键操作

# 对表格对象开启右键功能,触发table_right_menu函数。
    self.file_lists.setContextMenuPolicy(Qt.customContextMenu)
    self.file_lists.customContextMenuRequested.connect(self.table_right_menu)

在table_right_menu函数下,导入QMenu控件。

from PyQt5.QtWidgets import QMenu

函数内容:

def table_right_menu(self,pos):
    # 添加右键菜单按钮
    menu = QMenu()
    item_download = menu.addAction('下载')
    item_delete = menu.addAction('删除云端文件')
    # action表示右键选择哪个菜单行为
    action = menu.exec_(self.file_lists.mapToGlobal(pos))
    # action执行哪个操作?
    if action == item_download :
        # 如果选择了“下载”按钮,该执行的操作:
        # 当前操作:打印选择的第1行的行号。
        print(selected_item_list[0].row())
        pass
    if  action == item_delete :
        # 如果点击“删除云端文件”按钮,该执行的操作:
        pass

文件/文件夹选择窗口

QFileDialog类方法

类方法描述
getOpenFileName()返回用户所选择文件的名称,并打开该文件
getSaveFileName()使用用户选择的文件名保存文件
setFileMode()可以选择的文件类型,枚举常量是:
-QFileDialog.AnyFile:任何文件
-QFileDialog.ExistingFile:已存在的文件
-QFileDialog.Directory:文件目录
-QFileDialog.ExistingFiles:已经存在的多个文件
setFilter()设置过滤器,只显示过滤器允许的文件类型

使用QFileDialog类

引入QFileDialog类

from PyQt5.QtWidgets import QMessageBox,QMenu,QFileDialog

为按钮创建打开文件选择窗口事件

def choose_download_dir(self):
    # 创建选择文件夹对话框
    dir_dialog = QFileDialog()
    # 选择类型限定为文件夹
    dir_dialog.setFileMode(QFileDialog.Directory)
    if dir_dialog.exec_():
        dir_path = dir_dialog.selectedFiles()
        download_path = dir_path[0]
        # 选择的文件夹路径
        print(download_path)

作者: 猪皮怪

2024 © esia.asia 36 ms