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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 20:35:17