拖动窗口至其他屏幕触发_tkinter.TclError错误求助
问题描述
错误现象
将窗口拖动到其他屏幕时触发以下异常:
Exception in Tkinter callback _tkinter.TclError: invalid command name ".!sortingframe.!dynamicgrid.!imagebuttonwidget"
场景说明
通过tk.Text的window_create方法实现动态网格布局,点击按钮时需要清空Text组件内的所有按钮并重置Text,编写了如下clear_all函数:
def clear_all(self): self.text.configure(state='normal') tags = self.text.tag_names() for tag in tags: self.text.tag_delete(tag) self.text.delete('1.0', tk.END) self.text.configure(state='disabled')
清空Text后拖动窗口到其他屏幕就会触发上述错误,推测是按钮销毁方式存在问题,但除Text组件外没有其他按钮引用,无法定位具体原因。
完整代码
import tkinter as tk import customtkinter as ctk class DynamicGrid(ctk.CTkFrame): def __init__(self, parent, *args, **kwargs): super().__init__(parent, *args, **kwargs) bg = 'gray80' self.text = tk.Text(self, wrap="char", borderwidth=0, background=bg, selectbackground=bg, selectforeground=bg, exportselection=0, insertofftime=0, relief="flat", state="disabled", spacing2=8) self.text.grid(row=0, column=0, sticky='nswe') def append(self, object): self.text.configure(state="normal") self.text.window_create("end", window=object) self.text.insert("end", ' ') self.text.configure(state="disabled") def clear_all(self): self.text.configure(state='normal') tags = self.text.tag_names() for tag in tags: self.text.tag_delete(tag) self.text.delete('1.0', tk.END) self.text.configure(state='disabled') class ImageButtonWidget(ctk.CTkButton): def __init__(self, parent, filename): super().__init__(parent) self.configure(text=filename) class SortingFrame(ctk.CTkFrame): def __init__(self, parent): super().__init__(parent) self.images_frame = DynamicGrid(self) self.create_images() self.create_images() self.images_frame.grid(row=1, column=0, sticky='nswe', columnspan=2) def create_images(self): # 创建并展示ImageButtonWidget实例 self.images_frame.clear_all() for filename in range(2): image_widget = ImageButtonWidget(self.images_frame, str(filename)+'.png') self.images_frame.append(image_widget) class App(ctk.CTk): def __init__(self, *args, **kwargs): super().__init__(args, kwargs) self.title("Example") SortingFrame(self).grid(row=0, column=0, sticky='nsew') if __name__ == "__main__": app = App() app.mainloop()
完整回溯信息
Exception in Tkinter callback Traceback (most recent call last): File "...AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 1921, in __call__ return self.func(*args) File "...AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 839, in callit func(*args) File "...\explorerEnv\lib\site-packages\customtkinter\windows\widgets\scaling\scaling_tracker.py", line 187, in check_dpi_scaling cls.update_scaling_callbacks_for_window(window) File "...\explorerEnv\lib\site-packages\customtkinter\windows\widgets\scaling\scaling_tracker.py", line 64, in update_scaling_callbacks_for_window set_scaling_callback(cls.window_dpi_scaling_dict[window] * cls.widget_scaling, File "...\explorerEnv\lib\site-packages\customtkinter\windows\widgets\ctk_button.py", line 136, in _set_scaling super()._set_scaling(*args, **kwargs) File "...\explorerEnv\lib\site-packages\customtkinter\windows\widgets\core_widget_classes\ctk_base_class.py", line 228, in _set_scaling super().configure(width=self._apply_widget_scaling(self._desired_width), File "...AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 1675, in configure return self._configure('configure', cnf, kw) File "...AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 1665, in _configure self.tk.call(_flatten((self._w, cmd)) + self._options(cnf)) _tkinter.TclError: invalid command name ".!sortingframe.!dynamicgrid.!imagebuttonwidget"
解决方案
问题根源:调用text.delete只是把按钮从Text的显示中移除,但没有销毁按钮对象,CustomTkinter的ScalingTracker仍会跟踪这些已脱离Text的按钮。当窗口跨屏幕拖动触发DPI缩放更新时,ScalingTracker尝试调用这些按钮的_set_scaling方法,但此时按钮对应的Tcl命令已被销毁,导致报错。
修改clear_all函数,在删除Text内容前,先遍历Text中嵌入的所有窗口组件并手动销毁:
def clear_all(self): self.text.configure(state='normal') # 遍历Text中所有嵌入的窗口,逐个销毁 start = "1.0" while True: # 获取当前位置的嵌入窗口名称 window_info = self.text.window_cget(start, "window") if not window_info: break # 通过名称获取组件对象并销毁 window = self.nametowidget(window_info) window.destroy() # 移动到下一个位置 start = self.text.index(f"{start}+1c") tags = self.text.tag_names() for tag in tags: self.text.tag_delete(tag) self.text.delete('1.0', tk.END) self.text.configure(state='disabled')
原理说明
text.window_cget(start, "window"):获取指定位置的嵌入窗口名称self.nametowidget(window_info):通过Tcl组件名称获取对应的Python组件对象window.destroy():彻底销毁组件,同时移除CustomTkinter的ScalingTracker跟踪- 后续执行原有Text清理逻辑,确保无残留无效引用
修改后,清空操作会彻底销毁所有嵌入的按钮,跨屏幕拖动时ScalingTracker不会再尝试访问已销毁的组件,错误即可解决。
内容的提问来源于stack exchange,提问作者nokla
相关产品推荐
相关产品推荐

