Home > other >  python tkinter scrollable frame scroll with mousewheel
python tkinter scrollable frame scroll with mousewheel

Time:02-03

I have created a scrollable frame with tkinter and would like to use the mousewheel for the scrolling, I am also switching frames as pages. Everything works as expected for page 2 however the page does not scroll with the mousewheel on page 1, the scrollbar itself works and the mousewheel event is being triggered its just not scrolling.

Here is an example of what I have so far:-

import tkinter as tk


class ScrollableFrame(tk.Frame):
    def __init__(self, container, *args, **kwargs):
        super().__init__(container, *args, **kwargs)
        self.canvas = tk.Canvas(self)
        scrollbar = tk.Scrollbar(self, command=self.canvas.yview)
        self.scrollable_frame = tk.Frame(self.canvas)

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

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

        self.canvas.configure(yscrollcommand=scrollbar.set)

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

    def _on_mousewheel(self, event):
        caller = event.widget
        if "scrollableframe" in str(caller):
            if event.delta == -120:
                self.canvas.yview_scroll(2, "units")
            if event.delta == 120:
                self.canvas.yview_scroll(-2, "units")


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.title("tkinter scrollable frame example.py")
        self.geometry("700x450")

        # set grid layout 1x2
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(1, weight=1)

        # create navigation frame
        self.navigation_frame = tk.Frame(self)
        self.navigation_frame.grid(row=0, column=0, padx=20, pady=20, sticky="nsew")

        # create navigation buttons
        self.page1_button = tk.Button(
            self.navigation_frame,
            text="Page 1",
            command=self.page1_button_event,
        )
        self.page1_button.grid(row=0, column=0, sticky="ew")

        self.page2_button = tk.Button(
            self.navigation_frame,
            text="Page 2",
            command=self.page2_button_event,
        )
        self.page2_button.grid(row=1, column=0, sticky="ew")

        # create page1 frame
        self.page1_frame = ScrollableFrame(self)
        self.page1_frame.grid_columnconfigure(0, weight=1)

        # create page1 content
        self.page1_frame_label = tk.Label(
            self.page1_frame.scrollable_frame, text="Page 1"
        )
        self.page1_frame_label.grid(row=0, column=0, padx=20, pady=10)

        for i in range(50):
            tk.Label(
                self.page1_frame.scrollable_frame, text=str("Filler number "   str(i))
            ).grid(row=i   1, column=0)

        # create page2 frame
        self.page2_frame = ScrollableFrame(self)
        self.page2_frame.grid_columnconfigure(1, weight=1)

        # create page2 content
        self.page2_frame_label = tk.Label(
            self.page2_frame.scrollable_frame, text="Page 2"
        )
        self.page2_frame_label.grid(row=0, column=0, padx=20, pady=10)

        for i in range(50):
            tk.Label(
                self.page2_frame.scrollable_frame, text=str("Filler number "   str(i))
            ).grid(row=i   1, column=0)

        # show default frame
        self.show_frame("page1")
        
    # show selected frame
    def show_frame(self, name):
        self.page1_button.configure(
            background=("red") if name == "page1" else "#F0F0F0"
        )
        self.page2_button.configure(
            background=("red") if name == "page2" else "#F0F0F0"
        )

        if name == "page1":
            self.page1_frame.grid(row=0, column=1, padx=0, pady=0, sticky="nsew")
        else:
            self.page1_frame.grid_forget()
        if name == "page2":
            self.page2_frame.grid(row=0, column=1, sticky="nsew")
        else:
            self.page2_frame.grid_forget()

    def page1_button_event(self):
        self.show_frame("page1")

    def page2_button_event(self):
        self.show_frame("page2")


if __name__ == "__main__":
    app = App()
    app.mainloop()

Any ideas where I have gone wrong?

CodePudding user response:

The issue is caused by using .bind_all() which will make the final callback to be self.page2_frame._on_mousewheel(). Therefore self.canvas inside the function will refer to the canvas of self.page2_frame, that is why canvas in self.page1_frame cannot be scrolled by mouse wheel.

One of the solution is to find the correct canvas using the event.widget (i.e. the caller in your code):

    def find_canvas(self, widget):
        while not isinstance(widget, tk.Canvas):
            widget = widget.master
        return widget

    def _on_mousewheel(self, event):
        caller = event.widget
        if "scrollableframe" in str(caller) and "canvas" in str(caller):
            self.find_canvas(caller).yview_scroll(event.delta//-60, "units")
  • Related