如何修复 PyQt5 GUI 冻结

Posted

技术标签:

【中文标题】如何修复 PyQt5 GUI 冻结【英文标题】:How to fix PyQt5 GUI freezing 【发布时间】:2020-05-17 12:34:49 【问题描述】:

我使用 QtDesigner 为我的 python 应用程序创建了一个简单的 GUI。当我尝试运行我的代码时,它可以正常工作,但我现在的问题是,当我尝试使用 GUI 上的转换按钮运行我的代码时,界面冻结或无响应,并且只有在完成执行代码后才会响应。现在我的问题是如何解决这个问题?

这是我的代码:

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

# Form implementation generated from reading ui file 'GUI.ui'
#
# Created by: PyQt5 UI code generator 5.13.2
#
# WARNING! All changes made in this file will be lost!


from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog, QMessageBox

import sys
import excel2img
import openpyxl as xl


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(361, 303)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.btn_exit = QtWidgets.QPushButton(self.centralwidget)
        self.btn_exit.setGeometry(QtCore.QRect(270, 240, 75, 23))
        self.btn_exit.setObjectName("btn_exit")
        self.btn_openfile = QtWidgets.QPushButton(self.centralwidget)
        self.btn_openfile.setGeometry(QtCore.QRect(40, 30, 75, 23))
        self.btn_openfile.setObjectName("btn_openfile")
        self.btn_convert = QtWidgets.QPushButton(self.centralwidget)
        self.btn_convert.setGeometry(QtCore.QRect(40, 60, 75, 23))
        self.btn_convert.setObjectName("btn_convert")
        #self.btn_send = QtWidgets.QPushButton(self.centralwidget)
        #self.btn_send.setGeometry(QtCore.QRect(40, 90, 75, 23))
        #self.btn_send.setObjectName("btn_send")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 361, 21))
        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)

    #Widgets
        self.btn_openfile.clicked.connect(self.openfile)
        self.btn_exit.clicked.connect(self.exit)
        self.btn_convert.clicked.connect(self.convert)
    #Widgets

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.btn_exit.setText(_translate("MainWindow", "Exit"))
        self.btn_openfile.setText(_translate("MainWindow", "Open File"))
        self.btn_convert.setText(_translate("MainWindow", "Convert File"))
        #self.btn_send.setText(_translate("MainWindow", "Send Payroll"))

    #My Functons

    fileName = ''
    @classmethod
    def openfile(cls):
        fname = QFileDialog.getOpenFileName(None, "Open File", "", "Excel files (*.xlsx *xls)")
        cls.fileName = fname[0]

    def exit(self):      
        quit_msg = "Are you sure you want to exit the program?"
        reply = QMessageBox.question(None, 'Prompt', 
                         quit_msg, QMessageBox.Yes | QMessageBox.No)

        if reply == QMessageBox.Yes:
            sys.exit()

    def convert(self):
        wb = xl.load_workbook(self.fileName, read_only=True)
        ws = wb.active
        a, b = 2, 2
        x, y = 1, 44
        z, w = 1, 44

        max_row = ws.max_row
        temp = 0
        loop_num = 0
        while temp < max_row:
            temp += 52
            loop_num += 1

        print(loop_num)

        for i in range(loop_num * 2):
            if i % 2 == 0:
                cell = "Sheet1!A:F".format(x,y)
                ccell = 'B'.format(a)
                var = ws[ccell].value
                a += 52
                x += 52
                y += 52

            else:
                cell = "Sheet1!H:M".format(z,w)
                ccell = 'I'.format(b)
                var = ws[ccell].value       
                b += 52
                z += 52
                w += 52

            name = '.png'.format(var)
            excel2img.export_img(self.fileName, name, "", cell )

            print('generating image '.format(i + 1))




if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

【问题讨论】:

不要修改 Qt Designer 生成的代码,而是创建另一个继承自相应小部件的类并使用初始类来填充它。 【参考方案1】:

首先你不应该修改Qt Designer生成的类,所以在应用我的解决方案之前你必须重新生成.py,所以你必须再次使用pyuic:

pyuic5 your_ui.ui -o design.py -x


考虑到上述情况,问题在于您有一个耗时的任务阻塞了事件循环,从而阻止它完成其工作,例如对用户事件做出反应。一个可能的解决方案是使用线程:

import sys
import threading

from PyQt5 import QtCore, QtGui, QtWidgets

import excel2img
import openpyxl as xl

from design import Ui_MainWindow


def task(fileName):

    wb = xl.load_workbook(fileName, read_only=True)
    ws = wb.active
    a, b = 2, 2
    x, y = 1, 44
    z, w = 1, 44

    max_row = ws.max_row
    temp = 0
    loop_num = 0
    while temp < max_row:
        temp += 52
        loop_num += 1

    print(loop_num)

    for i in range(loop_num * 2):
        if i % 2 == 0:
            cell = "Sheet1!A:F".format(x, y)
            ccell = "B".format(a)
            var = ws[ccell].value
            a += 52
            x += 52
            y += 52

        else:
            cell = "Sheet1!H:M".format(z, w)
            ccell = "I".format(b)
            var = ws[ccell].value
            b += 52
            z += 52
            w += 52

        name = ".png".format(var)
        excel2img.export_img(fileName, name, "", cell)

        print("generating image ".format(i + 1))


class MainWindow(QtWidgets.Ui_MainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)

        # Widgets
        self.btn_openfile.clicked.connect(self.openfile)
        self.btn_exit.clicked.connect(self.exit)
        self.btn_convert.clicked.connect(self.convert)

    fileName = ""

    @classmethod
    def openfile(cls):
        cls.fileName, _ = QtWidgets.QFileDialog.getOpenFileName(
            None, "Open File", "", "Excel files (*.xlsx *xls)"
        )

    def exit(self):
        quit_msg = "Are you sure you want to exit the program?"
        reply = QtWidgets.QMessageBox.question(
            None,
            "Prompt",
            quit_msg,
            QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
        )

        if reply == QtWidgets.QMessageBox.Yes:
            sys.exit()

    def convert(self):
        threading.Thread(target=task, args=(self.fileName,), daemon=True).start()


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

【讨论】:

【参考方案2】:

您应该在 for 循环中调用 QtCore.QCoreApplication.processEvents() 以使 Qt 的事件循环继续传入事件(来自键盘或鼠标)

希望我有帮助

【讨论】:

感谢您的回答。我可以通过添加您提到的代码来稍微移动 GUI,但它看起来仍然冻结,我不希望这种情况发生,有什么办法吗?

以上是关于如何修复 PyQt5 GUI 冻结的主要内容,如果未能解决你的问题,请参考以下文章

PyQt5 在不冻结 GUI 的情况下绘制数据的最佳方法 [关闭]

在 PyQt5 中通过命令行调用外部程序时,有没有办法阻止 GUI 冻结?

如何修复我的活动指示器不出现/冻结?

文件下载后如何修复冻结页面?(TreeView SelectedNodeChanged中的TransmitFile)

如何冻结窗格/修复网站标题和水平导航栏?

如何使用 Python、PyQt5 和 Pyinstaller 修复未正确显示的按钮