[Python] 纯文本查看 复制代码
import os
import json
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from collections import defaultdict
import re
import time
import random
class 做题软件:
'''----------------------------------------UI--------------------------------------------------'''
def __init__(self, 主窗口):
self.主窗口 = 主窗口
self.主窗口.title("刷题助手")
self.当前题库 = []
self.错题本 = []
self.题目卡片组 = []
self.做题记录文件 = os.path.join("date","做题记录.json")
self.错题本文件 = os.path.join("date","错题本.json")
self.配置文件 = os.path.join("date","配置信息.json")
self.题目状态 = defaultdict(dict) # {题目ID: {状态: 对错, 正确次数: 0,错误次数:0}}
self.错题记录 = [] # {题目ID: 错误次数}
self.题号按钮组 = []
self.当前题库名称 = ""
self.错题模式 = False # 是否处于错题练习模式
self.字体大小 = 12 # 默认字体大小
self.用户答案 = {} # {题目ID: 答案}
self.多题模式 = False # 新增模式状态标识
self.当前显示卡片索引 = 0 #跟踪当前显示的卡片索引
self.当前页数 = 1
self.总页数 = 1
self.每页题数 = 50 # 可调整每页显示题目数
self.自动跳转 = tk.BooleanVar(value=True)
self.显示答案 = tk.BooleanVar(value=True)
self.自动提交单选 = tk.BooleanVar(value=True)
self.背题模式 = tk.BooleanVar(value=False)
'''--------左侧面板------------------------'''
# 创建左侧面板并保存为实例变量
self.左侧面板 = tk.Frame(主窗口, bd=0, relief=tk.FLAT, padx=10, pady=10, width=250)
self.左侧面板.pack_propagate(False) # 禁止子控件改变面板尺寸
self.左侧面板.pack(side=tk.LEFT, fill=tk.Y, anchor="nw")
self.题库树 = ttk.Treeview(self.左侧面板, show="tree", selectmode="browse")
ysb = ttk.Scrollbar(self.左侧面板, orient="vertical", command=self.题库树.yview)
self.题库树.configure(yscroll=ysb.set)
self.题库树.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)# 布局
ysb.pack(side=tk.RIGHT, fill=tk.Y)
# 初始化题库树
self.题库根路径 = "题库"
self.题库树.insert("", "end", text="题库", iid=self.题库根路径,
values=("dir", self.题库根路径), open=True)
self.加载子题库(self.题库根路径, parent=self.题库根路径)
# 绑定事件
self.题库树.bind("<<TreeviewOpen>>", self.展开目录)
self.题库树.bind("<<TreeviewSelect>>", self.选择题目库)
self.中间面板 = tk.Frame(主窗口, bd=0, relief=tk.FLAT, padx=10, pady=10, width=350)
self.中间面板.pack_propagate(False) # 禁止子控件改变面板尺寸
self.中间面板.pack(side=tk.LEFT, fill=tk.Y, anchor="nw")
# 配置区域
配置面板 = tk.Frame(self.中间面板)
配置面板.pack(pady=5, fill=tk.X)
tk.Checkbutton(配置面板,
text="自动跳转",
variable=self.自动跳转,
command=self.保存配置记录).pack(side=tk.LEFT)
tk.Checkbutton(配置面板,
text="显示答案",
variable=self.显示答案,
command=self.保存配置记录).pack(side=tk.LEFT)
tk.Checkbutton(配置面板,
text="单选自动提交",
variable=self.自动提交单选,
command=self.保存配置记录).pack(side=tk.LEFT)
tk.Checkbutton(配置面板,
text="背题模式",
variable=self.背题模式,
command=self.切换背题模式).pack(side=tk.LEFT)
# 操作按钮区域
操作框 = tk.Frame(self.中间面板)
操作框.pack(pady=10)
self.作答区 = tk.Frame(self.中间面板)
self.作答区.pack(pady=10)
# 初始化时创建所有按钮(但暂时隐藏)
self.上一题按钮 = tk.Button(self.作答区, text="上一题", command=self.上一题)
self.下一题按钮 = tk.Button(self.作答区, text="下一题", command=self.下一题)
self.提交按钮 = tk.Button(self.作答区, text="提交所有", command=self.提交所有答案)
self.上一页按钮 = tk.Button(self.作答区, text="上一页", command=lambda: self.切换页数(-1),state=tk.NORMAL if self.当前页数 >1 else tk.DISABLED)
self.页数标签 = tk.Label(self.作答区, text=f"{self.当前页数}/{self.总页数}")
self.下一页按钮 = tk.Button(self.作答区, text="下一页", command=lambda: self.切换页数(1),state=tk.NORMAL if self.当前页数 < self.总页数 else tk.DISABLED)
# 默认显示单题按钮
self.显示模式按钮()
self.模式切换按钮 = tk.Button(操作框, text="切换多题模式", command=self.切换显示模式)
self.模式切换按钮.pack(side=tk.LEFT, padx=10)
self.错题按钮 = tk.Button(操作框, text="错题练习", command=self.显示错题本)
self.错题按钮.pack(side="left", padx=5)
self.编辑按钮 = tk.Button(操作框, text="编辑题目", command=self.编辑题目)
self.编辑按钮.pack(side="left", padx=5)
# 导入按钮
self.导入按钮 = tk.Button(操作框, text="导入题库", command=self.导入题目)
self.导入按钮.pack(side=tk.LEFT, padx=5)
#调节字体大小
字体调节行 = tk.Frame(self.中间面板)
字体调节行.pack(anchor="nw", pady=5)
tk.Label(字体调节行, text="字体大小调节:").pack(side=tk.LEFT)
self.增大按钮 = tk.Button(字体调节行, text="+", command=lambda: self.调整字体大小(1), width=3)
self.增大按钮.pack(anchor="nw", side=tk.LEFT, padx=2)
self.减小按钮 = tk.Button(字体调节行, text="-", command=lambda: self.调整字体大小(-1), width=3)
self.减小按钮.pack(anchor="nw", side=tk.LEFT, padx=2)
# 加载历史数据
self.加载做题记录()
self.加载错题本()
self.导航框 = None # 导航框引用
'''--------右侧面板------------------------'''
# 右侧容器(用于管理题目)
self.右侧容器 = tk.Frame(主窗口)
self.右侧容器.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
# 初始化多题容器为None
self.多题容器 = None
self.滚动条 = None
self.内层框架 = None
self.右侧容器.bind("<Configure>", self.调整布局)
self.主窗口.update_idletasks()
self.加载配置信息()
'''---------------------------------------------功能------------------------------------------------'''
def 加载子题库(self, path, parent):
"""精准加载目录结构"""
# 获取现有子节点
现有文件 = [self.题库树.item(child, "text") for child in self.题库树.get_children(parent)]
try:
for entry in sorted(os.listdir(path)):
# 跳过已存在的项
if entry in 现有文件:
continue
except Exception as e:
print(f"加载错误:{str(e)}")
try:
# 先清空占位符(如果有)
for child in self.题库树.get_children(parent):
if self.题库树.item(child, "values")[0] == "placeholder":
self.题库树.delete(child)
# 获取目录项并按文件夹优先排序
entries = sorted(os.listdir(path),
key=lambda x: (not os.path.isdir(os.path.join(path, x)), x))
for entry in entries:
entry_path = os.path.join(path, entry)
# 跳过隐藏文件(以.开头)
if entry.startswith('.'):
continue
if os.path.isdir(entry_path):
# 创建目录节点并添加展开指示符
node = self.题库树.insert(parent, "end",
text=f"📁 {entry}",
values=("dir", entry_path),
tags=("folder",))
# 添加展开指示符(如果有内容)
if os.listdir(entry_path):
self.题库树.insert(node, "end",
text="点击展开...",
values=("placeholder", ""))
# 处理JSON文件
elif entry.endswith(".json"):
# 显示文件图标和去除扩展名的名称
self.题库树.insert(parent, "end",
text=f"📄 {os.path.splitext(entry)[0]}",
values=("file", entry_path),
tags=("file",))
except PermissionError:
print(f"无权限访问:{path}")
except Exception as e:
print(f"加载错误:{str(e)}")
def 展开目录(self, event):
"""精准处理目录展开"""
node = self.题库树.focus()
node_type, path = self.题库树.item(node, "values")
if node_type == "dir":
# 删除旧加载项
for child in self.题库树.get_children(node):
self.题库树.delete(child)
# 重新加载最新内容
self.加载子题库(path, parent=node)
def 选择题目库(self, event):
"""精准路径验证"""
selected = self.题库树.selection()
if not selected:
return
node = selected[0]
node_type, path = self.题库树.item(node, "values")
if node_type == "file":
# 严格路径校验
if not os.path.isfile(path):
messagebox.showerror("错误", f"文件不存在:{path}")
return
if not path.lower().endswith(".json"):
messagebox.showerror("错误", "仅支持.json格式文件")
return
try:
with open(path, 'r', encoding='utf-8') as f:
self.当前题库 = json.load(f)
# 记录完整路径而非显示名称
self.当前题库路径 = path
self.创建题号导航面板(self.当前题库)
self.创建题目组()
self.显示题目()
except json.JSONDecodeError:
messagebox.showerror("格式错误", "JSON文件格式不正确")
except Exception as e:
messagebox.showerror("加载失败", str(e))
def 刷新题库树(self):
"""刷新整个题库树"""
for child in self.题库树.get_children():
self.题库树.delete(child)
self.题库树.insert("", "end",
text="题库",
iid=self.题库根路径,
values=("dir", self.题库根路径),
open=True)
self.加载子题库(self.题库根路径, parent=self.题库根路径)
def 加载做题记录(self):
try:
if os.path.exists(self.做题记录文件):
with open(self.做题记录文件, 'r', encoding='utf-8') as f:
全记录 = json.load(f)
for 题库ID, 记录列表 in 全记录.items():
for 记录 in 记录列表:
self.题目状态[题库ID] = {
'状态': 记录.get('状态',''),
'正确次数': 记录.get('正确次数',0),
'错误次数': 记录.get('错误次数',0)
}
except Exception as e:
messagebox.showerror("错误", f"加载做题记录失败:{str(e)}")
def 加载错题本(self):
try:
if os.path.exists(self.错题本文件):
with open(self.错题本文件, 'r', encoding='utf-8') as f:
self.错题记录 = json.load(f)
# 兼容旧数据格式
for 错题 in self.错题记录:
if "剩余次数" not in 错题:
错题["剩余次数"] = 2
except Exception as e:
messagebox.showerror("错误", f"加载错题本失败:{str(e)}")
def 保存做题记录(self):
保存数据 = defaultdict(list)
# 重组数据结构
for key, value in self.题目状态.items():
保存数据[key].append({
"状态": value.get('状态',''),
"正确次数": value.get('正确次数',0),
"错误次数": value.get('错误次数',0)
})
# 确保数据目录存在
if not os.path.exists("date"):
os.makedirs("date")
with open(self.做题记录文件, 'w', encoding='utf-8') as f:
json.dump(保存数据, f, ensure_ascii=False)
def 保存配置记录(self):
保存数据 = {
"config": {
"auto_jump": self.自动跳转.get(),
"show_answer": self.显示答案.get(),
"auto_submit_single": self.自动提交单选.get()
}
}
with open(self.配置文件, 'w', encoding='utf-8') as f:
json.dump(保存数据, f, ensure_ascii=False)
def 加载配置信息(self):
if os.path.exists(self.配置文件):
with open(self.配置文件, 'r') as f:
数据 = json.load(f)
if "config" in 数据:
self.自动跳转.set(数据["config"].get("auto_jump", True))
self.显示答案.set(数据["config"].get("show_answer", True))
self.自动提交单选.set(数据["config"].get("auto_submit_single", True))
def 加载当前题库记录(self):
self.题目状态 = defaultdict(dict)
try:
if os.path.exists(self.做题记录文件):
with open(self.做题记录文件, 'r', encoding='utf-8') as f:
全记录 = json.load(f)
# 过滤当前题库记录
for key in 全记录:
if key.startswith(f"{self.当前题库名称}:"):
题目id = key.split(":")[1]
self.题目状态[题目id] = 全记录[key]
except Exception as e:
print(f"加载记录失败:{str(e)}")
def 保存错题本(self):
with open(self.错题本文件, 'w', encoding='utf-8') as f:
json.dump(self.错题记录, f, ensure_ascii=False, indent=2)
def 显示模式按钮(self):
"""根据模式显示对应按钮"""
# 先隐藏所有按钮
for btn in self.作答区.winfo_children():
btn.pack_forget()
if self.多题模式:
self.上一页按钮.pack(side="left", padx=5)
self.页数标签.pack(side="left", padx=5)
self.下一页按钮.pack(side="left", padx=5)
self.提交按钮.pack(side="left", padx=5)
else:
self.上一题按钮.pack(side="left", padx=5)
self.下一题按钮.pack(side="left", padx=5)
self.提交按钮.pack(side="left", padx=5)
def 切换背题模式(self):
"""完善背题模式的控件状态管理"""
if self.背题模式.get():
for idx, card in enumerate(self.题目卡片组):
题目 = self.当前题库[idx]
correct_answer = set(题目["answer"].upper())
# 更新反馈标签
card.feedback.config(
text=f"正确答案:{题目['answer'].upper()}",
fg="green"
)
for widget in card.option_widgets:
opt_idx = card.option_widgets.index(widget)
option_char = chr(65 + opt_idx)
# 设置正确答案标记
if option_char in correct_answer:
if not widget["correct_mark"]:
# 正确写法:获取选项行的父容器作为主控件
mark = tk.Label(widget["label"].master, # 修复父容器
text="✓",
fg="#2E8B57",
font=("宋体", self.字体大小, "bold"))
mark.pack(side=tk.RIGHT)
widget["correct_mark"] = mark
# 高亮正确选项文字
widget["label"].config(fg="#2E8B57")
# 禁用所有交互控件
if widget.get("radio"):
widget["radio"].config(state=tk.DISABLED)
# 自动选中正确答案
if option_char in correct_answer:
widget["radio"].select()
if widget.get("check"):
widget["check"].config(state=tk.DISABLED)
if option_char in correct_answer:
widget["check"].select()
# 移除标签的点击事件绑定
widget["label"].unbind("<Button-1>")
else:
# 退出背题模式时的恢复逻辑
for idx, card in enumerate(self.题目卡片组):
题目 = self.当前题库[idx]
# 更新反馈标签
card.feedback.config(text= "")
for widget in card.option_widgets:
# 恢复文字颜色
widget["label"].config(fg="black")
# 移除正确答案标记
if widget["correct_mark"]:
widget["correct_mark"].destroy()
widget["correct_mark"] = None
# 恢复控件状态
if widget.get("radio"):
widget["radio"].deselect()
widget["radio"].config(state=tk.NORMAL)
#widget["radio"].set('')
var = self.用户答案[题目["id"]]
if isinstance(var, tk.StringVar):
var.set(False) # 清空单选按钮变量
if widget.get("check"):
widget["check"].config(state=tk.NORMAL)
widget["check"].deselect()
# 重新绑定点击事件
题目 = self.当前题库[self.题目卡片组.index(card)]
is_multi = len(题目["answer"]) >1
widget["label"].bind("<Button-1>",
self.创建选项点击事件(题目,
card.option_widgets.index(widget),
is_multi))
self.显示题目()
def 切换显示模式(self):
self.跳转题目(0)
# 切换显示模式
self.多题模式 = not self.多题模式
self.当前页数 = 1 # 重置为第一页
self.总页数 = (len(self.当前题库) + self.每页题数 - 1) // self.每页题数
# 更新按钮显示
self.显示模式按钮()
self.当前显示卡片索引 = 0 # 确保切换到多题模式时从第一题开始
for card in enumerate(self.题目卡片组):
if hasattr(card, 'submit_container'):
if self.多题模式:
card.submit_container.pack_forget()
else:
# 触发状态检查
qid = self.当前题库[self.题目卡片组.index(card)]["id"]
if isinstance(self.用户答案[qid], dict): # 多选
self.更新提交按钮状态(card, self.用户答案[qid])
self.模式切换按钮.config(text="切换单题模式" if self.多题模式 else "切换多题模式")
self.显示题目()
self.调整布局()
# 分页按钮状态更新
self.更新分页按钮状态()
# 延迟确保布局完成
def 延迟滚动():
# 三重保障机制
self.当前显示卡片索引 = 0
self.多题容器.yview_moveto(0) # 滚动到最顶部
self.多题容器.event_generate("<Configure>") # 强制触发布局计算
self.主窗口.update_idletasks()
if self.多题模式 and self.多题容器:
self.主窗口.after(150, 延迟滚动) # 150ms延迟适应不同性能环境
def 更新分页按钮状态(self):
"""控制分页按钮可用状态"""
if self.多题模式:
self.上一页按钮["state"] = tk.NORMAL if self.当前页数 > 1 else tk.DISABLED
self.页数标签["text"] = f"{self.当前页数}/{self.总页数}"
self.下一页按钮["state"] = tk.NORMAL if self.当前页数 < self.总页数 else tk.DISABLED
def 切换页数(self, 增量):
new_page = self.当前页数 + 增量
if 1 <= new_page <= self.总页数:
self.当前页数 = new_page
self.显示题目()
self.更新分页按钮状态()
def 显示题目(self):
# 空题库处理
if not self.题目卡片组:
return
# 索引越界保护
if self.当前显示卡片索引 >= len(self.当前题库):
self.当前显示卡片索引 = 0
if self.多题模式:
# 计算当前页题目范围
start = (self.当前页数 -1) * self.每页题数
end = start + self.每页题数
#当前页题目 = self.当前题库[start:end]
# 隐藏所有卡片
for card in self.题目卡片组:
card.pack_forget()
# 显示当前页卡片
for idx in range(start, min(end, len(self.当前题库))):
self.题目卡片组[idx].pack(fill=tk.X, pady=5, padx=5)
# 滚动到顶部
self.多题容器.yview_moveto(0)
self.滚动条.grid()
self.更新分页按钮状态()
else:
for idx, card in enumerate(self.题目卡片组):
if idx == self.当前显示卡片索引:
# 单题模式填充整个空间
card.pack(fill=tk.BOTH, expand=True, padx=0, pady=10)
card.update_idletasks() # 关键:立即应用布局变化
self.调整布局() # 首次显示时触发布局计算
else:
card.pack_forget()
self.滚动条.grid_remove()
def 调整布局(self, event=None):
# 空数据校验
if not self.题目卡片组 or self.当前显示卡片索引 >= len(self.题目卡片组):
return
# 获取右侧容器完整宽度
container_width = self.右侧容器.winfo_width()
# 统一设置所有卡片宽度
for 卡片 in self.题目卡片组:
卡片.config(width=container_width) # 固定卡片宽度
# 题目标签填满容器
title_label = 卡片.winfo_children()[0]
title_label.config(wraplength=container_width-20) # 保留边距但允许自动换行
# 选项容器填满宽度
选项容器 = 卡片.winfo_children()[1]
选项容器.config(width=container_width)
for 选项行 in 选项容器.winfo_children():
选项行.config(width=container_width)
if len(选项行.winfo_children()) > 1:
# 选项标签强制填满剩余空间
lbl = 选项行.winfo_children()[1]
lbl.config(wraplength=container_width - 50 ) # 根据按钮宽度调整
def 创建题目组(self):
if hasattr(self, '多题容器') and self.多题容器:
self.多题容器.destroy()
self.多题容器 = None
if hasattr(self, '滚动条') and self.滚动条:
self.滚动条.destroy()
self.滚动条 = None
self.滚动条 = ttk.Scrollbar(self.右侧容器, orient="vertical")
self.配置滚动条样式()
# 创建通用容器
self.多题容器 = tk.Canvas(self.右侧容器, bg='white', highlightthickness=0,yscrollcommand=self.滚动条.set)
self.滚动条.configure(command=self.多题容器.yview)
# 初始隐藏滚动条
self.滚动条.grid(row=0, column=1, sticky="ns")
self.滚动条.grid_remove() # 默认隐藏
self.多题容器.grid(row=0, column=0, sticky="nsew")
self.右侧容器.grid_rowconfigure(0, weight=1)
self.右侧容器.grid_columnconfigure(0, weight=1)
# 创建内部框架
self.内层框架 = tk.Frame(self.多题容器)
self.多题容器.create_window((0,0), window=self.内层框架, anchor="nw")
# 生成所有题目卡片(始终存在)
self.题目卡片组 = []
for idx, 题目 in enumerate(self.当前题库):
卡片 = self.创建题目卡片(题目, idx)
卡片.pack_forget() # 初始全部隐藏
self.题目卡片组.append(卡片)
# 初始化分页
self.总页数 = (len(self.当前题库) + self.每页题数 -1) // self.每页题数
self.更新分页按钮状态()
self.内层框架.bind("<Configure>", self.更新滚动区域)
# 绑定鼠标滚轮事件
self.多题容器.bind("<MouseWheel>", self._处理鼠标滚动)
self.内层框架.bind("<MouseWheel>", self._处理鼠标滚动)
# 为滚动条添加额外绑定(增强兼容性)
self.滚动条.bind("<MouseWheel>", self._处理鼠标滚动)
# 设置滚动步长(影响滑块大小)
self.多题容器.configure(yscrollincrement=40) # 每步滚动40像素
def 配置滚动条样式(self):
"""使用ttk样式配置滚动条外观"""
style = ttk.Style()
# 定义滚动条元素布局
style.layout("Vertical.TScrollbar",
[('Vertical.Scrollbar.trough', {'children':
[('Vertical.Scrollbar.thumb', {'expand': '1', 'sticky': 'nswe'})],
'sticky': 'ns'})])
# 配置颜色和尺寸
style.configure("Vertical.TScrollbar",
troughcolor="#f0f0f0",
bordercolor="#c0c0c0",
arrowcolor="#808080",
gripcount=0,
arrowsize=14, # 箭头大小
sliderlength=40, # 滑块长度
thickness=14) # 滚动条宽度
# 应用样式
self.滚动条.configure(style="Vertical.TScrollbar")
def 更新滚动区域(self, event):
"""优化后的滚动区域更新"""
# 计算内容总高度
内容高度 = self.内层框架.winfo_height()
容器高度 = self.多题容器.winfo_height()
# 更新滚动区域
self.多题容器.configure(
scrollregion=self.多题容器.bbox("all"),
yscrollincrement=容器高度*0.1 # 按容器高度的10%步进
)
def _处理鼠标滚动(self, event):
"""通用鼠标滚动处理"""
if not self.多题模式:
return
# Windows/Mac的delta值处理
if event.delta:
units = -1 * (event.delta // 120)
# Linux的按钮事件处理
else:
units = -1 if event.num == 4 else 1
self.多题容器.yview_scroll(units, "units")
# 限制滚动范围
y0, y1 = self.多题容器.yview()
if y0 < 0:
self.多题容器.yview_moveto(0)
elif y1 > 1:
self.多题容器.yview_moveto(1)
return "break" # 阻止事件继续传播
def 更新提交按钮状态(self, 卡片框, vars_dict):
"""根据选项选择情况更新提交按钮可见性"""
has_selection = any(var.get() for var in vars_dict.values())
if has_selection and self.自动提交单选.get() :
卡片框.submit_container.pack(side="left",pady=5)
else:
卡片框.submit_container.pack_forget()
def 创建题目卡片(self, 题目, 索引):
卡片框 = tk.LabelFrame(self.内层框架,
text=f"第{索引+1}题",
padx=0,
pady=10,
width=self.右侧容器.winfo_width()) # 初始宽度同步
# 为卡片框绑定滚轮事件
卡片框.bind("<MouseWheel>", self._处理鼠标滚动)
卡片框.option_widgets = [] # 初始化选项控件列表
# 内容标签配置修改
内容标签 = tk.Label(
卡片框,
text=题目["title"],
font=("宋体", self.字体大小),
anchor="nw",
justify="left",
wraplength=self.右侧容器.winfo_width(), #-10, # 初始值同步容器
width=self.右侧容器.winfo_width() # 关键修改:固定标签宽度
)
内容标签.pack(anchor="nw", fill=tk.X, expand=True)
# 选项容器配置
选项容器 = tk.Frame(卡片框, width=self.右侧容器.winfo_width())
选项容器.pack(fill=tk.BOTH, expand=True)
# 创建提交按钮容器(初始隐藏)
提交容器 = tk.Frame(卡片框)
提交容器.pack_forget() # 初始隐藏
# 答案存储变量
is_multi = len(题目["answer"]) > 1
if is_multi:
# 多选题使用字典记录每个选项状态
vars_dict = {chr(65+i): tk.BooleanVar() for i in range(len(题目["option"]))}
self.用户答案[题目["id"]] = vars_dict
提交按钮 = tk.Button(
提交容器,
text="提交答案",
command=lambda: self.自动提交作答()
)
提交按钮.pack(pady=5)
# 存储提交按钮引用
卡片框.submit_container = 提交容器
卡片框.submit_button = 提交按钮
# 监控选项变更
for var in vars_dict.values():
var.trace_add("write", lambda *args: self.更新提交按钮状态(卡片框, vars_dict))
else:
# 单选题使用单个变量
var = tk.StringVar()
self.用户答案[题目["id"]] = var
var.set(False) # 初始化为未选择
# 动态创建选项
for opt_idx, 选项 in enumerate(题目["option"]):
选项行 = tk.Frame(选项容器, width=self.右侧容器.winfo_width())
选项行.pack(fill=tk.X, expand=True, anchor="nw", pady=2)
# 创建控件并存储引用
widget_data = {}
if is_multi:
# 多选题复选框
cb = tk.Checkbutton(选项行,
variable=vars_dict[chr(65+opt_idx)],
command=lambda k=chr(65+opt_idx): self.更新多选答案(题目["id"], k))
cb.pack(side="left")
widget_data["check"] = cb
else:
# 单选题单选按钮
rb = tk.Radiobutton(选项行,
variable= var,
value=chr(65+opt_idx),
command=lambda v=chr(65+opt_idx): self.处理单选自动提交(题目, v),
indicatoron=1)
rb.pack(side="left")
widget_data["radio"] = rb
# 选项文本(带点击事件绑定)
lbl = tk.Label(选项行,
text=f"{选项}",
font=("宋体", self.字体大小),
anchor="nw",
justify="left",
wraplength=self.右侧容器.winfo_width()-50,
width=self.右侧容器.winfo_width()-50 # 留出按钮空间
)
lbl.bind("<Button-1>", self.创建选项点击事件(题目, opt_idx, is_multi))
lbl.pack(side=tk.LEFT, fill=tk.X, expand=True)
widget_data["label"] = lbl
widget_data["correct_mark"] = None
# 存储控件引用到卡片对象
卡片框.option_widgets.append(widget_data)
# 反馈标签
feedback = tk.Label(卡片框, text="", font=("宋体", self.字体大小))
feedback.pack(anchor="nw")
# 保存反馈引用
卡片框.feedback = feedback
# 递归绑定所有子部件
def 递归绑定滚轮(widget):
widget.bind("<MouseWheel>", self._处理鼠标滚动)
for child in widget.winfo_children():
递归绑定滚轮(child)
递归绑定滚轮(卡片框)
return 卡片框
def 处理单选自动提交(self, 题目, 选项值):
self.用户答案[题目["id"]].set(选项值)
if self.自动提交单选.get() and not self.多题模式:
self.自动提交作答()
if self.自动跳转.get() and not self.多题模式:
self.主窗口.after(500, self.下一题)
def 创建选项点击事件(self, 题目, opt_idx, is_multi):
def 点击处理(event):
if is_multi:
current = self.用户答案[题目["id"]][chr(65+opt_idx)].get()
self.用户答案[题目["id"]][chr(65+opt_idx)].set(not current)
else:
self.处理单选自动提交(题目,chr(65+opt_idx))
return 点击处理
# 提交答案
def 提交所有答案(self):
"""背题模式禁用提交功能"""
if self.背题模式.get():
messagebox.showinfo("提示", "当前为背题模式,无法提交答案")
return
是否提交 = messagebox.askyesno(
"是否提交",
"确认提交请点'是',否则点'否'",
parent=self.主窗口
)
if 是否提交:
correct_count = 0
total_answered = 0
for 题目 in self.当前题库:
qid = 题目["id"]
用户选择 = ""
# 获取用户答案
if isinstance(self.用户答案[qid], tk.StringVar): # 单选题
用户选择 = self.用户答案[qid].get()
else: # 多选题
用户选择 = "".join([k for k, v in self.用户答案[qid].items() if v.get()])
# 跳过未作答题目
if not 用户选择:
continue
total_answered += 1
正确答案 = 题目["answer"].upper()
是否正确 = (set(用户选择) == set(正确答案)) if len(正确答案)>1 else (用户选择 == 正确答案)
# 更新反馈
for card in self.题目卡片组:
if card.winfo_children()[0].cget("text").startswith(题目["title"][:10]):
color = "green" if 是否正确 else "red"
symbol = "✓" if 是否正确 else "✗"
self.更新做题信息(是否正确, 题目)
card.feedback.config(
text=f"{symbol} 你的答案:{用户选择},正确答案:{正确答案}",
fg=color
)
if 是否正确:
correct_count += 1
break
# 显示统计结果
messagebox.showinfo("答题结果",
f"已答题目:{total_answered}\n正确率:{correct_count/total_answered:.1%}"
if total_answered else "请先作答题目!")
self.保存做题记录()
self.更新题号样式()
self.保存错题本()
self.主窗口.update_idletasks() # 强制刷新界面
def 自动提交作答(self):
"""单题提交逻辑"""
题目 = self.当前题库[self.当前显示卡片索引]
qid = 题目["id"]
用户选择 = self.获取当前卡片答案(qid)
# 验证逻辑
正确答案 = 题目["answer"].upper()
是否正确 = (set(用户选择) == set(正确答案)) if len(正确答案)>1 else (用户选择 == 正确答案)
# 更新反馈
feedback = self.题目卡片组[self.当前显示卡片索引].feedback
color = "green" if 是否正确 else "red"
symbol = "✓" if 是否正确 else "✗"
self.更新做题信息(是否正确, 题目)
if self.显示答案.get():
feedback_text = f"{symbol} 你的答案:{用户选择},正确答案:{正确答案}"
feedback.config(text=feedback_text, fg=color)
else:
feedback_text = symbol
self.保存做题记录()
self.更新题号样式()
self.保存错题本()
if self.自动跳转.get() and not self.多题模式:
self.主窗口.after(500, self.下一题)
self.主窗口.update_idletasks() # 强制刷新界面
def 更新多选答案(self, 题目ID, 选项):
# 自动更新字典状态,无需额外操作
pass
def 刷新当前卡片显示(self, 题目):
"""编辑后刷新卡片"""
idx = self.当前显示卡片索引
# 销毁旧卡片
self.题目卡片组[idx].destroy()
# 创建新卡片
new_card = self.创建题目卡片(题目, idx)
new_card.pack_forget()
self.题目卡片组[idx] = new_card
# 重新显示
self.跳转题目(idx)
def 创建题号导航面板(self, 题库数据, 是否是错题=False):
"""导航面板"""
def 鼠标滚动处理(event):
if not self.允许滚动: # 新增判断条件
return
# 优化滚动灵敏度(提高3倍滚动速度)
units = -1 * (event.delta // 30) # Windows/Linux标准单位
画布.yview_scroll(units, "units")
# 限制滚动范围
y0, y1 = 画布.yview()
if y0 < 0:
画布.yview_moveto(0)
elif y1 > 1:
画布.yview_moveto(1)
if self.导航框:
self.导航框.destroy()
# 生成标题
标题 = "错题导航" if 是否是错题 else f"{self.当前题库名称}导航"
总题数 = len(题库数据)
# 创建导航框架
self.导航框 = tk.LabelFrame(self.中间面板, text=f"{标题}({总题数}题)", padx=5, pady=5)
self.导航框.pack(fill=tk.BOTH, expand=True, pady=10)
# 创建滚动区域
画布 = tk.Canvas(self.导航框, borderwidth=0, highlightthickness=0)
滚动条容器 = tk.Frame(self.导航框, width=15) # 固定宽度避免闪烁
滚动条容器.pack(side="right", fill="y")
滚动条 = tk.Scrollbar(滚动条容器, orient="vertical", command=画布.yview)
滚动条.pack(expand=True, fill="y")
滚动容器 = tk.Frame(画布)
# 配置画布滚动
画布.configure(yscrollcommand=滚动条.set)
画布.pack(side="left", fill="both", expand=True)
# 将滚动容器放入画布
画布.create_window((0,0), window=滚动容器, anchor="nw")
# 配置网格布局
滚动容器.grid_rowconfigure(0, weight=1)
滚动容器.grid_columnconfigure(0, weight=1)
# 生成题号按钮
self.题号按钮组 = []
行号 = 0
for i in range(len(题库数据)):
列号 = i % 9 # 每行9个按钮
if i > 0 and i % 9 == 0:
行号 += 1
按钮 = tk.Button(
滚动容器,
text=str(i+1),
width=2,
relief="groove",
command=lambda idx=i: self.跳转题目(idx)
)
按钮.grid(row=行号, column=列号, padx=1, pady=1, sticky="nsew")
按钮.bind("<MouseWheel>", 鼠标滚动处理)
self.题号按钮组.append(按钮)
# 智能显示滚动条
def 更新滚动可见性():
容器高度 = 画布.winfo_height()
内容高度 = 滚动容器.winfo_reqheight()
# 精确判断是否需要滚动(误差控制在5像素内)
self.允许滚动 = 内容高度 > 容器高度 + 5 # 新增状态标志
if self.允许滚动:
# 显示滚动条并启用滚动
滚动条容器.pack(side="right", fill="y")
画布.configure(yscrollcommand=滚动条.set)
# 设置精确的滚动区域
画布.configure(scrollregion=(
0, 0,
滚动容器.winfo_reqwidth(),
max(内容高度, 容器高度) # 确保可滚动区域
))
else:
# 隐藏滚动条并禁用滚动
滚动条容器.pack_forget()
画布.configure(yscrollcommand=None)
# 固定滚动区域为可视区域
画布.configure(scrollregion=(
0, 0,
滚动容器.winfo_reqwidth(),
内容高度
))
# 配置滚动容器自动扩展
滚动容器.update_idletasks()
画布.configure(scrollregion=画布.bbox("all"))
# 绑定事件
画布.bind("<Configure>", lambda e: (更新滚动可见性(), 画布.configure(scrollregion=画布.bbox("all"))))
滚动容器.bind("<Configure>", lambda e: 更新滚动可见性())
画布.bind("<MouseWheel>", 鼠标滚动处理)
滚动容器.bind("<MouseWheel>", 鼠标滚动处理)
self.允许滚动 = False
# 初始更新
更新滚动可见性()
# 更新按钮样式
self.更新题号样式()
def 更新题号样式(self):
for i, 题目 in enumerate(self.当前题库):
按钮 = self.题号按钮组[i]
# 正常模式状态显示
记录ID = f"{题目['id']}"
记录 = self.题目状态.get(记录ID, {})
# 排除当前题号(单独处理)
if i == self.当前显示卡片索引:
continue
# 设置颜色和提示
if 记录.get('状态') == '正确':
按钮.config(bg='#89CFF0')
elif 记录.get('状态') == '错误':
按钮.config(bg='#FF6961')
else:
按钮.config(bg='SystemButtonFace', fg='SystemWindowText')
def 获取当前卡片答案(self, qid):
"""获取指定卡片的答案"""
answer_var = self.用户答案[qid]
if isinstance(answer_var, tk.StringVar): # 单选题
return answer_var.get()
else: # 多选题
return "".join([k for k, v in answer_var.items() if v.get()])
def 跳转题目(self, 题号索引):
if 0 <= 题号索引 < len(self.当前题库):
self.当前显示卡片索引 = 题号索引
self.显示题目()
if self.多题模式:
# 多题模式滚动到指定位置
self.多题容器.yview_moveto(题号索引/len(self.当前题库))
#messagebox.showinfo("提示", "该模式下无法进行跳转!")
return
else:
# 单题模式更新题号样式
self.更新题号样式()
def 更新做题信息(self, 是否正确, 题目):
# 错题模式处理
if self.错题模式:
# 找到对应的错题记录
错题条目 = next((q for q in self.错题记录 if q["id"] == 题目["id"]), None)
if 错题条目:
if 是否正确:
错题条目["剩余次数"] -= 1
if 错题条目["剩余次数"] <= 0:
self.错题记录.remove(错题条目)
# 从当前题库移除
self.当前题库.pop(self.当前显示卡片索引)
self.显示错题本()
if self.当前显示卡片索引 >= len(self.当前题库):
self.当前显示卡片索引 = 0
else:
错题条目["剩余次数"] = 2 # 答错重置次数
# 更新导航面板
self.创建题号导航面板(self.当前题库, 是否是错题=True)
else: #正常模式
key = f"{题目['id']}"
if not 是否正确:
self.题目状态[key]['状态'] = '错误'
self.题目状态[key]['错误次数'] = self.题目状态[key].get('错误次数', 0) + 1
# 记录完整错题信息
新错题 = 题目.copy()
新错题.update({
"剩余次数": 2,
"来源题库": self.当前题库名称
})
# 去重处理
if not any(q["id"] == 新错题["id"] for q in self.错题记录):
self.错题记录.append(新错题)
#self.保存错题本()
else:
self.题目状态[key]['状态'] = '正确'
self.题目状态[key]['正确次数'] = self.题目状态[key].get('正确次数', 0) + 1
#self.主窗口.after(500, self.自动下一题)
def 自动下一题(self):
if self.当前显示卡片索引 < len(self.当前题库)-1:
self.当前显示卡片索引 += 1
else:
self.当前显示卡片索引 = 0
self.显示题目()
self.更新题号样式()
def 退出错题模式(self):
# 恢复原状态时包括分页信息
(self.当前题库,
self.当前显示卡片索引,
self.当前题库名称,
self.当前页数) = self.原状态备份
self.错题模式 = False
# 强制重新创建题目容器
self.创建题目组() # 关键修复:恢复原题库卡片
# 更新界面元素
self.创建题号导航面板(self.当前题库)
self.显示题目()
self.调整布局()
self.错题按钮.config(text="错题", command=self.显示错题本)
def 上一题(self):
if self.当前显示卡片索引 > 0 :
self.跳转题目(self.当前显示卡片索引 - 1)
def 下一题(self):
if self.当前显示卡片索引 < len(self.当前题库)-1:
self.跳转题目(self.当前显示卡片索引 + 1)
def 显示错题本(self):
if not self.错题记录:
messagebox.showinfo("提示", "当前没有需要练习的错题")
return
self.原状态备份 = (self.当前题库.copy(), self.当前显示卡片索引, self.当前题库名称,self.当前页数)
self.错题模式 = True
self.当前题库 = [q.copy() for q in self.错题记录] # 使用副本避免修改原数据
self.当前题库名称 = "错题本"
self.创建题目组()
self.当前页数 = 1
self.当前显示卡片索引 = 0
self.创建题号导航面板(self.当前题库, 是否是错题=True)
self.显示题目()
self.调整布局()
self.错题按钮.config(text="返回正常模式", command=self.退出错题模式)
def 调整字体大小(self, 增量):
new_size = self.字体大小 + 增量
if 10 <= new_size <= 24:
self.字体大小 = new_size
# 定义递归更新函数
def 更新字体(widget):
# 更新当前组件
if isinstance(widget, (tk.Label, tk.Radiobutton, tk.Checkbutton)):
widget.config(font=("宋体", self.字体大小))
# 递归处理子组件
for child in widget.winfo_children():
更新字体(child)
# 遍历所有卡片
for card in self.题目卡片组:
更新字体(card)
# 强制刷新界面
self.主窗口.update_idletasks()
def 编辑题目(self): # 编辑题目的方法
if self.多题模式:
messagebox.showwarning("提示", "多题模式下无法编辑!")
return
if not self.当前题库 or self.当前显示卡片索引 >= len(self.当前题库):
messagebox.showwarning("提示", "请先选择题目")
return
当前题目 = self.当前题库[self.当前显示卡片索引]
self.弹出编辑对话框(当前题目)
def 弹出编辑对话框(self, 题目):
对话框 = tk.Toplevel(self.主窗口)
对话框.title("编辑题目")
对话框.grab_set() # 设置为模态对话框
# 题目内容
tk.Label(对话框, text="题目内容:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
题目内容输入 = tk.Text(对话框, wrap=tk.WORD, width=60, height=4)
题目内容输入.insert(tk.END, 题目.get('title', ''))
题目内容输入.grid(row=0, column=1, columnspan=3, padx=5, pady=5)
# 选项部分
tk.Label(对话框, text="选项:").grid(row=1, column=0, sticky="nw", padx=5, pady=5)
option_frame = tk.Frame(对话框)
option_frame.grid(row=1, column=1, columnspan=3, sticky="ew")
option_frames = [] # 存储所有选项行的Frame
# 初始化现有选项
for i, opt in enumerate(题目.get('option', [])):
frame = tk.Frame(option_frame)
frame.pack(fill=tk.X, pady=2)
label = tk.Label(frame, text=f"选项{chr(65 + i)}:")
label.pack(side=tk.LEFT)
entry = tk.Entry(frame)
entry.insert(0, opt)
entry.pack(side=tk.LEFT, expand=True, fill=tk.X)
# 删除按钮
def 删除处理(f=frame):
f.destroy()
option_frames.remove(f)
# 更新剩余选项的标签
for idx, fr in enumerate(option_frames):
fr_label = fr.winfo_children()[0]
fr_label.config(text=f"选项{chr(65 + idx)}:")
del_btn = tk.Button(frame, text="×", command=lambda f=frame: 删除处理(f))
del_btn.pack(side=tk.RIGHT)
option_frames.append(frame)
# 添加新选项按钮
def 添加新选项():
frame = tk.Frame(option_frame)
frame.pack(fill=tk.X, pady=2)
当前选项数 = len(option_frames)
label = tk.Label(frame, text=f"选项{chr(65 + 当前选项数)}:")
label.pack(side=tk.LEFT)
entry = tk.Entry(frame)
entry.pack(side=tk.LEFT, expand=True, fill=tk.X)
# 删除按钮
def 删除处理(f=frame):
f.destroy()
option_frames.remove(f)
for idx, fr in enumerate(option_frames):
fr_label = fr.winfo_children()[0]
fr_label.config(text=f"选项{chr(65 + idx)}:")
del_btn = tk.Button(frame, text="×", command=lambda f=frame: 删除处理(f))
del_btn.pack(side=tk.RIGHT)
option_frames.append(frame)
add_btn = tk.Button(对话框, text="添加选项", command=添加新选项)
add_btn.grid(row=2, column=1, pady=5)
# 正确答案
tk.Label(对话框, text="正确答案:").grid(row=3, column=0, sticky="w", padx=5, pady=5)
answer_var = tk.StringVar(value=题目.get('answer', ''))
answer_entry = tk.Entry(对话框, textvariable=answer_var)
answer_entry.grid(row=3, column=1, columnspan=3, sticky="ew", padx=5, pady=5)
# 解析
tk.Label(对话框, text="解析:").grid(row=4, column=0, sticky="w", padx=5, pady=5)
analysis_input = tk.Text(对话框, wrap=tk.WORD, width=60, height=4)
analysis_input.insert(tk.END, 题目.get('analysis', ''))
analysis_input.grid(row=4, column=1, columnspan=3, padx=5, pady=5)
def 保存修改():
new_title = 题目内容输入.get("1.0", tk.END).strip()
if not new_title:
messagebox.showerror("错误", "题目内容不能为空")
return
new_options = []
for frame in option_frames:
entry = frame.winfo_children()[1] # 获取Entry控件
opt_text = entry.get().strip()
if opt_text:
new_options.append(opt_text)
if not new_options:
messagebox.showerror("错误", "至少需要一个选项")
return
new_answer = answer_var.get().strip().upper()
if not new_answer:
messagebox.showerror("错误", "答案不能为空")
return
valid_chars = set(chr(65 + i) for i in range(len(new_options)))
for char in new_answer:
if char not in valid_chars:
messagebox.showerror("错误", f"答案 '{char}' 无效,有效选项为 {', '.join(valid_chars)}")
return
题目['title'] = new_title
题目['option'] = new_options
题目['answer'] = new_answer
题目['analysis'] = analysis_input.get("1.0", tk.END).strip()
try:
with open(self.当前题库路径, 'w', encoding='utf-8') as f:
json.dump(self.当前题库, f, ensure_ascii=False, indent=2)
messagebox.showinfo("成功", "题目已保存")
self.刷新当前卡片显示(题目) # 刷新当前题目显示
对话框.destroy()
except Exception as e:
messagebox.showerror("保存失败", f"无法保存文件:{str(e)}")
button_frame = tk.Frame(对话框)
button_frame.grid(row=5, column=0, columnspan=4, pady=10)
tk.Button(button_frame, text="保存", command=保存修改).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="取消", command=对话框.destroy).pack(side=tk.LEFT, padx=5)
对话框.columnconfigure(1, weight=1)
for i in range(5):
对话框.rowconfigure(i, weight=0)
# 导入题目方法
def 导入题目(self):
file_path = filedialog.askopenfilename(
parent=self.主窗口,
filetypes=[('文本文件', '*.txt')],
title="选择题目文件"
)
if not file_path:
return
# 创建模式选择对话框
mode = messagebox.askyesno(
"模式选择",
"答案在每题后请点'是',答案在末尾请点'否'",
parent=self.主窗口
)
try:
with open(file_path, 'r', encoding='UTF-8') as f:
data = f.read().replace('.', '.')
result = []
题库目录 = "题库"
if mode:
# 模式1:答案在题目后
pattern = re.compile(r'(?:^|\n\s*)\d+?[\.。]')
questions = pattern.split(data)
for q in questions:
if not q.strip():
continue
lines = re.split(r'\n', q, 1)
title = lines[0] if len(lines) > 0 else ''
content = lines[1] if len(lines) > 1 else ''
# 提取选项
options = re.findall(r'[A-E][.。]?(.+?)(?=\s+[A-E][.。]|\s*$)', re.split(r'答案[::]',content)[0], re.DOTALL)
# 提取答案和解析
answer_match = re.search(r'答案[::]([A-E]+)', content)
analysis_match = re.search(r'解析[::]([\s\S]+)', content)
answer = answer_match.group(1) if answer_match else ''
analysis = analysis_match.group(1).strip() if analysis_match else ''
result.append({
"id": f"{time.strftime('%Y%m%d%H%M')}{random.randint(0,1000000)}",
"title": title,
"option": [o.strip() for o in options if o.strip()],
"answer": answer,
"analysis": analysis
})
else:
# 模式2:答案在末尾
answer_section = re.search(r'答案[::]([\s\S]+)', data)
if not answer_section:
messagebox.showerror("错误", "未找到答案部分")
exit()
answers = re.findall(r'([A-E]+)\s*', answer_section.group(1))
questions_data = re.split(r'【答案】', data)[0]
# 分割题目
questions = list(filter(None, re.split(r'(?:^|\n\s*)\d+?[\.\。]', questions_data)))
if len(questions) != len(answers):
messagebox.showerror("错误", "题目数量与答案数量不匹配")
exit()
for idx, q in enumerate(questions):
lines = re.split(r'\n', q, 1)
title = lines[0] if len(lines) > 0 else ''
content = lines[1] if len(lines) > 1 else ''
# 提取选项
options = re.findall(r'[A-E][\.。]?(.+?)(?=\s+[A-E]|\s*$)', content, re.DOTALL)
result.append({
"id": f"{time.strftime('%Y%m%d%H%M')}{random.randint(0,1000000)}",
"title": title,
"option": [o.strip() for o in options if o.strip()],
"answer": answers[idx],
"analysis": "" # 该模式无法获取解析
})
# 确保题库目录存在
if not os.path.exists(题库目录):
os.makedirs(题库目录)
# 弹出目录选择对话框
目标节点 = self.选择保存目录()
if not 目标节点:
return
# 获取保存路径
目标路径 = self.题库树.item(目标节点, "values")[1]
if not os.path.isdir(目标路径):
目标路径 = os.path.dirname(目标路径)
# 生成文件名
基础名称 = os.path.splitext(os.path.basename(file_path))[0]
保存路径 = os.path.join(目标路径, f"{基础名称}.json")
序号 = 1
while os.path.exists(保存路径):
保存路径 = os.path.join(题库目录, f"{基础名称}_{序号}.json")
序号 += 1
# 保存题库文件
with open(保存路径, 'w', encoding='utf-8') as f:
json.dump(result, f, ensure_ascii=False, indent=2)
# 刷新对应目录
self.刷新目录节点(目标节点)
messagebox.showinfo("成功", "题库导入成功!")
except Exception as e:
messagebox.showerror("导入失败", str(e))
def 选择保存目录(self):
"""弹出目录选择对话框"""
当前选中 = self.题库树.selection()
if 当前选中:
当前节点 = 当前选中[0]
if self.题库树.item(当前节点, "values")[0] == "dir":
return 当前节点
return self.题库根路径
def 刷新目录节点(self, node):
"""刷新指定目录节点"""
# 收起再展开以触发刷新
self.题库树.item(node, open=False)
self.题库树.item(node, open=True)
# 强制更新子项
path = self.题库树.item(node, "values")[1]
for child in self.题库树.get_children(node):
self.题库树.delete(child)
self.加载子题库(path, parent=node)
if __name__ == "__main__":
窗口 = tk.Tk()
窗口.geometry("1100x600")
应用 = 做题软件(窗口)
窗口.mainloop()