Search code examples
pythonpython-3.xtkintertkinter-canvasttk

Frame is scrollable before scrollbar is activated


I was just trying the scrollbar widget and I encountered something weird. I saw this video on how to make an entire program scrollable and I just tweaked it to dynamically update the scrollregion of the canvas so that the scrollbar would adapt to the changes. One weird thing was that the scrollbar can scroll up before it is activated and push the content down. If you hit the up arrow it shoves the content aside and I don't want that. Also if you pack a couple of frames, enough for the scrollbar to activate, it won't do that. How can I fix this? My code:

from tkinter import *
from tkinter import ttk

available_row = 1
root = Tk()
root.title("Scrollbar!")
root.geometry("500x320")
my_canvas = Canvas(root)


def add_fr_prg():
    global available_row
    lbl_fr = LabelFrame(second_frame, text=f"I am in row: {available_row}", padx=5, pady=5)
    prg = ttk.Progressbar(lbl_fr, length=300, orient=HORIZONTAL, mode="indeterminate")
    prg.pack()
    prg.start(9)
    lbl_fr.grid(row=available_row, column=0, columnspan=2, padx=5, pady=3)
    available_row += 1


def scroll_up_down(event):
    my_canvas.yview_scroll(-1 * (int(event.delta / 120)), "units")


my_scrollbar = ttk.Scrollbar(root, orient=VERTICAL, command=my_canvas.yview)
my_canvas.configure(yscrollcommand=my_scrollbar.set)
my_canvas.bind_all("<MouseWheel>", scroll_up_down)
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
second_frame = Frame(my_canvas)
my_canvas.create_window(0, 0, window=second_frame, anchor='nw')
second_frame.bind('<Configure>', lambda e: my_canvas.configure(scrollregion=my_canvas.bbox('all')))
my_scrollbar.pack(side=RIGHT, fill=Y)
add_btn = ttk.Button(second_frame, text="Add!", width=30, takefocus=False, command=add_fr_prg)
add_btn.grid(row=0, column=0, padx=66)
root.mainloop()

Solution

  • Scrollbar.get() method returns a tuple. When there is no scroll bar it returns (0.0 , 1.0). when the scroll bar is active it returns a tuple where the first number is >=0 and the second number is <=1.

    so use this.

    def scroll_up_down(event):
        
        x, y = my_scrollbar.get()
        if x>0 or y < 1 : # or just if my_scrollbar.get() != (0.0, 1.0) :
            my_canvas.yview_scroll(-1 * (int(event.delta / 120)), "units")