Skip to content
Advertisement

Scrollable frame class in tkinter

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()
User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement