GUI Quiz App Project in Python Using Tkinter (With Source Code)

← Back to Projects

GUI Quiz App in Python.

Building graphical applications is an important milestone for Python developers. In this project, you’ll learn how to create a GUI-based Quiz Application in Python using Tkinter. The app displays multiple-choice questions, accepts user input, validates answers, and shows the final score. This project is ideal for learners who want hands-on experience with Python GUI programming.



What You Will Learn From This Project

  • How to create GUI applications using Python’s Tkinter library
  • How to handle button clicks and user input
  • How to display dynamic questions and options
  • How to calculate and display quiz scores
  • How to enhance a project with timers and external files

About the project: This is a project for GUI Quiz App in Python . This will be a standalone script that uses Python's built-in tkinter library to create a graphical user interface.

This application will present a series of multiple-choice questions, allow you to select an answer, and then provide feedback on whether your answer was correct.

At the end of the quiz, it will display your final score. You won't need to install any external libraries to run this, as tkinter comes with a standard Python installation.


Why Use Tkinter for GUI Applications?

Tkinter is Python’s standard GUI library and comes pre-installed with most Python distributions. It allows developers to create windows, buttons, labels, and dialogs without installing third-party packages. This makes Tkinter an excellent choice for beginners and small desktop applications.


This script provides a complete and interactive GUI Quiz App.

You can save the code as a Python file (e.g., quiz_app.py) and run it directly. The tkinter library will handle the window creation and button interactions for you.

If you are new to GUI programming, you may first want to explore Python Tkinter basics or practice with smaller projects like a Python Pomodoro Timer project.

Project Level: Intermediate

You can directly copy the below snippet code with the help of green copy button, paste it and run it in any Python editor you have.

Steps: Follow these steps

Step 1: Copy below code using green 'copy' button.

Step 2: Paste the code on your chosen editor.

Step 3: Save the code with filename and .py extention.

Step 4: Run (Press F5 if using python IDLE)




# quiz_app.py

import tkinter as tk
from tkinter import messagebox

class QuizApp:
    def __init__(self, root):
        """
        Initializes the main application window and quiz data.
        """
        self.root = root
        self.root.title("Python Quiz App")
        self.root.geometry("600x400")
        self.root.config(bg="#f0f0f0")

        # Sample quiz questions
        self.questions = [
            {
                "question": "What is the capital of France?",
                "options": ["London", "Berlin", "Paris", "Rome"],
                "answer": "Paris"
            },
            {
                "question": "Which planet is known as the 'Red Planet'?",
                "options": ["Earth", "Mars", "Jupiter", "Venus"],
                "answer": "Mars"
            },
            {
                "question": "What is the largest ocean on Earth?",
                "options": ["Atlantic Ocean", "Indian Ocean", "Arctic Ocean", "Pacific Ocean"],
                "answer": "Pacific Ocean"
            },
            {
                "question": "Who wrote 'To Kill a Mockingbird'?",
                "options": ["Harper Lee", "Mark Twain", "Ernest Hemingway", "F. Scott Fitzgerald"],
                "answer": "Harper Lee"
            },
            {
                "question": "What is the chemical symbol for gold?",
                "options": ["Au", "Ag", "Fe", "Pb"],
                "answer": "Au"
            }
        ]

        self.current_question_index = 0
        self.score = 0
        self.selected_option = tk.StringVar(value="")

        self.create_widgets()

    def create_widgets(self):
        """
        Creates all the GUI elements for the quiz.
        """
        # Create a frame for the quiz content
        self.quiz_frame = tk.Frame(self.root, padx=20, pady=20, bg="#ffffff")
        self.quiz_frame.pack(pady=20, padx=20, fill="both", expand=True)

        # Question label
        self.question_label = tk.Label(self.quiz_frame, text="", font=("Helvetica", 16), wraplength=500, bg="#ffffff")
        self.question_label.pack(pady=20)

        # Options frame
        self.options_frame = tk.Frame(self.quiz_frame, bg="#ffffff")
        self.options_frame.pack(pady=10)

        self.option_buttons = []
        for i in range(4):
            option_button = tk.Radiobutton(self.options_frame, text="", font=("Helvetica", 12), variable=self.selected_option, value="", bg="#ffffff", activebackground="#e0e0e0")
            option_button.pack(anchor="w", pady=5)
            self.option_buttons.append(option_button)

        # Submit button
        self.submit_button = tk.Button(self.root, text="Submit", command=self.check_answer, font=("Helvetica", 14), bg="#4CAF50", fg="white", activebackground="#45a049", relief="flat")
        self.submit_button.pack(pady=(0, 20), ipadx=10, ipady=5)

        self.display_question()

    def display_question(self):
        """
        Updates the GUI to show the current question and options.
        """
        if self.current_question_index < len(self.questions):
            question_data = self.questions[self.current_question_index]
            self.question_label.config(text=question_data["question"])
            
            # Reset selected option
            self.selected_option.set("")
            
            # Update option buttons
            for i in range(4):
                self.option_buttons[i].config(text=question_data["options"][i], value=question_data["options"][i])
        else:
            self.show_results()

    def check_answer(self):
        """
        Checks if the selected option is correct, updates the score, and moves to the next question.
        """
        user_answer = self.selected_option.get()
        if user_answer:
            correct_answer = self.questions[self.current_question_index]["answer"]
            
            if user_answer == correct_answer:
                self.score += 1
                messagebox.showinfo("Result", "Correct!")
            else:
                messagebox.showerror("Result", f"Incorrect. The correct answer was: {correct_answer}")
            
            self.current_question_index += 1
            self.display_question()
        else:
            messagebox.showwarning("Warning", "Please select an option before submitting.")

    def show_results(self):
        """
        Displays the final score to the user and closes the app.
        """
        messagebox.showinfo("Quiz Complete", f"Quiz finished!\nYour final score is: {self.score}/{len(self.questions)}")
        self.root.destroy()

def main():
    """
    Main function to initialize and run the application.
    """
    root = tk.Tk()
    app = QuizApp(root)
    root.mainloop()

if __name__ == "__main__":
    main()





Advanced Version of the GUI Quiz App

Below is an enhanced version of the quiz application with additional features such as:

  • File Loading: You can now load questions from an external file via the File menu. The app supports both JSON and CSV formats.
    • JSON Format: Your file should be an array of objects, with each object containing "question", "options", and "answer" keys.
    • CSV Format: Your file should have a header row and each subsequent row should contain the question, followed by four options, and finally the correct answer.
  • Timer: A countdown timer is now displayed in the top-right corner of the window. If the timer runs out before you submit an answer, the question is automatically marked as incorrect and the quiz moves on.
  • Styling: The UI has been updated with a more modern, dark theme. The buttons have a flat style and the layout uses frames to provide better spacing and visual appeal.

Below is another code snippet with more robust, including a timer for each question, the ability to load questions from external files, and more elaborate styling.




# quiz_app.py

import tkinter as tk
from tkinter import messagebox, filedialog
import json
import csv
import os

class QuizApp:
    def __init__(self, root):
        """
        Initializes the main application window and quiz data.
        """
        self.root = root
        self.root.title("Advanced Python Quiz App")
        self.root.geometry("700x500")
        self.root.config(bg="#34495e")  # Dark blue-gray background

        # Sample quiz questions (used if no file is loaded)
        self.questions = [
            {
                "question": "What is the capital of France?",
                "options": ["London", "Berlin", "Paris", "Rome"],
                "answer": "Paris"
            },
            {
                "question": "Which planet is known as the 'Red Planet'?",
                "options": ["Earth", "Mars", "Jupiter", "Venus"],
                "answer": "Mars"
            },
            {
                "question": "What is the largest ocean on Earth?",
                "options": ["Atlantic Ocean", "Indian Ocean", "Arctic Ocean", "Pacific Ocean"],
                "answer": "Pacific Ocean"
            }
        ]
        
        self.time_limit = 15  # seconds per question
        self.timer_id = None
        self.current_question_index = 0
        self.score = 0
        self.selected_option = tk.StringVar(value="")

        # --- Styling & Fonts ---
        self.font_title = ("Helvetica", 20, "bold")
        self.font_question = ("Helvetica", 16)
        self.font_options = ("Helvetica", 12)
        self.font_button = ("Helvetica", 14, "bold")

        self.create_widgets()
        self.create_menu()
        self.display_question()

    def create_menu(self):
        """Creates the menu bar for loading questions from a file."""
        menubar = tk.Menu(self.root)
        self.root.config(menu=menubar)

        file_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="File", menu=file_menu)
        file_menu.add_command(label="Load Questions (JSON)", command=lambda: self.load_questions_from_file('json'))
        file_menu.add_command(label="Load Questions (CSV)", command=lambda: self.load_questions_from_file('csv'))

    def load_questions_from_file(self, file_type):
        """
        Opens a file dialog to let the user select a question file.
        """
        file_path = ""
        if file_type == 'json':
            file_path = filedialog.askopenfilename(
                defaultextension=".json",
                filetypes=[("JSON Files", "*.json")]
            )
        elif file_type == 'csv':
            file_path = filedialog.askopenfilename(
                defaultextension=".csv",
                filetypes=[("CSV Files", "*.csv")]
            )

        if file_path:
            try:
                if file_type == 'json':
                    with open(file_path, 'r') as f:
                        new_questions = json.load(f)
                elif file_type == 'csv':
                    new_questions = []
                    with open(file_path, 'r', newline='') as f:
                        reader = csv.reader(f)
                        header = next(reader) # Skip header
                        for row in reader:
                            if len(row) >= 5: # Ensure the row has enough columns
                                new_questions.append({
                                    "question": row[0],
                                    "options": [row[1], row[2], row[3], row[4]],
                                    "answer": row[5] if len(row) > 5 else row[1] # Answer can be a specific column or the first option
                                })
                
                if new_questions:
                    self.questions = new_questions
                    self.current_question_index = 0
                    self.score = 0
                    messagebox.showinfo("Success", "Questions loaded successfully!")
                    self.display_question()
                else:
                    messagebox.showerror("Error", "The selected file is empty or invalid.")

            except (IOError, json.JSONDecodeError, csv.Error) as e:
                messagebox.showerror("Error", f"Could not read the file: {e}")
        else:
            messagebox.showwarning("Info", "File loading cancelled.")

    def create_widgets(self):
        """
        Creates all the GUI elements for the quiz.
        """
        # Main frame for a centered, clean look
        main_frame = tk.Frame(self.root, bg="#2c3e50", padx=15, pady=15)
        main_frame.pack(pady=20, padx=20, fill="both", expand=True)

        # Timer label
        self.timer_label = tk.Label(main_frame, text=f"Time: {self.time_limit}", font=("Helvetica", 14), bg="#2c3e50", fg="white")
        self.timer_label.pack(anchor="ne", padx=10, pady=5)

        # Question label
        self.question_label = tk.Label(main_frame, text="", font=self.font_question, wraplength=600, bg="#2c3e50", fg="#ecf0f1")
        self.question_label.pack(pady=(20, 10))

        # Options frame with a different color
        options_frame = tk.Frame(main_frame, bg="#34495e")
        options_frame.pack(pady=10)

        self.option_buttons = []
        for i in range(4):
            option_button = tk.Radiobutton(options_frame, text="", font=self.font_options, variable=self.selected_option, value="",
                                           bg="#34495e", fg="white", selectcolor="#2980b9", activebackground="#34495e",
                                           indicatoron=0, width=40, relief="flat", padx=10, pady=10)
            option_button.pack(anchor="w", pady=5)
            self.option_buttons.append(option_button)

        # Submit button
        self.submit_button = tk.Button(main_frame, text="Submit Answer", command=self.check_answer, font=self.font_button,
                                       bg="#27ae60", fg="white", activebackground="#2ecc71", relief="flat", padx=15, pady=8)
        self.submit_button.pack(pady=(20, 0))

    def display_question(self):
        """
        Updates the GUI to show the current question and options.
        """
        if self.current_question_index < len(self.questions):
            question_data = self.questions[self.current_question_index]
            self.question_label.config(text=question_data["question"])
            
            # Reset selected option
            self.selected_option.set("")
            
            # Update option buttons
            for i in range(4):
                self.option_buttons[i].config(text=question_data["options"][i], value=question_data["options"][i])
            
            # Start the timer for the new question
            self.time_left = self.time_limit
            self.countdown()
        else:
            self.show_results()

    def countdown(self):
        """
        Handles the timer for each question.
        """
        self.timer_label.config(text=f"Time: {self.time_left}")
        if self.time_left > 0:
            self.time_left -= 1
            self.timer_id = self.root.after(1000, self.countdown)
        else:
            # Time's up, auto-submit the answer
            self.check_answer(timed_out=True)
            
    def check_answer(self, timed_out=False):
        """
        Checks if the selected option is correct, updates the score, and moves to the next question.
        """
        # Stop the timer before checking
        if self.timer_id:
            self.root.after_cancel(self.timer_id)
            self.timer_id = None
            
        user_answer = self.selected_option.get()
        correct_answer = self.questions[self.current_question_index]["answer"]
        
        if not user_answer and not timed_out:
            messagebox.showwarning("Warning", "Please select an option before submitting.")
            self.time_left = self.time_limit # Reset timer and continue countdown
            self.countdown()
            return
            
        if user_answer == correct_answer:
            self.score += 1
            messagebox.showinfo("Result", "Correct!", parent=self.root)
        else:
            messagebox.showerror("Result", f"Incorrect. The correct answer was: {correct_answer}", parent=self.root)
        
        self.current_question_index += 1
        self.display_question()

    def show_results(self):
        """
        Displays the final score to the user and closes the app.
        """
        messagebox.showinfo("Quiz Complete", f"Quiz finished!\nYour final score is: {self.score}/{len(self.questions)}", parent=self.root)
        self.root.destroy()

def main():
    """
    Main function to initialize and run the application.
    """
    root = tk.Tk()
    app = QuizApp(root)
    root.mainloop()

if __name__ == "__main__":
    main()





Frequently Asked Questions


Is this Python quiz app suitable for beginners?

Yes. The basic version is beginner-friendly and helps you understand Tkinter widgets and event handling.

Do I need to install Tkinter separately?

No. Tkinter comes pre-installed with standard Python installations.

Can I add my own quiz questions?

Yes. The advanced version allows loading questions from JSON or CSV files.


← Back to Projects