0

I want my quiz to wait for user interactions with the buttons of different values and then decide if the "text" inside the button matches the answer to the question, This system works but instead of waiting for the user to pick one option. It goes through all the questions outputting all the answers and questions, doesn't require interactions.

I tried to use partial and lambda to make it go to the function after the user pressed something and it works but it doesn't accept button presses and goes through every question. And threading basically does the same thing but stays at the first question and shows a million popups of every option without user interaction.

Is there an easier way of doing this and allow the function to see what button was pressed or am I doing it right?

listofdifnumbers contains all the question numbers for difficulties so let's say the user picked 10 easy, 20 normal, and 30 hard questions then listofdifnumbers will be [10,20,30].

questions contains all the questions in the form of a nested dictionary, E.g. {1: {'Question': 'WWW stands for?', 'fakes': 'World Word Web', 'Answer': 'World Wide Web'}, 2: {'Question': 'Which of the following is a group of independent computers attached to one another through communication media', 'fakes': 'Internet', 'Answer': 'Network'}}

def wrongcheck(num):
    global  Questionnum, questionseasy1, questionsmedium1, questionshard1,answer,score
    global choice, correctorwrong
    global score1of1

    if score1of1<=listofdifnumbers[0]:
        if num == 1:
            if showeasybutton1["text"]== questions[score1of1].get("Answer"):
                picked = showeasybutton1["text"]
                choice.append(picked)
                correctorwrong.append("correct")
                error.showerror("Correct",("%s is the correct answer \n Welldone \n Score +1"%(picked)),icon="info")
                score+=1
            else:
                picked = showeasybutton1["text"]
                choice.append(picked)
                correctorwrong.append("Wrong")
                error.showerror("Wrong",("%s was the correct answer,\n You chose %s"%(questions[score1of1].get("Answer"),picked)),icon="info")
        elif num == 2:
            if showeasybutton2["text"]== questions[score1of1].get("Answer"):
                picked = showeasybutton2["text"]
                choice.append(picked)
                correctorwrong.append("correct")
                error.showerror("Correct",("%s is the correct answer \n Welldone \n Score +1"%(picked)),icon="info")
                score+=1
            else:
                picked = showeasybutton2["text"]
                choice.append(picked)
                correctorwrong.append("Wrong")
                error.showerror("Wrong",("%s was the correct answer,\n You chose %s"%(questions[score1of1].get("Answer"),picked)),icon="info")

        print(correctorwrong)
        print(choice)
        print(score1of1)


def quizinterfacestarter():
    global timer
    global login
    global login, lookwhereat2,quizconfigurationframe, AllquizSTATSlabel
    global  Questionnum, questionseasy1, questionsmedium1, questionshard1, MaxMarkss, MaxMarks
    global questions

    print(questions)
    print(len(questions))
    screen_width = login.winfo_screenwidth()
    screen_height = login.winfo_screenheight()
    for widget in login.winfo_children():
        widget.destroy()
    height1 = int(screen_height)/4
    height2 = int(screen_height)*3
    tophalfframe= Frame(login,width=int(screen_width),height=height1,bg="light coral")
    tophalfframe.grid(sticky="new")
    goback= PhotoImage(file='icons/goback.png')
    exitframebutton= Button(tophalfframe,image=goback,text="Return     ",compound="left",bg='aquamarine',relief='flat', font="Ariel 25",command=subjectgoback)
    exitframebutton.grid(column=1,sticky="nws")
    movealonglabel = Label(text="                                       ",bg="light coral")
    movealonglabel.grid(row=1,column=2,columnspan=2)
    QuestionLabelAsker = Label(tophalfframe,text="Questions Loading ...",relief='raised', font="Ariel 20")
    QuestionLabelAsker.grid(column=5,row=1,columnspan=7)
    if timer == True:
        initiatetimerstart = time.strftime("%H:%M:%S")
    global fsos,showeasybutton1,showeasybutton2,answer
    showeasybutton1=Button(login,text="Answer 1",font="Ariel 30")
    showeasybutton1.grid(sticky="new")
    showeasybutton2=Button(login,text="Answer 2",font="Ariel 30")
    showeasybutton2.grid(sticky="new")
    showmediumbutton=Button(login,text="Answer 3",font="Ariel 30")
    showmediumbutton.grid(sticky="new")
    showhardbutton=Button(login,text="Answer 4",font="Ariel 30")
    showhardbutton.grid(sticky="new")
    global choice, correctorwrong,score
    choice = []
    correctorwrong =[]
    score=0
    sonumisone=0
    global score1of1
    score1of1=0
    ##works up to here
    while sonumisone != (sum(listofdifnumbers)+1):
        sonumisone+=1
        if sonumisone<=listofdifnumbers[0]:
            score1of1+=1
            QuestionLabelAsker["text"]=questions[score1of1].get("Question")
            answers = [questions[score1of1].get("fakes"),questions[score1of1].get("Answer")]
            fsos = r.choice(answers)
            showeasybutton1["text"]= fsos
            answers.remove(fsos)
            showeasybutton2["text"]= answers[0]
            showeasybutton2.config(command=lambda *args: wrongcheck(2))
            showeasybutton1.config(command=lambda *args: wrongcheck(1))
            showmediumbutton["state"]="disabled"
            showhardbutton["state"]="disabled"

    login.mainloop()
martineau
  • 119,623
  • 25
  • 170
  • 301
Bond 007
  • 210
  • 2
  • 12
  • 2
    You're not thinking about this in an event-driven way. You don't wait for anything. You set up all your controls, then you fall into root.mainloop. At that point, Tk will call your functions when anything happens (like button clicks). Your handlers do your simple updates, then return back to the Tk mainloop. When you are not in the Tk mainloop, your UI cannot respond. No one is listening. We don't have all of the code here, so we can't really suggest how to reorganize this properly. – Tim Roberts Apr 28 '21 at 18:27
  • And you don't typically add new UI elements in your handlers. You can add empty elements to begin with and fill in text in the handler. – Tim Roberts Apr 28 '21 at 18:28
  • 1
    Unless it is outrageously long, that's the best way. As it is, we can't see how these functions fit into your overall program. – Tim Roberts Apr 28 '21 at 18:29
  • all the questions work (with everything else) and are picked but can't find a way to enumerate through all the questions, get user input and then mark it notifying the user of their score, after that head to the next question – Bond 007 Apr 28 '21 at 18:30
  • Do you have a "move to next" button to reset things to set up for the next question? By the way, the font is "Arial", not "Ariel". "Ariel" is a mermaid, ;) And this still isn't the whole code. No one is calling these functions, and there's no definition for "listofdifnumbers", but it's not clear it's worth the trouble. – Tim Roberts Apr 28 '21 at 18:39
  • I have no reset button as I thought that after the function, it will go back to the while statement loading and updating the labels/ buttons with the next questions and repeats until all questions are done. Even if I had a continue button, how would I return to the mainloop() if it doesnt want to work with partial or lambda? – Bond 007 Apr 28 '21 at 18:41
  • You don't seem to understand how event-driven programs like those based on `tkinter` work. See [Tkinter — executing functions over time](https://stackoverflow.com/questions/9342757/tkinter-executing-functions-over-time) for some information on the subject. – martineau Apr 28 '21 at 18:51
  • You're not in the `while` loop any more. Remember, NOTHING will be visible on the screen until you get to `login.mainloop()`. That's where all the accumulated setup messages get handled, and stuff actually get drawn. Your program is a slave to that mainloop. You have to handle the button click callbacks by updating your UI and then returning, which goes back to the mainloop. – Tim Roberts Apr 28 '21 at 18:53
  • I doubt that's the kind of question — How do I write a GUI? — a site like Stack Overflow can answer for you. Also note that tkinter is not thread-safe, so using them with it is very difficult. – martineau Apr 28 '21 at 19:03
  • ok, so how can I get user input inside the same statement, this will skip the whole threading issue. – Bond 007 Apr 28 '21 at 19:07
  • @Bond007 Try looking at some tkinter tutorials. From what I can see you will also need to look at [this](https://en.wikipedia.org/wiki/Event-driven_programming) – TheLizzard Apr 28 '21 at 19:21

2 Answers2

2

I know you may not be interested in starting over, but here is how I might implement such a program. Our main entry-point of the script is the main function, which defines a list of "question objects" (really just dictionaries) which describe all questions and their associated answers. There are just two questions in total for this example, and each question has four possible answers. We instantiate an instance of our Application class and run its .mainloop() to start the event-loop. The application instance is initialized with our list of questions. It doesn't keep track of the question list directly, but rather, it creates an iterator of that list, such that we can iterate over the questions later using next. The whole program works in an event-driven way - first, we create out widgets and set the layout. Then, we invoke goto_next_question to grab the next question from our question iterator, and then we fill the text of the label and buttons. We also change all the buttons' callbacks, such that only one of them will lead us to the next question. When the user presses the button with the correct answer, we invoke goto_next_question again, and keep going in this way until no questions remain:

import tkinter as tk


class Application(tk.Tk):

    def __init__(self, questions, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        self.title("Questions")
        self.geometry("256x128")
        self.resizable(width=False, height=False)

        self.grid_columnconfigure(0, weight=1)
        self.grid_columnconfigure(1, weight=1)
        self.grid_rowconfigure(0, weight=1)
        self.grid_rowconfigure(1, weight=1)
        self.grid_rowconfigure(2, weight=1)

        self.questions = iter(questions)

        self.question_label = tk.Label(self, text="hello world" * 3, borderwidth=2, relief="ridge")
        self.question_label.grid(row=0, column=0, columnspan=2)

        self.default_button_color = tk.Button().cget("bg")

        self.buttons = []
        for y in range(2):
            for x in range(2):
                button = tk.Button(self, text="123")
                button.grid(row=y+1, column=x, sticky=tk.N + tk.S + tk.E + tk.W)
                self.buttons.append(button)

        self.goto_next_question()

    def goto_next_question(self):
        from random import shuffle
        
        question = next(self.questions, None)
        if question is None:
            self.destroy()
            return
        shuffle(question["answers"])
        
        self.question_label["text"] = question["text"]
        for button, answer in zip(self.buttons, question["answers"]):
            button["text"] = answer["text"]
            button["bg"] = self.default_button_color
            if answer["isCorrect"]:
                button["command"] = self.goto_next_question
            else:
                button["command"] = lambda button=button: self.answered_wrong(button)

    def answered_wrong(self, button):
        from tkinter.messagebox import showerror
        button["bg"] = "red"
        showerror(message="Wrong!")


def main():

    questions = [
        
        {
            "text": "What is the capitol of Switzerland?",
            "answers": [
                {
                    "text": "Bern",
                    "isCorrect": True
                },
                {
                    "text": "Zürich",
                    "isCorrect": False
                },
                {
                    "text": "Lucerne",
                    "isCorrect": False
                },
                {
                    "text": "Geneva",
                    "isCorrect": False
                }
            ]
        },

        {
            "text": "What is the height of Mount Everest?",
            "answers": [
                {
                    "text": "8,849m",
                    "isCorrect": True
                },
                {
                    "text": "7,994m",
                    "isCorrect": False
                },
                {
                    "text": "4,021m",
                    "isCorrect": False
                },
                {
                    "text": "10,119m",
                    "isCorrect": False
                }
            ]

        }
    ]

    application = Application(questions)
    application.mainloop()

    return 0


if __name__ == "__main__":
    import sys
    sys.exit(main())

Paul M.
  • 10,481
  • 2
  • 9
  • 15
0

Taking into account what you told me, I moved the whole question extractor to a new function and changed the while statement to an if statement and this does exactly what I wanted. Thanks to all those who answered.

def wrongcheck(num):
    global  Questionnum, questionseasy1, questionsmedium1, questionshard1,answer,score
    global choice, correctorwrong
    global score1of1
    login.update()
    if score1of1<=listofdifnumbers[0]:
        if num == 1:
            if showeasybutton1["text"]== questions[score1of1].get("Answer"):
                picked = showeasybutton1["text"]
                choice.append(picked)
                correctorwrong.append("correct")
                error.showerror("Correct",("%s is the correct answer \n Welldone \n Score +1"%(picked)),icon="info")
                score+=1
            else:
                picked = showeasybutton1["text"]
                choice.append(picked)
                correctorwrong.append("Wrong")
                error.showerror("Wrong",("%s was the correct answer,\n You chose %s"%(questions[score1of1].get("Answer"),picked)),icon="info")  
        elif num == 2:
            if showeasybutton2["text"]== questions[score1of1].get("Answer"):
                picked = showeasybutton2["text"]
                choice.append(picked)
                correctorwrong.append("correct")
                error.showerror("Correct",("%s is the correct answer \n Welldone \n Score +1"%(picked)),icon="info")
                score+=1
            else:
                picked = showeasybutton2["text"]
                choice.append(picked)
                correctorwrong.append("Wrong")
                error.showerror("Wrong",("%s was the correct answer,\n You chose %s"%(questions[score1of1].get("Answer"),picked)),icon="info")
    print(correctorwrong)
    print(choice)
    print(score1of1)
    fixquizinterfaceproblem()


def fixquizinterfaceproblem():
    global timer
    global login, lookwhereat2,quizconfigurationframe, AllquizSTATSlabel
    global  Questionnum, questionseasy1, questionsmedium1, questionshard1, MaxMarkss, MaxMarks
    global questions
    global sonumisone,score1of1,score
    if sonumisone != (sum(listofdifnumbers)+1):
        sonumisone+=1
        print(score1of1,sonumisone)
        if sonumisone<=listofdifnumbers[0]:
            score1of1+=1
            login.update()
            QuestionLabelAsker["text"]=questions[score1of1].get("Question")
            answers = [questions[score1of1].get("fakes"),questions[score1of1].get("Answer")]
            fsos = r.choice(answers)
            showeasybutton1["text"]= fsos
            answers.remove(fsos)
            showeasybutton2["text"]= answers[0]
            showeasybutton2.config(command=lambda *args: wrongcheck(2))
            showeasybutton1.config(command=lambda *args: wrongcheck(1))
            showmediumbutton["state"]="disabled"
            showhardbutton["state"]="disabled"
    else:
        pass
    login.mainloop()

            
def quizinterfacestarter():
    global timer
    global login
    global login, lookwhereat2,quizconfigurationframe, AllquizSTATSlabel
    global  Questionnum, questionseasy1, questionsmedium1, questionshard1, MaxMarkss, MaxMarks
    global questions
    print(questions)
    print(len(questions))         
    screen_width = login.winfo_screenwidth()
    screen_height = login.winfo_screenheight()
    for widget in login.winfo_children():
        widget.destroy()
    height1 = int(screen_height)/4
    height2 = int(screen_height)*3
    tophalfframe= Frame(login,width=int(screen_width),height=height1,bg="light coral")
    tophalfframe.grid(sticky="new")
    goback= PhotoImage(file='icons/goback.png')
    exitframebutton= Button(tophalfframe,image=goback,text="Return     ",compound="left",bg='aquamarine',relief='flat', font="Ariel 25",command=subjectgoback)
    exitframebutton.grid(column=1,sticky="nws")
    movealonglabel = Label(text="                                       ",bg="light coral")
    movealonglabel.grid(row=1,column=2,columnspan=2)
    global QuestionLabelAsker
    QuestionLabelAsker = Label(tophalfframe,text="Questions Loading ...",relief='raised', font="Ariel 20")
    QuestionLabelAsker.grid(column=5,row=1,columnspan=7)
    if timer == True:
        initiatetimerstart = time.strftime("%H:%M:%S")
    global fsos,showeasybutton1,showeasybutton2,showmediumbutton, showhardbutton, answer
    showeasybutton1=Button(login,text="Answer 1",font="Ariel 30")
    showeasybutton1.grid(sticky="new")
    showeasybutton2=Button(login,text="Answer 2",font="Ariel 30")
    showeasybutton2.grid(sticky="new")
    showmediumbutton=Button(login,text="Answer 3",font="Ariel 30")
    showmediumbutton.grid(sticky="new")
    showhardbutton=Button(login,text="Answer 4",font="Ariel 30")
    showhardbutton.grid(sticky="new")
    global choice, correctorwrong,score
    choice = []
    correctorwrong =[]    
    score=0
    global sonumisone
    sonumisone=0
    global score1of1
    score1of1=0
    fixquizinterfaceproblem()
    login.mainloop()
Bond 007
  • 210
  • 2
  • 12