Search code examples
pythontkinterscrollbarframemousewheel

How to add a scrollbar for Frame in tkinter python and scroll it with mousewheel?


I need to create a scrollbar for Frame with mousewheel scorlling in tkinter library.

I tried to set yview for Frame scrollbar = Scrollbar(master, command=frame.yview) but frame doesn't have yview method.

I tried to set frame.yscrollcommand for scrollbar too, but it doesn't work

Please, help me.


Solution

  • Tkinter doesn't have a scrollable frame.

    What you can do instead:
    Create a Canvas and make it scrollable. Like acw1668 said.
    I created a class for that that runs only on Linux

    import tkinter as tk
    
    class Scroll_Frame(tk.Frame):
        """ frame_options is the scrollable frame"""
        def __init__(self, master=None, bg="grey22", height=300, width=300, *args, **kwargs):
            self.master = master
            self.height_ = height
            self.width_ = width
            self.background = bg
            tk.Frame.__init__(self, self.master)
            self.grid()
            self.rowconfigure((0), weight=1)
            self.columnconfigure((0), weight=1)
            
            self.flag_focus_canvas = False
            
            self.canvas_scroll = tk.Canvas(self, bg=self.background, height=self.height_, width=self.width_, *args, **kwargs)
            self.canvas_scroll.grid(row=0, column=0, sticky="wesn")#, padx=20, pady=20)
            
            self.scrollbar_v = tk.Scrollbar(self, orient="vertical", 
                                             command=self.canvas_scroll.yview)
            self.scrollbar_v.grid(row=0, column=2, sticky="ns")
            self.canvas_scroll.configure(yscrollcommand=self.scrollbar_v.set) 
                   
            self.scrollbar_h = tk.Scrollbar(self, orient="horizontal", 
                                             command=self.canvas_scroll.xview)
            self.scrollbar_h.grid(row=1, column=0, sticky="we")
            self.canvas_scroll.configure(xscrollcommand=self.scrollbar_h.set) 
            
            self.frame_options = tk.Frame(self.canvas_scroll, bg=self.background, *args, **kwargs)
    
            self.frame_options.bind("<Configure>", 
                                    lambda e: self.canvas_scroll.configure(scrollregion=self.canvas_scroll.bbox("all")))
    
            self.canvas_scroll.create_window(0,0, window=self.frame_options, anchor="nw", tags="scrollable")
            
            self.canvas_scroll.bind_all("<Button-4>", lambda e: self._on_mousewheel(e))
            self.canvas_scroll.bind_all("<Button-5>", lambda e: self._on_mousewheel(e))
            self.canvas_scroll.bind("<Enter>", lambda e: self.set_focus_scroll(e))
            self.canvas_scroll.bind("<Leave>", lambda e: self.unset_focus_scroll(e))
        
        def set_focus_scroll(self, event):
            self.flag_focus_canvas = True
    
        def unset_focus_scroll(self, event):
            self.flag_focus_canvas = False
            
        def _on_mousewheel(self, event):
            if event.num == 4:
                if self.flag_focus_canvas:
                    self.canvas_scroll.yview_scroll(int(-1), "units")
            if event.num == 5:
                if self.flag_focus_canvas:
                    self.canvas_scroll.yview_scroll(int(1), "units")
                    
        def get_scroll_frame(self):
            """returns the real scroll frame,
            this is the place you put all your widgets in"""
            return self.frame_options
            
            
    if __name__ == "__main__":
        root = tk.Tk()
        app = Scroll_Frame(root)  
        scroll_frame = app.get_scroll_frame()  
        for i in range(20):
            tk.Button(scroll_frame, text=f"Button {i}").grid(sticky="we")
        root.mainloop()      
    

    Now you can get the frame scroll_frame = app.get_scroll_frame() and put in whatever you want, for demonstration purposes I put buttons.

    Windows creates different events, so you need to change bindings:

    #Windows bind mousewheel
                    
    def _on_mousewheel(self, event):
        if self.flag_focus_canvas:
            self.canvas_scroll.yview_scroll(int(-1*(event.delta/120)), "units")
            
    self.canvas_scroll.bind("<MouseWheel>", self._on_mousewheel)  
    

    Enjoy