In order to have more hands-on experience with Python and creating GUIs, I decided to create a flashcards quiz app. Initially, I created a simple function that accepted a csv file name, shuffled the answers and the questions, and implemented a for
loop to ask the user to input an answer for each question. I ran it through CMD, and it functioned perfectly.
Next, I used the Qt designer to create a simple Qt Dialog window with a textBrowser
for ouput and a lineEdit
for input. (Side note: I know that you are not supposed to modify the generated ui file, so I copied the code and saved it to a difference directory so I could work with it safely.) I put the quizzing function inside of the Dialog class, and have it called on the app's execution. However, in order to wait for user input to be entered, I needed to add a QEventLoop
to the quizzing function that starts after the question is posed and quits when lineEdit.returnPressed
is triggered. If I cycle through the entire deck of cards, the shuffle function gets completed and when I close the GUI (via the X button) the code stops regularly. But if I try closing the window between a question getting asked and being answered (while QEventLoop
is running), the GUI closes but the function is still running, and the aboutToQuit
detector I set up doesn't get triggered.
I'm pretty sure that this issue is because the quizzing function gets hung on executing the QEventLoop
, and as of yet I have not found a successful way to register that GUI has closed and quit the QEventLoop
without finishing the entire question/answer loop.
Would having the window and the QEventLoop
run synchronously fix my problem? Is there a way to prematurely break out of the QEventLoop
in the case of an event like the function's window closing? Or should I be using a different process like QTimer
here?
# 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.