Python 3 and Tkinter- how to control a quiz loop - python

I'm sure the answer to this is obvious but I cannot spot it! I have a very basic quiz(using Tkinter and Python 3) which uses 2 arrays , the question is displayed and then the answer entered is matched using the array index when the submit button is clicked.
The question at index 0 is displayed twice- cannot see why
The score does not increment correctly- even though it is a global variable- it just shows 1 each time.
How can I get the quiz to move to a print statement after the end of the list is reached?
I have tried putting an IF statement in the submit function to check the value of i but cannot get this to work. Can anyone point out my errors please?
from tkinter import *
global questions
questions =["What is the name of the Simpsons' next door neighbour?","What is the name of the school bus driver?",
"Who runs the Kwik-e-mart?","What does Bart do at the end of the opening credits?"]
global answers
answers = [ "Ned Flanders","Otto","Apu","Write On The Blackboard"]
global score
score = 0
global i
i = 0
def submit():
'''runs the submit button'''
global i
global score
question.config(text=questions[i])
if answer.get().lower()==answers[i].lower():
score+=1
else:
score=score
i+=1
scoretxt.config(text =str(score))
answer.delete(0,END)
window = Tk()
window.title("Simpsons Quiz")
window.wm_iconbitmap("homer.ico")
window.configure(background ="#ffd600")
banner = PhotoImage(file ="the-simpsons-banner.gif")
Label(window,image = banner).grid(row = 0,columnspan = 6)
Label(window,text = "Question : ",bg ="#ffd600",justify=LEFT).grid(row = 1,column = 0)
Label(window,text = "Type answer here: ",bg = "#ffd600",justify=LEFT).grid(row = 3, column = 0)
scoreLabel =Label(window,bg = "#ffd600")
scoretxt = Label(window,text ="Your score is: ?",bg = "#ffd600")
scoreLabel.grid(row=5,column = 2)
scoretxt.grid(row = 6,column = 2)
question=Label(window,bg = "white",text= questions[0],justify=LEFT)
question.grid(row =1,column=1)
answer = Entry(window,bg ="white",width = 30)
answer.grid(row = 3,column=1)
# make a submit button
Button(window,text= "Submit",bg = "white",command = submit).grid(row = 3,column = 2)
mainloop()

1) You are printing the question before you increment i. That's why you get twice.
2) It is always 1 becuase of your usage of global. In python you use global keyword in which scope you want to change your variable. Sadly, my english is not good enough to explain this. Please check out these answers.
3) You can use try-except block. I used it there because that is the exact line where you get the error. You can expand its range.
from tkinter import *
questions =["What is the name of the Simpsons' next door neighbour?","What is the name of the school bus driver?",
"Who runs the Kwik-e-mart?","What does Bart do at the end of the opening credits?"]
answers = [ "Ned Flanders","Otto","Apu","Write On The Blackboard"]
#removed globals from here
score = 0
i = 0
def submit():
'''runs the submit button'''
global i
global score
if answer.get().lower()==answers[i].lower():
score+=1
i+=1 #first increment, then show the question since you already show it at startup
try: #since you get the IndexError on this line, I used on here
question.config(text=questions[i])
except IndexError:
print ("something")
scoretxt.config(text = "Your score is: {}".format(str(score)))
answer.delete(0,END)
window = Tk()
window.title("Simpsons Quiz")
window.wm_iconbitmap("homer.ico")
window.configure(background ="#ffd600")
banner = PhotoImage(file ="the-simpsons-banner.gif")
Label(window,image = banner).grid(row = 0,columnspan = 6)
Label(window,text = "Question : ",bg ="#ffd600",justify=LEFT).grid(row = 1,column = 0)
Label(window,text = "Type answer here: ",bg = "#ffd600",justify=LEFT).grid(row = 3, column = 0)
scoreLabel =Label(window,bg = "#ffd600")
scoretxt = Label(window,text ="Your score is: ?",bg = "#ffd600")
scoreLabel.grid(row=5,column = 2)
scoretxt.grid(row = 6,column = 2)
question=Label(window,bg = "white",text= questions[0],justify=LEFT)
question.grid(row =1,column=1)
answer = Entry(window,bg ="white",width = 30)
answer.grid(row = 3,column=1)
# make a submit button
Button(window,text= "Submit",bg = "white",command = submit).grid(row = 3,column = 2)
mainloop()
Also you might want to use a dictionary instead of questions-answers lists.

Related

using entry.get in a list count in python

I try to use the ID entry from the GUI to count the similar IDs in the Excel column.
I always get a 0 in the if-loop and red color shows.
But there are similar IDs in the column.
My code
l1 = tk.Label(tab2, text="Status Check")
l1.place(x=10, y=10)
l2 = tk.Label(tab2, text="ID")
l2.place(x=10, y=60)
ID = tk.Entry(tab2)
ID.place(x=80, y=60)
l2 = tk.Label(tab2, text="Status")
l2.place(x=10, y=100)
t1 = tk.Entry(tab2)
t1.place(x=80, y=100)
comment = tk.Label(tab2)
comment.place(x=240, y=100)
df = pd.read_excel(r'Excel.xlsx')
IDlist = df['ID'].tolist()
id = ID.get()
def immunity_check():
d = IDlist.count(id)
print(d)
if d >= 2:
t1.config(bg= "Green")
comment.configure(text="Fully vaccinated!")
elif d == 1:
t1.config(bg= "Yellow")
comment.configure(text="Vaccinated!")
else d <= 0:
t1.config(bg= "Red")
comment.configure(text="Not vaccinated!")
Can anyone give an advice on how to fix it?
I totally agree with furas comment. Thank him, he solved it.
Issue
Currently the code is reading the input from your text-field before button is pressed. Place a print(id) behind the ID.get() statement and watch console, like:
# GUI initialization omitted for brevity
df = pd.read_excel(r'Excel.xlsx') # read from Excel before button-pressed
IDlist = df['ID'].tolist()
id = ID.get() # get input from text-field before button-pressed
print(id)
# wait for a button press
# below is called on button-press and uses previously read id as argument
def immunity_check():
Solution
This is how you could solve it. The id should be read from text-input after button was pressed. So put move statement into the method:
# part 1: GUI initialization omitted for brevity
# part 2: define functions to call later
def read_ids():
df = pd.read_excel(r'Excel.xlsx')
return df['ID'].tolist()
def immunity_check():
id = ID.get() # read the id to search/count
d = id_list.count(id)
print(f"occurrences of id '{id}' in list: {d}")
if d >= 2:
t1.config(bg= "Green")
comment.configure(text="Fully vaccinated!")
elif d == 1:
t1.config(bg= "Yellow")
comment.configure(text="Vaccinated!")
else d <= 0:
t1.config(bg= "Red")
comment.configure(text="Not vaccinated!")
# part 3: main starts
id_list = read_ids()
# add button with trigger to function immunity_check()
button = tk.Button(tab2,text="Check",command=immunity_check) button.place(x=10,y=180)

Tkinter/Python hanging on user response if response is the last option in a while or if loop

The following code works for requesting input from a user through the Tkinter GUI and turning that input into a usable variable in the main script. However, any value that I put as the last in a list in the if statement (here "4") will hang and crash the program upon enter. This was also the case for "n" in a yes/no scenario. It also happens if I replace the if statement with a while not in [values] - the final value will crash the program. Is this just a quirk of Tkinter or is there something that I am missing?
import tkinter as tk
from tkinter import *
# get choice back from user
global result
badinput = True
while badinput == True:
boxwidth = 1
result = getinput(boxwidth).strip().lower()
if result in ['1', '2', '3', '4']:
badinput = False
# iterate through play options
if result == '1':
# Do Something
elif result =='2':
# Do Something
elif result =='3':
# Do Something
else:
# Do Something
def getinput(boxwidth):
# declaring string variable for storing user input
answer_var = tk.StringVar()
# defining a function that will
# get the answer and set it
def user_response(event):
answer_var.set(answer_entry.get())
return
answer_entry = tk.Entry(root, width = boxwidth, borderwidth = 5)
# making it so that enter calls function
answer_entry.bind('<Return>', user_response)
# placing the entry
answer_entry.pack()
answer_entry.focus()
answer_entry.wait_variable(answer_var)
answer_entry.destroy()
return answer_var.get()
In case anyone is following this question, I did end up solving my problem with a simple if statement within the callback. I can feed a dynamic "choicelist" of acceptable responses into the callback upon user return. If the answer is validated, the gate_var triggers the wait function and sends the program and user response back into the program.
'''
def getinput(boxwidth, choicelist):
# declaring string variable for storing user input
answer_var = tk.StringVar()
gate_var = tk.StringVar()
dumplist = []
# defining a function that will
# get the answer and set it
def user_response(event):
answer_var.set(answer_entry.get())
if choicelist == None:
clearscreen(dumplist)
gate_var.set(answer_entry.get())
return
if answer_var.get() in choicelist:
# passes a validated entry on to gate variable
clearscreen(dumplist)
gate_var.set(answer_entry.get())
else:
# return to entry function and waits if invalid entry
clearscreen(dumplist)
ErrorLabel = tk.Label(root, text = "That is not a valid response.")
ErrorLabel.pack()
ErrorLabel.config(font = ('verdana', 18), bg ='#BE9CCA')
dumplist.append(ErrorLabel)
return
global topentry
if topentry == True:
answer_entry = tk.Entry(top, width = boxwidth, borderwidth = 5)
else:
answer_entry = tk.Entry(root, width = boxwidth, borderwidth = 5)
# making it so that enter calls function
answer_entry.bind('<Return>', user_response)
# placing the entry
answer_entry.pack()
answer_entry.focus()
answer_entry.wait_variable(gate_var)
answer_entry.destroy()
return answer_var.get()
'''

How to continue the program outside the function?

I'm new to python and got stuck while trying to build a GUI. I can't find a way to extract data from the 'login' function, which would be the new TopLevel window created after the user logs in. Because of that, I have to write the remaining code inside the 'login function', but I have the impression that there must be another way around. I tried making the new top level global, but it returns that the new variable is not defined.
from tkinter import *
from tkinter import messagebox
root = Tk()
login_frame = LabelFrame(root, text = "login info").pack()
user_field = Label(login_frame, text = "user: ")
user_field.grid(row = 0,column = 0)
pass_field = Label(login_frame, text = "pass: ")
pass_field.grid(row = 1, column = 0)
user_input = Entry(login_frame)
user_input.grid(row = 0, column = 1)
pass_input = Entry(login_frame, show = "*")
pass_input.grid(row = 1, column = 1)
def login():
if user_input.get() == "user" and pass_input.get() == "user":
if messagebox.showinfo("blah", "blah") == "ok":
pass_input.delete(0, END)
user_input.delete(0, END)
root.withdraw()
**app = Toplevel()**
else:
messagebox.showerror("blah", "blah")
pass_input.delete(0, END)
user_input.delete(0, END)
login_btn = Button(login_frame, text = "LOGIN")
login_btn.grid(row = 2, column = 0)
exit_btn = Button(login_frame, text = "SAIR")
exit_btn.grid(row = 2, column = 1)
root.mainloop()
Your code is breaking indentation. The lines following the definition of the function must be inside the scope of the function, like this:
def login():
if user_input.get() == "user" and pass_input.get() == "user":
if messagebox.showinfo("blah", "blah") == "ok":
...
Regardless of that, you may return any type of data at the end of a function. Consider exposing your TopLevel app like this:
return TopLevel()

How do I get a tkinter screen that isn't the root to disapere using either the destroy or withdraw fuction?

I have a log and quiz system using tkinter GUI. The gui screen allows for loging in and registering. This infomation is stored on an excel file saved as a csv.
The quiz portion is done on the python shell. What I want to do, is hide both the log in screen and main screen once the user has logged in and then if they choose the option of Logout, by entering 'D'. The main screen then comes back up.
I have been successful in getting rid of the main screen using the .withdraw function and can get it to appear back using, .deconify. But for some reason, I can't get rid of the log in screen.
It is possible that it's just in the wrong place, but I get an Attribute Error, which states 'function' object has no attribute 'withdraw'(I get the same for destroy)
Below is my code. It's not all of it. But the parts I think you'd need to be able to fix it.
def Destroy_menu():
main_screen.withdraw()
Login.withdraw()
def Quiz(quizfile, User):
print(User, quizfile)
global var
NumberList = [1,2,3,4,5,6,7]
Questions = True
score = 0
questions_answered = 0
while Questions == True:
try:
Number = random.choice(NumberList)
NumberList.remove(Number)
File = open(quizfile + ".csv", "r")
for line in File:
details = line.split(",")
ID_Number = int(details[0])
if ID_Number == Number:
Question = (details[1])
print("Question:",Question)
Answer_one = (details[2])
print("A):",Answer_one)
Answer_Two = (details[3])
print("B):",Answer_Two)
Answer_Three = (details[4])
print("C):",Answer_Three)
Correct = (details[8])
var = StringVar()
X = input("Answer (e.g. A): ")
print("\n")
if X == Correct:
print("Correct")
score += 1
print("Score:", score)
questions_answered = questions_answered + 1
else:
print("Incorrect, answer was:",Correct)
print("Score:", score)
print("\n")
questions_answered = questions_answered + 1
except:
File.close()
print("Quiz Completed")
print("Final Score:", score, "/ 7")
input("Press enter to continue")
Questions = False
#Writing to file
file_writer = csv.writer(open(r"E:\NEA\Quiz\Scores.csv","a",newline =""))
file_writer.writerow([User,quizfile,score,"NA"])
Quiz_choice(User)
def Quiz_choice(User):
Destroy_menu()
flag = False
print("\n" * 50)
print("Pick a quiz")
print("English Quiz (A)")
print("Maths Quiz (B)")
print("Science Quiz (C)")
print("Logout(D)")
while flag == False:
opt = input(">>>: ")
opt = opt.upper()
if opt == "A":
quizfile = "English"
Quiz(quizfile,User)
flag = True
elif opt == "B":
quizfile = "Maths"
Quiz(quizfile,User)
flag = True
elif opt == "C":
quizfile = "Science"
Quiz(quizfile,User)
flag = True
elif opt == "D":
print("Goodbye")
main_screen()
main_screen = Tk()
main_screen.deiconify()
else:
print("Invalid input. Please input a letter")
def Login():
global login_screen
Login_screen = Toplevel(main_screen)
Login_screen.title("Log in")
Login_screen.geometry ("400x234")
Label(Login_screen, text = "Please enter details below to login").pack()
Label(Login_screen, text = "").pack()
global Username_verify
global Password_verify
Username_verify = StringVar()
Password_verify = StringVar()
global Username_login_entry
global Password_login_entry
Label(Login_screen, text = "Username").pack()
Username_login_entry = Entry(Login_screen, textvariable = Username_verify)
Username_login_entry.pack()
Label(Login_screen, text = "").pack()
Label(Login_screen , text = "Password").pack()
Password_login_entry = Entry(Login_screen, textvariable = Password_verify, show = '*')
Password_login_entry.pack()
Label(Login_screen, text ="").pack()
Button(Login_screen, text = "Log in", width = 10, height = 20,command = Login_verify).pack()
Thanks in advance, and ask any questions you need to.

How to use an arbitrary amount of usable text boxes from a user inputted number in Python GUI

I am writing a program in Python that is a scoring system for a dice game. The game can have any amount of players so I have an input that allows the user to say how many players they have.
I have been able to print Header Labels into the GUI with each players name. However my attempt to print a text box for each person so that their round score can be input is giving me trouble. I tried running a for loop up to the number of players and printing a text box for each person. The problem with that method, is that it is reusing my self.PlayerXroundScore so only the last text box created is usable.
Here is my code, I have tried to comment as best as possible to make it easier to read.
#Allows user to input total number of players
NumPlayers = input("How many players? ")
#Creates a list that is the number of players long
NameList = [0]*NumPlayers
#Allows for input of each Players name
#and stores those names in the list NameList
for i in range(0,NumPlayers):
x = raw_input("Player %d Name? " %(i+1))
NameList[i] = x
#creates the GUI
from Tkinter import *
from tkMessageBox import *
class App(Tk):
def __init__(self):
Tk.__init__(self)
self.Title = ("10,000 scorekeeping")
self.Header = Label(self, text = "Welcome to 10,000 scoring Module, Have Fun!!", font = ("helvetica", "20", "bold")).grid(row = 0, column = 0, columnspan = (NumPlayers * 3))
for NameCount in range(1,(NumPlayers+1)):
#Allows me to create the names as column headers
self.PlayerName = Label(self, text = "%s" %NameList[NameCount - 1],font = ("helvetica","12","bold")).grid(row = 1, column = ((2 * NameCount)))
#This if just makes things more aesthetically pleasing, not relevant to my question
if NameCount < (NumPlayers):
self.PlayerName = Label(self, text = "|",font = ("helvetica","12","bold")).grid(row = 1, column = ((2 * NameCount + 1)))
#This is my problem
#It succesffully prints the correct number of text boxes
#however upon button click which calls the vals in each text box
#only the last created box is useful
#because that is the box corresponding to PlayerXroundScore
self.PlayerXroundScore = Entry(self, width = 4)
self.PlayerXroundScore.grid(row = 2, column = (2 * NameCount))
self.PlayerXroundScore.insert(0, "0000")
self.NextRound = Button(self, text = "Next round", command = self.CalcRoundTotals)
self.NextRound.grid(row = 1, column = 0)
#This is not completed yet, because I wanted to make sure this is the best way to do it before putting in the time
#Its obviously doing erroneous things but that will change,
#I will encounter the same problem in quite a few different places
#but if it can be figured out this once, I can incorporate it elsewhere
def CalcRoundTotals(self):
print x
if __name__ == "__main__":
a = App()
a.mainloop()
It really has me vexed. I thought about concatenating, however, when doing self.ConcatenatedVarName = Entry(...) I don't quite know how to do that. Because, when I concatenate, i do eval("Player" + CounterInForLoop + "roundScore") but SPE doesn't like it when I do that.
Any help at all would be great. I would really like to not write 50(?) if statements that print different amounts of text boxes if i == NumPlayers
Thank you.
Nevermind, figured it out on my own, here is the solution for anyone else with a similar problem.
self.PlayerTextBox = []
for NameCount in range(1,(NumPlayers + 1)):
self.PlayerTextBox.append(Entry(self, width = 4))
self.PlayerTextBox[NameCount - 1].grid(row = 2, column =
(2 * NameCount))
self.PlayerTextBox[NameCount - 1].insert(0, " 0")
Have some other things to figure out as far as how to print things, but those are minor and I will be able to fix them once I try to.
Thank you to anyone who looked at it and tried to figure it out even if you were unsuccessful.

Categories