pythonlistboxscrollbartkintermousewheel

Scrolling multiple Tkinter listboxes together


I have multiple Tkinter listboxes that I have scrolling together using a single scrollbar, but I'd ALSO like them to scroll together for mousewheel activity over any of the listboxes.

How to do this?

My current code is based on the last pattern discussed here: http://effbot.org/tkinterbook/listbox.htm It works fine when using only the scrollbar, but the listboxes scroll independently when the mousewheel is used.


Solution

  • Solve the problem pretty much the same way as you did to connect the two widgets to a single scrollbar: create custom bindings for the mousewheel and have those bindings affect both listboxes rather than just one.

    The only real trick is knowing that you get different events for the mousewheel depending on the platform: windows and the Mac gets <MouseWheel> events, linux gets <Button-4> and <Button-5> events.

    Here's an example, tested on my Mac with python 2.5:

    import Tkinter as tk
    
    class App:
        def __init__(self):
            self.root=tk.Tk()
            self.vsb = tk.Scrollbar(orient="vertical", command=self.OnVsb)
            self.lb1 = tk.Listbox(self.root, yscrollcommand=self.vsb.set)
            self.lb2 = tk.Listbox(self.root, yscrollcommand=self.vsb.set)
            self.vsb.pack(side="right",fill="y")
            self.lb1.pack(side="left",fill="x", expand=True)
            self.lb2.pack(side="left",fill="x", expand=True)
            self.lb1.bind("<MouseWheel>", self.OnMouseWheel)
            self.lb2.bind("<MouseWheel>", self.OnMouseWheel)
            for i in range(100):
                self.lb1.insert("end","item %s" % i)
                self.lb2.insert("end","item %s" % i)
            self.root.mainloop()
    
        def OnVsb(self, *args):
            self.lb1.yview(*args)
            self.lb2.yview(*args)
    
        def OnMouseWheel(self, event):
            self.lb1.yview("scroll", event.delta,"units")
            self.lb2.yview("scroll",event.delta,"units")
            # this prevents default bindings from firing, which
            # would end up scrolling the widget twice
            return "break"
    
    app=App()