In this tutorial, we’ll build a Random Joke Generator desktop app using Python + Tkinter.
You’ll learn how to:
Create a GUI with Tkinter
Use dropdowns, buttons, and listboxes
Generate random jokes
Save data to a file
Handle threading to keep the UI responsive
Add dark mode support 🌙
Beginner-friendly, no prior GUI experience required.
📦 Step 1: Import Required Modules
We’ll start by importing everything we need.
import sys
import os
import threading
import random
Why these?
sys & os → file paths (useful for packaging apps later)
threading → prevents the UI from freezing
random → selects jokes randomly
Now let’s import Tkinter and related UI helpers:
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
tkinter → base GUI toolkit
ttk → modern themed widgets
messagebox → pop-up alerts
filedialog → save files
Optional (but nice): modern theming with sv_ttk.
import sv_ttk
🧰 Step 2: Helper Functions
📁 Resource Path Helper
This helps your app work both normally and when bundled with PyInstaller.
def resource_path(file_name):
base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
return os.path.join(base_path, file_name)
You don’t need this right now—but it’s a great habit.
📢 Status Bar Helper
We’ll use this to show messages like “Generating joke…”.
def set_status(msg):
status_var.set(msg)
root.update_idletasks()
🪟 Step 3: Create the Main Application Window
root = tk.Tk()
root.title("Random Joke Generator")
root.geometry("600x500")
root.minsize(600, 500)
This:
Creates the app window
Sets its title
Defines size and minimum resize limits
Apply a modern theme:
sv_ttk.set_theme("light")
🌍 Step 4: Global Variables
Tkinter uses special variable classes that auto-update UI elements.
dark_mode_var = tk.BooleanVar(value=False)
joke_result_var = tk.StringVar(value="")
We’ll also store joke history:
joke_history = [] # (joke_type, joke)
🌗 Step 5: Dark Mode Toggle
def toggle_theme():
bg = "#2E2E2E" if dark_mode_var.get() else "#FFFFFF"
fg = "white" if dark_mode_var.get() else "black"
root.configure(bg=bg)
for w in ["TFrame", "TLabel", "TLabelframe", "TLabelframe.Label", "TCheckbutton"]:
style.configure(w, background=bg, foreground=fg)
✔ Uses a checkbox
✔ Updates widget colors dynamically
✔ Beginner-friendly logic
😂 Step 6: Joke Data
JOKES = {
"Classic": [
"Why did the scarecrow win an award? Because he was outstanding in his field!",
"Why don’t scientists trust atoms? Because they make up everything!"
],
"Dad Jokes": [
"I only know 25 letters of the alphabet… I don’t know y."
],
"Programming": [
"Why do programmers prefer dark mode? Because light attracts bugs."
]
}
🎯 Joke Generator Function
def generate_joke(joke_type):
jokes = JOKES.get(joke_type, sum(JOKES.values(), []))
return random.choice(jokes)
If no type is found → it falls back to all jokes.
🧵 Step 7: Threaded Joke Generation
Button Action
def create_joke():
joke_type = joke_type_var.get()
if not joke_type:
messagebox.showwarning("Select Type", "Please select a joke type.")
return
set_status("Generating joke...")
threading.Thread(
target=_generate_joke_thread,
args=(joke_type,),
daemon=True
).start()
💡 Threading keeps the UI responsive.
Background Worker
def _generate_joke_thread(joke_type):
joke = generate_joke(joke_type)
joke_result_var.set(joke)
add_to_history(joke_type, joke)
set_status("Joke generated!")
🗂 Step 8: Joke History Handling
def add_to_history(joke_type, joke):
joke_history.append((joke_type, joke))
preview = joke[:80] + "..." if len(joke) > 80 else joke
history_list.insert(tk.END, f"[{joke_type}] {preview}")
✔ Stores full joke
✔ Displays preview in listbox
📤 Step 9: Export History to a File
def export_history_txt():
if not joke_history:
messagebox.showinfo("Empty History", "No jokes to export.")
return
Choose a save location:
file_path = filedialog.asksaveasfilename(
defaultextension=".txt",
filetypes=[("Text Files", "*.txt")]
)
Write to file:
with open(file_path, "w", encoding="utf-8") as f:
for i, (jtype, joke) in enumerate(joke_history, 1):
f.write(f"{i}. [{jtype}] {joke}\n\n")
👀 Step 10: View Full Joke in a Popup
def view_selected_joke(event=None):
index = history_list.curselection()[0]
_, joke = joke_history[index]
win = tk.Toplevel(root)
win.title("Full Joke")
tk.Label(
win,
text=joke,
wraplength=480,
font=("Segoe UI", 14)
).pack(padx=10, pady=10)
Double-click = full joke view ✨
🎨 Step 11: Styling
style = ttk.Style()
style.theme_use("clam")
style.configure(
"Action.TButton",
font=("Segoe UI", 11, "bold"),
padding=8
)
🧱 Step 12: Build the UI Layout
Title
ttk.Label(
main,
text="Random Joke Generator",
font=("Segoe UI", 22, "bold")
).grid(row=0, column=0)
Dropdown
joke_type_var = tk.StringVar()
ttk.Combobox(
main,
textvariable=joke_type_var,
values=list(JOKES.keys())
).grid(row=2, column=0, sticky="ew")
Buttons
ttk.Button(
controls,
text="😂 Generate Joke",
command=create_joke
).grid(row=0, column=0)
ttk.Button(
controls,
text="📤 Export History",
command=export_history_txt
).grid(row=0, column=1)
▶️ Step 13: Run the App
root.mainloop()
This starts the Tkinter event loop—your app is live! 🎉
🚀 Final Thoughts
You just built a:
Fully functional GUI app
With threading
Dark mode
History management
File exporting
Ideas to extend it:
Add API-based jokes
Add sound effects
Package it as an .exe
Add categories dynamically

Top comments (0)