吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2447|回复: 25
收起左侧

[原创工具] 「中文打字练习锁」附源码

  [复制链接]
Sn0wh1te 发表于 2025-11-20 23:59
本帖最后由 Sn0wh1te 于 2025-11-21 00:02 编辑

平时让学生练习打字基本功,总是喜欢偷偷玩,试着做了这个软件,希望能帮到有需要大家~~

「中文打字练习锁」是一款专为学校环境设计的中文打字能力训练工具,通过创新的强制练习机制,有效提升学生的中文输入速度与准确性,帮助学生养成良好的键盘操作习惯。

## 功能特点
1. **强制练习模式**:程序启动后会保持在所有窗口最前端,且无法移动
2. **进度追踪**:实时显示正确字数、错误字数、所用时间和打字速度
3. **拼音提示**:显示当前练习汉字的拼音和对应的键盘按键
4. **自动退出机制**:完成目标练习字数后自动关闭程序
5. **开机自启**:程序会自动添加到Windows开机启动项(仅Windows系统)
6. **调试模式**:支持开启调试模式,方便测试和开发

## 运行截图
image.png


## 配置选项

在程序开头的配置区域可以修改以下设置:

- `DEBUG_MODE`:设为True时显示退出按钮(测试用),设为False时隐藏(正式用)

- `target_count`:在`TypingDrillApp`类的`__init__`方法中可修改目标正确字数

- `CHAR_DB`:自定义待练习的汉字字库


## 注意事项

- 程序运行时,默认添加到开机自启项,如需取消请手动从注册表中删除
- 调试模式下可以使用强制退出按钮,正式模式下无法直接关闭程序
- 目前仅支持Windows系统的开机自启功能

## 特别说明

如果您想修改或扩展此程序,可以关注以下关键方法:

- `next_question()`:生成下一个练习汉字
- `check_input()`:验证用户输入并更新统计信息
- `check_finish()`:检查是否达到退出条件
- `add_to_startup()`:处理开机自启的注册表操作

##使用建议
- 分阶段设置目标:低年级学生可设置较少目标字数,高年级学生可适当增加
- 强调正确姿势:提醒学生在练习过程中保持正确的坐姿和指法

##源代码(有需要的兄弟们可以自行修改~~~
[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()


## 如何使用
1. 安装依赖
    pip install pypinyin
2.运行程序
    python main.py

## 打包程序
1. 安装依赖
    pip install pyinstaller
2.运行打包
    pyinstaller -F -w .\main.py

为大家打包了一个默认100字的版本。
↓↓↓自取↓↓↓
https://snowh1te.lanzn.com/iH2a93bpfwuh
密码:3tki

免费评分

参与人数 4吾爱币 +9 热心值 +4 收起 理由
confiant + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
zhangwei6929 + 1 + 1 谢谢@Thanks!
UT2514227 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
风之暇想 + 7 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| Sn0wh1te 发表于 2025-11-25 12:19
lingdianwuhsiqi 发表于 2025-11-24 16:29
不能提前退出这个不太好

默认学生不能自己退出,但是教师可以通过极域这种远程关闭程序实现退出。
 楼主| Sn0wh1te 发表于 2025-11-25 12:23
lc01t1watz9 发表于 2025-11-24 20:13
正好需要,可以设置英文和标点符号的练习吗?

将COMMON_CHARS中的内容换掉即可,比如 = ( "ABCD,。、?;") 就是在ABCD,。、?;中选择一个进行练习。
 楼主| Sn0wh1te 发表于 2025-11-27 08:56
本帖最后由 Sn0wh1te 于 2025-11-27 19:25 编辑

image.png
更新了界面大小,重新设计了一下UI。
image.png
新增了一个icon,需要自取哟。

↓↓↓自取连接↓↓↓


https://snowh1te.lanzn.com/iNkkv3c8qnyh
密码:8a23

Tips:虽然不能学生自己退出,但是教师可以使用极域教室管理功能,远程推出。
image.png
zm89 发表于 2025-11-29 22:13
无聊下载下来,然后...不想关机..强制打了一百个字才出来!!!
fjt465932593 发表于 2025-11-24 07:39
正好需要,可以设置英文和标点符号的练习吗?
头像被屏蔽
Asy_少洋 发表于 2025-11-24 10:26
提示: 作者被禁止或删除 内容自动屏蔽
jiadongjunwx 发表于 2025-11-24 13:00
感谢发布原创作品
lingdianwuhsiqi 发表于 2025-11-24 16:29
不能提前退出这个不太好
善良的狼 发表于 2025-11-24 17:52
确实不错,建议把退出设置成密码,增加英文标点练习。
lc01t1watz9 发表于 2025-11-24 20:13

正好需要,可以设置英文和标点符号的练习吗?
 楼主| Sn0wh1te 发表于 2025-11-25 12:27
fjt465932593 发表于 2025-11-24 07:39
正好需要,可以设置英文和标点符号的练习吗?

将COMMON_CHARS中的内容换掉即可,比如 = ( "ABCD,。、?;") 就是在ABCD,。、?;中选择一个进行练习。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-7-20 18:11

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表