为同时具备垂直和水平滚动条的框架添加鼠标滚轮滚动支持

编程语言 2026-07-09
import tkinter as tk

root = tk.Tk()
root.geometry("400x300")

outer_frame = tk.Frame(root)
outer_frame.pack(fill="both", expand=True)

vertical_scrollbar = tk.Scrollbar(outer_frame, orient="vertical")
vertical_scrollbar.pack(side="right", fill="y")
horizontal_scrollbar = tk.Scrollbar(outer_frame, orient="horizontal")
horizontal_scrollbar.pack(side="bottom", fill="x")

canvas = tk.Canvas(outer_frame)
canvas.pack(side="left", fill="both", expand=True)
canvas.configure(yscrollcommand=vertical_scrollbar.set)
canvas.configure(xscrollcommand=horizontal_scrollbar.set)

inner_frame = tk.Frame(canvas)
inner_frame.bind("<Configure>", lambda event: canvas.configure(scrollregion=canvas.bbox("all")))
canvas.create_window((0, 0), window=inner_frame, anchor="nw")

vertical_scrollbar.configure(
    command=lambda *args, **kwargs: None
    if canvas.bbox("all")[3] <= canvas.winfo_height()
    else canvas.yview(*args, **kwargs),
)
horizontal_scrollbar.configure(
    command=lambda *args, **kwargs: None
    if canvas.bbox("all")[2] <= canvas.winfo_width()
    else canvas.xview(*args, **kwargs),
)
horizontal_scrollbar.update()
vertical_scrollbar.pack_configure(pady=(0, horizontal_scrollbar.winfo_height()))

# count_x, count_y = 1, 1
# count_x, count_y = 100, 1
# count_x, count_y = 1, 100
count_x, count_y = 100, 100
for i in range(count_y):
    text = " ".join(f"Label{i + 1}" for _ in range(count_x))
    tk.Label(inner_frame, text=text).pack(side="top", anchor="nw")

root.mainloop()

取决于 count_xcount_y 的取值,示例窗口有时会长成这样:

屏幕截图

在这四种情景下

  • 没有滚动
  • 仅垂直滚动
  • 仅水平滚动
  • 同时具备垂直和水平滚动

通过滚动条滚动框架内容(或不滚动)的效果如预期。这也包括通过鼠标滚轮滚动(在水平滚动条的情况下,通过 Shift 搭配鼠标滚轮进行滚动)。

然而,当光标没有悬停在相应的滚动条上时,鼠标滚轮滚动将不起作用。

解决方案

由于在悬停滚动条时鼠标滚轮已经可以工作,我们可以在框架画布上监听相应的事件(用于垂直滚动条的是 <MouseWheel><Button-4>,取决于你的平台,并配合 <Button-5>;同样,水平滚动条使用 <Shift-MouseWheel><Shift-Button-4>,以及 <Shift-Button-5>),并将它们传播给滚动条。

for event_name in (
    "<MouseWheel>",
    "<Button-4>",
    "<Button-5>",
    "<Shift-MouseWheel>",
    "<Shift-Button-4>",
    "<Shift-Button-5>",
):
    def _callback(event, event_name=event_name):
        widget = event.widget
        while widget:
            if widget == canvas:
                target = horizontal_scrollbar if "Shift" in event_name else vertical_scrollbar
                kwargs = {"delta": event.delta} if "MouseWheel" in event_name else {}
                return target.event_generate(event_name, **kwargs)
            widget = widget.master
    canvas.bind_all(event_name, _callback, add="+")

将此集成到上面的代码中,以获得一个完整可运行的示例:

import tkinter as tk

root = tk.Tk()
root.geometry("400x300")

outer_frame = tk.Frame(root)
outer_frame.pack(fill="both", expand=True)

vertical_scrollbar = tk.Scrollbar(outer_frame, orient="vertical")
vertical_scrollbar.pack(side="right", fill="y")
horizontal_scrollbar = tk.Scrollbar(outer_frame, orient="horizontal")
horizontal_scrollbar.pack(side="bottom", fill="x")

canvas = tk.Canvas(outer_frame)
canvas.pack(side="left", fill="both", expand=True)
canvas.configure(yscrollcommand=vertical_scrollbar.set)
canvas.configure(xscrollcommand=horizontal_scrollbar.set)

inner_frame = tk.Frame(canvas)
inner_frame.bind("<Configure>", lambda event: canvas.configure(scrollregion=canvas.bbox("all")))
canvas.create_window((0, 0), window=inner_frame, anchor="nw")

vertical_scrollbar.configure(
    command=lambda *args, **kwargs: None
    if canvas.bbox("all")[3] <= canvas.winfo_height()
    else canvas.yview(*args, **kwargs),
)
horizontal_scrollbar.configure(
    command=lambda *args, **kwargs: None
    if canvas.bbox("all")[2] <= canvas.winfo_width()
    else canvas.xview(*args, **kwargs),
)
horizontal_scrollbar.update()
vertical_scrollbar.pack_configure(pady=(0, horizontal_scrollbar.winfo_height()))

# count_x, count_y = 1, 1
# count_x, count_y = 100, 1
# count_x, count_y = 1, 100
count_x, count_y = 100, 100
for i in range(count_y):
    text = " ".join(f"Label{i + 1}" for _ in range(count_x))
    tk.Label(inner_frame, text=text).pack(side="top", anchor="nw")

for event_name in (
    "<MouseWheel>",
    "<Button-4>",
    "<Button-5>",
    "<Shift-MouseWheel>",
    "<Shift-Button-4>",
    "<Shift-Button-5>",
):
    def _callback(event, event_name=event_name):
        widget = event.widget
        while widget:
            if widget == canvas:
                target = horizontal_scrollbar if "Shift" in event_name else vertical_scrollbar
                kwargs = {"delta": event.delta} if "MouseWheel" in event_name else {}
                return target.event_generate(event_name, **kwargs)
            widget = widget.master
    canvas.bind_all(event_name, _callback, add="+")

root.mainloop()
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章