解决CustomTkinter的AttributeError及无SizeNotifier的响应式布局方案
问题:CustomTkinter中AttributeError及响应式布局实现
错误信息
AttributeError: '_tkinter.tkapp' object has no attribute 'sizeNotifier'
问题代码
App类代码
class App(ctk.CTk): def __init__(self): super().__init__(fg_color=WHITE) self.title('SQL Connector') self._set_appearance_mode('dark') self.geometry('600x400') self.minsize(400, 300) # self.maxsize(800, 800) self.columnconfigure(index=(0, 1, 2, 3, 4), weight=1, uniform='a') self.rowconfigure(index=(0, 1, 2, 3, 4), weight=1, uniform='b') self.minsize(400, 300) # Data self.entryVar = ctk.StringVar() self.labelVar = ctk.StringVar(value='SQL Connector') # widgets self.entry = Entry(self, entryVar=self.entryVar) self.entry.grid(row=2, column=1, columnspan=3) self.button = Button(self, 'press For Commands') self.button.grid(row=2, column=1, columnspan=3, rowspan=2, pady=10) SlidePanelButton(self).grid(row=3, column=1, columnspan=3) self.slider = SlidePanel(parent=self, startPos=0, endPos=-0.3) self.slider.place() # event binding try: self.entry.bind('<Return>', self.entryBinding) except Exception as e: print(e) # threshold values for window try: self.sizeNotifier = SizeNotifier( self, { 300: self.sizeNotifier.create_small_layout(), 600: self.sizeNotifier.create_medium_layout(), 1200: self.sizeNotifier.createLargeLayout() } ) # self.bind('<Configure>', lambda event:self.responsiveWindow(event = event)) except Exception as e: print(e) def entryBinding(self): self.button.executeCmds() def responsiveWindow(self, event): # window attributes self.windowWidth = event.width self.windowHeight = event.height if (self.windowWidth >= 600 and self.windowHeight <= 600): self.button.grid(row=2, column=1, columnspan=3, rowspan=2) self.entry.grid(row=2, column=1, columnspan=3) SlidePanelButton(self).grid(row=3, column=1, columnspan=3) print(self.windowWidth, self.windowHeight) else: self.button.grid(row=1, column=1, columnspan=3, rowspan=2) self.entry.grid(row=2, column=1, columnspan=3) SlidePanelButton(self).grid(row=3, column=1, columnspan=3)
SizeNotifier类代码
import tkinter as tk from tkinter import ttk class SizeNotifier: def __init__(self, window, size_dict): self.window = window self.size_dict = {key: value for key, value in sorted(size_dict.items())} self.current_min_size = None self.window.bind('<Configure>', self.check_size) self.window.update() min_height = self.window.winfo_height() min_width = list(self.size_dict)[0] self.window.minsize(min_width, min_height) def check_size(self, event): if event.widget == self.window: window_width = event.width checked_size = None for min_size in self.size_dict: delta = window_width - min_size if delta >= 0: checked_size = min_size if checked_size != self.current_min_size: self.current_min_size = checked_size self.size_dict[self.current_min_size]() def create_small_layout(self): print('Hello') def create_medium_layout(self): print('Medium layout created') def createLargeLayout(self): print('Large Layout Created')
核心需求
CustomTkinter无内置SizeNotifier,需解决上述错误并实现不依赖该类的响应式布局。
解决方案
第一步:错误根源说明
你遇到的AttributeError是因为初始化SizeNotifier时,提前引用了还未创建的self.sizeNotifier对象(比如self.sizeNotifier.create_small_layout()),此时self.sizeNotifier还没赋值,自然会报错。
第二步:无SizeNotifier的响应式布局实现
直接基于Tkinter的<Configure>事件监听窗口尺寸变化,把布局逻辑整合到App类中即可,无需额外的SizeNotifier类。
修改后的完整代码
import customtkinter as ctk # 补充自定义控件的基础实现(如果你的代码里已有可忽略) class Entry(ctk.CTkEntry): def __init__(self, parent, entryVar, **kwargs): super().__init__(parent, textvariable=entryVar, **kwargs) class Button(ctk.CTkButton): def __init__(self, parent, text, **kwargs): super().__init__(parent, text=text, **kwargs) def executeCmds(self): print("Command executed") class SlidePanelButton(ctk.CTkButton): def __init__(self, parent, **kwargs): super().__init__(parent, text="Slide Panel", **kwargs) class SlidePanel(ctk.CTkFrame): def __init__(self, parent, startPos, endPos, **kwargs): super().__init__(parent, **kwargs) self.startPos = startPos self.endPos = endPos def place(self): super().place(relx=self.startPos, rely=0, relwidth=0.3, relheight=1) WHITE = "#ffffff" class App(ctk.CTk): def __init__(self): super().__init__(fg_color=WHITE) self.title('SQL Connector') self._set_appearance_mode('dark') self.geometry('600x400') self.minsize(400, 300) self.columnconfigure(index=(0, 1, 2, 3, 4), weight=1, uniform='a') self.rowconfigure(index=(0, 1, 2, 3, 4), weight=1, uniform='b') # Data self.entryVar = ctk.StringVar() self.labelVar = ctk.StringVar(value='SQL Connector') # 控件保存为类属性,避免重复创建 self.entry = Entry(self, entryVar=self.entryVar) self.button = Button(self, 'press For Commands') self.slide_panel_btn = SlidePanelButton(self) self.slider = SlidePanel(parent=self, startPos=0, endPos=-0.3) self.slider.place() # 初始化布局 self.update_layout(self.winfo_width()) # 绑定事件 self.entry.bind('<Return>', self.entryBinding) self.bind('<Configure>', self.on_window_resize) def entryBinding(self, event): self.button.executeCmds() def on_window_resize(self, event): # 只处理窗口本身的尺寸变化,忽略子控件的触发事件 if event.widget == self: self.update_layout(event.width) def update_layout(self, window_width): # 根据窗口宽度切换布局 if window_width >= 600: # 中/大尺寸布局 self.entry.grid(row=2, column=1, columnspan=3) self.button.grid(row=2, column=1, columnspan=3, rowspan=2, pady=10) self.slide_panel_btn.grid(row=3, column=1, columnspan=3) print(f"中/大尺寸布局: {window_width}px") else: # 小尺寸布局 self.entry.grid(row=2, column=1, columnspan=3) self.button.grid(row=1, column=1, columnspan=3, rowspan=2, pady=10) self.slide_panel_btn.grid(row=3, column=1, columnspan=3) print(f"小尺寸布局: {window_width}px") if __name__ == "__main__": app = App() app.mainloop()
关键改进点
- 移除SizeNotifier:直接用
<Configure>事件监听窗口尺寸,逻辑更简洁。 - 控件复用:所有控件都保存为App类属性,避免每次布局切换时创建新控件导致界面混乱。
- 布局逻辑集中:把布局切换逻辑封装到
update_layout方法,代码更易维护。 - 事件过滤:判断事件来源,只处理窗口本身的尺寸变化,避免子控件触发无效更新。
额外优化建议
- 可以添加更多尺寸阈值(比如900px),实现多档响应式布局。
- 配合
grid的weight设置+relwidth/relheight等相对参数,让控件自动适配窗口。 - 复杂布局可以拆分不同尺寸的布局为独立方法,提升代码可读性。
内容的提问来源于stack exchange,提问作者GANESH312006
相关产品推荐
相关产品推荐

