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

Python Tkinter中如何排除find_closest匹配的线条对象

Tkinter 排除find_closest匹配范围内指定画布对象的实现方案

问题描述

  • 基于Tkinter Canvas开发六边形网格路径记录工具,通过监听鼠标左键点击事件,在相邻点击点之间绘制连线标识移动路径。
  • 现有故障:绘制的路径连线属于独立画布对象,默认会被find_closest()方法纳入匹配范围。由于连线绘制在六边形上层,随着连线数量增加,下层六边形几乎无法被选中,网格交互完全失效。
  • 最小复现代码如下:
import tkinter
from tkinter import *
from math import radians, cos, sin, sqrt


class App:
    def __init__(self, parent):
        self.parent = parent

        self.c1 = Canvas(self.parent, width=int(1.5*340), height=int(1.5*270), bg='white')
        self.c1.grid(column=0, row=0, sticky='nsew')

        self.clickcount = 0
        self.clicks = [(0,0)]
        self.startx = int(20*1.5)
        self.starty = int(20*1.5)
        self.radius = int(20*1.5)  # 六边形边长
        self.hexagons = []
        self.columns = 10
        self.initGrid(self.startx, self.starty, self.radius, self.columns)
        self.c1.bind("<Button-1>", self.click)

    def initGrid(self, x, y, radius, cols):
        """
        绘制二维六边形网格
        """
        radius = radius
        column = 0
        for j in range(cols):
            startx = x
            starty = y
            for i in range(6):
                breadth = column * (1.5 * radius)
                if column % 2 == 0:
                    offset = 0
                else:
                    offset = radius * sqrt(3) / 2

                self.draw(startx + breadth, starty + offset, radius)
                starty = starty + 2 * (radius * sqrt(3) / 2)

            column = column + 1

    def draw(self, x, y, radius):
        start_x = x
        start_y = y
        angle = 60
        coords = []
        for i in range(6):
            end_x = start_x + radius * cos(radians(angle * i))
            end_y = start_y + radius * sin(radians(angle * i))
            coords.append([start_x, start_y])
            start_x = end_x
            start_y = end_y

        hex = self.c1.create_polygon(coords[0][0], coords[0][1], coords[1][0], coords[1][1], coords[2][0],
                                        coords[2][1], coords[3][0], coords[3][1], coords[4][0], coords[4][1],
                                        coords[5][0], coords[5][1], fill='black')
        self.hexagons.append(hex)

    def click(self, evt):
        self.clickcount = self.clickcount + 1
        x, y = evt.x, evt.y
        tuple_alfa = (evt.x, evt.y)
        self.clicks.append(tuple_alfa)

        if self.clickcount >= 2:
            start = self.clicks[self.clickcount - 1]
            startx = start[0]
            starty = start[1]
            self.c1.create_line(evt.x, evt.y, startx, starty, fill='white')
        clicked = self.c1.find_closest(x, y)[0]
        print(clicked)


root = tkinter.Tk()
App(root)
root.mainloop()

注:原代码连线逻辑存在索引bug,取点时使用了当前点击点作为连线起点/终点,会绘制零长度无效线条,修复方案见下文修改代码。

实现思路

Tkinter Canvas没有提供直接给find_closest()传排除名单的参数,最稳定高效的方案是利用标签系统+局部范围搜索实现过滤:

  • 给不同类型的画布对象打独立标签做区分,六边形统一打hex标签,路径连线统一打path标签
  • 放弃全画布范围的find_closest()调用,改为搜索点击点周围3像素范围内的所有对象(范围可根据点击精度调整)
  • 遍历搜索结果,跳过连线类对象,第一个匹配到的六边形就是目标选中对象,从根源上避免连线干扰选中逻辑

修改后可运行代码

仅需修改3处逻辑即可解决问题:

  1. 创建六边形时添加hex标签
  2. 创建连线时添加path标签,同时修复连线索引bug
  3. 替换选中逻辑为局部搜索+标签过滤
import tkinter
from tkinter import *
from math import radians, cos, sin, sqrt


class App:
    def __init__(self, parent):
        self.parent = parent

        self.c1 = Canvas(self.parent, width=int(1.5*340), height=int(1.5*270), bg='white')
        self.c1.grid(column=0, row=0, sticky='nsew')

        self.clickcount = 0
        self.clicks = [(0,0)]
        self.startx = int(20*1.5)
        self.starty = int(20*1.5)
        self.radius = int(20*1.5)  # 六边形边长
        self.hexagons = []
        self.columns = 10
        self.initGrid(self.startx, self.starty, self.radius, self.columns)
        self.c1.bind("<Button-1>", self.click)

    def initGrid(self, x, y, radius, cols):
        """
        绘制二维六边形网格
        """
        radius = radius
        column = 0
        for j in range(cols):
            startx = x
            starty = y
            for i in range(6):
                breadth = column * (1.5 * radius)
                if column % 2 == 0:
                    offset = 0
                else:
                    offset = radius * sqrt(3) / 2

                self.draw(startx + breadth, starty + offset, radius)
                starty = starty + 2 * (radius * sqrt(3) / 2)

            column = column + 1

    def draw(self, x, y, radius):
        start_x = x
        start_y = y
        angle = 60
        coords = []
        for i in range(6):
            end_x = start_x + radius * cos(radians(angle * i))
            end_y = start_y + radius * sin(radians(angle * i))
            coords.append([start_x, start_y])
            start_x = end_x
            start_y = end_y

        # 给六边形添加统一标签hex
        hex_item = self.c1.create_polygon(
            coords[0][0], coords[0][1], coords[1][0], coords[1][1], coords[2][0],
            coords[2][1], coords[3][0], coords[3][1], coords[4][0], coords[4][1],
            coords[5][0], coords[5][1], fill='black', tags='hex'
        )
        self.hexagons.append(hex_item)

    def click(self, evt):
        self.clickcount = self.clickcount + 1
        x, y = evt.x, evt.y
        current_click = (evt.x, evt.y)
        self.clicks.append(current_click)

        if self.clickcount >= 2:
            # 修复连线索引bug,取上一个点击点作为连线起点,给连线加path标签
            last_click = self.clicks[self.clickcount - 2]
            self.c1.create_line(
                last_click[0], last_click[1], x, y,
                fill='white', width=2, tags='path'
            )
        
        # 局部搜索+标签过滤,跳过连线只选六边形
        nearby_items = self.c1.find_overlapping(x-3, y-3, x+3, y+3)
        clicked = None
        for item in nearby_items:
            if 'hex' in self.c1.gettags(item):
                clicked = item
                break
        print(clicked)


root = tkinter.Tk()
App(root)
root.mainloop()

补充说明

  • 局部搜索的范围(当前设置为3像素)可以根据实际需求调整,数值越大点击容错越高,数值越小选中精度越高
  • 该方案性能远高于遍历所有六边形计算距离的实现,即使网格规模很大、连线数量很多,选中操作也不会有卡顿
  • 如果后续需要新增其他不参与选中的画布元素(比如路径标注文字),只需要给对应元素打独立标签,在过滤逻辑里跳过即可,扩展性很强

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 15:21:17