You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在CustomTkinter中实现支持双向(水平和垂直)滚动的Frame

如何在CustomTkinter中实现支持双向(水平和垂直)滚动的Frame

嘿,这个问题我之前也踩过坑!默认的CTkScrollableFrame确实只支持单一方向滚动,不过我们完全可以自己搞定双向滚动的需求,两种方法都挺靠谱的,我给你详细唠唠:

方法一:自定义封装双向滚动Frame(推荐,可复用)

我平时做项目常用这个方法,把逻辑封装成类之后,后续用起来和普通Frame没区别,特别省心。核心思路就是用Canvas当滚动容器,同时绑定水平、垂直两个Scrollbar,再把内容Frame嵌到Canvas里:

import customtkinter as ctk

class CTkBidirectionalScrollableFrame(ctk.CTkFrame):
    def __init__(self, master, **kwargs):
        super().__init__(master, **kwargs)
        
        # 配置外层Frame的网格布局,让Canvas和滚动条占满整个空间
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)
        
        # 创建Canvas滚动容器,去掉边框和高亮效果
        self.canvas = ctk.CTkCanvas(self, bg=self.cget("fg_color"), bd=0, highlightthickness=0)
        self.canvas.grid(row=0, column=0, sticky="nsew")
        
        # 垂直滚动条
        self.v_scrollbar = ctk.CTkScrollbar(self, orientation="vertical", command=self.canvas.yview)
        self.v_scrollbar.grid(row=0, column=1, sticky="ns")
        # 水平滚动条
        self.h_scrollbar = ctk.CTkScrollbar(self, orientation="horizontal", command=self.canvas.xview)
        self.h_scrollbar.grid(row=1, column=0, sticky="ew")
        
        # 绑定Canvas和滚动条的滚动命令
        self.canvas.configure(yscrollcommand=self.v_scrollbar.set, xscrollcommand=self.h_scrollbar.set)
        
        # 创建内部内容Frame,所有子控件都要加到这里!
        self.content_frame = ctk.CTkFrame(self.canvas, fg_color=self.cget("fg_color"))
        self.content_frame_id = self.canvas.create_window((0, 0), window=self.content_frame, anchor="nw")
        
        # 绑定事件:内容Frame大小变化时,更新Canvas的滚动范围
        self.content_frame.bind("<Configure>", self._on_content_configure)
        # 鼠标滚轮事件:普通滚轮垂直滚,按住Shift水平滚
        self.canvas.bind_all("<MouseWheel>", self._on_mouse_wheel)
        self.canvas.bind_all("<Shift-MouseWheel>", self._on_shift_mouse_wheel)
        
        # 可选:Canvas大小变化时,自动调整内容Frame宽度(避免内容过窄留白)
        self.canvas.bind("<Configure>", self._on_canvas_configure)
    
    def _on_content_configure(self, event):
        # 更新Canvas的滚动区域,确保能覆盖整个内容Frame
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))
    
    def _on_mouse_wheel(self, event):
        # 统一滚轮步长,不同系统的event.delta值不一样,除以120是通用处理
        self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")
    
    def _on_shift_mouse_wheel(self, event):
        # 按住Shift触发水平滚动
        self.canvas.xview_scroll(int(-1*(event.delta/120)), "units")
    
    def _on_canvas_configure(self, event):
        # 如果内容Frame宽度小于Canvas,让内容Frame和Canvas同宽
        if self.canvas.bbox("all")[2] < event.width:
            self.canvas.itemconfig(self.content_frame_id, width=event.width)

使用示例

用这个自定义类和普通CTkFrame几乎一样,唯一要注意的是控件要加到内部的content_frame

if __name__ == "__main__":
    ctk.set_appearance_mode("light")
    app = ctk.CTk()
    app.geometry("400x400")
    app.title("双向滚动Frame测试")
    
    # 创建双向滚动Frame,填充整个窗口
    scroll_frame = CTkBidirectionalScrollableFrame(app)
    scroll_frame.pack(fill="both", expand=True, padx=10, pady=10)
    
    # 往内容Frame里加测试控件(比如20行10列的按钮)
    for row in range(20):
        for col in range(10):
            btn = ctk.CTkButton(scroll_frame.content_frame, text=f"按钮 {row+1}-{col+1}")
            btn.grid(row=row, column=col, padx=8, pady=8)
    
    app.mainloop()

方法二:手动用Canvas搭建(适合临时场景)

如果只是临时需要,不想封装类,也可以直接手动写Canvas+Scrollbar的组合,原理和上面一样,就是代码没封装:

import customtkinter as ctk

ctk.set_appearance_mode("light")
app = ctk.CTk()
app.geometry("400x400")

# 外层容器Frame
container = ctk.CTkFrame(app)
container.pack(fill="both", expand=True, padx=10, pady=10)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)

# Canvas滚动容器
canvas = ctk.CTkCanvas(container, bg=container.cget("fg_color"), bd=0, highlightthickness=0)
canvas.grid(row=0, column=0, sticky="nsew")

# 滚动条
v_scroll = ctk.CTkScrollbar(container, orientation="vertical", command=canvas.yview)
v_scroll.grid(row=0, column=1, sticky="ns")
h_scroll = ctk.CTkScrollbar(container, orientation="horizontal", command=canvas.xview)
h_scroll.grid(row=1, column=0, sticky="ew")

canvas.configure(yscrollcommand=v_scroll.set, xscrollcommand=h_scroll.set)

# 内容Frame
content_frame = ctk.CTkFrame(canvas, fg_color=container.cget("fg_color"))
canvas.create_window((0,0), window=content_frame, anchor="nw")

# 绑定事件
def update_scrollregion(event):
    canvas.configure(scrollregion=canvas.bbox("all"))
content_frame.bind("<Configure>", update_scrollregion)

def on_mouse_wheel(event):
    canvas.yview_scroll(int(-1*(event.delta/120)), "units")
def on_shift_wheel(event):
    canvas.xview_scroll(int(-1*(event.delta/120)), "units")
canvas.bind_all("<MouseWheel>", on_mouse_wheel)
canvas.bind_all("<Shift-MouseWheel>", on_shift_wheel)

# 添加测试控件
for i in range(20):
    for j in range(10):
        btn = ctk.CTkButton(content_frame, text=f"测试按钮 {i}-{j}")
        btn.grid(row=i, column=j, padx=5, pady=5)

app.mainloop()

小提醒

  • 一定要把子控件加到content_frame(或手动搭建时的内部Frame)上,不然控件不会跟着滚动!
  • 鼠标滚动逻辑在Windows和Mac上都能正常工作,Shift+滚轮就是水平滚动,和系统默认行为一致。
  • 如果想调整滚动灵敏度,可以修改滚轮事件里的步长参数,比如把event.delta/120改成event.delta/60,滚动速度会变快。

内容来源于stack exchange

火山引擎 最新活动