非常感谢你提供的错误日志!
### ❌ 错误原因
```
_tkinter.TclError: cannot use geometry manager pack inside ... which already has slaves managed by grid
```
这是 Tkinter 的一个 **核心规则**:
> 🔴 **不能在同一个容器(父控件)中混用 `pack` 和 `grid` 布局管理器!**
---
### 📌 出错位置分析
你在使用 `grid` 管理主框架 `main_frame` 时:
```python
main_frame = tk.Frame(sel_win)
main_frame.pack(fill="both", expand=True, padx=10, pady=10) # ✅ 外层用 pack 没问题
top_bar.grid(...) # ← 子控件用 grid
listbox.grid(...) # ← 子控件用 grid
add_separator(main_frame) # ← 这里又对 main_frame 用了 pack!❌ 冲突!
```
虽然 `main_frame` 是用 `pack` 加入窗口的,但它的子控件已经统一用 `grid` 布局了。此时再向它添加一个用 `pack` 的分隔线,就会报错。
---
### ✅ 正确修复方式:全部改用 `grid` 或 全部用 `pack`
我们选择 **统一使用 `grid`** 来保持布局精确性。
---
### ✅ 替换 `select_mistake_file` 方法(彻底修复混用问题)
```python
def select_mistake_file(self):
files = [f for f in os.listdir('.') if f.startswith('mistakes_') and f.endswith('.jsonl')]
if not files:
messagebox.showinfo("提示", "暂无任何错题本可供复习。")
return
sel_win = Toplevel(self.root)
sel_win.title("选择错题本")
sel_win.geometry("450x400")
sel_win.resizable(False, False)
# 主容器
main_frame = tk.Frame(sel_win)
main_frame.pack(fill="both", expand=True, padx=10, pady=10)
# 配置 grid 权重
main_frame.grid_rowconfigure(1, weight=1) # 第二行(列表)可扩展
main_frame.grid_columnconfigure(0, weight=1)
# 顶部工具栏
top_bar = tk.Frame(main_frame)
top_bar.grid(row=0, column=0, sticky="ew", pady=(0, 10))
top_bar.grid_columnconfigure(0, weight=1)
top_bar.grid_columnconfigure(1, weight=0)
tk.Label(top_bar, text="请选择要复习的错题本:", font=("Arial", 12, "bold")).grid(row=0, column=0, sticky="w")
manage_btn = tk.Button(
top_bar,
text="⚙️ 管理",
font=("Arial", 10),
bg="gray",
fg="white",
width=8,
height=1
)
manage_btn.grid(row=0, column=1, sticky="e")
# 列表框
listbox = Listbox(
main_frame,
width=50,
height=12,
font=("Arial", 11),
bd=0,
highlightthickness=0
)
listbox.grid(row=1, column=0, sticky="nsew", pady=(0, 10))
# 分隔线函数(使用 Frame + grid)
def add_separator():
sep = tk.Frame(main_frame, height=1, bg="lightgray")
sep.grid(row=listbox.grid_info()['row'] + 1, column=0, sticky="ew", padx=30, pady=2)
# 更新后续行号
for widget in main_frame.grid_slaves():
r = widget.grid_info().get('row')
if r > sep.grid_info()['row']:
widget.grid_configure(row=r + 1)
# 按编号排序
sorted_files = sorted(files, key=lambda x: int(x.split('_')[1].split('.')[0]) if '_' in x else 0)
display_map = {}
current_row = listbox.grid_info()['row'] + 1 # 初始下一行
for i, f in enumerate(sorted_files):
display_name = self.get_display_name(f)
listbox.insert(END, display_name)
display_map[display_name] = f
if i < len(sorted_files) - 1:
# 插入分隔线
sep = tk.Frame(main_frame, height=1, bg="lightgray")
sep.grid(row=current_row, column=0, sticky="ew", padx=30, pady=2)
current_row += 1
# 刷新函数
def refresh_listbox():
listbox.delete(0, END)
display_map.clear()
for slave in main_frame.grid_slaves():
if isinstance(slave, tk.Frame) and slave.cget('height') == 1:
slave.destroy()
current_files = [f for f in os.listdir('.') if f.startswith('mistakes_') and f.endswith('.jsonl')]
if not current_files:
messagebox.showinfo("提示", "所有错题本已被删除。")
sel_win.destroy()
return
sorted_current = sorted(current_files, key=lambda x: int(x.split('_')[1].split('.')[0]))
new_row = listbox.grid_info()['row'] + 1
for i, f in enumerate(sorted_current):
display_name = self.get_display_name(f)
listbox.insert(END, display_name)
display_map[display_name] = f
if i < len(sorted_current) - 1:
sep = tk.Frame(main_frame, height=1, bg="lightgray")
sep.grid(row=new_row, column=0, sticky="ew", padx=30, pady=2)
new_row += 1
# 绑定管理按钮
manage_btn.config(command=lambda: self.manage_mistakebooks(refresh_listbox, sel_win))
# 按钮区域
btn_frame = tk.Frame(main_frame)
btn_frame.grid(row=999, column=0, pady=10) # 放到最后
def on_confirm():
selection = listbox.curselection()
if not selection:
messagebox.showwarning("提示", "请先选择一个错题本")
return
selected_name = listbox.get(selection[0])
filename = display_map[selected_name]
sel_win.destroy()
self.review_mistakes_from_file(filename)
def on_cancel():
sel_win.destroy()
Button(btn_frame, text="✅ 确定", font=("Arial", 10), bg="blue", fg="white", width=10, command=on_confirm).pack(side="left", padx=10)
Button(btn_frame, text="❌ 取消", font=("Arial", 10), bg="gray", fg="white", width=10, command=on_cancel).pack(side="left", padx=10)
sel_win.protocol("WM_DELETE_WINDOW", on_cancel)
```
---
### ✅ 修改说明总结
| 问题 | 修复方式 |
|------|----------|
| `pack` 和 `grid` 混用 | 所有子控件都通过 `grid` 添加到 `main_frame` |
| 分隔线用 `pack` 导致冲突 | 改为用 `grid` 定位,并动态管理行号 |
| 删除后刷新列表异常 | 使用 `refresh_listbox` 完全重建 UI |
---
### ✅ 如何测试?
1. 确保有多个错题本文件(如 `mistakes_1.jsonl`, `mistakes_2.jsonl`)
2. 运行脚本 → 点击“错题本”
3. 查看是否显示正常、无崩溃
4. 点击“管理” → 删除一个 → 返回确认是否消失
---