CustomTkinter中组件不可见时canvasx(winfo_x())返回值异常问题
问题:CustomTkinter滚动Canvas中子组件坐标获取异常
我用Python的CustomTkinter开发应用,有一个可滚动Canvas,内部嵌套了另一个Canvas作为子组件。需求是获取子组件在父Canvas中的坐标:
- 子组件可见时,
canvasx(widget.winfo_x())能返回稳定值,不受滚动影响; - 子组件不可见时,该方法返回值随滚动量变化,和可见时的数值不符。
目标是无论子组件可见与否,都能获取一致的Canvas坐标,滚动后数值保持匹配。尝试过加减滚动量但没解决,发现子组件不可见时widget.winfo_x()不随滚动变化,推测这是问题根源,但不确定。
可复现代码:
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,x,y): super().__init__(master,width=width,height=height,bg=bg) self.x=x self.y=y #Positionning the object self.master.create_window(self.x,self.y, window=self) #Attributes self.master = master self.canvas_hbar=self.master.hbar # The scrollbar of the main canvas 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.frame=ctk.CTkFrame(self) self.frame.pack(side='left',fill='both') self.B=ctk.CTkButton(self.frame,text="print",command=self.f1) self.B.grid(row=0,column=0,pady=100) #Labels self.label1=ctk.CTkLabel(self.frame,text="Winfo_x() = ") self.label1.grid(row=1,column=0) self.label2=ctk.CTkLabel(self.frame,text="Canvasx(Winfo_x()) = ") self.label2.grid(row=2,column=0) self.label3=ctk.CTkLabel(self.frame,text="Canvasx(scroll) = ") self.label3.grid(row=3,column=0) #Main canvas self.c=Canvas_composants( self, "white") #Object inside canvas width = 107 height = 190 bg = "black" self.object=dessin_composant(self.c, width, height, bg,600,350) def f1(self): """Function which prints object position inside the main canvas""" #self.c.delete(self.txt) obj = self.object self.off = self.c.hbar.get() # The amount scrolled self.label1.configure(text=f"Winfo_x() = {obj.winfo_x()} ") self.label2.configure(text=f"Canvasx(Winfo_x()) = {self.c.canvasx(obj.winfo_x())} ") self.label3.configure(text=f"Canvasx(scroll) = {self.c.canvasx(self.off[0])} ") 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()
核心原因
当子组件滚动出Canvas可视区域后,Tkinter会将其winfo_x()固定为边界值(如0),不再同步其实际的Canvas内坐标,导致canvasx()计算结果偏离真实值。
修复方案
放弃依赖winfo_x(),直接通过Canvas的coords()方法获取子组件对应的window对象的绝对坐标,该坐标不受滚动可视区域影响。
具体修改:
- 在
dessin_composant类中,保存父Canvas创建window时返回的ID:
class dessin_composant(ctk.CTkCanvas): """How to define the object inside the main canvas""" def __init__(self,master,width,height,bg,x,y): super().__init__(master,width=width,height=height,bg=bg) self.x=x self.y=y # 保存window对象的ID,用于后续获取坐标 self.window_id = self.master.create_window(self.x,self.y, window=self) self.master = master self.canvas_hbar=self.master.hbar
- 修改
f1()方法,通过coords()获取真实坐标:
def f1(self): """Function which prints object position inside the main canvas""" obj = self.object # 获取子组件在Canvas中的绝对坐标 canvas_x, canvas_y = self.c.coords(obj.window_id) self.label1.configure(text=f"Winfo_x() = {obj.winfo_x()} ") self.label2.configure(text=f"Canvas绝对坐标X = {canvas_x} ") self.label3.configure(text=f"滚动偏移量 = {self.c.hbar.get()[0]} ")
效果验证
修改后,无论子组件是否在可视区域内,点击print按钮都会显示稳定的600(即创建时的X坐标),完全符合需求。
内容的提问来源于stack exchange,提问作者Adrien
相关产品推荐
相关产品推荐

