0

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()
mcu
  • 3,302
  • 8
  • 38
  • 64
  • 2
    Voted to close: You haven't provided any code so difficult to tell you how to change your code to make it work. Basically, you'd bind each object inside the Text widget to respond to the mouse wheel action but pass the event to the text widget instead. Perhaps you can provide a simple example with one object inside a text widget. – scotty3785 Sep 17 '20 at 16:04
  • @scotty3785: there is a better way than to bind to every widget in the text widget. – Bryan Oakley Sep 17 '20 at 18:03
  • @BryanOakley Enlighten us with your wisdom :P – scotty3785 Sep 18 '20 at 08:01
  • @BryanOakley I certainly hope there is a better way than binding everything inside `Text`. – mcu Oct 04 '20 at 14:02

1 Answers1

3

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>.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Thanks. So there is no way to prevent the non-scrollable widgets inside `Text` from getting the `` event in the first place? – mcu Oct 04 '20 at 15:56
  • @mcu: AFAIK there is no way to prevent that, much like there's no way to prevent buttons from receiving mouse clicks, or prevent entry widgets from receiving keyboard events. – Bryan Oakley Oct 04 '20 at 16:03