Qt Dialog 窗口关闭时如何终止 QEventLoop

问题描述

为了获得更多 Python 和创建 GUI 的实践经验,我决定创建一个抽认卡测验应用程序。最初,我创建了一个简单的函数,它接受一个 csv 文件名,将答案和问题打乱,并实现了一个 for 循环来要求用户为每个问题输入一个答案。我通过 CMD 运行它,它运行良好。

接下来,我使用 Qt 设计器创建了一个简单的 Qt 对话框窗口,其中 textbrowser 用于输出lineEdit 用于输入。 (旁注:我知道你不应该修改生成的 ui 文件,所以我复制了代码并将其保存到不同的目录中,以便我可以安全地使用它。)我将测验功能放在 Dialog 类中,并在应用程序的执行上调用它。但是,为了等待用户输入,我需要向测验功能添加一个 QEventLoop,该功能在提出问题后启动并在触发 lineEdit.returnpressed退出。如果我循环遍历整副纸牌,洗牌功能就会完成,当我关闭 GUI(通过 X 按钮)时,代码会定期停止。但是,如果我尝试关闭提问和回答之间的窗口(当 QEventLoop 正在运行时),GUI 会关闭函数仍在运行,并且我设置的 aboutToQuit 检测器不会被触发。

我很确定这个问题是因为测验功能在执行 QEventLoop 时挂起,到目前为止我还没有找到一种成功的方法注册 GUI 已关闭退出 {{1 }} 无需完成整个问答循环。 让窗口和 QEventLoop 同步运行会解决我的问题吗?在函数窗口关闭等事件的情况下,有没有办法过早地突破 QEventLoop ?或者我应该在这里使用不同的流程,例如 QEventLoop

QTimer

解决方法

正如您已经指出的,您不应该修改 pyuic 生成的代码,因此您必须使用以下命令重新生成文件:

pyuic5 filename.ui -o design_ui.py -x

另一方面,没有必要或不建议使用 QEventLoop,因为它们会产生意外行为,在这种情况下,使用迭代器就足够了。

您还必须将逻辑与 GUI 分开,例如创建一个将问题与答案相关联的数据结构,以及另一个提供和管理测试的类。

最后,您只需要实现处理用户交互时发出的信号的 GUI 逻辑。

ma​​in.py

import csv
from dataclasses import dataclass
import random
import sys

from PyQt5 import QtWidgets

from design_ui import Ui_Dialog


@dataclass
class Test:
    question: str
    answer: str

    def verify(self,answer):
        return self.answer == answer


class TestProvider:
    def __init__(self,tests):
        self._tests = tests
        self._current_test = None
        self.init_iterator()

    def init_iterator(self):
        self._test_iterator = iter(self.tests)

    @property
    def tests(self):
        return self._tests

    @property
    def number_of_tests(self):
        return len(self._tests)

    @property
    def current_test(self):
        return self._current_test

    def next_text(self):
        try:
            self._current_test = next(self._test_iterator)
        except StopIteration as e:
            return False
        else:
            return True


class Dialog(QtWidgets.QDialog,Ui_Dialog):
    def __init__(self,parent=None):
        super().__init__(parent)
        self.setupUi(self)

        self.lineEdit.returnPressed.connect(self.handle_pressed)
        self.lineEdit.setEnabled(False)

    def load_tests_from_filename(self,filename):
        self._number_of_correct_answers = 0
        tests = []
        with open(filename,encoding="utf-8") as tsv:
            reader = csv.reader(tsv,delimiter="\t")
            for row in reader:
                question,answer = row
                test = Test(question,answer)
                tests.append(test)
        seed = random.random()
        random.seed(seed)
        random.shuffle(tests)
        self._test_provider = TestProvider(tests)
        self.load_test()
        self.lineEdit.setEnabled(True)

    def load_test(self):
        if self._test_provider.next_text():
            self.print_text(
                f"What does {self._test_provider.current_test.question} mean?"
            )
            return True
        return False

    def handle_pressed(self):
        if self._test_provider is None:
            return
        guess = self.lineEdit.text().strip().lower()
        self.textBrowser.append(guess)
        if self._test_provider.current_test.answer.strip().lower() == guess:
            self.print_text("You are right!")
            self._number_of_correct_answers += 1
        else:
            self.print_text(
                f"You are wrong. The answer is {self._test_provider.current_test.answer}; better luck next time!"
            )

        self.lineEdit.clear()
        if not self.load_test():
            self.print_text(
                f"You got {(round(self._number_of_correct_answers / self._test_provider.number_of_tests,2) * 100)}% ({self._number_of_correct_answers}) of the cards right."
            )
            self.lineEdit.setEnabled(False)

    def print_text(self,text):
        self.textBrowser.append(text)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    w = Dialog()
    w.load_tests_from_filename("Decks/Animals.csv")
    w.show()
    sys.exit(app.exec_())