Qt Dialog window 关闭时如何终止 QEventLoop
How to terminate a QEventLoop when a Qt Dialog window is closed
为了获得更多 Python 和创建 GUI 的实践经验,我决定创建一个抽认卡 quiz 应用程序。最初,我创建了一个接受 csv 文件名的简单函数,打乱了答案和问题,并实现了一个 for
循环来要求用户输入每个问题的答案。我通过 CMD 运行 它,它运行完美。
接下来,我使用 Qt 设计器创建了一个简单的 Qt 对话框 window,其中 textBrowser
用于输出,lineEdit
用于输入。 (旁注:我知道你不应该修改生成的 ui 文件,所以我复制了代码并将其保存到不同的目录,这样我就可以安全地使用它。)我把 quiDialog class 中的 zzing 函数,并在应用程序执行时调用它。但是,为了等待用户输入输入,我需要向 quizzing 函数添加一个 QEventLoop
,该函数在问题提出后启动,而 quits 当 lineEdit.returnPressed
被触发。如果我循环遍历整副牌,洗牌功能就会完成,当我关闭 GUI(通过 X 按钮)时,代码会定期停止。但是如果我尝试关闭问题被问和被回答之间的 window(而 QEventLoop
是 运行ning),GUI 关闭但功能仍然是 运行ning,我设置的 aboutToQuit
检测器没有被触发。
我很确定这个问题是因为 quizzing 函数在执行 QEventLoop
时挂起,到目前为止我还没有找到成功注册该 GUI 的方法已经关闭并且 quit QEventLoop
没有 完成整个 question/answer 循环。
让 window 和 QEventLoop
运行 同步解决我的问题吗?在函数的 window 关闭等事件的情况下,有没有办法过早地突破 QEventLoop
?或者我应该在这里使用 QTimer
之类的不同流程吗?
# If this helps, here's the code for the program.
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import *
import csv
import random
import sys
class Ui_Dialog(QWidget):
loop = QtCore.QEventLoop()
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(361, 163)
self.lineEdit = QtWidgets.QLineEdit(Dialog)
self.lineEdit.setGeometry(QtCore.QRect(20, 120, 321, 20))
self.lineEdit.setObjectName("lineEdit")
self.lineEdit.returnPressed.connect(self.acceptText)
self.textBrowser = QtWidgets.QTextBrowser(Dialog)
self.textBrowser.setGeometry(QtCore.QRect(20, 20, 321, 91))
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
def printText(self, contents):
self.textBrowser.append(contents)
def acceptText(self):
input = self.lineEdit.text().strip().lower()
self.loop.quit()
return input
def shuffleDeck(self, filename):
# sets up values to be referenced later
questions = []
answers = []
index = 0
numright = 0
# contains the entire reading, shuffling, and quizzing process
with open(filename, encoding='utf-8') as tsv:
reader = csv.reader(tsv, delimiter="\t")
for row in reader:
questions.append(row[0][::-1])
answers.append(row[1].lower())
seed = random.random()
random.seed(seed)
random.shuffle(questions)
random.seed(seed)
random.shuffle(answers)
for question in questions:
# handles input & output
self.printText("What does " + question + " mean?")
self.loop.exec_()
guess = self.acceptText()
self.textBrowser.append(guess)
self.lineEdit.clear()
# compares input to answer, returns correct/incorrect prompts accordingly
if guess == answers[index]:
self.printText("You are right!")
index += 1
numright += 1
else:
self.printText("You are wrong. The answer is " + str(answers[index]) + "; better luck next time!")
index += 1
self.printText("You got " + str(round(numright / len(questions), 2) * 100) + "% (" + str(
numright) + ") of the cards right.")
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.show()
# I temporarily hardcoded a csv file
ui.shuffleDeck("Decks/Animals.csv")
# linear processing requires shuffleDeck to be completed before the window loops, right?
sys.exit(app.exec_())
#An example of the csv text would be:
מאוד VERY
עוד MORE
כמו כ AS
שם THERE
#I would have included only English characters, but this is the deck that I hardcoded in.
正如您已经指出的那样,您不应修改 pyuic 生成的代码,因此您必须使用以下命令重新生成文件:
pyuic5 filename.ui -o design_ui.py -x
另一方面,没有必要也不建议使用 QEventLoop,因为它们会产生意想不到的行为,在这种情况下使用迭代器就足够了。
您还必须将逻辑与 GUI 分开,例如创建一个将问题与答案相关联的数据结构,以及另一个 class 提供和管理测试。
最后,您只需实现处理用户交互时发出的信号的 GUI 逻辑。
main.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_())
为了获得更多 Python 和创建 GUI 的实践经验,我决定创建一个抽认卡 quiz 应用程序。最初,我创建了一个接受 csv 文件名的简单函数,打乱了答案和问题,并实现了一个 for
循环来要求用户输入每个问题的答案。我通过 CMD 运行 它,它运行完美。
接下来,我使用 Qt 设计器创建了一个简单的 Qt 对话框 window,其中 textBrowser
用于输出,lineEdit
用于输入。 (旁注:我知道你不应该修改生成的 ui 文件,所以我复制了代码并将其保存到不同的目录,这样我就可以安全地使用它。)我把 quiDialog class 中的 zzing 函数,并在应用程序执行时调用它。但是,为了等待用户输入输入,我需要向 quizzing 函数添加一个 QEventLoop
,该函数在问题提出后启动,而 quits 当 lineEdit.returnPressed
被触发。如果我循环遍历整副牌,洗牌功能就会完成,当我关闭 GUI(通过 X 按钮)时,代码会定期停止。但是如果我尝试关闭问题被问和被回答之间的 window(而 QEventLoop
是 运行ning),GUI 关闭但功能仍然是 运行ning,我设置的 aboutToQuit
检测器没有被触发。
我很确定这个问题是因为 quizzing 函数在执行 QEventLoop
时挂起,到目前为止我还没有找到成功注册该 GUI 的方法已经关闭并且 quit QEventLoop
没有 完成整个 question/answer 循环。
让 window 和 QEventLoop
运行 同步解决我的问题吗?在函数的 window 关闭等事件的情况下,有没有办法过早地突破 QEventLoop
?或者我应该在这里使用 QTimer
之类的不同流程吗?
# If this helps, here's the code for the program.
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import *
import csv
import random
import sys
class Ui_Dialog(QWidget):
loop = QtCore.QEventLoop()
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(361, 163)
self.lineEdit = QtWidgets.QLineEdit(Dialog)
self.lineEdit.setGeometry(QtCore.QRect(20, 120, 321, 20))
self.lineEdit.setObjectName("lineEdit")
self.lineEdit.returnPressed.connect(self.acceptText)
self.textBrowser = QtWidgets.QTextBrowser(Dialog)
self.textBrowser.setGeometry(QtCore.QRect(20, 20, 321, 91))
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
def printText(self, contents):
self.textBrowser.append(contents)
def acceptText(self):
input = self.lineEdit.text().strip().lower()
self.loop.quit()
return input
def shuffleDeck(self, filename):
# sets up values to be referenced later
questions = []
answers = []
index = 0
numright = 0
# contains the entire reading, shuffling, and quizzing process
with open(filename, encoding='utf-8') as tsv:
reader = csv.reader(tsv, delimiter="\t")
for row in reader:
questions.append(row[0][::-1])
answers.append(row[1].lower())
seed = random.random()
random.seed(seed)
random.shuffle(questions)
random.seed(seed)
random.shuffle(answers)
for question in questions:
# handles input & output
self.printText("What does " + question + " mean?")
self.loop.exec_()
guess = self.acceptText()
self.textBrowser.append(guess)
self.lineEdit.clear()
# compares input to answer, returns correct/incorrect prompts accordingly
if guess == answers[index]:
self.printText("You are right!")
index += 1
numright += 1
else:
self.printText("You are wrong. The answer is " + str(answers[index]) + "; better luck next time!")
index += 1
self.printText("You got " + str(round(numright / len(questions), 2) * 100) + "% (" + str(
numright) + ") of the cards right.")
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.show()
# I temporarily hardcoded a csv file
ui.shuffleDeck("Decks/Animals.csv")
# linear processing requires shuffleDeck to be completed before the window loops, right?
sys.exit(app.exec_())
#An example of the csv text would be:
מאוד VERY
עוד MORE
כמו כ AS
שם THERE
#I would have included only English characters, but this is the deck that I hardcoded in.
正如您已经指出的那样,您不应修改 pyuic 生成的代码,因此您必须使用以下命令重新生成文件:
pyuic5 filename.ui -o design_ui.py -x
另一方面,没有必要也不建议使用 QEventLoop,因为它们会产生意想不到的行为,在这种情况下使用迭代器就足够了。
您还必须将逻辑与 GUI 分开,例如创建一个将问题与答案相关联的数据结构,以及另一个 class 提供和管理测试。
最后,您只需实现处理用户交互时发出的信号的 GUI 逻辑。
main.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_())