Python+CustomTkinter:滚动画布后组件坐标获取异常问题
解决方案
问题根源
- 坐标获取方式错误:子画布通过主画布的
create_window添加,winfo_x()/winfo_y()返回的是子组件相对于主画布可见区域的位置,而非画布逻辑坐标系中的真实位置,滚动后必然错位。 - 拖拽逻辑错误:原拖拽函数的坐标计算混淆了鼠标位置、组件位置和滚动偏移,导致滚动后拖拽定位失效。
修正代码
import customtkinter as ctk import tkinter as tk import matplotlib class Canvas_composants(ctk.CTkCanvas): """The main canvas which is scrollable""" def __init__(self,master,bg): super().__init__(master=master,bg=bg) self.pack(fill="both",expand=True) # Scrollbar self.hbar=ctk.CTkScrollbar(self,orientation="horizontal") self.hbar.pack(side='top',fill='x') #Configuration scrollbar and canvas self.configure(xscrollcommand=self.hbar.set) self.hbar.configure(command=self.xview) #Configuration of the scrollregion self.configure(scrollregion=(0,0,50000,50000)) # Bind scroll to mousewheel self.bind("<MouseWheel>", self.scroll_canvas) def scroll_canvas(self,event): """Function executed when the mousewheel is used""" if event.delta: self.xview_scroll(-1 * (event.delta // 120), "units") class dessin_composant(ctk.CTkCanvas): """How to define the object inside the main canvas""" def __init__(self,master,width,height,bg): super().__init__(master,width=width,height=height,bg=bg) # 保存create_window返回的ID,用于后续获取/修改坐标 self.window_id = self.master.create_window(150,150, window=self) #Attributes self.master = master # 绑定拖拽相关事件:按下记录初始位置,拖动更新坐标 self.bind("<ButtonPress-1>", self.start_drag) self.bind("<B1-Motion>", self.drag) def start_drag(self, event): """记录拖拽初始位置:鼠标在主画布中的逻辑坐标、组件当前坐标""" self.start_x = self.master.canvasx(event.x) self.start_y = self.master.canvasy(event.y) self.component_x, self.component_y = self.master.coords(self.window_id) def drag(self,event): """计算拖拽偏移,更新组件位置""" # 获取当前鼠标在主画布中的逻辑坐标 current_x = self.master.canvasx(event.x) current_y = self.master.canvasy(event.y) # 计算偏移量 delta_x = current_x - self.start_x delta_y = current_y - self.start_y # 更新组件位置 new_x = self.component_x + delta_x new_y = self.component_y + delta_y self.master.coords(self.window_id, new_x, new_y) # 更新初始位置,确保拖拽流畅 self.start_x = current_x self.start_y = current_y self.component_x = new_x self.component_y = new_y class App(ctk.CTk): def __init__(self): super().__init__() # Executed when closed self.protocol("WM_DELETE_WINDOW", self.Fermeture) # Screen resolution screen_x = int(self.winfo_screenwidth()) screen_y = int(self.winfo_screenheight()) self_X = screen_x self_Y = screen_y posX = (screen_x // 2) -(self_X // 2) posY = (screen_y // 2) -(self_Y // 2) geo="{}x{}+{}+{}".format(self_X,self_Y,posX,posY) self.geometry(geo) #Button self.B=ctk.CTkButton(self,text="print",command=self.f1) self.B.pack(side='left',padx=20) #Main canvas self.c=Canvas_composants( self, "white") #Object inside canvas width=27+80 height=100+90 bg="black" self.object=dessin_composant(self.c, width, height, bg) self.txt=None def f1(self): """Function which prints object position inside the main canvas""" self.c.delete(self.txt) # 通过window_id获取组件在主画布逻辑坐标系中的真实坐标 x, y = self.c.coords(self.object.window_id) x = int(x) y = int(y) self.txt=self.c.create_text((800,200),text=f"x={x} pixel, y={y} pixel") print(f"x={x} pixel,y={y} pixel") def Fermeture(self): """Executed when the window is closed""" if tk.messagebox.askokcancel("Quit", "Do you really want to quit ? "): matplotlib.use('module://matplotlib_inline.backend_inline') self.quit() self.destroy() if __name__ == "__main__": Fenetre = App() Fenetre.mainloop()
关键修改说明
- 保存window_id:在
dessin_composant的__init__中,保存create_window返回的ID,这是主画布中标识该子组件的唯一凭证,后续通过它获取/修改坐标。 - 正确的拖拽逻辑:
start_drag记录鼠标按下时的主画布逻辑坐标,以及组件当前的逻辑坐标。drag时计算鼠标偏移量,直接用coords方法更新组件位置,完全基于画布逻辑坐标系,不受滚动影响。
- 坐标获取修正:
f1方法中使用self.c.coords(self.object.window_id)获取组件在主画布中的真实逻辑坐标,彻底解决滚动后的坐标错位问题。
内容的提问来源于stack exchange,提问作者Adrien
相关产品推荐
相关产品推荐

