Python Tkinter中destroy()在组件循环更新时异常问题排查
问题描述
尝试通过销毁Frame再重建的方式实现定期更新并移除标签的功能,代码如下:
self.list1 = [["test1", 0, 2, 5], ["test2", 3, 1, 4], ["test3", 4, 5 ,6], ["test4", 1, 4 ,6]] self.label_wrapper = 0 def x(self): row = 0 if self.label_wrapper != 0: #self.label_wrapper.grid_forget() self.label_wrapper.destroy() for i in self.list1: self.label_wrapper = customtkinter.CTkFrame(master = self.frame_info) self.label_wrapper.grid(row=row, column=0, sticky='nwe') self.label_wrapper.columnconfigure((0,1,2,3), weight=1) for n, v in enumerate(i): text = v label1 = customtkinter.CTkLabel(master=self.label_wrapper, text=text) label1.grid(row = 0, column = n, sticky="nwe") row = row + 1 self.list1.pop(0) #测试列表项是否删除、旧Frame是否销毁并重建新Frame self.after(10000, self.x)
程序运行表现:
- 启动后正常显示4个列表项:
["test1", 0, 2, 5] ["test2", 3, 1, 4] ["test3", 4, 5 ,6] ["test4", 1, 4 ,6]
- 第一次迭代后,列表首项被删除,Frame正常显示3个标签:
["test2", 3, 1, 4] ["test3", 4, 5 ,6] ["test4", 1, 4 ,6]
- 第二次迭代后,列表首项已删除,但Frame显示异常,仍有3个标签且末尾重复出现
["test3", 4, 5 ,6]:
["test3", 4, 5 ,6] ["test4", 1, 4 ,6] ["test3", 4, 5 ,6]
- 后续迭代后,Frame显示3个重复的
["test3", 4, 5 ,6]标签:
["test3", 4, 5 ,6] ["test3", 4, 5 ,6] ["test3", 4, 5 ,6]
补充测试:若去掉destroy(),4次迭代后输出为:
["test4", 1, 4 ,6] ["test4", 1, 4 ,6] ["test4", 1, 4 ,6] ["test4", 1, 4 ,6]
显然destroy()的使用方式存在问题,但无法定位具体错误点,寻求解决方案。
问题分析与修复
错误根源
代码中使用单个变量self.label_wrapper存储Frame对象,每次循环都会覆盖该变量的值,导致仅最后一个Frame被保留引用,之前创建的Frame成为无引用的"孤儿"对象。调用self.label_wrapper.destroy()时,只能销毁最后一个Frame,旧的Frame仍留在界面上,叠加新创建的Frame后就出现了重复显示的异常。
比如首次循环创建4个Frame,self.label_wrapper最终指向第4个Frame;第二次执行x()时,仅销毁第4个Frame,前3个旧Frame未被处理,再新建3个Frame叠加,就造成了显示混乱。
修复代码
改用列表存储所有创建的Frame,确保每次更新时能销毁所有旧Frame:
self.list1 = [["test1", 0, 2, 5], ["test2", 3, 1, 4], ["test3", 4, 5 ,6], ["test4", 1, 4 ,6]] # 用列表存储所有Frame,初始为空 self.label_wrappers = [] def x(self): row = 0 # 销毁所有旧Frame for frame in self.label_wrappers: frame.destroy() # 清空列表,准备存储新Frame self.label_wrappers.clear() for i in self.list1: frame = customtkinter.CTkFrame(master=self.frame_info) frame.grid(row=row, column=0, sticky='nwe') frame.columnconfigure((0,1,2,3), weight=1) for n, v in enumerate(i): label1 = customtkinter.CTkLabel(master=frame, text=v) label1.grid(row=0, column=n, sticky="nwe") # 将新创建的Frame添加到列表中 self.label_wrappers.append(frame) row += 1 if self.list1: # 避免列表为空时pop报错 self.list1.pop(0) self.after(10000, self.x)
优化提示
- 添加列表非空判断,防止
pop(0)在列表为空时抛出异常 - 若组件规模较大,可考虑复用Frame:先清空Frame内的所有子组件,再重新添加标签,能减少组件销毁重建的性能开销
内容的提问来源于stack exchange,提问作者Zercon
相关产品推荐
相关产品推荐

