33

I'm trying to make a canvas scrollable. However, once I try to set up scrollbars to work with the canvas, tkinter seems to completely ignore the dimensions I initially set for my canvas. I've tried packing them all in a frame, setting the canvas to fill the frame and then setting the frame size, but that presents the same problem unless I set the frame to fill the window as well, which isn't what I want. Basically, I want a fixed-size canvas with scrollbars on it. My current code looks like this (in python 3.1):

from tkinter import *
root=Tk()
frame=Frame(root,width=300,height=300)
frame.grid(row=0,column=0)
canvas=Canvas(frame,bg='#FFFFFF',width=300,height=300,scrollregion=(0,0,500,500))
hbar=Scrollbar(canvas,orient=HORIZONTAL)
hbar.pack(side=BOTTOM,fill=X)
hbar.config(command=canvas.xview)
vbar=Scrollbar(canvas,orient=VERTICAL)
vbar.pack(side=RIGHT,fill=Y)
vbar.config(command=canvas.yview)
canvas.config(width=300,height=300)
canvas.config(xscrollcommand=hbar.set, yscrollcommand=vbar.set)
canvas.pack(side=LEFT,expand=True,fill=BOTH)

root.mainloop()
martineau
  • 119,623
  • 25
  • 170
  • 301

2 Answers2

40

Your scrollbars need to have the Frame as a parent, not the Canvas:

from tkinter import *
root=Tk()
frame=Frame(root,width=300,height=300)
frame.pack(expand=True, fill=BOTH) #.grid(row=0,column=0)
canvas=Canvas(frame,bg='#FFFFFF',width=300,height=300,scrollregion=(0,0,500,500))
hbar=Scrollbar(frame,orient=HORIZONTAL)
hbar.pack(side=BOTTOM,fill=X)
hbar.config(command=canvas.xview)
vbar=Scrollbar(frame,orient=VERTICAL)
vbar.pack(side=RIGHT,fill=Y)
vbar.config(command=canvas.yview)
canvas.config(width=300,height=300)
canvas.config(xscrollcommand=hbar.set, yscrollcommand=vbar.set)
canvas.pack(side=LEFT,expand=True,fill=BOTH)

root.mainloop()

The reason why this works is due to how pack works. By default it will attempt to shrink (or grow) a container to exactly fit its children. Because the scrollbars are children of the canvas in the original example, the canvas shrinks to fit.

If you want the scrollbars to appear inside the canvas, the trick is to use an extra frame. Place the canvas and scrollbars in this inner frame, turn the borders off of the canvas and on for the frame. Set the background of the frame to be the same as the canvas and it will appear that the scrollbars are inside the canvas.

GaryMBloom
  • 5,350
  • 1
  • 24
  • 32
Shish
  • 2,773
  • 19
  • 16
  • 4
    Why must we pack the canvas last? I've noticed that when I pack the canvas before any of the scrollbars it ends up overlapping the horizontal scrollbar - I just started using tkinter, but I thought that pack wouldn't overlap stuff. Thanks – Lanfear Dec 26 '15 at 11:54
  • 7
    And the core is `scrollregion=(0,0,500,500)`, without it the whole can't work!! – ch271828n Aug 10 '16 at 04:34
  • 1
    @Bryan Oakley, why do you set the width and height of the canvas with `.config` when you already set them when you created it with `canvas=Canvas(...)`? – Korzak Mar 25 '19 at 19:46
  • 3
    @Korzak: I don't know. I didn't write that code. There's no reason to do it. – Bryan Oakley Mar 25 '19 at 19:48
  • 2
    Okay thanks. @BryanOakley do you know why when I used this code, it only will scroll down and not up with the mousewheel? I can post in a new question if that's best. Thanks. – Korzak Mar 25 '19 at 20:38
  • 4
    @Korzak: that should be a new question. Before you ask, search this site for similar questions. There are several questions related to the mousewheel on this site. – Bryan Oakley Mar 25 '19 at 21:35
  • is there any option to show scrollbar inside canvas region using grid() ? – MathanKumar Jul 29 '20 at 12:05
  • For some reason when I use this script with bindings to the canvas like that of B1-Motion and all then it seems to have its event.x and event.y based on window coordinates and not canvas coordinates @BryanOakley do you know why that might be the case. – typedecker May 02 '21 at 15:26
  • 1
    Ok nevermind so for anyone that might have the problem of window coordinates the window coords from event.x and event.y can be converted into canvas coords using the canvas.canvasx and canvas.canvasy functions of the canvas. – typedecker May 02 '21 at 15:33
  • Also I think instead of scrollregion = (0, 0, 500, 500), scrollregion = canvas.bbox('all') will be more general of a solution. – typedecker May 02 '21 at 15:34
-1

The following code works, when I use the .pack option. But with the .place option, it is not working

from tkinter import ttk
import tkinter as tk



root = tk.Tk()
container = ttk.Frame(root)
container.pack()
canvas = tk.Canvas(container)
scrollbar = ttk.Scrollbar(container, orient="vertical", command=canvas.yview)
scrollable_frame = ttk.Frame(canvas)

scrollable_frame.bind(
    "<Configure>",
    lambda e: canvas.configure(
        scrollregion=canvas.bbox("all")
    )
)

canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")

canvas.configure(yscrollcommand=scrollbar.set)


x=ttk.Label(scrollable_frame, text="Sample scrolling label")
x.place(x=50,y=50)


canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")

root.mainloop()

Why is it not working when I use .place?

  • 1
    If you have a new question, please ask it by clicking the [Ask Question](https://stackoverflow.com/questions/ask) button. Include a link to this question if it helps provide context. - [From Review](/review/late-answers/30906435) – Flair Jan 29 '22 at 06:24
  • Did U solve it? I have this issue also – Mote Zart Jun 15 '22 at 17:32