tkinter canvas with mousewheel

Question:

I’m trying to make a canvas scrollable with a mousewheel. I tried this to see if I could at least print something with the action of the mousewheel:

from tkinter import *
from tkinter import ttk
import platform

class MainWindow:
    def __init__(self):
        self.metrics = []
        self.content = ttk.Frame(root)
        self.content.grid_rowconfigure(0, weight = 1)
        self.content.grid_columnconfigure(0, weight = 1)
        self.scrollable_canvas = Canvas(self.content)
        self.vscrollbar = ttk.Scrollbar(self.content, orient = VERTICAL, command = self.scrollable_canvas.yview)
        self.hscrollbar = ttk.Scrollbar(self.content, orient = HORIZONTAL, command = self.scrollable_canvas.xview)
        self.scrollable_canvas.configure(yscrollcommand=self.vscrollbar.set, xscrollcommand = self.hscrollbar.set)
        self.scrollable_canvas.bind('<Configure>',
            lambda e: self.scrollable_canvas.configure(scrollregion = self.scrollable_canvas.bbox("all")))
        self.inner_frame = Frame(self.scrollable_canvas)
        self.scrollable_canvas.create_window((0, 0), window = self.inner_frame, anchor = "nw", width = 1000)

        self.scrollable_canvas.xview_moveto(0)
        self.scrollable_canvas.yview_moveto(0)

        self.class_label = Label(self.inner_frame, text = "labels")

        self.A_label = Label(self.inner_frame, text = "A")
        self.A_entry = Entry(self.inner_frame, width = 5)
        self.A_input = self.A_entry.get()
        self.A_hint = Label(self.inner_frame, text = "A")

        self.B_label = Label(self.inner_frame, text = "B")
        self.B_entry = Entry(self.inner_frame, width = 5)
        self.B_input = self.B_entry.get()
        self.B_hint = Label(self.inner_frame, text = "B")

        self.C = Label(self.inner_frame, text = "C")

        self.space = Label(self.inner_frame, text = "")

        self.D_label = Label(self.inner_frame, text = "D")
        self.D_entry = Entry(self.inner_frame, width = 5)
        self.D_hint = Label(self.inner_frame, text = "D")

        self.E_label = Label(self.inner_frame, text = "E")
        self.E_entry = Entry(self.inner_frame, width = 5)
        self.E_hint = Label(self.inner_frame, text = "D")

        self.F_label = Label(self.inner_frame, text = "F")
        self.F_entry = Entry(self.inner_frame, width = 5)
        self.F_hint = Label(self.inner_frame, text = "F")

        self.G_label = Label(self.inner_frame, text = "G")
        self.G_entry = Entry(self.inner_frame, width = 5)
        self.G_hint = Label(self.inner_frame, text = "G")

        self.H_label = Label(self.inner_frame, text = "H")
        self.H_entry = Entry(self.inner_frame, width = 5)
        self.H_hint = Label(self.inner_frame, text = "H")

        self.I_label = Label(self.inner_frame, text = "I")
        self.I_entry = Entry(self.inner_frame, width = 5)
        self.I_hint = Label(self.inner_frame, text = "I")

        self.J_label = Label(self.inner_frame, text = "J")
        self.J_entry = Entry(self.inner_frame, width = 5)
        self.J_hint = Label(self.inner_frame, text = "J")

        self.K_label = Label(self.inner_frame, text = "K")
        self.K_entry = Entry(self.inner_frame, width = 5)
        self.K_hint = Label(self.inner_frame, text = "K")

        self.space2 = Label(self.inner_frame, text = "")

        self.L_label = Label(self.inner_frame, text = "L")
        self.L_entry = Entry(self.inner_frame, width = 5)

        self.content.pack(fill = BOTH, expand = 1)

        self.scrollable_canvas.grid(row = 0, column = 0, sticky = "nsew")
        self.vscrollbar.grid(row = 0, column = 1, sticky = "ns")
        self.hscrollbar.grid(row = 1, column = 0, sticky = "ew")

        self.class_label.grid(row = 0, column = 0)

        self.A_label.grid(row = 1, column = 0)
        self.A_entry.grid(row = 1, column = 1)
        self.A_hint.grid(row = 1, column = 3, sticky = "w")

        self.B_label.grid(row = 2, column = 0)
        self.B_entry.grid(row = 2, column = 1)
        self.B_hint.grid(row = 2, column = 3, sticky = "w")

        self.C.grid(row = 3, column = 3, sticky = "w")

        self.space.grid(row = 4, column = 0)

        self.D_label.grid(row = 6, column = 0)
        self.D_entry.grid(row = 6, column = 1)
        self.D_hint.grid(row = 6, column = 3, sticky = "w")

        self.E_label.grid(row = 7, column = 0)
        self.E_entry.grid(row = 7, column = 1)
        self.E_hint.grid(row = 7, column = 3, sticky = "w")

        self.F_label.grid(row = 8, column = 0)
        self.F_entry.grid(row = 8, column = 1)
        self.F_hint.grid(row = 8, column = 3, sticky = "w")

        self.G_label.grid(row = 9, column = 0)
        self.G_entry.grid(row = 9, column = 1)
        self.G_hint.grid(row = 9, column = 3, sticky = "w")

        self.H_label.grid(row = 10, column = 0)
        self.H_entry.grid(row = 10, column = 1)
        self.H_hint.grid(row = 10, column = 3, sticky = "w", columnspan = 2)

        self.I_label.grid(row = 11, column = 0)
        self.I_entry.grid(row = 11, column = 1)
        self.I_hint.grid(row = 11, column = 3, sticky = "w", columnspan = 2)

        self.J_label.grid(row = 12, column = 0)
        self.J_entry.grid(row = 12, column = 1)
        self.J_hint.grid(row = 12, column = 3, sticky = "w", columnspan = 2)

        self.K_label.grid(row = 13, column = 0)
        self.K_entry.grid(row = 13, column = 1)
        self.K_hint.grid(row = 13, column = 3, sticky = "w", columnspan = 2)

        self.space2.grid(row = 14, column = 0)

        self.L_label.grid(row = 19, column = 0)
        self.L_entry.grid(row = 19, column = 1)

        root.mainloop()

def mouse_wheel(event):
    print("test mouse wheel event")
    global count
    if event.num == 5 or event.delta < 0:
        count -= 1
    if event.num == 4 or event.delta > 0:
        count += 1
    print(count)

if __name__ == '__main__':
    root = Tk()
    count = 0
    if platform.system() == "Windows":
        print("line 276")
        root.bind("<MouseWheel>", mouse_wheel)
        print("line 278")
    else:
        print("line 280")
        root.bind("<Button-4>", mouse_wheel)
        print("line 282")
        root.bind("<Button-5>", mouse_wheel)
        print("line 284")
    root.title("title")
    new_window = MainWindow()

However, the function mouse_weel is never called. How come I get "line 280", "line 282" and "line 284" but not "test mouse wheel event".

I can get the mouse wheel to work with a non-OO program.

I found this solution but couldn’t adapt it to my problem.

Asked By: Nocxy

||

Answers:

Here is the solution I found:

from tkinter import *
from tkinter import ttk
import platform

class MainWindow:
    def __init__(self):
        self.metrics = []
        self.content = ttk.Frame(root)
        self.content.grid_rowconfigure(0, weight = 1)
        self.content.grid_columnconfigure(0, weight = 1)
        self.scrollable_canvas = Canvas(self.content)
        self.vscrollbar = ttk.Scrollbar(self.content, orient = VERTICAL, command = self.scrollable_canvas.yview)
        self.hscrollbar = ttk.Scrollbar(self.content, orient = HORIZONTAL, command = self.scrollable_canvas.xview)

        self.scrollable_canvas.configure(yscrollcommand=self.vscrollbar.set, xscrollcommand = self.hscrollbar.set)
        self.scrollable_canvas.bind('<Configure>',
            lambda e: self.scrollable_canvas.configure(scrollregion = self.scrollable_canvas.bbox("all")))
        self.inner_frame = Frame(self.scrollable_canvas)
        self.scrollable_canvas.create_window((0, 0), window = self.inner_frame, anchor = "nw", width = 1000)

        self.scrollable_canvas.xview_moveto(0)
        self.scrollable_canvas.yview_moveto(0)

        if platform.system() == "Linux":
            self.inner_frame.bind("<Button-4>", self.mouse_wheel)
            self.inner_frame.bind("<Button-5>", self.mouse_wheel)
        else:
            self.inner_frame.bind("<MouseWheel>", self.mouse_wheel)

        self.class_label = Label(self.inner_frame, text = "labels")

        self.A_label = Label(self.inner_frame, text = "A")
        self.A_entry = Entry(self.inner_frame, width = 5)
        self.A_input = self.A_entry.get()
        self.A_hint = Label(self.inner_frame, text = "A")

        self.B_label = Label(self.inner_frame, text = "B")
        self.B_entry = Entry(self.inner_frame, width = 5)
        self.B_input = self.B_entry.get()
        self.B_hint = Label(self.inner_frame, text = "B")

        self.C = Label(self.inner_frame, text = "C")

        self.space = Label(self.inner_frame, text = "")

        self.D_label = Label(self.inner_frame, text = "D")
        self.D_entry = Entry(self.inner_frame, width = 5)
        self.D_hint = Label(self.inner_frame, text = "D")

        self.E_label = Label(self.inner_frame, text = "E")
        self.E_entry = Entry(self.inner_frame, width = 5)
        self.E_hint = Label(self.inner_frame, text = "D")

        self.F_label = Label(self.inner_frame, text = "F")
        self.F_entry = Entry(self.inner_frame, width = 5)
        self.F_hint = Label(self.inner_frame, text = "F")

        self.G_label = Label(self.inner_frame, text = "G")
        self.G_entry = Entry(self.inner_frame, width = 5)
        self.G_hint = Label(self.inner_frame, text = "G")

        self.H_label = Label(self.inner_frame, text = "H")
        self.H_entry = Entry(self.inner_frame, width = 5)
        self.H_hint = Label(self.inner_frame, text = "H")

        self.I_label = Label(self.inner_frame, text = "I")
        self.I_entry = Entry(self.inner_frame, width = 5)
        self.I_hint = Label(self.inner_frame, text = "I")

        self.J_label = Label(self.inner_frame, text = "J")
        self.J_entry = Entry(self.inner_frame, width = 5)
        self.J_hint = Label(self.inner_frame, text = "J")

        self.K_label = Label(self.inner_frame, text = "K")
        self.K_entry = Entry(self.inner_frame, width = 5)
        self.K_hint = Label(self.inner_frame, text = "K")

        self.space2 = Label(self.inner_frame, text = "")

        self.L_label = Label(self.inner_frame, text = "L")
        self.L_entry = Entry(self.inner_frame, width = 5)

        self.content.pack(fill = BOTH, expand = 1)

        self.scrollable_canvas.grid(row = 0, column = 0, sticky = "nsew")
        self.vscrollbar.grid(row = 0, column = 1, sticky = "ns")
        self.hscrollbar.grid(row = 1, column = 0, sticky = "ew")

        self.class_label.grid(row = 0, column = 0)

        self.A_label.grid(row = 1, column = 0)
        self.A_entry.grid(row = 1, column = 1)
        self.A_hint.grid(row = 1, column = 3, sticky = "w")

        self.B_label.grid(row = 2, column = 0)
        self.B_entry.grid(row = 2, column = 1)
        self.B_hint.grid(row = 2, column = 3, sticky = "w")

        self.C.grid(row = 3, column = 3, sticky = "w")

        self.space.grid(row = 4, column = 0)

        self.D_label.grid(row = 6, column = 0)
        self.D_entry.grid(row = 6, column = 1)
        self.D_hint.grid(row = 6, column = 3, sticky = "w")

        self.E_label.grid(row = 7, column = 0)
        self.E_entry.grid(row = 7, column = 1)
        self.E_hint.grid(row = 7, column = 3, sticky = "w")

        self.F_label.grid(row = 8, column = 0)
        self.F_entry.grid(row = 8, column = 1)
        self.F_hint.grid(row = 8, column = 3, sticky = "w")

        self.G_label.grid(row = 9, column = 0)
        self.G_entry.grid(row = 9, column = 1)
        self.G_hint.grid(row = 9, column = 3, sticky = "w")

        self.H_label.grid(row = 10, column = 0)
        self.H_entry.grid(row = 10, column = 1)
        self.H_hint.grid(row = 10, column = 3, sticky = "w", columnspan = 2)

        self.I_label.grid(row = 11, column = 0)
        self.I_entry.grid(row = 11, column = 1)
        self.I_hint.grid(row = 11, column = 3, sticky = "w", columnspan = 2)

        self.J_label.grid(row = 12, column = 0)
        self.J_entry.grid(row = 12, column = 1)
        self.J_hint.grid(row = 12, column = 3, sticky = "w", columnspan = 2)

        self.K_label.grid(row = 13, column = 0)
        self.K_entry.grid(row = 13, column = 1)
        self.K_hint.grid(row = 13, column = 3, sticky = "w", columnspan = 2)

        self.space2.grid(row = 14, column = 0)

        self.L_label.grid(row = 19, column = 0)
        self.L_entry.grid(row = 19, column = 1)

        root.mainloop()

    def mouse_wheel(self, event):
        print(str(event.delta) + " is event.delta")
        print(str(event.state) + " is event.state")
        if event.state == 1:
            self.scrollable_canvas.xview_scroll(int(-1*(event.delta)/scrollable_malus), "units")
        elif event.state == 0:
            self.scrollable_canvas.yview_scroll(int(-1*(event.delta)/scrollable_malus), "units")

if __name__ == '__main__':
    root = Tk()
    count = 0
    global scrollable_malus
    if platform.system() == "Windows":
        scrollable_malus = 120
    else:
        scrollable_malus = 1
    root.title("title")
    new_window = MainWindow()

In mouse_wheel, xview_scroll and yview_scroll were already used for the scrollbars. These commands can scroll by page or unit. In this case, I chose unit.

event.state is 1 when scrolling horizontally and 0 when scrolling vertically. When scrolling up, event.delta is positive but it is also positive when scrolling left so event.state is required to know the direction.

On Windows, event.delta is a multiple of 120 so I had to create a malus that would be worth 120 on Windows.

Depending on whether or not you want to invert the scrolling direction, you may want to delete the -1 multiplier.

I both cases of mouse_wheel, I should have written:

if event.num == 5 or event.delta < 0:
        self.scrollable_canvas.xview_scroll(int(-1*(event.delta)/scrollable_malus), "units")
if event.num == 4 or event.delta > 0:
        self.scrollable_canvas.xview_scroll(int(-1*(event.delta)/scrollable_malus), "units")

Since it is the same formula on both cases, I refactored it.

I left event.delta and event.state to show the scrolling direction so that other users can use it and understand why I did what I did.

Answered By: Nocxy