Tkinter中保存按钮点击位置并绘制连接线的实现问题
Tkinter记录两次按钮点击位置并绘制连线
我需要实现的功能:点击按钮时保存其位置,完成两次点击后将两个位置存入列表,再在两点间绘制一条线。以下是我目前的代码:
import tkinter as tk from tkinter.messagebox import showinfo # 假设CreateButton是继承tk.Button的自定义类,Window是继承tk.Tk的自定义类 class CreateButton(tk.Button): pass class Window(tk.Tk): def __init__(self, title, size): super().__init__() self.title(title) self.geometry(size) class ImageButton(CreateButton): def __init__(self, parent, image, command, location): Button.__init__(self, parent, image=image, command=command) self.image = image self.style = { "bg": "#7d7d7d", "bd": 0, "highlightbackground": "#000000", "highlightthickness": 0 } self.place(x=location[0], y=location[1]) self.config(self.style) class MainGui(Window): def __init__(self): Window.__init__(self, "Window", "800x500") self.called = 0 # 假设r_vfr是预先加载的图片对象 self.r_vfr = None icao_codes = ["XXX", "YYY"] self.airfields_btn = [] self.airfields_btn.append( ImageButton(self, self.r_vfr, lambda: showinfo(icao_codes[0]), [260, 375])) self.airfields_btn.append( ImageButton(self, self.r_vfr, lambda: showinfo(icao_codes[1]), [205, 270])) def set_vector(self): self.count = self.count + 1 if (self.count % 2) != 0: index = 0 btn_id = 0 for items in self.airfields_btn: items.config( command=lambda index=index: self.get_location(index)) index = index + 1 else: self.canvas.delete(self.line) def get_location(self, index): placement = [] self.called = self.called + 1 if (self.called % 2) != 0: placement.append(self.airfields_btn[index].winfo_rootx()) placement.append(self.airfields_btn[index].winfo_rooty()) else: placement.append(self.airfields_btn[index].winfo_rootx()) placement.append(self.airfields_btn[index].winfo_rooty()) def vector(self, placement): x1, y1 = placement[0], placement[1] x2, y2 = placement[2], placement[3] self.canvas.delete(self.line) self.line = self.canvas.create_line(x1, y1, x2, y2, fill="White", width=3, dash=(10,5))
问题分析
现有代码存在几个关键问题:
get_location中的placement是局部变量,无法累计两次点击的位置called和count两个计数器逻辑重复,易混乱winfo_rootx()获取的是屏幕绝对坐标,而Canvas是窗口内组件,应使用相对窗口的坐标vector函数未被触发调用,无法完成画线- 按钮命令切换逻辑未保留原命令,无法恢复初始状态
修正后的代码
import tkinter as tk from tkinter.messagebox import showinfo class CreateButton(tk.Button): pass class Window(tk.Tk): def __init__(self, title, size): super().__init__() self.title(title) self.geometry(size) class ImageButton(CreateButton): def __init__(self, parent, image, command, location): super().__init__(parent, image=image, command=command) self.image = image self.style = { "bg": "#7d7d7d", "bd": 0, "highlightbackground": "#000000", "highlightthickness": 0 } self.place(x=location[0], y=location[1]) self.config(self.style) class MainGui(Window): def __init__(self): super().__init__("Window", "800x500") # 初始化Canvas用于画线 self.canvas = tk.Canvas(self, bg="#333333", width=800, height=500) self.canvas.place(x=0, y=0) self.line = None # 存储点击位置的列表 self.click_positions = [] # 存储按钮原命令,用于恢复 self.original_commands = [] # 示例图片(可替换为实际图片) self.r_vfr = tk.PhotoImage(width=50, height=50) icao_codes = ["XXX", "YYY"] self.airfields_btn = [] self.airfields_btn.append( ImageButton(self, self.r_vfr, lambda: showinfo("ICAO", icao_codes[0]), [260, 375])) self.airfields_btn.append( ImageButton(self, self.r_vfr, lambda: showinfo("ICAO", icao_codes[1]), [205, 270])) def set_vector(self): # 切换连线模式:奇数次数开启,偶数次关闭 self.count = getattr(self, 'count', 0) + 1 if self.count % 2 != 0: # 保存原命令,绑定位置获取命令 self.original_commands.clear() for idx, btn in enumerate(self.airfields_btn): self.original_commands.append(btn['command']) btn.config(command=lambda idx=idx: self.get_location(idx)) else: # 恢复原命令,清除线条和未完成的点击记录 for idx, btn in enumerate(self.airfields_btn): btn.config(command=self.original_commands[idx]) if self.line is not None: self.canvas.delete(self.line) self.line = None self.click_positions.clear() def get_location(self, index): btn = self.airfields_btn[index] # 获取按钮中心的相对窗口坐标(更适合连线) x = btn.winfo_x() + btn.winfo_width() // 2 y = btn.winfo_y() + btn.winfo_height() // 2 self.click_positions.append((x, y)) # 收集到两个点后触发画线 if len(self.click_positions) == 2: self.vector(self.click_positions) self.click_positions.clear() def vector(self, positions): x1, y1 = positions[0] x2, y2 = positions[1] # 删除原有线条 if self.line is not None: self.canvas.delete(self.line) # 绘制新线 self.line = self.canvas.create_line(x1, y1, x2, y2, fill="White", width=3, dash=(10,5)) if __name__ == "__main__": app = MainGui() # 添加一个触发模式切换的按钮(示例) toggle_btn = tk.Button(app, text="切换连线模式", command=app.set_vector) toggle_btn.place(x=10, y=10) app.mainloop()
关键改进点
- 使用
self.click_positions全局列表存储点击位置,自动判断是否收集到两次点击 - 改用按钮中心的相对窗口坐标,避免屏幕坐标与Canvas坐标不匹配的问题
- 保留按钮原命令,支持连线模式的开启/关闭切换
- 自动触发画线逻辑,无需额外手动调用
- 初始化Canvas组件(原代码缺失),确保画线有载体
内容的提问来源于stack exchange,提问作者golem
相关产品推荐
相关产品推荐

