GUI Quiz Application using Tkinter and Open Trivia DB
Tags: programming, python, tkinter, gui
Published: 2021-10-26 04:33:33

---
title: GUI Quiz Application using Tkinter and Open Trivia DB
published: true
date: 2021-10-24 18:28:03 UTC
tags: Programming, Python, Tkinter, GUI
canonical_url: http://iread.ga/posts/56/gui-quiz-application-using-tkinter-and-open-trivia-db
---

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b63abr93kms5nyfbi87a.png)

In this blog, we'll learn to build a [Graphical User Interface](https://en.wikipedia.org/wiki/Graphical_user_interface) (GUI) **Quiz Application** using the [Tkinter](https://docs.python.org/3/library/tkinter.html) Python built-in module. The task is to ask multiple-choice questions, collect user answers and finally display the results. Before coding the GUI, we'll first see how to fetch multiple-choice questions, their correct answers, and the choices from the [Open Trivia DB API](https://opentdb.com/). The **Open Trivia Database** provides a completely free JSON API for use in programming projects. Use of this API **does not require** an API Key. To make the task more interesting, we'll also randomize the order of choices. 

Watch the demo video to see what we are building:

{% youtube 2hYci86bmjs %}

The following modules and concepts will be utilized in this project:

- [tkinter module](https://docs.python.org/3/library/tkinter.html)
- [random module](https://docs.python.org/3/library/random.html)
- [requests library](https://pypi.org/project/requests/)
- [Python Classes](https://docs.python.org/3/tutorial/classes.html)

We'll be segregating our different functionalities into different classes and files.  
 

# **Fetching Questions**

As we discussed above, we'll be using the Open Trivia DB API to get the questions. Head over to [their API](https://opentdb.com/api_config.php), select the number of questions, category, and difficulty. The question type should be _Multiple Choice_ and the encoding should be _Default Encoding_. Click on Generate API URL and you'll get an API URL. 

Sample API URL: `https://opentdb.com/api.php?amount=10&type=multiple`

For fetching the questions, we'll be using `requests` module. It can be installed as:

```bash
$ pip install requests
```

Let's create a Python file `quiz_data.py` to fetch the quiz questions and answers using the API URL generated above.

```py
import requests

parameters = {
    "amount": 10,
    "type": "multiple"
}

response = requests.get(url="https://opentdb.com/api.php", params=parameters)
question_data = response.json()["results"]
```

In the above script, instead of directly adding the `amount` and `type` parameters in the URL, we have created a `parameters` dictionary and added the respective values. After that, we're making a **GET** request using _requests_ library on the Open Trivia DB API URL. A sample JSON response looks like this:

```json
{
  "response_code": 0,
  "results": [
    {
      "category": "Entertainment: Video Games",
      "type": "multiple",
      "difficulty": "hard",
      "question": "What was the name of the hero in the 80s animated video game &#039;Dragon&#039;s Lair&#039;?",
      "correct_answer": "Dirk the Daring",
      "incorrect_answers": ["Arthur", "Sir Toby Belch", "Guy of Gisbourne"]
    },
    {
      "category": "Entertainment: Video Games",
      "type": "multiple",
      "difficulty": "medium",
      "question": "Which of these game franchises were made by Namco?",
      "correct_answer": "Tekken",
      "incorrect_answers": ["Street Fighter", "Mortal Kombat", "Dragon Quest"]
    }
  ]
}

```

The JSON data contains a dictionary with two keys - `response_code` and `results`. The `response_code` tells developers what the API is doing. The `results` is a list we are interested in. So, we have stored the value of results in a variable `question_data`.  
 

# **Question Model**

Question model is nothing but a Python class with three attributes - `question_text`, `correct_answer` and `choices`. _question\_text_ is the question, _correct\_answer_ is the correct answer for that question and _choices_ is a list of options for that question.

Let's create a `question_model.py` file and create the class in it:

```py
class Question:
    def __init__ (self, question: str, correct_answer: str, choices: list):
        self.question_text = question
        self.correct_answer = correct_answer
        self.choices = choices

```

 

# **Quiz Brain**

The `QuizBrain`, as the name suggests, is the brain of the application. Let's create `quiz_brain.py` file and add the following code there:

```py
class QuizBrain:

    def __init__ (self, questions):
        self.question_no = 0
        self.score = 0
        self.questions = questions
        self.current_question = None

    def has_more_questions(self):
        """To check if the quiz has more questions"""
        
        return self.question_no < len(self.questions)

    def next_question(self):
        """Get the next question by incrementing the question number"""
        
        self.current_question = self.questions[self.question_no]
        self.question_no += 1
        q_text = self.current_question.question_text
        return f"Q.{self.question_no}: {q_text}"

    def check_answer(self, user_answer):
        """Check the user answer against the correct answer and maintain the score"""
        
        correct_answer = self.current_question.correct_answer
        if user_answer.lower() == correct_answer.lower():
            self.score += 1
            return True
        else:
            return False

    def get_score(self):
        """Get the number of correct answers, wrong answers and score percentage."""
        
        wrong = self.question_no - self.score
        score_percent = int(self.score / self.question_no * 100)
        return (self.score, wrong, score_percent)

```

The `QuizBrain` class takes `questions`, list of questions. Additionally, the `question_no` and `score` attributes are initialized with `0` and the `current_question` is set to `None` initially.

The first method `has_more_questions()` checks whether the quiz has more questions or not. The next method `next_question()` gets the question from the `questions` list at index `question_no` and then increments the `question_no` attribute returns a formatted question. The `check_answer()` method takes `user_answer` as an argument and checks whether the user's answer is correct or not. It also maintains the _score_ and returns boolean values. The last method `get_score()` returns the number of _correct answers_, _wrong answers,_ and _score percentage_.  
 

# **Quiz UI**

Let's move to the next part where we create the user interface of the application. Create a `quiz_ui.py` file and add the following content:

```py
from tkinter import Tk, Canvas, StringVar, Label, Radiobutton, Button, messagebox
from quiz_brain import QuizBrain

THEME_COLOR = "#375362"

class QuizInterface:

    def __init__ (self, quiz_brain: QuizBrain) -> None:
        self.quiz = quiz_brain
        self.window = Tk()
        self.window.title("iQuiz App")
        self.window.geometry("850x530")

        # Display Title
        self.display_title()

        # Creating a canvas for question text, and dsiplay question
        self.canvas = Canvas(width=800, height=250)
        self.question_text = self.canvas.create_text(400, 125,
                                                     text="Question here",
                                                     width=680,
                                                     fill=THEME_COLOR,
                                                     font=(
                                                         'Ariel', 15, 'italic')
                                                     )
        self.canvas.grid(row=2, column=0, columnspan=2, pady=50)
        self.display_question()

        # Declare a StringVar to store user's answer
        self.user_answer = StringVar()

        # Display four options(radio buttons)
        self.opts = self.radio_buttons()
        self.display_options()

        # To show whether the answer is correct or wrong
        self.feedback = Label(self.window, pady=10, font=("ariel", 15, "bold"))
        self.feedback.place(x=300, y=380)

        # Next and Quit Button
        self.buttons()

        # Mainloop
        self.window.mainloop()

    def display_title(self):
        """To display title"""

        # Title
        title = Label(self.window, text="iQuiz Application",
                      width=50, bg="green", fg="white", font=("ariel", 20, "bold"))

        # place of the title
        title.place(x=0, y=2)

    def display_question(self):
        """To display the question"""

        q_text = self.quiz.next_question()
        self.canvas.itemconfig(self.question_text, text=q_text)

    def radio_buttons(self):
        """To create four options (radio buttons)"""

        # initialize the list with an empty list of options
        choice_list = []

        # position of the first option
        y_pos = 220

        # adding the options to the list
        while len(choice_list) < 4:

            # setting the radio button properties
            radio_btn = Radiobutton(self.window, text="", variable=self.user_answer,
                                    value='', font=("ariel", 14))

            # adding the button to the list
            choice_list.append(radio_btn)

            # placing the button
            radio_btn.place(x=200, y=y_pos)

            # incrementing the y-axis position by 40
            y_pos += 40

        # return the radio buttons
        return choice_list

    def display_options(self):
        """To display four options"""

        val = 0

        # deselecting the options
        self.user_answer.set(None)

        # looping over the options to be displayed for the
        # text of the radio buttons.
        for option in self.quiz.current_question.choices:
            self.opts[val]['text'] = option
            self.opts[val]['value'] = option
            val += 1

    def next_btn(self):
        """To show feedback for each answer and keep checking for more questions"""

        # Check if the answer is correct
        if self.quiz.check_answer(self.user_answer.get()):
            self.feedback["fg"] = "green"
            self.feedback["text"] = 'Correct answer! \U0001F44D'
        else:
            self.feedback['fg'] = 'red'
            self.feedback['text'] = ('\u274E Oops! \n'
                                     f'The right answer is: {self.quiz.current_question.correct_answer}')

        if self.quiz.has_more_questions():
            # Moves to next to display next question and its options
            self.display_question()
            self.display_options()
        else:
            # if no more questions, then it displays the score
            self.display_result()

            # destroys the self.window
            self.window.destroy()

    def buttons(self):
        """To show next button and quit button"""

        # The first button is the Next button to move to the
        # next Question
        next_button = Button(self.window, text="Next", command=self.next_btn,
                             width=10, bg="green", fg="white", font=("ariel", 16, "bold"))

        # palcing the button on the screen
        next_button.place(x=350, y=460)

        # This is the second button which is used to Quit the self.window
        quit_button = Button(self.window, text="Quit", command=self.window.destroy,
                             width=5, bg="red", fg="white", font=("ariel", 16, " bold"))

        # placing the Quit button on the screen
        quit_button.place(x=700, y=50)

    def display_result(self):
        """To display the result using messagebox"""
        correct, wrong, score_percent = self.quiz.get_score()

        correct = f"Correct: {correct}"
        wrong = f"Wrong: {wrong}"

        # calculates the percentage of correct answers
        result = f"Score: {score_percent}%"

        # Shows a message box to display the result
        messagebox.showinfo("Result", f"{result}\n{correct}\n{wrong}")

```

Well, a very large script but if you look at it carefully, it's quite easy to understand. The code is well documented.  
 

# **Getting Things Together**

Since all the components are ready to be integrated together, let's create a `main.py` file and add the following content there:

```py
from question_model import Question
from quiz_data import question_data
from quiz_brain import QuizBrain
from quiz_ui import QuizInterface
from random import shuffle
import html

question_bank = []
for question in question_data:
    choices = []
    question_text = html.unescape(question["question"])
    correct_answer = html.unescape(question["correct_answer"])
    incorrect_answers = question["incorrect_answers"]
    for ans in incorrect_answers:
        choices.append(html.unescape(ans))
    choices.append(correct_answer)
    shuffle(choices)
    new_question = Question(question_text, correct_answer, choices)
    question_bank.append(new_question)

quiz = QuizBrain(question_bank)

quiz_ui = QuizInterface(quiz)

print("You've completed the quiz")
print(f"Your final score was: {quiz.score}/{quiz.question_no}")

```

Let's import all the classes from the different files we have created. In addition to that, we also need `shuffle` method from the `random` module and the `html` module. We have a list `question_bank`. We are iterating over the `question_data` that we receive from `quiz_data.py` file. If you see the sample response, you will find some text such as `&#039;Dragon&#039`. They need to be unescaped using the `html.unescape` method. We have a `choices` list that will contain the correct answer as well as the incorrect answers. The list will be shuffled using the `shuffle` method from the `random` module. After shuffling, we are creating question using the `Question` model from `quiz_model.py` file and appending it the `question_bank` list. 

Next, we're creating an object `quiz` of _QuizBrain_ class which requires a list of questions. So, we're passing `question_bank` to it. After that, we're creating an object `quiz_ui` of _QuizInterface_ class which requires an object of QuizBrain class, so we have passed the newly created `quiz` object to it. 

Now that everything is ready, we are ready to run the application.

```bash
$ python main.py
```

###   
<u><strong>Screenshots</strong></u>

![](https://res.cloudinary.com/dlomjljb6/image/upload/v1/media/blog/uploads/2021/10/24/screenshot-2021-10-24-234659_sgyfkl)

![](https://res.cloudinary.com/dlomjljb6/image/upload/v1/media/blog/uploads/2021/10/24/screenshot-2021-10-24-234725_jhddoy)

![](https://res.cloudinary.com/dlomjljb6/image/upload/v1/media/blog/uploads/2021/10/24/screenshot-2021-10-24-234803_r08dgy)

![](https://res.cloudinary.com/dlomjljb6/image/upload/v1/media/blog/uploads/2021/10/24/screenshot-2021-10-24-234843_bcoxtl)
 

# **Conclusion**

Hope you liked the quiz application. You can add more features and make the UI more attractive if you wish to.

Code Repository: [https://github.com/ashutoshkrris/GUI-Quiz-Tkinter](https://github.com/ashutoshkrris/GUI-Quiz-Tkinter)

[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/T6T23YR7C)