为何Tkinter拖拽组件代码在CustomTkinter中失效?如何修复?
CustomTkinter标签拖拽排序失效修复
我有两段实现标签拖拽排序的代码,核心逻辑完全一致,唯一区别是一段基于原生Tkinter,另一段基于CustomTkinter。Tkinter版本运行正常,但CustomTkinter版本的拖拽功能完全失效。我已在CustomTkinter代码中用#this changed注释标注了与Tkinter版本的差异点,需要找到修复方法。
Tkinter版本代码
import tkinter def changeOrder(widget1, widget2, initial): target = widget1.grid_info() widget1.grid(row=initial['row'], column=initial['column']) widget2.grid(row=target['row'], column=target['column']) def on_click(event): widget = event.widget print(widget) if isinstance(widget, tkinter.Label): start = (event.x, event.y) grid_info = widget.grid_info() widget.bind("<B1-Motion>", lambda event: drag_motion(event, widget, start)) widget.bind("<ButtonRelease-1>", lambda event: drag_release(event, widget, grid_info)) else: root.unbind("<ButtonRelease-1>") def drag_motion(event, widget, start): x = widget.winfo_x() + event.x - start[0] y = widget.winfo_y() + event.y - start[1] widget.lift() widget.place(x=x, y=y) def drag_release(event, widget, grid_info): widget.lower() x, y = root.winfo_pointerxy() target_widget = root.winfo_containing(x, y) if isinstance(target_widget, tkinter.Label): changeOrder(target_widget, widget, grid_info) else: widget.grid(row=grid_info['row'], column=grid_info['column']) root = tkinter.Tk() _entry = {} rows = 3 columns = 2 for row in range(rows): for column in range(columns): index = (row, column) myTextLabel1 = tkinter.Label(root, text="Label 1", bg='yellow') myTextLabel1.grid(row=row, column=column, padx=5, pady=5) _entry[index] = myTextLabel1 _entry[index].configure(bg='red') root.bind("<Button-1>", on_click) root.mainloop()
CustomTkinter版本原代码(拖拽失效)
# WITH CUSTOMTKINTER import customtkinter def changeOrder(widget1, widget2, initial): target = widget1.grid_info() widget1.grid(row=initial['row'], column=initial['column']) widget2.grid(row=target['row'], column=target['column']) def on_click(event): widget = event.widget print(widget) if isinstance(widget, customtkinter.CTkLabel): #this changed start = (event.x, event.y) grid_info = widget.grid_info() widget.bind("<B1-Motion>", lambda event: drag_motion(event, widget, start)) widget.bind("<ButtonRelease-1>", lambda event: drag_release(event, widget, grid_info)) else: root.unbind("<ButtonRelease-1>") def drag_motion(event, widget, start): x = widget.winfo_x() + event.x - start[0] y = widget.winfo_y() + event.y - start[1] widget.lift() widget.place(x=x, y=y) def drag_release(event, widget, grid_info): widget.lower() x, y = root.winfo_pointerxy() target_widget = root.winfo_containing(x, y) if isinstance(target_widget, customtkinter.CTkLabel): #this changed changeOrder(target_widget, widget, grid_info) else: widget.grid(row=grid_info['row'], column=grid_info['column']) root = customtkinter.CTk() _entry = {} rows = 3 columns = 2 for row in range(rows): for column in range(columns): index = (row, column) myTextLabel1 = customtkinter.CTkLabel(root, text="Label 1", bg_color='yellow') #this changed myTextLabel1.grid(row=row, column=column, padx=5, pady=5) _entry[index] = myTextLabel1 _entry[index].configure(bg_color='red') #this changed root.bind("<Button-1>", on_click) root.mainloop()
问题原因
CustomTkinter的CTkLabel是原生Tkinter标签的包装组件,内部存在名为_label的子组件,点击事件实际触发在这个子组件上,而非CTkLabel本身。原代码中event.widget获取到的是内部子组件,导致isinstance(widget, customtkinter.CTkLabel)判断失败,后续拖拽绑定逻辑无法执行。
修复方案
- 新增工具函数从子组件向上追溯,找到真正的
CTkLabel实例 - 调整拖拽起始坐标计算,抵消内部子组件与
CTkLabel的坐标偏移 - 拖拽释放时,同样处理目标组件为内部子组件的情况
修改后的CustomTkinter代码
import customtkinter def changeOrder(widget1, widget2, initial): target = widget1.grid_info() widget1.grid(row=initial['row'], column=initial['column']) widget2.grid(row=target['row'], column=target['column']) def get_ctk_label(widget): # 从子组件向上查找CTkLabel实例 while widget: if isinstance(widget, customtkinter.CTkLabel): return widget widget = widget.master return None def on_click(event): widget = event.widget ctk_label = get_ctk_label(widget) print(ctk_label) if ctk_label: # 计算点击位置相对于CTkLabel的坐标 start_x = event.x + widget.winfo_x() - ctk_label.winfo_x() start_y = event.y + widget.winfo_y() - ctk_label.winfo_y() start = (start_x, start_y) grid_info = ctk_label.grid_info() # 绑定事件到真正的CTkLabel实例 ctk_label.bind("<B1-Motion>", lambda event: drag_motion(event, ctk_label, start)) ctk_label.bind("<ButtonRelease-1>", lambda event: drag_release(event, ctk_label, grid_info)) else: root.unbind("<ButtonRelease-1>") def drag_motion(event, widget, start): x = widget.winfo_x() + event.x - start[0] y = widget.winfo_y() + event.y - start[1] widget.lift() widget.place(x=x, y=y) def drag_release(event, widget, grid_info): widget.lower() x, y = root.winfo_pointerxy() target_widget = root.winfo_containing(x, y) target_ctk_label = get_ctk_label(target_widget) if target_ctk_label: changeOrder(target_ctk_label, widget, grid_info) else: widget.grid(row=grid_info['row'], column=grid_info['column']) root = customtkinter.CTk() _entry = {} rows = 3 columns = 2 for row in range(rows): for column in range(columns): index = (row, column) myTextLabel1 = customtkinter.CTkLabel(root, text="Label 1", bg_color='yellow') myTextLabel1.grid(row=row, column=column, padx=5, pady=5) _entry[index] = myTextLabel1 _entry[index].configure(bg_color='red') root.bind("<Button-1>", on_click) root.mainloop()
内容的提问来源于stack exchange,提问作者RafaelTech
相关产品推荐
相关产品推荐

