ich habe mal eine Vokabeltrainer gebaut, dies ist mein erstes "richtiges" Programm, daher bitte ich um etwas Nachsicht. Es läuft jedenfalls und ich bin mit dem Ergebnis sehr zufieden.
Mein Sohn nutzt es jedenfalls und lernt damit besser als mit seinem Schulbuch. Natürlich könnte man da noch viele Sachen verbessern und das werde ich bestimmt auch noch nach und nach, aber fürs
Erste ist das ok so. Am meisten Schmerzen hat der ganze GUI Kram verursacht...
Code: Alles auswählen
import tkinter as tk
from PIL import Image, ImageTk
import os
import random
import re
from difflib import SequenceMatcher
import tkinter.messagebox as messagebox
VOCAB_FILE = "vocab.csv"
def load_vocab_by_chapter():
vocab_by_chapter = {}
if not os.path.exists(VOCAB_FILE):
return vocab_by_chapter
with open(VOCAB_FILE, encoding="latin1") as f:
for line in f:
line = line.strip()
if not line or line.count(",") < 2:
continue
chapter, word1, word2 = line.split(",", 2)
chapter = chapter.strip().strip('"')
word1 = word1.strip().strip('"')
word2 = word2.strip().strip('"')
if chapter not in vocab_by_chapter:
vocab_by_chapter[chapter] = []
vocab_by_chapter[chapter].append([word1, word2])
return vocab_by_chapter
STYLE_PROFILES = {
"Englisch": {
"bg": "#FFFFFF",
"fg_primary": "#00247D",
"fg_secondary": "#CF142B",
"button_bg": "#CF142B",
"button_fg": "#FFFFFF",
"flag_image": "union_jack.png"
},
"Spanisch": {
"bg": "#FFFFF0",
"fg_primary": "#AA151B",
"fg_secondary": "#F1BF00",
"button_bg": "#F1BF00",
"button_fg": "#000000",
"flag_image": "spanish_flag.png"
}
}
class VocabTrainerApp:
def __init__(self, root, selected_language="Englisch"):
self.root = root
self.root.title("Vokabeltrainer")
self.root.geometry("600x550")
self.selected_language = selected_language
self.style = STYLE_PROFILES[selected_language]
self.canvas = tk.Canvas(self.root, highlightthickness=0)
self.canvas.pack(fill="both", expand=True)
self.bg_image = None
self.bg_label = self.canvas.create_image(0, 0, anchor="nw")
self.vocab_by_chapter = load_vocab_by_chapter()
self.current_vocab = []
self.current_word = None
self.correct_first_try = 0
self.total_vocab = 0
self.answered_words = {}
self.chapter_var = tk.StringVar()
self.direction_var = tk.StringVar(value="Deutsch_Sprache")
self._create_widgets()
self.switch_language(self.selected_language)
self.root.bind("<Configure>", self._resize_background)
self.root.after(100, self._load_background_image)
def _load_background_image(self):
width = self.canvas.winfo_width()
height = self.canvas.winfo_height()
if width < 10 or height < 10:
self.root.after(100, self._load_background_image)
return
img_path = self.style.get("flag_image")
if not os.path.exists(img_path):
return
try:
image = Image.open(img_path)
image = image.resize((width, height), resample=Image.LANCZOS)
self.bg_image = ImageTk.PhotoImage(image)
self.canvas.itemconfig(self.bg_label, image=self.bg_image)
self.canvas.tag_lower(self.bg_label)
except Exception as e:
print(f"Fehler beim Laden des Bildes: {e}")
def _resize_background(self, event):
self._load_background_image()
def _create_widgets(self):
font_label = ("Arial", 11, "bold")
font_button = ("Arial", 10, "bold")
flag_frame = tk.Frame(self.root, bg=self.style["bg"])
self.en_button = tk.Button(flag_frame, text="🇬🇧 Englisch", font=("Arial", 10),
command=lambda: self.switch_language("Englisch"))
self.en_button.grid(row=0, column=0, padx=10)
self.es_button = tk.Button(flag_frame, text="🇪🇸 Spanisch", font=("Arial", 10),
command=lambda: self.switch_language("Spanisch"))
self.es_button.grid(row=0, column=1, padx=10)
self.canvas.create_window(300, 50, window=flag_frame)
chapter_label = tk.Label(self.root, text="Kapitel auswählen:", font=font_label, bg=self.style["bg"],
fg=self.style["fg_primary"])
self.canvas.create_window(300, 100, window=chapter_label)
self.chapter_dropdown = tk.OptionMenu(self.root, self.chapter_var, "")
self.canvas.create_window(300, 130, window=self.chapter_dropdown)
direction_label = tk.Label(self.root, text="Richtung auswählen:", font=font_label, bg=self.style["bg"],
fg=self.style["fg_primary"])
self.canvas.create_window(300, 170, window=direction_label)
self.direction_dropdown = tk.OptionMenu(self.root, self.direction_var, "Sprache_Deutsch", "Deutsch_Sprache")
self.canvas.create_window(300, 200, window=self.direction_dropdown)
button_frame = tk.Frame(self.root, bg=self.style["bg"])
self.start_button = tk.Button(button_frame, text="Abfrage starten",
font=font_button, bg=self.style["button_bg"], fg=self.style["button_fg"],
command=self.start_test)
self.start_button.pack(side="left", padx=10)
self.view_vocab_button = tk.Button(button_frame, text="Vokabeln ansehen",
font=font_button, bg=self.style["button_bg"], fg=self.style["button_fg"],
command=self.show_vocab_list)
self.view_vocab_button.pack(side="left", padx=10)
self.multi_chapter_button = tk.Button(button_frame, text="Mehrere Kapitel auswählen",
font=font_button, bg=self.style["button_bg"], fg=self.style["button_fg"],
command=self.open_multi_chapter_selection)
self.multi_chapter_button.pack(side="left", padx=10)
self.canvas.create_window(300, 250, window=button_frame)
self.question_label = tk.Label(self.root, text="Übersetze:", font=("Arial", 16, "bold"),
bg=self.style["bg"], fg="#444444")
self.canvas.create_window(300, 290, window=self.question_label)
self.answer_entry = tk.Entry(self.root, font=("Arial", 12), insertbackground=self.style["bg"])
self.canvas.create_window(300, 330, window=self.answer_entry)
self.answer_entry.bind("<Return>", lambda event: self.check_answer())
self.check_button = tk.Button(self.root, text="Antwort prüfen",
font=font_button, bg=self.style["button_bg"], fg=self.style["button_fg"],
command=self.check_answer)
self.canvas.create_window(300, 370, window=self.check_button)
self.feedback_label = tk.Label(self.root, text="Richtig oder Falsch?", font=("Arial", 12),
bg=self.style["bg"], fg="#444444")
self.canvas.create_window(300, 410, window=self.feedback_label)
self.result_label = tk.Label(self.root, text="Ergebnis: ? im ersten Versuch richtig!",
font=("Arial", 12, "italic"),
bg=self.style["bg"], fg="#444444")
self.canvas.create_window(300, 450, window=self.result_label)
def reset_feedback_label(self):
self.feedback_label.config(text="Richtig oder Falsch?", fg="#444444")
def reset_feedback_and_next_question(self):
self.reset_feedback_label()
self.ask_next_question()
def switch_language(self, language):
self.selected_language = language
self.style = STYLE_PROFILES.get(language, STYLE_PROFILES["Englisch"])
self._load_background_image()
prefix = "EN_" if language == "Englisch" else "ES_"
filtered_chapters = [ch for ch in self.vocab_by_chapter.keys() if ch.startswith(prefix)]
self.chapter_var.set(filtered_chapters[0] if filtered_chapters else "")
menu = self.chapter_dropdown["menu"]
menu.delete(0, "end")
for chapter in filtered_chapters:
menu.add_command(label=chapter, command=lambda value=chapter: self.chapter_var.set(value))
self.question_label.config(bg=self.style["bg"], fg=self.style["fg_primary"])
self.feedback_label.config(bg=self.style["bg"])
self.result_label.config(bg=self.style["bg"], fg=self.style["fg_primary"])
self.check_button.config(bg=self.style["button_bg"], fg=self.style["button_fg"])
self.start_button.config(bg=self.style["button_bg"], fg=self.style["button_fg"])
self.view_vocab_button.config(bg=self.style["button_bg"], fg=self.style["button_fg"])
def start_test(self):
chapter = self.chapter_var.get()
direction = self.direction_var.get()
if not chapter or chapter not in self.vocab_by_chapter:
self.feedback_label.config(text="Bitte ein gültiges Kapitel auswählen.")
return
self.current_vocab = self.vocab_by_chapter[chapter][:]
random.shuffle(self.current_vocab)
self.total_vocab = len(self.current_vocab)
self.answered_words = {}
self.correct_first_try = 0
self.direction_var.set(direction)
self.ask_next_question()
def ask_next_question(self):
if not self.current_vocab:
self.question_label.config(text="Abfrage abgeschlossen!")
self.answer_entry.config(state="disabled")
self.check_button.config(state="disabled")
self.result_label.config(
text=f"Ergebnis: {self.correct_first_try}/{self.total_vocab} beim ersten Versuch richtig!"
)
return
self.current_word = self.current_vocab.pop(0)
show_word = self.current_word[0] if self.direction_var.get() == "Sprache_Deutsch" else self.current_word[1]
self.question_label.config(text=f"Übersetze: {show_word}")
self.answer_entry.config(state="normal")
self.answer_entry.delete(0, tk.END)
self.answer_entry.focus()
self.reset_feedback_label()
@staticmethod
def normalize_answer(text):
text = text.lower().strip()
text = re.sub(r"\(.*?\)", "", text)
text = re.sub(r"\s+", " ", text)
return text
@staticmethod
def extract_variants(text):
text = VocabTrainerApp.normalize_answer(text)
variants = re.split(r"[\/;,]", text)
return [v.strip() for v in variants if v.strip()]
def check_answer(self):
user_input = self.answer_entry.get().strip()
if not user_input:
self.result_label.config(text="⚠️ Keine Eingabe erfolgt!", fg="red")
return
else:
self.result_label.config(text="Ergebnis: ? im ersten Versuch richtig!", fg="#444444")
user_input = user_input.lower()
user_variants = VocabTrainerApp.extract_variants(user_input)
correct_raw = self.current_word[1] if self.direction_var.get() == "Sprache_Deutsch" else self.current_word[0]
correct_variants = VocabTrainerApp.extract_variants(correct_raw)
match_found = any(u in correct_variants for u in user_variants) or any(
c in user_variants for c in correct_variants)
if match_found:
if self.current_word[0] not in self.answered_words:
self.correct_first_try += 1
self.answered_words[self.current_word[0]] = True
self.feedback_label.config(text="✅ Richtig!", fg="green")
else:
ratio = max(
SequenceMatcher(None, u, c).ratio()
for u in user_variants for c in correct_variants
)
if ratio >= 0.8:
self.feedback_label.config(text=f"🟡 Fast richtig! Korrekt wäre: {correct_raw}", fg="orange")
else:
self.feedback_label.config(text=f"❌ Falsch! Korrekt wäre: {correct_raw}", fg="red")
self.answered_words[self.current_word[0]] = False
self.current_vocab.append(self.current_word)
self.root.after(2000, self.reset_feedback_and_next_question)
def open_multi_chapter_selection(self):
selection_window = tk.Toplevel(self.root)
selection_window.title("Mehrere Kapitel auswählen")
selection_window.configure(bg="#F0F0F0")
label = tk.Label(selection_window, text="Kapitel auswählen:", font=("Arial", 12, "bold"), bg="#F0F0F0")
label.pack(pady=(10, 5))
listbox = tk.Listbox(selection_window, selectmode="multiple", font=("Arial", 11), bg="white", width=40,
height=10)
listbox.pack(padx=10, pady=(0, 10))
prefix = "EN_" if self.selected_language == "Englisch" else "ES_"
chapters = [ch for ch in self.vocab_by_chapter.keys() if ch.startswith(prefix)]
for chapter in chapters:
listbox.insert(tk.END, chapter)
amount_frame = tk.Frame(selection_window, bg="#F0F0F0")
amount_frame.pack(pady=(0, 10))
amount_label = tk.Label(amount_frame, text="Anzahl Vokabeln:", font=("Arial", 11), bg="#F0F0F0")
amount_label.pack(side="left")
amount_var = tk.StringVar(value="20")
amount_entry = tk.Entry(amount_frame, textvariable=amount_var, font=("Arial", 11), width=5)
amount_entry.pack(side="left", padx=5)
def start_multi_chapter_test():
selected_indices = listbox.curselection()
selected_chapters = [listbox.get(i) for i in selected_indices]
if not selected_chapters:
messagebox.showwarning("Keine Auswahl", "Bitte mindestens ein Kapitel auswählen!")
return
try:
total_vocab_needed = int(amount_var.get())
if total_vocab_needed <= 0:
raise ValueError
except ValueError:
messagebox.showerror("Ungültige Zahl", "Bitte eine gültige positive Zahl eingeben!")
return
selected_vocab = []
vocab_per_chapter = total_vocab_needed // len(selected_chapters)
for chapter in selected_chapters:
chapter_vocab = self.vocab_by_chapter.get(chapter, [])
random.shuffle(chapter_vocab)
selected_vocab.extend(chapter_vocab[:vocab_per_chapter])
if len(selected_vocab) < total_vocab_needed:
remaining_vocab = []
for chapter in selected_chapters:
remaining_vocab.extend(self.vocab_by_chapter.get(chapter, []))
random.shuffle(remaining_vocab)
selected_vocab.extend(remaining_vocab[:total_vocab_needed - len(selected_vocab)])
self.current_vocab = selected_vocab[:total_vocab_needed]
random.shuffle(self.current_vocab)
self.total_vocab = len(self.current_vocab)
self.answered_words = {}
self.correct_first_try = 0
self.ask_next_question()
selection_window.destroy()
confirm_button = tk.Button(selection_window, text="Auswahl starten", font=("Arial", 10, "bold"),
bg="#4CAF50", fg="white", command=start_multi_chapter_test)
confirm_button.pack(pady=(0, 10))
def show_vocab_list(self):
chapter = self.chapter_var.get()
if not chapter or chapter not in self.vocab_by_chapter:
self.feedback_label.config(text="Bitte ein gültiges Kapitel auswählen.")
return
vocab_list = self.vocab_by_chapter[chapter]
direction = self.direction_var.get()
vocab_window = tk.Toplevel(self.root)
vocab_window.title("Vokabelliste")
vocab_window.configure(bg="#E0E0E0")
container = tk.Frame(vocab_window, bg="#E0E0E0")
container.pack(fill="both", expand=True, padx=10, pady=10)
canvas = tk.Canvas(container, bg="#E0E0E0", highlightthickness=0)
scrollbar = tk.Scrollbar(container, orient="vertical", command=canvas.yview)
scrollable_frame = tk.Frame(canvas, bg="#E0E0E0")
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(
scrollregion=canvas.bbox("all"),
width=scrollable_frame.winfo_reqwidth()
)
)
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
for idx, word_pair in enumerate(vocab_list):
left_word = word_pair[0] if direction == "Sprache_Deutsch" else word_pair[1]
right_word = word_pair[1] if direction == "Sprache_Deutsch" else word_pair[0]
lbl_left = tk.Label(scrollable_frame, text=left_word, font=("Arial", 12), anchor="w", bg="#E0E0E0", fg="#333333")
lbl_arrow = tk.Label(scrollable_frame, text="➔", font=("Arial", 12, "bold"), bg="#E0E0E0", fg="#666666")
lbl_right = tk.Label(scrollable_frame, text=right_word, font=("Arial", 12), anchor="w", bg="#E0E0E0", fg="#333333")
lbl_left.grid(row=idx, column=0, sticky="w", padx=(5, 10), pady=2)
lbl_arrow.grid(row=idx, column=1, sticky="w", padx=5)
lbl_right.grid(row=idx, column=2, sticky="w", padx=(10, 5), pady=2)
vocab_window.update_idletasks()
window_width = scrollable_frame.winfo_reqwidth() + 30
window_height = min(scrollable_frame.winfo_reqheight() + 30, 600)
vocab_window.geometry(f"{window_width}x{window_height}")
if __name__ == "__main__":
root = tk.Tk()
app = VocabTrainerApp(root)
root.mainloop()