YOLO813

再次学习PyQt5制作GUI程序

    之前在帮朋友写数据处理程序时刚接触GUI编程,写的界面真是稀烂,文章链接: python制作GUI可视化程序 ,这一次刚好老师也有个数据处理的需求,正好深度学习一下GUI编程。

    上一次编程完全没有借助工具,单纯靠一个vs code撸代码着实是麻烦且不实用,本次编程前先做了准备工作,下载pycharm社区版本工具,配置3种外部工具(名字可自定义),Qt Designer+PyUIC+PyRCC,先说下他们的作用,Qt Designer用于通过pycharm启动Qt Designer软件,PyUIC用于把Qt Designer生成的UI文件一键转成python文件,PyRCC是图片资源的加载(我用的很少),也就是说,其实这三个工具并非必须项,只是为了便捷开发而已,网上参考文件很多,可以自行了解具体配置项。

    写GUI程序之前,当然首先得把python程序写好才好整理界面逻辑,我是使用了jupyter notebook先行写好了处理程序,再开始写exe程序,具体的python处理程序代码我放在了文末以供参考,本篇文章主要介绍pyqt5。

    配置好外部工具之后就可以直接通过如下路径直接启动designer了

    我们在designer中设计完界面,保存之后可以看到在目录下会生成一个以ui后缀结尾的文件,选中这个文件(不要选错了文件,否则会把你现有的python文件清空),再点击外部工具中的PyUIC,即可生成一个对应的python文件

    这里面有一个问题,如果在转化后的python文件中写代码逻辑(上图中为first.py),你会发现每次重新设计了ui,自己写的逻辑代码都会被清空,所以最好的办法是让逻辑与界面分离。

    我们可以新建一个入口文件,上图中为xiaoduV1.py,代码如下:

from PyQt5 import QtWidgets,QtGui,QtCore
from py 文件名 import 类名
class MainWindow(QtWidgets.QMainWindow, 类名):
  def __init__(self, parent=None):
   super(MainWindow, self).__init__(parent)
   self.setupUi(self)
   # 此处编写业务逻辑代码
if __name__ == "__main__":
  importsys
  app = QtWidgets.QApplication(sys.argv)
  mainWindow = MainWindow()
  mainWindow.show()
  sys.exit(app.exec_())

    这样每一次都不用担心要修改UI窗口了。

    xiaoduV1.py代码如下,对于widget的部件使用就不再介绍了,看下源文档的函数基本能了解它的作用,网上参考文档也很多

# -*- coding: utf-8 -*-
from PyQt5 import QtWidgets,QtGui,QtCore
from PyQt5.QtWidgets import QMessageBox,QFileDialog
from first import Ui_MainWindow
import pandas as pd
import numpy as np
import warnings
from pathlib import Path
import random
from datetime import date
warnings.simplefilter("ignore")

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setupUi(self)
        self.pushButton.clicked.connect(self.processFile)
        self.pushButton_2.clicked.connect(self.getFilePath)
        # 此处编写业务逻辑代码
        self.file_flag = False

    def getFilePath(self):
        self.file_flag = True
        base_dir = QFileDialog.getExistingDirectory(self, "请选择文件夹路径", "C:")
        self.base_dir = base_dir
        self.label_3.setText(self.base_dir)

    def processFile(self):
        if not self.file_flag:
            QMessageBox.warning(self, "请注意", "请输入文件夹路径", QMessageBox.Ok)
            return
        base_dir = Path(self.base_dir)

        input_text = self.textEdit.toPlainText()
        if not input_text:
            QMessageBox.warning(self,"请注意","请输入考点内容",QMessageBox.Ok)
            return

        input_list = input_text.strip("\n").strip("%").split("%")

        kaodian= []
        for i in range(len(input_list)):
            each_kaodian_str = input_list[i].split(",")[0].strip()
            each_number_str = int(input_list[i].split(",")[1].strip())
            kaodian.append((each_kaodian_str, each_number_str))

        output_dir = base_dir / "output"
        Path(output_dir).mkdir(parents=True, exist_ok=True)

        data_list_file = []
        for file in base_dir.glob("*.xlsx"):
            _ = pd.read_excel(file, sheet_name='需标考点题目表', dtype=object)
            data_list_file.append(_)
        zxkd_df = pd.read_excel(file, sheet_name='最小考点', header=None, index_col=0, dtype=object)
        data_concat = pd.concat(data_list_file)

        data_concat = data_concat[[x for x in data_concat.columns if "Unnamed" not in x]]
        data_concat.dropna(subset=['题目ID'], inplace=True)
        data_concat.dropna(axis='columns', how='all', inplace=True)
        data_concat.fillna(value="None", inplace=True)
        data_concat['题目ID'] = data_concat['题目ID'].apply(lambda x: str(x).replace(".0", ""))
        data_concat.to_excel(output_dir / Path('xlsx合并文件供检查参考.xlsx'), index=False)

        choices = zxkd_df.index.values

        data_concat['kaodian_list'] = data_concat['题目ID'].map(str) + "," + data_concat['新标考点(最小级)第一考点'] + "," + data_concat['新标考点(最小级)第二考点'] + "," + data_concat['新标考点(最小级)第三考点'] + "," + data_concat['新标考点(最小级)第四考点']

        concat_dict = {}
        for choice in choices:
            empty_list = []
            data_concat['kaodian_list'].apply(self.getIDlist, zxkd=choice, empty_list=empty_list)
            concat_dict.update({choice: list(set(empty_list))})

        group_number = self.spinBox.value()
        output_dict = {}
        # 定义在全局的编号列表
        is_already_used_list = []

        for h in range(len(kaodian)):
            for k, v in concat_dict.items():
                wanna_id_list = []
                all_id_list = []
                if k == kaodian[h][0]:
                    all_id_list = v
                    for j in range(group_number):
                        m_group = random.sample(all_id_list, kaodian[h][1])
                        is_already_used_list += m_group
                        is_already_used_list = list(set(is_already_used_list))
                        all_id_list = list(set(all_id_list) - set(is_already_used_list) - set(m_group))

                        wanna_id_list.append(m_group)
                        if not all_id_list:
                            print("questions is less than you want!")
                    output_dict.update({f"{k}": wanna_id_list})
        pd.DataFrame(output_dict).to_excel(output_dir / Path(f'output-file-{date.today()}.xlsx'), index=False)
        print("end!")
        self.label_4.setText("Success!")

    def getIDlist(self,kaodian_list, zxkd, empty_list):
        if zxkd in kaodian_list:
            number = kaodian_list.split(",")[0]
            empty_list.append(str(number))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    mainWindow = MainWindow()
    mainWindow.show()
    sys.exit(app.exec_())


   pyinstaller打包注意事项,你所打包的模块必须是在全局环境变量下拥有,举个例子,我的虚拟环境中存在pyqt5,但是全局环境下没有安装,打包的exe程序运行就会闪退,你可以通过命令行CMD模式下运行exe程序即可看到报错提示,例如我的错误提示就是“pyqt5模块未找到”。

pyinstaller -F -w x.py
-F , --onefile 打包成單一個執行檔,但是打开软件可能会很慢
-w , --windowed , --noconsole 打包時會去除命令視窗


参考:

# pycharm配置pyqt5
https://blog.csdn.net/qq_35451572/article/details/85229408