[Python] 纯文本查看 复制代码
import tkinter as tk
from tkinter import messagebox
import time
import random
import sys
import os
from pypinyin import pinyin, Style
# 仅在 Windows 上导入 winreg 模块,用于注册表操作
if sys.platform == 'win32':
try:
import winreg
except ImportError:
winreg = None
else:
winreg = None
# =================配置区域=================
# 【开关】 True = 显示退出按钮(测试用); False = 隐藏按钮(正式用)
DEBUG_MODE = True
# =========================================
# 待练习的字库
CHAR_DB = list("天地玄黄宇宙洪荒日月盈昃辰宿列张寒来暑往秋收冬藏闰余成岁律吕调阳云腾致雨露结为霜")
def add_to_startup():
"""将当前程序路径添加到 Windows 注册表的启动项中。"""
if winreg is None:
return
APP_NAME = "TypingDrillLock"
exe_path = sys.executable
key_path = r"Software\Microsoft\Windows\CurrentVersion\Run"
try:
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path, 0, winreg.KEY_SET_VALUE)
winreg.SetValueEx(key, APP_NAME, 0, winreg.REG_SZ, exe_path)
winreg.CloseKey(key)
# print(f"成功添加开机自启:{exe_path}")
except Exception as e:
# print(f"添加开机自启失败:{e}")
pass
class TypingDrillApp:
def __init__(self, root):
self.root = root
self.target_count = 100 # 目标正确字数
self.setup_window()
self.started = False
self.start_time = 0
self.correct_cnt = 0
self.error_cnt = 0
self.current_char = ""
self.create_widgets()
self.next_question()
self.root.focus_force()
self.entry.focus_set()
def setup_window(self):
screen_width = self.root.winfo_screenwidth()
screen_height = self.root.winfo_screenheight()
w, h = 600, 650
x = (screen_width - w) // 2
y = (screen_height - h) // 2
self.root.geometry(f"{w}x{h}+{x}+{y}")
self.root.configure(bg='#f0f0f0')
self.root.attributes('-topmost', True)
self.root.overrideredirect(True)
def create_widgets(self):
# 1. 顶部状态栏
self.stats_frame = tk.Frame(self.root, bg='#ddd', pady=10)
self.stats_frame.pack(fill='x')
self.lbl_timer = tk.Label(self.stats_frame, text="所用时间 00:00", font=("Arial", 12), bg='#ddd')
self.lbl_timer.pack(side='left', padx=20)
self.lbl_correct = tk.Label(self.stats_frame, text="正确: 0", font=("Arial", 12), fg='green', bg='#ddd')
self.lbl_correct.pack(side='left', padx=10)
self.lbl_error = tk.Label(self.stats_frame, text="错误: 0", font=("Arial", 12), fg='red', bg='#ddd')
self.lbl_error.pack(side='left', padx=10)
self.lbl_speed = tk.Label(self.stats_frame, text="速度: 0", font=("Arial", 12), bg='#ddd')
self.lbl_speed.pack(side='left', padx=10)
self.lbl_progress = tk.Label(self.stats_frame, text=f"进度: 0/{self.target_count}", font=("Arial", 12, "bold"), bg='#ddd')
self.lbl_progress.pack(side='right', padx=20)
# 2. 中间显示区域
self.center_frame = tk.Frame(self.root, bg='#f0f0f0')
self.center_frame.pack(expand=True, pady=20)
self.lbl_pinyin = tk.Label(self.center_frame, text="pīn yīn", font=("Arial", 24), fg='red', bg='#f0f0f0')
self.lbl_pinyin.pack(pady=(0, 5))
self.lbl_target = tk.Label(self.center_frame, text="字", font=("SimHei", 80, "bold"), fg='red', bg='#f0f0f0')
self.lbl_target.pack()
self.lbl_keys = tk.Label(self.center_frame, text="[对应按键]", font=("Consolas", 20, "bold"), fg='blue', bg='#f0f0f0')
self.lbl_keys.pack(pady=(10, 0))
# 3. 底部输入区
self.input_frame = tk.Frame(self.root, bg='#f0f0f0', pady=10)
self.input_frame.pack(side='bottom', fill='x')
# --- DEBUG 区域(位于最底部)---
if DEBUG_MODE:
# 红色退出按钮 (最底部)
self.debug_btn = tk.Button(self.input_frame, text="【测试模式】强制退出",
command=self.root.destroy,
bg='#ff4d4d', fg='white', font=("Arial", 10, "bold"), bd=0, padx=10, pady=5)
# pack(side='bottom') 会让这个按钮在最下面
self.debug_btn.pack(side='bottom', pady=(5, 20))
# ---------------------------------
# 永久退出提示信息(始终显示,动态关联 target_count)
self.exit_hint = tk.Label(self.input_frame,
text=f"正确完成 {self.target_count} 字后自动退出软件",
font=("Arial", 10, "bold"),
fg='#0078d7', # 蓝色,与提交按钮配色统一
bg='#f0f0f0')
self.exit_hint.pack(side='bottom', pady=(10, 10))
self.btn_submit = tk.Button(self.input_frame, text="提交 (Submit)", command=self.check_input, bg='#0078d7', fg='white', font=("Arial", 12), width=15)
self.btn_submit.pack(side='bottom', pady=10)
self.lbl_tips = tk.Label(self.input_frame, text="完成输入后按回车键 Enter 或点击按钮提交", font=("Arial", 10), fg='#555', bg='#f0f0f0')
self.lbl_tips.pack(side='bottom', pady=5)
self.entry = tk.Entry(self.input_frame, font=("SimHei", 24), justify='center')
self.entry.pack(side='bottom', pady=5)
self.entry.bind("<Key>", self.on_key_press)
self.entry.bind("<Return>", self.check_input)
def next_question(self):
self.current_char = random.choice(CHAR_DB)
self.lbl_target.config(text=self.current_char)
py_list = pinyin(self.current_char)
py_str = py_list[0][0]
py_keys_list = pinyin(self.current_char, style=Style.NORMAL)
py_keys = py_keys_list[0][0]
formatted_keys = " ".join(list(py_keys.upper()))
self.lbl_pinyin.config(text=py_str)
self.lbl_keys.config(text=formatted_keys)
self.entry.delete(0, 'end')
def on_key_press(self, event):
if not self.started and event.keysym not in ('Return', 'Shift_L', 'Shift_R', 'Control_L'):
self.started = True
self.start_time = time.time()
self.update_timer()
def update_timer(self):
if not self.started:
return
elapsed = int(time.time() - self.start_time)
mins, secs = divmod(elapsed, 60)
self.lbl_timer.config(text=f"所用时间 {mins:02}:{secs:02}")
if elapsed > 0:
wpm = int(self.correct_cnt / (elapsed / 60))
self.lbl_speed.config(text=f"速度: {wpm}")
self.root.after(1000, self.update_timer)
def check_input(self, event=None):
user_input = self.entry.get().strip()
if not user_input:
return
if user_input == self.current_char:
self.correct_cnt += 1
self.lbl_correct.config(text=f"正确: {self.correct_cnt}")
else:
self.error_cnt += 1
self.lbl_error.config(text=f"错误: {self.error_cnt}")
self.entry.config(bg='#ffcccc')
self.root.after(200, lambda: self.entry.config(bg='white'))
self.check_finish()
def check_finish(self):
self.lbl_progress.config(text=f"进度: {self.correct_cnt}/{self.target_count}")
if self.correct_cnt >= self.target_count:
elapsed = int(time.time() - self.start_time)
messagebox.showinfo("恭喜", f"练习完成!\n耗时: {elapsed}秒")
self.root.destroy()
else:
self.next_question()
if __name__ == "__main__":
# 自动添加开机自启功能
add_to_startup()
root = tk.Tk()
app = TypingDrillApp(root)
def on_closing():
if DEBUG_MODE:
root.destroy()
else:
pass
root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()