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

Python+CustomTkinter:滚动画布后组件坐标获取异常问题

解决方案

问题根源

  1. 坐标获取方式错误:子画布通过主画布的create_window添加,winfo_x()/winfo_y()返回的是子组件相对于主画布可见区域的位置,而非画布逻辑坐标系中的真实位置,滚动后必然错位。
  2. 拖拽逻辑错误:原拖拽函数的坐标计算混淆了鼠标位置、组件位置和滚动偏移,导致滚动后拖拽定位失效。

修正代码

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()     

关键修改说明

  1. 保存window_id:在dessin_composant的__init__中,保存create_window返回的ID,这是主画布中标识该子组件的唯一凭证,后续通过它获取/修改坐标。
  2. 正确的拖拽逻辑:
    • start_drag记录鼠标按下时的主画布逻辑坐标,以及组件当前的逻辑坐标。
    • drag时计算鼠标偏移量,直接用coords方法更新组件位置,完全基于画布逻辑坐标系,不受滚动影响。
  3. 坐标获取修正:f1方法中使用self.c.coords(self.object.window_id)获取组件在主画布中的真实逻辑坐标,彻底解决滚动后的坐标错位问题。

内容的提问来源于stack exchange,提问作者Adrien

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 17:53:29