не могу сделать корректную пагинацию в 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)

Ответы (0 шт):