Search code examples
pythontkintercomboboxtkinter-canvastkinter-scrolledtext

Python Tkinter: Unbinding Mouse Scroll Wheel on ComboBox


I have a combobox within a scrollable canvas frame- when I open the combobox and attempt to scroll through the options, the combobox and the entire window both scroll together. It would be nice to pause canvas scrolling while the combobox is open, but unbinding the mousewheel scroll from the combobox would also work.

Here is the scrollable canvas code:

root = Tk()
width=800
height=1020
root.geometry(str(width)+"x"+str(height)+"+10+10")

main_frame = Frame(root,width=width,height=height)
main_frame.place(x=0,y=0)
canvas = Canvas(main_frame, width=width, height=height)
canvas.place(x=0,y=0)
scrolly = ttk.Scrollbar(main_frame, orient=VERTICAL, command=canvas.yview)
scrolly.place(x=width-15,y=0,height=height)
canvas.configure(yscrollcommand=scrolly.set)
canvas.bind('<Configure>', lambda e: canvas.configure(scrollregion = canvas.bbox("all")))
def _on_mouse_wheel(event):
    canvas.yview_scroll(-1 * int((event.delta / 120)), "units")
canvas.bind_all("<MouseWheel>", _on_mouse_wheel)
w = Frame(canvas,width=width,height=height)
w.place(x=0,y=0)
canvas.create_window((0,0), window=w, anchor="nw")
w.configure(height=3000)

Here is the combobox initialization:

sel = Combobox(w, values=data)
sel.place(x=xval, y=yval)

I have tried unbinding the mousewheel for the combobox

sel.unbind_class("TCombobox", "<MouseWheel>") # windows

as well as rebinding it to an empty function

def dontscroll(event):
    return 'break'

sel.bind('<MouseWheel>', dontscroll)

but neither method worked.


I also attempted both methods in a separate test file (complete code):

from tkinter import *
from tkinter import ttk
from tkinter.ttk import Combobox

root = Tk()
root.geometry(str(300)+"x"+str(300)+"+10+10")

def dontscroll(event):
    return 'break'

sel = Combobox(root, values=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20])
sel.place(x=10, y=10)
sel.unbind_class("TCombobox", "<MouseWheel>") # on windows
sel.bind('<MouseWheel>', dontscroll)

This still didn't work. Any help is appreciated, thanks.


Solution

  • I've come up with a solution which will hopefully be helpful for anyone running into a similar issue:

    Basically, comboboxes are composed of two subcomponents: an entry and a listbox. When binding to a combobox, it appears to bind the command solely to the entry and not to the listbox, which caused the issue at hand. I don't know of way to access the subcomponents of a combobox instance and modify the listbox from there (drop an answer if you know how to do that), but I figured that I could bind the entire listbox class like so:

    w.bind_class('Listbox', '<MouseWheel>', dontscroll)
    

    And now, the canvas scrolls while the listboxes don't. But we can do better:

    Instead of binding the listbox class to an antiscrolling method, I decided to bind it to two other methods that work together to pause frame scrolling while the cursor is hovering over the listbox.

    def dontscroll(e):
        return "dontscroll"
    
    def on_enter(e):
        w.bind_all("<MouseWheel>", dontscroll)
    
    def on_leave(e):
        w.bind_all("<MouseWheel>", _on_mouse_wheel)
        w.event_generate('<Escape>')
    
    w.bind_class('Listbox', '<Enter>',
                           on_enter)
    w.bind_class('Listbox', '<Leave>',
                           on_leave)
    

    Now, the canvas only scrolls when the mouse is not hovering over a listbox widget.

    complete code here