tkinter: binding mousewheel to scrollbar

Question:

I have this scroll-able frame (frame inside canvas actually).

import Tkinter as tk
class Scrollbarframe():
    def __init__(self, parent,xsize,ysize,xcod,ycod):
        def ScrollAll(event):
                canvas1.configure(scrollregion=canvas1.bbox("all"),width=xsize,height=ysize,bg='white')
        self.parent=parent
        self.frame1=tk.Frame(parent,bg='white')
        self.frame1.place(x=xcod,y=ycod)
        canvas1=tk.Canvas(self.frame1)
        self.frame2=tk.Frame(canvas1,bg='white',relief='groove',bd=1,width=1230,height=430)
        scrollbar1=tk.Scrollbar(self.frame1,orient="vertical",command=canvas1.yview)
        canvas1.configure(yscrollcommand=scrollbar1.set)
        scrollbar1.pack(side="right",fill="y")
        canvas1.pack(side="left")
        canvas1.create_window((0,0),window=self.frame2,anchor='nw')
        self.frame2.bind("<Configure>",ScrollAll)

I would like to bind mouse wheel to the scrollbar so that user can scroll down the frame without having to use arrow buttons on the scrollbar. After looking around, i added a binding to my canvas1 like this

self.frame1.bind("<MouseWheel>", self.OnMouseWheel)

This is the function:

def OnMouseWheel(self,event):
    self.scrollbar1.yview("scroll",event.delta,"units")
    return "break" 

But the scroll bar won’t move when i use mousewheel. Can anyone help me with this? All i want is when the user use mousewheel (inside the frame area/on the scrollbar), the canvas should automatically scroll up or down.

Asked By: Chris Aung

||

Answers:

This link gives you an example as to how to use the scrollwheel.

http://www.daniweb.com/software-development/python/code/217059/using-the-mouse-wheel-with-tkinter-python

I hope this helps!

# explore the mouse wheel with the Tkinter GUI toolkit
# Windows and Linux generate different events
# tested with Python25
import Tkinter as tk
def mouse_wheel(event):
    global count
    # respond to Linux or Windows wheel event
    if event.num == 5 or event.delta == -120:
        count -= 1
    if event.num == 4 or event.delta == 120:
        count += 1
    label['text'] = count
count = 0
root = tk.Tk()
root.title('turn mouse wheel')
root['bg'] = 'darkgreen'
# with Windows OS
root.bind("<MouseWheel>", mouse_wheel)
# with Linux OS
root.bind("<Button-4>", mouse_wheel)
root.bind("<Button-5>", mouse_wheel)
label = tk.Label(root, font=('courier', 18, 'bold'), width=10)
label.pack(padx=40, pady=40)
root.mainloop()
Answered By: Joe Michail

Perhaps the simplest solution is to make a global binding for the mousewheel. It will then fire no matter what widget is under the mouse or which widget has the keyboard focus. You can then unconditionally scroll the canvas, or you can be smart and figure out which of your windows should scroll.

For example, on windows you would do something like this:

self.canvas = Canvas(...)
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
...
def _on_mousewheel(self, event):
    self.canvas.yview_scroll(-1*(event.delta/120), "units")

Note that self.canvas.bind_all is a bit misleading — you more correctly should call root.bind_all but I don’t know what or how you define your root window. Regardless, the two calls are synonymous.

Platform differences:

  • On Windows, you bind to <MouseWheel> and you need to divide event.delta by 120 (or some other factor depending on how fast you want the scroll)
  • on OSX, you bind to <MouseWheel> and you need to use event.delta without modification
  • on X11 systems you need to bind to <Button-4> and <Button-5>, and you need to divide event.delta by 120 (or some other factor depending on how fast you want to scroll)

There are more refined solutions involving virtual events and determining which window has the focus or is under the mouse, or passing the canvas window reference through the binding, but hopefully this will get you started.

Answered By: Bryan Oakley

Based on @BryanOakley’s answer, here is a way to scroll only the focused widget (i.e. the one you have mouse cursor currently over).

Bind to <Enter> and <Leave> events happening on your scrollable frame which sits inside a canvas, the following way (scrollframe is the frame that is inside the canvas):

    ...

    self.scrollframe.bind('<Enter>', self._bound_to_mousewheel)
    self.scrollframe.bind('<Leave>', self._unbound_to_mousewheel)

    return None

def _bound_to_mousewheel(self, event):
    self.canv.bind_all("<MouseWheel>", self._on_mousewheel)

def _unbound_to_mousewheel(self, event):
    self.canv.unbind_all("<MouseWheel>")

def _on_mousewheel(self, event):
    self.canv.yview_scroll(int(-1*(event.delta/120)), "units")
Answered By: Mikhail T.

To get rid of the weird factor 120 we could just look at the sign of the event.delta value. This makes it easy to use the same handler under Windows, Linux and Mac OS.

# Mouse wheel handler for Mac, Windows and Linux
# Windows, Mac: Binding to <MouseWheel> is being used
# Linux: Binding to <Button-4> and <Button-5> is being used

def MouseWheelHandler(event):
    global count

    def delta(event):
        if event.num == 5 or event.delta < 0:
            return -1 
        return 1 

    count += delta(event)
    print(count)

import tkinter
root = tkinter.Tk()
count = 0
root.bind("<MouseWheel>",MouseWheelHandler)
root.bind("<Button-4>",MouseWheelHandler)
root.bind("<Button-5>",MouseWheelHandler)
root.mainloop()
Answered By: Marvo

In case you are interested

How to scroll 2 listbox at the same time

#listbox scrollbar

from tkinter import *
root = Tk()

def scrolllistbox2(event):
    listbox2.yview_scroll(int(-1*(event.delta/120)), "units")


scrollbar = Scrollbar(root)
#scrollbar.pack(side=RIGHT, fill=Y)
listbox = Listbox(root)
listbox.pack()
for i in range(100):
    listbox.insert(END, i)
# attach listbox to scrollbar
listbox.config(yscrollcommand=scrollbar.set)
listbox.bind("<MouseWheel>", scrolllistbox2)

listbox2 = Listbox(root)
listbox2.pack()
for i in range(100):
    listbox2.insert(END, i+100)
listbox2.config(yscrollcommand=scrollbar.set)

#scrollbar.config(command=listbox.yview)

root.mainloop()

Or…

from tkinter import *
root = Tk()
root.geometry("400x400")
def scrolllistbox(event):
    ''' scrolling both listbox '''
    listbox2.yview_scroll(int(-1*(event.delta/120)), "units")
    listbox1.yview_scroll(int(-1*(event.delta/120)), "units")


def random_insert():
    ''' adding some numbers to the listboxes '''
    for i in range(100):
        listbox1.insert(END, i)
        listbox2.insert(END, i + 100)

# SCROLLBAR
scrollbar = Scrollbar(root)
#scrollbar.pack(side=RIGHT, fill=Y)

# LISTBOX 1
listbox1 = Listbox(root)
listbox1.pack()
# attach listbox to scrollbar with yscrollcommand
# listbox1.config(yscrollcommand=scrollbar.set)

# The second one
listbox2 = Listbox(root)
listbox2.pack()
listbox2.config(yscrollcommand=scrollbar.set)
# scroll the first one when you're on the second one
# listbox2.bind("<MouseWheel>", scrolllistbox)
root.bind("<MouseWheel>", scrolllistbox)

# scroll also the second list when you're on the first
listbox1.bind("<MouseWheel>", scrolllistbox)

random_insert()
#scrollbar.config(command=listbox.yview)

root.mainloop()
Answered By: PythonProgrammi

As an addendum to the above, the "delta" scaling factor is easy to calculate, since platform information is available through the sys and platform modules (and possibly others).

def my_mousewheel_handler(event):
    if sys.platform == 'darwin': # for OS X # also, if platform.system() == 'Darwin':
        delta = event.delta
    else:                            # for Windows, Linux
        delta = event.delta // 120   # event.delta is some multiple of 120
    if event.widget in (widget1, widget2, ):
        'do some really cool stuff...'
Answered By: Gary02127

Mikhail T.’s answer worked really well for me. Here is perhaps a more generic set up that others might find useful (I really need to start giving things back)

def _setup_mousewheel(self,frame,canvas):
    frame.bind('<Enter>', lambda *args, passed=canvas: self._bound_to_mousewheel(*args,passed))
    frame.bind('<Leave>', lambda *args, passed=canvas: self._unbound_to_mousewheel(*args,passed))

def _bound_to_mousewheel(self, event, canvas):
    canvas.bind_all("<MouseWheel>", lambda *args, passed=canvas: self._on_mousewheel(*args,passed))

def _unbound_to_mousewheel(self, event, canvas):
    canvas.unbind_all("<MouseWheel>")

def _on_mousewheel(self, event, canvas):
    canvas.yview_scroll(int(-1*(event.delta/120)), "units")

Then setting a canvas/frame up for mousewheel scrolling is just:

self._setup_mousewheel(frame, canvas)
Answered By: Julian Evans