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

Tkinter绑定<Enter>事件无限循环及鼠标悬停异常问题

问题解决方案

问题1:鼠标进入文本区域触发Leave事件的解决

原因是文本是独立的Canvas元素,鼠标从圆形移到文本上时,会离开圆形元素触发Leave事件。解决方法是让文本和圆形共享同一个tag,这样鼠标在文本上时仍属于该tag的区域,不会触发Leave。

问题2:快速移动导致Enter/Leave循环崩溃的解决

添加状态变量跟踪当前悬停的tag,仅当悬停状态真正变化时执行操作,避免重复触发事件导致的资源错误。


修改后的完整代码

from tkinter import *
from math import sin, cos, pi
root = Tk()

class TimePicker(Canvas):
    def __init__(self, master, **kwargs):
        self.tformat = kwargs.pop('format', 24)
        # 修复原代码中radious的赋值错误
        rd = kwargs.pop('radious', 100)
        self.width, self.height = kwargs['width'], kwargs['height']
        self.radious = rd / 4
        assert self.tformat in [12, 24], "Time Format must be '12' or '24'"
        super(TimePicker, self).__init__(master, **kwargs)
        self.active_line = None
        self.current_hover_tag = None  # 跟踪当前悬停的tag
        self.create_center_circle(self.width/2, self.height/2, self.radious*2, fill="white", outline="#000", width=1)
        self.circle_numbers(self.width/2, self.height/2, self.radious+5, 10, [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 'Helvetica 11 bold', "Hours")
        self.circle_numbers(self.width/2, self.height/2, self.radious*2-15, 10, [0, 15, 30, 45], 'Helvetica 11 bold', "Minutes")

    def create_center_circle(self, x, y, r, **kwargs):
        return super().create_oval(x-r, y-r, x+r, y+r, **kwargs)
    
    def create_circle_arc(self, x, y, r, **kwargs):
        if "start" in kwargs and "end" in kwargs:
            kwargs["extent"] = kwargs["end"] - kwargs["start"]
            del kwargs["end"]
        return super().create_arc(x-r, y-r, x+r, y+r, **kwargs)
    
    def circle_numbers(self, x: int, y: int, r: int, cr:int, numbers: list, font: str, tp:str):
        _angle = 360/len(numbers)
        for n in numbers:
            ax =  r * sin(pi * 2 * (360-_angle*n-180) / 360);
            ay = r * cos(pi * 2 * (360-_angle*n-180) / 360);
            tag = f'{tp}:{str(n)}'
            # 创建圆形,绑定tag
            cl = self.create_center_circle(x+ax, y+ay, cr, fill="white", outline="#000", width=1, tag=tag)
            # 创建文本,同时添加主tag和文本专属tag
            tx = self.create_text(x+ax, y+ay, text=str(n).zfill(2), fill="black", font=(font), tag=(tag, 'tx'+tag))
            # 绑定事件到tag(包含圆形和文本)
            self.tag_bind(tag, '&lt;Enter&gt;', lambda e, c=(x+ax, y+ay), t=tag: self._hover(e, c, True, t))
            self.tag_bind(tag, '&lt;Leave&gt;', lambda e, c=(x+ax, y+ay), t=tag: self._hover(e, c, False, t))
            self.tag_bind(tag, '&lt;Button-1&gt;', lambda e, c=cl, s=tx, n=n, t=tp: self._set_number(e, c, s, n, t))

    def _hover(self, event, coords, state, tag):
        # 处理Enter事件:仅当当前没有悬停或悬停的是其他tag时执行
        if state:
            if self.current_hover_tag == tag:
                return
            # 清除之前的悬停状态
            if self.current_hover_tag is not None:
                prev_tag = self.current_hover_tag
                cl = self.find_withtag(prev_tag)
                tx = self.find_withtag('tx'+prev_tag)
                self.itemconfigure(cl, fill='white')
                self.itemconfigure(tx, fill="black")
                if self.active_line is not None:
                    self.delete(self.active_line)
                    self.active_line = None
            # 设置新的悬停状态
            self.current_hover_tag = tag
            cl = self.find_withtag(tag)
            tx = self.find_withtag('tx'+tag)
            self.itemconfigure(cl, fill='black')
            self.itemconfigure(tx, fill="white")
            self.active_line = self.create_line(self.width/2, self.height/2, coords[0], coords[1], fill="black", width=2)
        else:
            # 处理Leave事件:检查鼠标是否真的离开了该tag的所有元素
            x, y = event.x, event.y
            # 获取当前位置下的所有元素
            items = self.find_overlapping(x-1, y-1, x+1, y+1)
            # 检查是否还有该tag的元素
            has_tag = any(tag in self.gettags(item) for item in items)
            if has_tag:
                return
            # 确认离开,清除状态
            if self.current_hover_tag == tag:
                self.current_hover_tag = None
                cl = self.find_withtag(tag)
                tx = self.find_withtag('tx'+tag)
                self.itemconfigure(cl, fill='white')
                self.itemconfigure(tx, fill="black")
                if self.active_line is not None:
                    self.delete(self.active_line)
                    self.active_line = None

    def _set_number(self, event, cl, tx, number, tp):
        print('set', cl, tx, number, tp)

if __name__ == '__main__':
    # 修复原代码中未指定width和height的问题
    tp=TimePicker(root, format=24, background="red", radious=100, width=400, height=400)
    tp.pack(side=TOP)
    root.mainloop()

关键修改点说明

  • 共享tag解决文本区域Leave问题:创建文本时将主tag(如Hours:12)添加到文本的tag列表中,鼠标在文本上时仍属于该tag区域,不会触发圆形的Leave事件。
  • 添加当前悬停标记:用self.current_hover_tag跟踪当前悬停的tag,避免快速移动时重复触发Enter/Leave事件。
  • Leave事件验证:在Leave事件中检查鼠标当前位置是否仍有该tag的元素,防止快速移动导致的误触发。
  • 修复原代码小错误:修正radious赋值逻辑错误,补充实例化时缺失的width和height参数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 06:12:09