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, '<Enter>', lambda e, c=(x+ax, y+ay), t=tag: self._hover(e, c, True, t)) self.tag_bind(tag, '<Leave>', lambda e, c=(x+ax, y+ay), t=tag: self._hover(e, c, False, t)) self.tag_bind(tag, '<Button-1>', 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
相关产品推荐
相关产品推荐

