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

拖动窗口至其他屏幕触发_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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 11:32:02