You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

解决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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 04:24:56