如何在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




