[Asm] 纯文本查看 复制代码
import pandas as pd
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import os
import re
import random
from PIL import Image, ImageTk
import pygame
class DictationApp:
def __init__(self, root):
self.root = root
self.root.title("单词听写软件")
self.root.geometry("800x600")
self.root.resizable(True, True)
self.root.configure(bg="#f0f8ff")
# 初始化pygame用于发音功能
pygame.mixer.init()
self.excel_file = None
self.df = None
self.days = []
self.current_words = []
self.current_index = 0
self.correct_count = 0
self.incorrect_count = 0
self.dictation_active = False
# 创建自定义样式
self.create_styles()
# 创建界面
self.create_widgets()
# 尝试加载默认文件
self.try_load_default_file()
def create_styles(self):
"""创建自定义控件样式"""
style = ttk.Style()
style.configure("TFrame", background="#f0f8ff")
style.configure("TLabel", background="#f0f8ff", font=("微软雅黑", 10))
style.configure("Title.TLabel", background="#4682b4", foreground="white",
font=("微软雅黑", 14, "bold"), padding=10)
style.configure("TButton", font=("微软雅黑", 10))
style.configure("Start.TButton", font=("微软雅黑", 12, "bold"), foreground="green")
style.configure("Submit.TButton", font=("微软雅黑", 11), width=15)
style.configure("Control.TFrame", background="#e6f2ff", relief="ridge", borderwidth=2)
style.configure("Dictation.TFrame", background="#ffffff", relief="sunken", borderwidth=2)
style.configure("Day.TCheckbutton", background="#e6f2ff", font=("微软雅黑", 10))
style.configure("Progress.Horizontal.TProgressbar", thickness=20)
style.map("TButton", background=[("active", "#e1f0ff")])
def create_widgets(self):
"""创建界面组件"""
# 标题栏
title_frame = ttk.Frame(self.root, style="TFrame")
title_frame.pack(fill="x", padx=10, pady=5)
title_label = ttk.Label(title_frame, text="单词听写软件", style="Title.TLabel")
title_label.pack(fill="x")
# 主容器
main_frame = ttk.Frame(self.root, style="TFrame")
main_frame.pack(fill="both", expand=True, padx=10, pady=5)
# 左侧控制面板
control_frame = ttk.LabelFrame(main_frame, text="控制面板", style="Control.TFrame", width=250)
control_frame.pack(side="left", fill="y", padx=(0, 5), pady=5)
# 文件选择区域
file_frame = ttk.Frame(control_frame)
file_frame.pack(fill="x", padx=10, pady=10)
ttk.Label(file_frame, text="选择单词文件:").pack(anchor="w")
file_select_frame = ttk.Frame(file_frame)
file_select_frame.pack(fill="x", pady=5)
self.file_entry = ttk.Entry(file_select_frame, state="readonly")
self.file_entry.pack(side="left", fill="x", expand=True, padx=(0, 5))
self.select_btn = ttk.Button(file_select_frame, text="浏览...", width=8,
command=self.select_file)
self.select_btn.pack(side="right")
# 状态标签
self.status_label = ttk.Label(file_frame, text="请选择Excel文件", foreground="blue")
self.status_label.pack(anchor="w", pady=(5, 0))
# 天数选择区域
days_frame = ttk.LabelFrame(control_frame, text="选择听写范围")
days_frame.pack(fill="x", padx=10, pady=5)
self.days_container = ttk.Frame(days_frame)
self.days_container.pack(fill="both", padx=5, pady=5)
ttk.Label(self.days_container, text="加载文件后显示天数选项").pack(pady=20)
# 听写选项
options_frame = ttk.LabelFrame(control_frame, text="听写选项")
options_frame.pack(fill="x", padx=10, pady=5)
# 随机顺序选项
self.random_var = tk.BooleanVar(value=True)
ttk.Checkbutton(options_frame, text="随机顺序", variable=self.random_var,
style="Day.TCheckbutton").pack(anchor="w", padx=5, pady=2)
# 显示音标选项
self.show_phonetic_var = tk.BooleanVar(value=True)
ttk.Checkbutton(options_frame, text="显示音标", variable=self.show_phonetic_var,
style="Day.TCheckbutton").pack(anchor="w", padx=5, pady=2)
# 发音选项
self.pronounce_var = tk.BooleanVar(value=True)
ttk.Checkbutton(options_frame, text="自动发音", variable=self.pronounce_var,
style="Day.TCheckbutton").pack(anchor="w", padx=5, pady=2)
# 开始按钮
self.start_btn = ttk.Button(control_frame, text="开始听写", style="Start.TButton",
command=self.start_dictation, state="disabled")
self.start_btn.pack(pady=15, ipady=5)
# 右侧听写区
dictation_frame = ttk.LabelFrame(main_frame, text="听写区", style="Dictation.TFrame")
dictation_frame.pack(side="right", fill="both", expand=True, padx=(5, 0), pady=5)
# 单词显示区域
self.word_frame = ttk.Frame(dictation_frame)
self.word_frame.pack(fill="x", pady=(20, 10), padx=20)
self.chinese_label = ttk.Label(self.word_frame, text="请先选择Excel文件",
font=("微软雅黑", 16), wraplength=400,
justify="center", anchor="center")
self.chinese_label.pack(fill="x", pady=(10, 5))
self.phonetic_label = ttk.Label(self.word_frame, text="",
font=("Arial", 12), foreground="#666666")
self.phonetic_label.pack(fill="x", pady=(0, 20))
# 输入区域
input_frame = ttk.Frame(dictation_frame)
input_frame.pack(fill="x", padx=50, pady=(0, 20))
self.entry = ttk.Entry(input_frame, font=("Consolas", 14))
self.entry.pack(fill="x", pady=5)
self.entry.bind("<Return>", lambda e: self.check_answer())
# 按钮区域
btn_frame = ttk.Frame(dictation_frame)
btn_frame.pack(fill="x", padx=50, pady=10)
self.submit_btn = ttk.Button(btn_frame, text="提交答案", style="Submit.TButton",
command=self.check_answer, state="disabled")
self.submit_btn.pack(side="left", padx=(0, 10))
self.pronounce_btn = ttk.Button(btn_frame, text="🔊 发音",
command=self.pronounce_word, state="disabled")
self.pronounce_btn.pack(side="left", padx=(10, 0))
# 进度区域
progress_frame = ttk.Frame(dictation_frame)
progress_frame.pack(fill="x", padx=20, pady=(10, 5))
self.progress_label = ttk.Label(progress_frame, text="等待开始...",
font=("Arial", 10))
self.progress_label.pack(anchor="w")
self.progress = ttk.Progressbar(progress_frame, orient="horizontal",
mode="determinate", length=400,
style="Progress.Horizontal.TProgressbar")
self.progress.pack(fill="x", pady=5)
# 统计区域
stats_frame = ttk.Frame(dictation_frame)
stats_frame.pack(fill="x", padx=20, pady=(5, 20))
ttk.Label(stats_frame, text="听写统计:").pack(anchor="w")
stats_subframe = ttk.Frame(stats_frame)
stats_subframe.pack(fill="x", pady=5)
ttk.Label(stats_subframe, text="正确:").pack(side="left")
self.correct_label = ttk.Label(stats_subframe, text="0", foreground="green",
font=("Arial", 10, "bold"))
self.correct_label.pack(side="left", padx=(0, 15))
ttk.Label(stats_subframe, text="错误:").pack(side="left")
self.incorrect_label = ttk.Label(stats_subframe, text="0", foreground="red",
font=("Arial", 10, "bold"))
self.incorrect_label.pack(side="left")
def try_load_default_file(self):
"""尝试加载默认文件"""
default_file = "eng.xlsx"
if os.path.exists(default_file):
self.excel_file = default_file
self.file_entry.config(state="normal")
self.file_entry.delete(0, "end")
self.file_entry.insert(0, default_file)
self.file_entry.config(state="readonly")
self.status_label.config(text="找到默认文件", foreground="green")
if self.load_data():
self.setup_days()
self.start_btn.config(state="normal")
self.status_label.config(text="文件加载成功", foreground="green")
def select_file(self):
"""选择Excel文件"""
file_path = filedialog.askopenfilename(
title="选择Excel文件",
filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")]
)
if file_path:
self.excel_file = file_path
self.file_entry.config(state="normal")
self.file_entry.delete(0, "end")
self.file_entry.insert(0, os.path.basename(file_path))
self.file_entry.config(state="readonly")
if self.check_prerequisites(file_path) and self.load_data():
self.setup_days()
self.start_btn.config(state="normal")
self.status_label.config(text="文件加载成功", foreground="green")
else:
self.start_btn.config(state="disabled")
def check_prerequisites(self, file_path):
"""检查运行环境要求"""
errors = []
# 检查文件存在性
if not os.path.exists(file_path):
errors.append("未找到Excel文件")
# 检查文件可读性
else:
try:
test_df = pd.read_excel(file_path, engine='openpyxl', nrows=1)
required_columns = {'单词', '释义', '音标'}
if not required_columns.issubset(test_df.columns):
missing = required_columns - set(test_df.columns)
errors.append(f"缺少必要列: {', '.join(missing)}")
except Exception as e:
errors.append(f"文件读取失败: {str(e)}")
# 显示错误提示
if errors:
msg = "文件验证失败:\n\n" + "\n".join(errors) + \
"\n\n请检查:\n1. 文件格式是否正确\n2. 是否被其他程序占用"
messagebox.showerror("文件错误", msg)
self.status_label.config(text="文件加载失败", foreground="red")
return False
return True
def load_data(self):
"""安全加载数据"""
try:
self.df = pd.read_excel(self.excel_file, engine='openpyxl')
# 检查是否有day列
day_columns = [col for col in self.df.columns if 'day' in col.lower()]
if not day_columns:
# 尝试从单词列中提取天数信息
day_values = self.df['单词'].str.extract(r'(day\d+)', flags=re.IGNORECASE)[0]
self.days = sorted(list(set(day_values.dropna())))
else:
# 从day列中获取天数信息
self.days = []
for col in day_columns:
self.days.extend([f"{col}: {day}" for day in self.df[col].dropna().unique()])
return True
except Exception as e:
messagebox.showerror("数据错误", f"数据加载失败:\n{str(e)}\n\n建议操作:\n1. 检查Excel格式\n2. 重新选择文件")
self.status_label.config(text="数据加载失败", foreground="red")
return False
def setup_days(self):
"""设置天数选择复选框"""
# 清空容器
for widget in self.days_container.winfo_children():
widget.destroy()
if not self.days:
ttk.Label(self.days_container, text="未找到天数分组信息").pack()
return
# 添加滚动条
scroll_frame = ttk.Frame(self.days_container)
scroll_frame.pack(fill="both", expand=True)
canvas = tk.Canvas(scroll_frame, height=150)
scrollbar = ttk.Scrollbar(scroll_frame, orient="vertical", command=canvas.yview)
scrollable_frame = ttk.Frame(canvas)
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
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")
# 添加天数复选框
self.day_vars = {}
for day in self.days:
self.day_vars[day] = tk.BooleanVar()
cb = ttk.Checkbutton(scrollable_frame, text=day,
variable=self.day_vars[day],
style="Day.TCheckbutton")
cb.pack(anchor="w", padx=5, pady=2)
def start_dictation(self):
"""开始听写流程"""
# 获取选中的天数
selected_days = [day for day, var in self.day_vars.items() if var.get()]
if not selected_days:
messagebox.showwarning("选择错误", "请至少选择一个天数范围!")
return
# 收集选中的单词
self.current_words = []
# 根据是否有day列决定收集方式
if any('day' in col.lower() for col in self.df.columns):
# 如果有day列,按列收集
for day in selected_days:
col, val = day.split(": ")
mask = self.df[col.strip()] == val.strip()
words = self.df[mask][['单词', '释义', '音标']].to_dict('records')
self.current_words.extend(words)
else:
# 按行收集
for day in selected_days:
mask = self.df['单词'].str.contains(day, case=False, na=False)
words = self.df[mask][['单词', '释义', '音标']].to_dict('records')
self.current_words.extend(words)
if not self.current_words:
messagebox.showwarning("数据错误", "所选天数没有可用单词!")
return
# 随机打乱顺序
if self.random_var.get():
random.shuffle(self.current_words)
self.current_index = 0
self.correct_count = 0
self.incorrect_count = 0
self.dictation_active = True
self.update_stats()
self.progress.config(maximum=len(self.current_words))
self.toggle_dictation_widgets(True)
self.show_current_word()
def toggle_dictation_widgets(self, active):
"""切换听写组件状态"""
state = "normal" if active else "disabled"
self.entry.config(state=state)
self.submit_btn.config(state=state)
self.pronounce_btn.config(state=state)
self.entry.delete(0, "end")
# 禁用文件选择按钮
self.select_btn.config(state="disabled" if active else "normal")
self.start_btn.config(state="disabled" if active else "normal")
# 更新开始按钮文本
self.start_btn.config(text="重新开始" if active else "开始听写")
if active:
self.entry.focus()
def show_current_word(self):
"""显示当前单词"""
if not self.dictation_active or self.current_index >= len(self.current_words):
self.finish_dictation()
return
word = self.current_words[self.current_index]
# 显示释义
self.chinese_label.config(text=word['释义'])
# 显示音标
if self.show_phonetic_var.get() and word['音标']:
self.phonetic_label.config(text=f"/{word['音标']}/")
else:
self.phonetic_label.config(text="")
# 更新进度
self.progress_label.config(
text=f"进度: {self.current_index+1}/{len(self.current_words)}")
self.progress['value'] = self.current_index
# 自动发音
if self.pronounce_var.get():
self.pronounce_word()
self.entry.delete(0, "end")
self.entry.focus()
def pronounce_word(self):
"""发音当前单词(模拟)"""
if self.dictation_active and self.current_index < len(self.current_words):
word = self.current_words[self.current_index]['单词']
# 在实际应用中,这里可以调用发音API
# 这里使用简单的系统提示音代替
try:
pygame.mixer.Sound.play(pygame.mixer.Sound(
pygame.mixer.Sound.get_num_channels(pygame.mixer)
))
except:
# 如果pygame不可用,使用系统beep
import winsound
winsound.Beep(440, 100)
def check_answer(self):
"""验证答案"""
if not self.dictation_active or self.current_index >= len(self.current_words):
return
user_answer = self.entry.get().strip().lower()
correct_word = self.current_words[self.current_index]['单词'].lower()
if user_answer == correct_word:
self.correct_count += 1
self.current_index += 1
self.show_current_word()
else:
self.incorrect_count += 1
self.update_stats()
messagebox.showerror("错误",
f"正确答案: {correct_word}\n"
f"音标: /{self.current_words[self.current_index]['音标']}")
self.entry.delete(0, "end")
self.entry.focus()
def update_stats(self):
"""更新统计信息"""
self.correct_label.config(text=str(self.correct_count))
self.incorrect_label.config(text=str(self.incorrect_count))
def finish_dictation(self):
"""完成听写"""
self.dictation_active = False
self.toggle_dictation_widgets(False)
accuracy = self.correct_count / (self.correct_count + self.incorrect_count) * 100
messagebox.showinfo("听写完成",
f"听写完成!\n\n"
f"正确: {self.correct_count}\n"
f"错误: {self.incorrect_count}\n"
f"正确率: {accuracy:.1f}%")
# 重置进度条
self.progress['value'] = self.progress['maximum']
self.chinese_label.config(text="听写已完成")
self.phonetic_label.config(text="")
self.progress_label.config(text="听写已完成")
def main():
root = tk.Tk()
app = DictationApp(root)
root.mainloop()
if __name__ == "__main__":
main()