I can’t sort out how to make a custom widget receive mouse scroll events. If I bind to the root window, then notifications occur. And if I bind to a child of root window other than my widget (here a simple Listbox), the notifications also occur (evidenced by watching the list move when I move the wheel). What am I overlooking?
Example code where roll() never gets called:
#!/usr/bin/python3
from tkinter import *
from tkinter.ttk import *
class CustomWidget(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.width = 200
self.height = 200
self.canvas = Canvas(self, width=200, height=200)
self.canvas.config(background='red')
self.canvas.pack()
self.bind('<MouseWheel>', self.roll)
self.bind('<Button-4>', self.roll)
self.bind('<Button-5>', self.roll)
def roll(self, event):
print("detected mouse roll!");
if __name__ == "__main__":
root = Tk()
root.wm_title("TestRoot")
sb = Scrollbar(root, orient=VERTICAL)
lb = Listbox(root, yscrollcommand=sb.set)
sb.config(command=lb.yview)
cw = CustomWidget(root)
for char in list("abcdefghijklmnopqrstuvwxyz"):
lb.insert(END, char)
cw.pack()
lb.pack()
sb.pack()
root.update()
root.mainloop()
So in order for a frame to receive events, it needs to have focus. You can call
frame.set_focus()on it, but as soon as you give another widget focus it won’t work. To get around that we could bind<Button-1>to the frame and have that set the focus to the frame, but your canvas takes up the entire size of the frame, so You will need to bind the<Button-1>events to that instead.Adding:
after your other bindings in
CustomWidget.__init__will make your bindings work as long as the widget has focus, which it will when the user clicks it (similar how to the listbox works). If the canvas is never as large as it’s frame, you may need to add another<Button-1>binding to the frame.