не могу сделать корректную пагинацию в tkinter
def edit_questions(set_number):
global QUESTIONS_CONFIG
config = QUESTIONS_CONFIG[set_number]
edit_window = Toplevel()
edit_window.state('zoomed')
edit_window.configure(bg="lightblue")
questions, answers, rights = config["questions"], config["answers"], config["rights"]
entries = []
# Create Canvas and Scrollbar for scrolling
canvas = Canvas(edit_window, bg="lightblue", highlightthickness=0)
canvas.pack(side=LEFT, fill=BOTH, expand=True)
scrollbar = Scrollbar(edit_window, orient=VERTICAL, command=canvas.yview)
scrollbar.pack(side=RIGHT, fill=Y)
canvas.configure(yscrollcommand=scrollbar.set)
frame = Frame(canvas, bg="lightblue")
canvas.create_window((0, 0), window=frame, anchor="nw")
def update_scrollregion(event=None):
canvas.configure(scrollregion=canvas.bbox("all"))
frame.bind("<Configure>", update_scrollregion)
main_button_frame = Frame(frame, bg="lightblue")
main_button_frame.pack(side=BOTTOM, fill=X)
content_frame = Frame(frame, bg="lightblue")
content_frame.pack(side=TOP, fill=BOTH, expand=True)
# Bind scrolling events
canvas.bind_all("<MouseWheel>", lambda event: scroll(event, canvas))
def scroll(event, canvas):
current_scroll = canvas.yview()
if (event.delta > 0 and current_scroll[0] > 0) or (event.delta < 0 and current_scroll[1] < 1):
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
def create_text_widget(parent, default_text="", width=50, height=1):
text_widget = Text(parent, font=("Verdana", 14), width=width, height=height, wrap="char")
text_widget.insert('1.0', default_text)
text_widget.bind("<<Modified>>", lambda event: (__update_height(text_widget), limit_text_length(text_widget)))
return text_widget
def __update_height(text_widget):
text_widget.update_idletasks()
height = text_widget.tk.call((text_widget._w, "count", "-update", "-displaylines", "1.0", "end"))
text_widget.configure(height=height)
text_widget.edit_modified(False)
def limit_text_length(entry, max_length=200):
text_content = entry.get("1.0", "end-1c")
if len(text_content) > max_length:
entry.delete("1.0", "end")
entry.insert("1.0", text_content[:max_length])
def add_question():
new_frame = Frame(content_frame, bg="lightblue")
new_frame.pack(pady=10)
Label(new_frame, text=f"{len(entries) + 1})", font=("Verdana", 14), bg="lightblue").grid(row=0, column=0, pady=(0, 5))
q_entry = create_text_widget(new_frame, "d")
q_entry.grid(row=0, column=1, pady=(0, 5))
a_entries = [create_text_widget(new_frame, "d") for j in range(4)]
for j, a_entry in enumerate(a_entries):
a_entry.grid(row=j + 1, column=1)
button_frame = Frame(new_frame, bg="lightblue")
button_frame.grid(row=5, column=1, sticky="nsew")
r_entry = Spinbox(button_frame, from_=1, to=4, font=("Verdana", 14), width=5)
r_entry.pack(side="left", fill="none", padx=(260, 0), pady=(15, 0))
Button(new_frame, text="Удалить", font=("Verdana", 14), command=lambda f=new_frame: confirm_delete(f, entries)).grid(row=6, column=1, pady=(15, 0))
entries.append((q_entry, a_entries, r_entry, new_frame))
update_scrollregion()
canvas.yview_moveto(1.0)
def confirm_delete(frame, entries):
if messagebox.askquestion("Подтверждение", "Вы уверены, что хотите удалить этот вопрос?") == 'yes':
entries[:] = [(q, a, r, f) for q, a, r, f in entries if f != frame]
frame.destroy()
clean_entries(entries)
save_changes(entries)
update_scrollregion()
if not entries:
main_button_frame.pack(side="top", fill=X)
def clean_entries(entries):
entries[:] = [(q, a, r, f) for q, a, r, f in entries if q.get("1.0", END).strip() and all(ae.get("1.0", END).strip() for ae in a) and r.get().strip()]
def save_changes(entries):
new_questions, new_answers, new_rights = [], [], []
def focus_window():
edit_window.lift()
edit_window.focus_force()
for q_entry, a_entries, r_entry, _ in entries:
question = q_entry.get("1.0", END).strip()
answer_list = [a_entry.get("1.0", END).strip() for a_entry in a_entries]
right_answer = r_entry.get().strip()
if not question or not all(answer_list) or not right_answer:
messagebox.showerror("Ошибка", "Пожалуйста, заполните все поля.")
focus_window()
return
new_questions.append(question)
new_answers.append(answer_list)
new_rights.append(int(right_answer))
config["questions"] = new_questions
config["answers"] = new_answers
config["rights"] = new_rights
QUESTIONS_CONFIG[set_number] = config
save_config()
messagebox.showinfo("Сохранено", "Изменения сохранены")
focus_window()
Button(main_button_frame, text="Добавить вопрос", font=("Verdana", 14), command=add_question).pack(side=LEFT, padx=10, pady=20)
Button(main_button_frame, text="Сохранить изменения", font=("Verdana", 14), command=lambda: save_changes(entries)).pack(side=LEFT, padx=10, pady=20)
def center_elements():
edit_window.update_idletasks()
x = (edit_window.winfo_screenwidth() // 2) - 250
main_button_frame.pack_configure(padx=x)
center_elements()
for i, question in enumerate(questions):
q_frame = Frame(content_frame, bg="lightblue")
q_frame.pack(pady=10)
Label(q_frame, text=f"{i + 1})", font=("Verdana", 14), bg="lightblue").grid(row=0, column=0, pady=(0, 5))
q_entry = create_text_widget(q_frame, question)
q_entry.grid(row=0, column=1, pady=(0, 5))
a_entries = [create_text_widget(q_frame, answer) for answer in answers[i]]
for j, a_entry in enumerate(a_entries):
a_entry.grid(row=j + 1, column=1)
button_frame = Frame(q_frame, bg="lightblue")
button_frame.grid(row=5, column=1, sticky="nsew")
r_entry = Spinbox(button_frame, from_=1, to=4, font=("Verdana", 14), width=5)
r_entry.pack(side="left", fill="none", padx=(260, 0), pady=(15, 0))
Button(q_frame, text="Удалить", font=("Verdana", 14), command=lambda f=q_frame: confirm_delete(f, entries)).grid(row=len(answers[i]) + 2, column=1, pady=(15, 0))
entries.append((q_entry, a_entries, r_entry, q_frame))
update_scrollregion()
Надо сделать ленивую загрузку, чтобы отображалось только 5 видимых вопросов. Вернее сделать пагинацию, где будут кнопки назад и вперёд. Но у меня с ней очень много проблем, то добавление вопроса не работает, то сохранение, то удаление, то неверно отображаются вопросы, то их вообще не видно. Прошу помочь.
Вот моя попытка сделать пагинацию, но она совсем неправильно работает, много багов; тут я ещё решил убрать кнопку сохранить и сделать просто сохранение после выхода из окна:
def edit_questions(set_number):
global QUESTIONS_CONFIG
config = QUESTIONS_CONFIG[set_number]
edit_window = Toplevel()
edit_window.state('zoomed')
edit_window.configure(bg="lightblue")
questions, answers, rights = config["questions"], config["answers"], config["rights"]
entries = []
current_page = 0
questions_per_page = 5
# Create Canvas and Scrollbar for scrolling
canvas = Canvas(edit_window, bg="lightblue", highlightthickness=0)
canvas.pack(side=LEFT, fill=BOTH, expand=True)
scrollbar = Scrollbar(edit_window, orient=VERTICAL, command=canvas.yview)
scrollbar.pack(side=RIGHT, fill=Y)
canvas.configure(yscrollcommand=scrollbar.set)
frame = Frame(canvas, bg="lightblue")
canvas.create_window((0, 0), window=frame, anchor="nw")
def update_scrollregion(event=None):
canvas.configure(scrollregion=canvas.bbox("all"))
frame.bind("<Configure>", update_scrollregion)
main_button_frame = Frame(frame, bg="lightblue")
main_button_frame.pack(side=BOTTOM, fill=X)
content_frame = Frame(frame, bg="lightblue")
content_frame.pack(side=TOP, fill=BOTH, expand=True)
# Bind scrolling events
canvas.bind_all("<MouseWheel>", lambda event: scroll(event, canvas))
def scroll(event, canvas):
current_scroll = canvas.yview()
if (event.delta > 0 and current_scroll[0] > 0) or (event.delta < 0 and current_scroll[1] < 1):
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
def create_text_widget(parent, default_text="", width=50, height=1):
text_widget = Text(parent, font=("Verdana", 14), width=width, height=height, wrap="char")
text_widget.insert('1.0', default_text)
text_widget.bind("<<Modified>>", lambda event: (__update_height(text_widget), limit_text_length(text_widget)))
return text_widget
def __update_height(text_widget):
text_widget.update_idletasks()
height = text_widget.tk.call((text_widget._w, "count", "-update", "-displaylines", "1.0", "end"))
text_widget.configure(height=height)
text_widget.edit_modified(False)
def limit_text_length(entry, max_length=200):
text_content = entry.get("1.0", "end-1c")
if len(text_content) > max_length:
entry.delete("1.0", "end")
entry.insert("1.0", text_content[:max_length])
def display_questions():
# Clear the content frame
for widget in content_frame.winfo_children():
widget.destroy()
# Calculate the start and end indices for the current page
start_index = current_page * questions_per_page
end_index = start_index + questions_per_page
visible_questions = questions[start_index:end_index]
visible_answers = answers[start_index:end_index]
for i, question in enumerate(visible_questions):
q_frame = Frame(content_frame, bg="lightblue")
q_frame.pack(pady=10)
Label(q_frame, text=f"{start_index + i + 1})", font=("Verdana", 14), bg="lightblue").grid(row=0, column=0, pady=(0, 5))
q_entry = create_text_widget(q_frame, question)
q_entry.grid(row=0, column=1, pady=(0, 5))
a_entries = [create_text_widget(q_frame, answer) for answer in visible_answers[i]]
for j, a_entry in enumerate(a_entries):
a_entry.grid(row=j + 1, column=1)
button_frame = Frame(q_frame, bg="lightblue")
button_frame.grid(row=5, column=1, sticky="nsew")
r_entry = Spinbox(button_frame, from_=1, to=4, font=("Verdana", 14), width=5)
r_entry.pack(side="left", fill="none", padx=(260, 0), pady=(15, 0))
Button(q_frame, text="Удалить", font=("Verdana", 14), command=lambda f=q_frame: confirm_delete(f, entries)).grid(row=len(visible_answers[i]) + 2, column=1, pady=(15, 0))
entries.append((q_entry, a_entries, r_entry, q_frame))
update_scrollregion()
def add_question():
new_frame = Frame(content_frame, bg="lightblue")
new_frame.pack(pady=10)
Label(new_frame, text=f"{len(entries) + 1})", font=("Verdana", 14), bg="lightblue").grid(row=0, column=0, pady=(0, 5))
q_entry = create_text_widget(new_frame, "d")
q_entry.grid(row=0, column=1, pady=(0, 5))
a_entries = [create_text_widget(new_frame, "d") for j in range(4)]
for j, a_entry in enumerate(a_entries):
a_entry.grid(row=j + 1, column=1)
button_frame = Frame(new_frame, bg="lightblue")
button_frame.grid(row=5, column=1, sticky="nsew")
r_entry = Spinbox(button_frame, from_=1, to=4, font=("Verdana", 14), width=5)
r_entry.pack(side="left", fill="none", padx=(260, 0), pady=(15, 0))
Button(new_frame, text="Удалить", font=("Verdana", 14), command=lambda f=new_frame: confirm_delete(f, entries)).grid(row=6, column=1, pady=(15, 0))
entries.append((q_entry, a_entries, r_entry, new_frame))
update_scrollregion()
canvas.yview_moveto(1.0)
def clean_entries(entries):
entries[:] = [(q, a, r, f) for q, a, r, f in entries if q.get("1.0", END).strip() and all(ae.get("1.0", END).strip() for ae in a) and r.get().strip()]
def confirm_delete(frame, entries):
if messagebox.askquestion("Подтверждение", "Вы уверены, что хотите удалить этот вопрос?") == 'yes':
entries[:] = [(q, a, r, f) for q, a, r, f in entries if f != frame]
frame.destroy()
clean_entries(entries)
update_scrollregion()
if not entries:
main_button_frame.pack(side="top", fill=X)
def save_changes():
new_questions, new_answers, new_rights = [], [], []
for q_entry, a_entries, r_entry, frame in entries:
if frame.winfo_exists(): # Проверяем, существует ли фрейм
question = q_entry.get("1.0", END).strip()
answer_list = [a_entry.get("1.0", END).strip() for a_entry in a_entries if a_entry.winfo_exists()]
right_answer = r_entry.get().strip()
if not question or not all(answer_list) or not right_answer:
messagebox.showerror("Ошибка", "Пожалуйста, заполните все поля.")
return # Завершаем выполнение, если есть ошибка
new_questions.append(question)
new_answers.append(answer_list)
new_rights.append(int(right_answer))
# Обновляем все вопросы в наборе
config["questions"] = new_questions
config["answers"] = new_answers
config["rights"] = new_rights
QUESTIONS_CONFIG[set_number] = config
save_config()
messagebox.showinfo("Сохранено", "Изменения сохранены")
def next_page():
nonlocal current_page
if (current_page + 1) * questions_per_page < len(questions):
current_page += 1
display_questions()
def previous_page():
nonlocal current_page
if current_page > 0:
current_page -= 1
display_questions()
Button(main_button_frame, text="Добавить вопрос", font=("Verdana", 14), command=add_question).pack(side=LEFT, padx=10, pady=20)
Button(main_button_frame, text="Назад", font=("Verdana", 14), command=previous_page).pack(side=LEFT, padx=10, pady=20)
Button(main_button_frame, text="Вперед", font=("Verdana", 14), command=next_page).pack(side=LEFT, padx=10, pady=20)
def on_close():
save_changes() # Automatically save changes upon closing the window
edit_window.destroy()
edit_window.protocol("WM_DELETE_WINDOW", on_close)
display_questions() # Initial display of questions
update_scrollregion()
Вот функция save_config(), которая сохраняет вопросы в файл, если надо:
def save_config():
config_path = os.path.join(script_dir, 'questions_config.json')
with open(config_path, 'w', encoding='utf-8') as f:
json.dump(QUESTIONS_CONFIG, f, ensure_ascii=False, indent=4)