import tkinter as tk import platform # ************************ # Scrollable Frame Class # ************************ class ScrollFrame(tk.Frame): def __init__(self, parent): super().__init__(parent) # create a frame (self) self.canvas = tk.Canvas(self, borderwidth=0, background="#ffffff") #place canvas on self self.viewPort = tk.Frame(self.canvas, background="#ffffff") #place a frame on the canvas, this frame will hold the child widgets self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview) #place a scrollbar on self self.canvas.configure(yscrollcommand=self.vsb.set) #attach scrollbar action to scroll of canvas self.vsb.pack(side="right", fill="y") #pack scrollbar to right of self self.canvas.pack(side="left", fill="both", expand=True) #pack canvas to left of self and expand to fil self.canvas_window = self.canvas.create_window((4,4), window=self.viewPort, anchor="nw", #add view port frame to canvas tags="self.viewPort") self.viewPort.bind("", self.onFrameConfigure) #bind an event whenever the size of the viewPort frame changes. self.canvas.bind("", self.onCanvasConfigure) #bind an event whenever the size of the canvas frame changes. self.viewPort.bind('', self.onEnter) # bind wheel events when the cursor enters the control self.viewPort.bind('', self.onLeave) # unbind wheel events when the cursorl leaves the control self.onFrameConfigure(None) #perform an initial stretch on render, otherwise the scroll region has a tiny border until the first resize def onFrameConfigure(self, event): '''Reset the scroll region to encompass the inner frame''' print("onFrameConfig") print(event) print(self.viewPort, id(self.viewPort)) print("-----") print("scrollregiion : ", self.canvas.bbox("all")) self.canvas.configure(scrollregion=self.canvas.bbox("all")) #whenever the size of the frame changes, alter the scroll region respectively. def onCanvasConfigure(self, event): '''Reset the canvas window to encompass inner frame when required''' print("Canvas config") print(event) canvas_width = event.width self.canvas.itemconfig(self.canvas_window, width = canvas_width) #whenever the size of the canvas changes alter the window region respectively. def onMouseWheel(self, event): # cross platform scroll wheel event #print("onMouseWheel") #print(event) if platform.system() == 'Windows': self.canvas.yview_scroll(int(-1* (event.delta/120)), "units") elif platform.system() == 'Darwin': self.canvas.yview_scroll(int(-1 * event.delta), "units") else: if event.num == 4: self.canvas.yview_scroll( -1, "units" ) elif event.num == 5: self.canvas.yview_scroll( 1, "units" ) def onEnter(self, event): # bind wheel events when the cursor enters the control #print("onEnter") #print(event) if platform.system() == 'Linux': self.canvas.bind_all("", self.onMouseWheel) self.canvas.bind_all("", self.onMouseWheel) else: self.canvas.bind_all("", self.onMouseWheel) def onLeave(self, event): # unbind wheel events when the cursorl leaves the control #print("onLeave") #print(event) if platform.system() == 'Linux': self.canvas.unbind_all("") self.canvas.unbind_all("") else: self.canvas.unbind_all("") # ******************************** # Example usage of the above class # ******************************** class Example(tk.Frame): def __init__(self, root): tk.Frame.__init__(self, root) self.scrollFrame = ScrollFrame(self) # add a new scrollable frame. # Now add some controls to the scrollframe. # NOTE: the child controls are added to the view port (scrollFrame.viewPort, NOT scrollframe itself) for row in range(100): a = row tk.Label(self.scrollFrame.viewPort, text="%s" % row, width=3, borderwidth="1", relief="solid").grid(row=row, column=0) t="this is the second column for row %s" %row tk.Button(self.scrollFrame.viewPort, text=t, command=lambda x=a: self.printMsg("Hello " + str(x))).grid(row=row, column=1) # when packing the scrollframe, we pack scrollFrame itself (NOT the viewPort) self.scrollFrame.pack(side="top", fill="both", expand=True) def printMsg(self, msg): print(msg) if __name__ == "__main__": root=tk.Tk() Example(root).pack(side="top", fill="both", expand=True) root.mainloop()