简体   繁体   中英

Make the scrollbar automatically scroll back up in tkinter

I have an interface made with tkinter and it has many scrollable frames, where the user can press a button and each button will show the corresponding frame (and removes the previously shown frame).

When changing from a frame to another and back to the first frame the scrollbar stays in it's previous place. Is there a way to automatically make the scrollbar scroll back up when changing from a frame to another?

here's my code:

import tkinter as tk
from tkinter import *
from tkinter import ttk
import platform

# ++++++++++++++++++++++++++++++++++
# Custom Class for Scrollable Frames
# ++++++++++++++++++++++++++++++++++
class ScrollableFrame(tk.Frame):

    def onFrameConfigure(self, event):
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

    def onCanvasConfigure(self, event):
        canvas_width = event.width
        self.canvas.itemconfig(self.canvas_window, width=canvas_width)

    def onMouseWheel(self, event):
        if platform.system() == 'Windows':
            self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
        else:
            if event.num == 4:
                self.canvas.yview_scroll(-1, "units")
            elif event.num == 5:
                self.canvas.yview_scroll(1, "units")

    def onEnter(self, event):
        if platform.system() == 'Linux':
            self.canvas.bind_all("<Button-4>", self.onMouseWheel)
            self.canvas.bind_all("<Button-5>", self.onMouseWheel)
        else:
            self.canvas.bind_all("<MouseWheel>", self.onMouseWheel)

    def onLeave(self, event):
        if platform.system() == 'Linux':
            self.canvas.unbind_all("<Button-4>")
            self.canvas.unbind_all("<Button-5>")
        else:
            self.canvas.unbind_all("<MouseWheel>")

    def __init__(self, parent):
        super().__init__(parent)  # create a frame (self)

        self.canvas = tk.Canvas(self, borderwidth=0, height=canvas_height, width=canvas_width, background="white")
        self.viewPort = tk.Frame(self.canvas, background="white")
        self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview, background="white")
        self.canvas.configure(yscrollcommand=self.vsb.set)

        self.vsb.pack(side="right", fill="y")
        self.canvas.pack(side="left", fill="both", expand=True)
        self.canvas_window = self.canvas.create_window((4, 4), window=self.viewPort, anchor="nw", tags="self.viewPort")

        self.canvas.bind("<Configure>", self.onCanvasConfigure)
        self.viewPort.bind("<Configure>", self.onFrameConfigure)

        self.viewPort.bind('<Enter>', self.onEnter)
        self.viewPort.bind('<Leave>', self.onLeave)

        self.onFrameConfigure(None)

# ----- resets all frames -----
def reset():
    update_frame.pack_forget()
    home_frame.pack_forget()


# ----- Custom Side Menu Buttons -----
def menubttn(x, y, text, cmd):
    def on_entera(e):
        myButton1['background'] = bcolor

    def on_leavea(e):
        myButton1['background'] = fcolor

    myButton1 = Button(sidemenu, text=text,
                       width=15,
                       height=2,
                       border=0,
                       bg=fcolor,
                       activebackground=bcolor,
                       command=cmd)

    myButton1.bind("<Enter>", on_entera)
    myButton1.bind("<Leave>", on_leavea)

    myButton1.place(x=x, y=y)


# ----- helper function to insert entry boxes on frames -----
def entrybox(frame, index):
    entry_box = Entry(frame,width=50)
    entry_box.grid(row=index, column=1, pady=5, padx=(0, 100))
    return entry_box


# ++++++++++++++++++++++++++++++++++
# functions that handle frames
# ++++++++++++++++++++++++++++++++++

# ----- Handles homepage -----
def homepage():
    reset()
    home_frame.pack()


# ----- Handles update frame -----
def update():
    reset()
    update_frame.pack()

    def delete():
        homepage()
        update_lf.pack_forget()

    def edit(self):
        if(dropdown_menu.get() != "choose"):
            update_lf.pack()
            entry_boxes = []
            for i in range(15):
                Label(update_lf, text="name", background="white").grid(row=i, column=0, sticky=W, padx=(100, 20))
                entry_boxes.append(entrybox(update_lf, i))

            delete_button = Button(update_lf, text="delete", command=delete)
            delete_button.grid(row=i+1, column=1, columnspan=2)

        else:
            update_lf.pack_forget()

    dropdown_menu.pack()
    dropdown_menu.bind("<<ComboboxSelected>>", edit)


# +++++++++++++++
# Initializations
# +++++++++++++++

bcolor = '#77B9CF'
fcolor = '#D5E2EB'
frame_width = 900
frame_height = 500
sidemenu_width = 111
canvas_width = frame_width - sidemenu_width - 24
canvas_height = frame_height - 40
canvas_height_scroll = canvas_height - 85

root = Tk()
root.geometry(f'{frame_width}x{frame_height}')
root.configure(bg='white')
root.resizable(False, False)
root.title("test")

# +++++++++++++
# Create Frames
# +++++++++++++

# ----- main projects frame -----
projects_frame = Frame(root, height=frame_height, width=frame_width - sidemenu_width, bg='white')
projects_frame.place(x=sidemenu_width, y=0)

# ----- sidemenu frame -----
sidemenu = Frame(root, width=sidemenu_width, height=frame_height, bg='#D5E2EB')
sidemenu.place(x=0, y=0)

# ----- homepage frame -----
home_frame = Frame(projects_frame, height=frame_height, width=frame_width - sidemenu_width, bg='white')
home_frame.pack()
Label(home_frame, text="WELCOME!", background="white", font=("", 40), foreground=bcolor).place(relx=0.5, rely=0.3,
                                                                                                  anchor=CENTER)
Label(home_frame, text="this is the homepage", background="white", font=("", 20), foreground=bcolor).place(relx=0.5, rely=0.4,
                                                                                                           anchor=CENTER)

# ----- update frame -----
menu =["elem1","elem2","elem3"]
update_frame = ScrollableFrame(projects_frame)
update_lf = LabelFrame(update_frame.viewPort, text="frame", padx=10, pady=10, background="white")
dropdown_menu = ttk.Combobox(update_frame.viewPort, state="readonly",
                             values=["choose"] + menu, width=55)
dropdown_menu.current(0)


# ++++++++++++++++++++++++
# Create Side Menu Buttons
# ++++++++++++++++++++++++
menubttn(0, 100, 'HOMEPAGE', homepage)
menubttn(0, 200, 'UPDATE', update)

root.mainloop()

The problem happens when I press the delete button in the update frame and go back to the update frame.. and I assume that it's because the frame is scrolled down but I might be wrong..

It's hard to help without knowing what you've already tried - please post a Minimal Reproducible Example

That said, I believe you can accomplish this by binding an event to your scrollable widgets.

# I don't know what your widgets are called, so I called it 'scrollable_frame' here
scrollable_frame.bind( 
    '<FocusOut>',  # whenever the widget loses focus...
    lambda _event: scrollable_frame.yview_moveto(0)  # scroll to top
)

You'll need to bind a similar event to each of your scrollable frames separately.

In order to ensure the frame you just scrolled has focus (so you can properly track when it loses focus, you may want to add another event binding to each frame like so

scrollable_frame.bind(
    '<MouseWheel>',  # when scrolling with the mouse wheel...
    lambda _event: self.scrollable_frame.focus_force()  # force focus
)

The story is slightly different if you're scrolling with a scrollbar, but the idea is the same in principle.

I can edit my answer to be more specific to your situation once you post your code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM