tkinter.Text 内的小部件破坏了鼠标滚轮

Widgets inside tkinter.Text break mouse wheel

tkinter.Text 小部件允许其他小部件(例如按钮)与纯文本一起插入。

tkinter.Text 小部件通过滚动内容来响应鼠标滚轮。但是,如果光标恰好位于子部件上方,则该部件将获得鼠标滚轮事件并且 Text 不会滚动。相反,我希望 Text 获得此鼠标滚轮事件。

解决这个问题的好方法是什么?


这是 tkinter.Text 内部小部件的默认行为,但这里有一些代码可以演示该问题。

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()

MouseWheel 事件发送到光标下的小部件。这使得用鼠标控制多个可滚动的小部件成为可能。在旧版本的 tkinter 中,它使用焦点滚动 window。

对于不可滚动的小部件,没有默认行为。当您在按钮或标签上移动鼠标滚轮时,滚动停止,因为事件转到按钮或标签而不是文本。

看来您不希望出现这种情况,因此您需要为 non-scrollable 小部件的鼠标滚轮提供自己的绑定。如果您将这些绑定应用到小部件 class 而不是单独的小部件,那么您将不必绑定到每个单独的小部件。不过,您可以根据需要绑定到各个小部件。

下面是一个示例,它为 ButtonLabel 小部件 class 添加绑定以将事件传递给其父级。

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()

注意:如果您使用的是基于 X11 的系统,则需要调整此代码以绑定到 <Button-4><Button-5> 而不是 <MouseWheel>