I have implemented my own scrollable frame class in tkinter:
class scrolledFrame(tk.Frame): def __init__(self, parent): tk.Frame.__init__(self, parent) self.canvas = tk.Canvas(self) self.canvas.pack(fill = "both", expand = True, side = "left") self.scroll = tk.Scrollbar(self, command = self.canvas.yview) self.scroll.pack(side = "right", fill = "y") self.canvas.config(yscrollcommand = self.scroll.set) self.content = tk.Frame(self.canvas) self.content.bind("<Configure>", self.resizeCanvas) self.contentWindow = self.canvas.create_window((0,0), window = self.content, anchor = "nw") self.content.bind("<Enter>", self.enableScrollCanvas) self.content.bind("<Leave>", self.disableScrollCanvas) def scrollCanvas(self, event): self.canvas.yview_scroll(int(-1*(event.delta/120)), "units") def enableScrollCanvas(self, event): self.canvas.bind_all("<MouseWheel>", self.scrollCanvas) def disableScrollCanvas(self, event): self.canvas.unbind_all("<MouseWheel>") def resizeCanvas(self, event): self.update_idletasks() self.canvas.config(scrollregion = self.canvas.bbox("all")) self.canvas.itemconfig(self.contentWindow, width = self.canvas.winfo_width()) root = tk.Tk() exampleFrame = scrolledFrame(root) exampleFrame.pack() exampleLabel = tk.Label(exampleFrame.content, text = "I'm in the scrolled frame!") exampleLabel.pack() root.mainloop()
This works fine, but the problem is to add widgets to the scrolled frame, the parent has to be exampleFrame.content
. I have looked at several other examples which all have the same limitation. Is it possible to configure the class so exampleFrame
can be the parent of the widgets instead of exampleFrame.content
? Thanks in advance.
Advertisement
Answer
If you don’t mind a little trickery, you can simulate what you want. It’s a bit of a hack though.
The trick is that when you call tk.Frame.__init__
, you need to be giving it the canvas as the parent, making self
the content frame. Of course, to do that you have to create the canvas first, and to create the canvas you have to create the outer frame.
It looks something like this:
class scrolledFrame(tk.Frame): def __init__(self, parent): self.outer = tk.Frame(parent) self.canvas = tk.Canvas(self.outer) self.scroll = tk.Scrollbar(self.outer, command = self.canvas.yview) tk.Frame.__init__(self, self.canvas) self.contentWindow = self.canvas.create_window((0,0), window = self, anchor = "nw")
However, when you do the above and you try to call pack
, place
, or grid
on the instance of scrolledFrame
it’s going to do the wrong thing since the instance points to the inner frame rather than the outer frame.
Here’s the trickery: the solution to that is to redirect calls to pack
, place
, and grid
to the outer frame.
class scrolledFrame(tk.Frame): def __init__(self, parent): ... self.pack = self.outer.pack self.place = self.outer.place self.grid = self.outer.grid
With that, you can use scrolledFrame
like you want, as long as you use pack
, place
, or grid
when adding it to the layout.
exampleFrame = scrolledFrame(root) exampleFrame.pack(fill="both", expand=True) for i in range(100): exampleLabel = tk.Label(exampleFrame, text = f"I'm in the scrolled frame! ({i})") exampleLabel.pack()
Here’s a complete working example, though I’ve removed the mousewheel code for brevity.
import tkinter as tk class scrolledFrame(tk.Frame): def __init__(self, parent): self.outer = tk.Frame(parent) self.canvas = tk.Canvas(self.outer) self.scroll = tk.Scrollbar(self.outer, command = self.canvas.yview) tk.Frame.__init__(self, self.canvas) self.contentWindow = self.canvas.create_window((0,0), window = self, anchor = "nw") self.canvas.pack(fill = "both", expand = True, side = "left") self.scroll.pack(side = "right", fill = "y") self.canvas.config(yscrollcommand = self.scroll.set) self.bind("<Configure>", self.resizeCanvas) self.pack = self.outer.pack self.place = self.outer.place self.grid = self.outer.grid def resizeCanvas(self, event): self.canvas.config(scrollregion = self.canvas.bbox("all")) self.canvas.itemconfig(self.contentWindow, width = self.canvas.winfo_width()) root = tk.Tk() exampleFrame = scrolledFrame(root) exampleFrame.pack(fill="both", expand=True) for i in range(100): exampleLabel = tk.Label(exampleFrame, text = f"I'm in the scrolled frame! ({i})") exampleLabel.pack(fill="x") root.mainloop()