tkinter Canvas Scrollbar with Grid? tkinter Canvas Scrollbar with Grid? tkinter tkinter

tkinter Canvas Scrollbar with Grid?


The height of your scrollbar didn't match the buttons frame height because you did't tell it to stick North and South .grid(..., sticky='ns')

Then, the scrolling behavior you want to achieve is described here: Adding a Scrollbar to a group of widgets

See also @martineau's answer for a more general object-oriented solution with 2D scrolling (horizontal & vertical)

scrolling example

import tkinter as tkroot = tk.Tk()root.grid_rowconfigure(0, weight=1)root.columnconfigure(0, weight=1)frame_main = tk.Frame(root, bg="gray")frame_main.grid(sticky='news')label1 = tk.Label(frame_main, text="Label 1", fg="green")label1.grid(row=0, column=0, pady=(5, 0), sticky='nw')label2 = tk.Label(frame_main, text="Label 2", fg="blue")label2.grid(row=1, column=0, pady=(5, 0), sticky='nw')label3 = tk.Label(frame_main, text="Label 3", fg="red")label3.grid(row=3, column=0, pady=5, sticky='nw')# Create a frame for the canvas with non-zero row&column weightsframe_canvas = tk.Frame(frame_main)frame_canvas.grid(row=2, column=0, pady=(5, 0), sticky='nw')frame_canvas.grid_rowconfigure(0, weight=1)frame_canvas.grid_columnconfigure(0, weight=1)# Set grid_propagate to False to allow 5-by-5 buttons resizing laterframe_canvas.grid_propagate(False)# Add a canvas in that framecanvas = tk.Canvas(frame_canvas, bg="yellow")canvas.grid(row=0, column=0, sticky="news")# Link a scrollbar to the canvasvsb = tk.Scrollbar(frame_canvas, orient="vertical", command=canvas.yview)vsb.grid(row=0, column=1, sticky='ns')canvas.configure(yscrollcommand=vsb.set)# Create a frame to contain the buttonsframe_buttons = tk.Frame(canvas, bg="blue")canvas.create_window((0, 0), window=frame_buttons, anchor='nw')# Add 9-by-5 buttons to the framerows = 9columns = 5buttons = [[tk.Button() for j in range(columns)] for i in range(rows)]for i in range(0, rows):    for j in range(0, columns):        buttons[i][j] = tk.Button(frame_buttons, text=("%d,%d" % (i+1, j+1)))        buttons[i][j].grid(row=i, column=j, sticky='news')# Update buttons frames idle tasks to let tkinter calculate buttons sizesframe_buttons.update_idletasks()# Resize the canvas frame to show exactly 5-by-5 buttons and the scrollbarfirst5columns_width = sum([buttons[0][j].winfo_width() for j in range(0, 5)])first5rows_height = sum([buttons[i][0].winfo_height() for i in range(0, 5)])frame_canvas.config(width=first5columns_width + vsb.winfo_width(),                    height=first5rows_height)# Set the canvas scrolling regioncanvas.config(scrollregion=canvas.bbox("all"))# Launch the GUIroot.mainloop()


Although this is a somewhat dated question, here's a different answer which doesn't use tkinter event handling thereby avoiding the unnecessary overhead it requires.

Although the code is derived from the OP's, I've made a number of code formatting changes so it conforms better to the PEP 8 - Style Guide for Python Code which resulted in many variable names being changed. I've also modified the architecture so the application is a subclass of the root tkinter.Tk window widget class. I did these things with the hope that the results will be more understandable and provide a better template for writing similar tkinter-based applications.

Like @Josselin's answer, it nests the Canvas and each of the Scrollbar widgets inside another Frame which allows the them to easily be positioned alongside one another both vertically and horizontally using tkinter's grid() layout manager.

The code has been further extended so the grid also has a horizontal scrollbar allowing scrolling of its contents in that direction as well as vertically.

import tkinter as tkLABEL_BG = "#ccc"  # Light gray.ROWS, COLS = 10, 6  # Size of grid.ROWS_DISP = 3  # Number of rows to display.COLS_DISP = 4  # Number of columns to display.class MyApp(tk.Tk):    def __init__(self, title="Sample App", *args, **kwargs):        tk.Tk.__init__(self, *args, **kwargs)        self.title(title)        self.configure(background="Gray")        self.columnconfigure(0, weight=1)        self.rowconfigure(0, weight=1)        master_frame = tk.Frame(self, bg="Light Blue", bd=3, relief=tk.RIDGE)        master_frame.grid(sticky=tk.NSEW)        master_frame.columnconfigure(0, weight=1)        label1 = tk.Label(master_frame, text="Frame1 Contents", bg=LABEL_BG)        label1.grid(row=0, column=0, pady=5, sticky=tk.NW)        frame1 = tk.Frame(master_frame, bg="Green", bd=2, relief=tk.GROOVE)        frame1.grid(row=1, column=0, sticky=tk.NW)        cb_var1 = tk.IntVar()        checkbutton1 = tk.Checkbutton(frame1, text="StartCheckBox", variable=cb_var1)        checkbutton1.grid(row=0, column=0, padx=2)        label2 = tk.Label(master_frame, text="Frame2 Contents", bg=LABEL_BG)        label2.grid(row=2, column=0, pady=5, sticky=tk.NW)        # Create a frame for the canvas and scrollbar(s).        frame2 = tk.Frame(master_frame)        frame2.grid(row=3, column=0, sticky=tk.NW)        # Add a canvas in that frame.        canvas = tk.Canvas(frame2, bg="Yellow")        canvas.grid(row=0, column=0)        # Create a vertical scrollbar linked to the canvas.        vsbar = tk.Scrollbar(frame2, orient=tk.VERTICAL, command=canvas.yview)        vsbar.grid(row=0, column=1, sticky=tk.NS)        canvas.configure(yscrollcommand=vsbar.set)        # Create a horizontal scrollbar linked to the canvas.        hsbar = tk.Scrollbar(frame2, orient=tk.HORIZONTAL, command=canvas.xview)        hsbar.grid(row=1, column=0, sticky=tk.EW)        canvas.configure(xscrollcommand=hsbar.set)        # Create a frame on the canvas to contain the buttons.        buttons_frame = tk.Frame(canvas, bg="Red", bd=2)        # Add the buttons to the frame.        for i in range(1, ROWS+1):            for j in range(1, COLS+1):                button = tk.Button(buttons_frame, padx=7, pady=7, relief=tk.RIDGE,                                   text="[%d, %d]" % (i, j))                button.grid(row=i, column=j, sticky='news')        # Create canvas window to hold the buttons_frame.        canvas.create_window((0,0), window=buttons_frame, anchor=tk.NW)        buttons_frame.update_idletasks()  # Needed to make bbox info available.        bbox = canvas.bbox(tk.ALL)  # Get bounding box of canvas with Buttons.        #print('canvas.bbox(tk.ALL): {}'.format(bbox))        # Define the scrollable region as entire canvas with only the desired        # number of rows and columns displayed.        w, h = bbox[2]-bbox[1], bbox[3]-bbox[1]        dw, dh = int((w/COLS) * COLS_DISP), int((h/ROWS) * ROWS_DISP)        canvas.configure(scrollregion=bbox, width=dw, height=dh)        label3 = tk.Label(master_frame, text="Frame3 Contents", bg=LABEL_BG)        label3.grid(row=4, column=0, pady=5, sticky=tk.NW)        frame3 = tk.Frame(master_frame, bg="Blue", bd=2, relief=tk.GROOVE)        frame3.grid(row=5, column=0, sticky=tk.NW)        cb_var2 = tk.IntVar()        checkbutton2 = tk.Checkbutton(frame3, text="EndCheckBox", variable=cb_var2)        checkbutton2.grid(row=0, column=0, padx=2)if __name__ == "__main__":    app = MyApp("Scrollable Canvas")    app.mainloop()

Here's what it looks like running:

screenshot of what it looks like running


One way to scroll a group of widgets is to put them (with grid of pack) inside a frame and put this frame inside a canvas.

The two key elements (besides connecting the scrollbar to the canvas) for the scrolling to work are:

  • Use canvas.create_window(x, y, window=frame) to put the frame inside the canvas so that it is treated like a canvas item.
  • Update the canvas scrollregion each time the size of the frame changes (for instance after adding a new widget) with canvas.configure(scrollregion=canvas.bbox('all')).

Here is an adaptation of the code of the question Python Tkinter scrollbar for frame, but using the widgets name from the OP's question and grid instead of pack:

import tkinter as tkdef update_scrollregion(event):    photoCanvas.configure(scrollregion=photoCanvas.bbox("all"))root = tk.Tk()   photoFrame = tk.Frame(root, width=250, height=190, bg="#EBEBEB")photoFrame.grid()photoFrame.rowconfigure(0, weight=1) photoFrame.columnconfigure(0, weight=1) photoCanvas = tk.Canvas(photoFrame, bg="#EBEBEB")photoCanvas.grid(row=0, column=0, sticky="nsew")canvasFrame = tk.Frame(photoCanvas, bg="#EBEBEB")photoCanvas.create_window(0, 0, window=canvasFrame, anchor='nw')for i in range(10):   element = tk.Button(canvasFrame, text='Button %i' % i, borderwidth=0, bg="#EBEBEB")   element.grid(padx=5, pady=5, sticky="nsew")photoScroll = tk.Scrollbar(photoFrame, orient=tk.VERTICAL)photoScroll.config(command=photoCanvas.yview)photoCanvas.config(yscrollcommand=photoScroll.set)photoScroll.grid(row=0, column=1, sticky="ns")canvasFrame.bind("<Configure>", update_scrollregion)root.mainloop()