mcu
mcu

Reputation: 3512

Widgets inside tkinter.Text break mouse wheel

tkinter.Text widget allows other widgets, such as buttons, to be inserted along with plain text.

tkinter.Text widget responds to mouse wheel by scrolling the contents. However, if the cursor happens to be over a child widget, that widget gets the mouse wheel event and the Text does not scroll. Instead, I want the Text to get this mouse wheel event.

What is a good way to fix this?


This is default behavior for widgets inside tkinter.Text, but here is some code to demonstrate the problem.

import tkinter as tk
root = tk.Tk()
s = '\nTesting mouse wheel scroll with widgets inside tkinter.Text.\n'
txt = tk.Text(root, width=40, height=6)
for i in range(5):
    b = tk.Button(txt, text='I Break Scroll')
    txt.window_create(tk.END, window=b, padx=5, pady=5)
    txt.insert(tk.END, s)
txt.pack()
root.mainloop()

Upvotes: 0

Views: 560

Answers (1)

Bryan Oakley
Bryan Oakley

Reputation: 386265

MouseWheel events are sent to the widget under the cursor. This makes it possible to control multiple scrollable widgets with the mouse. In older versions of tkinter it scrolled the window with the focus.

For widgets that aren't scrollable, there is no default behavior. When you move the mouse wheel while over a button or label the scrolling stops, since the event goes to the button or label rather than the text.

It appears you don't want this behavior, so you need to provide your own bindings for the mouse wheel for non-scrollable widgets. If you apply these bindings to the widget class rather than individual widgets then you won't have to bind to every individual widget. Though, you could bind to individual widgets if you wish.

Here's an example that adds bindings for the Button and Label widget classes to pass the event along to its parent.

import tkinter as tk

root = tk.Tk()
text = tk.Text(root, wrap="word")
vsb = tk.Scrollbar(root, command=text.yview)
text.configure(yscrollcommand=vsb.set)

vsb.pack(side="right", fill="y")
text.pack(side="left", fill="both", expand=True)

for i in range(200):
    text.insert("end", f"Item #{i}")
    if i%5 == 0:
        b = tk.Button(text, text=f"A button")
        text.window_create("end", window=b)
    elif i%3 == 0:
        l = tk.Button(text, text=f"A label")
        text.window_create("end", window=l)
    text.insert("end", "\n")

def scroll_parent(event):
    parent = root.nametowidget(event.widget.winfo_parent())
    parent.event_generate("<MouseWheel>", delta=event.delta, when="now")

root.bind_class("Button", "<MouseWheel>", scroll_parent)
root.bind_class("Label", "<MouseWheel>", scroll_parent)

root.mainloop()

Note: if you are on an X11-based system you'll need to adjust this code to bind to <Button-4> and <Button-5> rather than <MouseWheel>.

Upvotes: 3

Related Questions