FocusOut事件触发两个回调,而不是Python/Tkinter中预期的一个回调

FocusOut事件触发两个回调,而不是Python/Tkinter中预期的一个回调,python,events,tkinter,event-handling,widget,Python,Events,Tkinter,Event Handling,Widget,我正在使用我发现的一些代码为tkinter创建一个timepicker小部件,它由两个Spinbox组成(一个用于小时值,另一个用于分钟值),但是当我在两个Spinbox上为事件添加行为时,两个Spinbox都被触发,只有一个Spinbox中有一个FocusOut事件 预期的行为是,只有来自失去焦点的spinbox的回调才会被触发,而不是两者都被触发 编辑:在acw1668的评论之后,现在我知道问题是messagebox对话框从下一个Spinbox窃取了焦点,所以现在我正在寻找一个合适的解决方案

我正在使用我发现的一些代码为tkinter创建一个timepicker小部件,它由两个Spinbox组成(一个用于小时值,另一个用于分钟值),但是当我在两个Spinbox上为事件添加行为时,两个Spinbox都被触发,只有一个Spinbox中有一个FocusOut事件

预期的行为是,只有来自失去焦点的spinbox的回调才会被触发,而不是两者都被触发

编辑:在acw1668的评论之后,现在我知道问题是messagebox对话框从下一个Spinbox窃取了焦点,所以现在我正在寻找一个合适的解决方案或良好的实践来解决这个问题

代码如下:

import tkinter as tk
from tkinter import messagebox


class Timepicker(tk.Frame):
    def __init__(self, parent):
        super().__init__(parent)
        self.parent = parent
        self.hourstr = tk.StringVar(self, '10')
        self.hour = tk.Spinbox(self, from_=0, to=23, wrap=True, name='hourspin',
                               textvariable=self.hourstr, width=4)
        vcmd = (parent.register(self._validate_hour), '%P')
        self.hour.configure(validate='key', validatecommand=vcmd)
        self.hour.bind('<FocusOut>', self._focus_hour)

        self.minstr = tk.StringVar(self, '30')
        self.minstr.trace("w", self.trace_var)
        self.last_value = ""
        self.min = tk.Spinbox(self, from_=0, to=59, wrap=True, name='minspin',
                              textvariable=self.minstr, width=4, )
        vcmd = (parent.register(self._validate_minutes), '%P')
        self.min.configure(validate='key', validatecommand=vcmd)
        self.min.bind('<FocusOut>', self._focus_minutes)

        self.hour.grid()
        self.min.grid(row=0, column=1)

    def trace_var(self, *args):
        if self.last_value == "59" and self.minstr.get() == "0":
            self.hourstr.set(int(self.hourstr.get()) + 1 if self.hourstr.get() != "23" else 0)
        self.last_value = self.minstr.get()

    def _focus_hour(self, event):
        result = self._validate_generic(self.hourstr.get(), 23)
        if (not result or self.hourstr.get() == '') \
                and self.parent.focus_get() != '.!timepicker.hourspin':
            messagebox.showerror('Invalid value', f'{self.hourstr.get()} is not a valid input')

    def _focus_minutes(self, event):
        result = self._validate_generic(self.minstr.get(), 23)
        if (not result or self.minstr.get() == '') \
                and self.parent.focus_get() != '.!timepicker.minspin':
            messagebox.showerror('Invalid value', f'{self.minstr.get()} is not a valid input')

    def _validate_hour(self, new_value):
        # Returning True allows the edit to happen, False prevents it.
        if new_value == '':
            return True

        result = self._validate_generic(new_value, 23)
        return result

    def _validate_minutes(self, new_value):
        # Returning True allows the edit to happen, False prevents it.
        if new_value == '':
            return True

        result = self._validate_generic(new_value, 59)
        return result

    def _validate_generic(self, new_value, maxvalue):
        if not new_value.isdigit():
            return False

        return len(new_value) <= 2 \
                  and int(new_value) <= maxvalue

root = tk.Tk()
Timepicker(root).pack()
root.mainloop()
将tkinter作为tk导入
从tkinter导入消息框
类时间选择器(tk.Frame):
定义初始化(自身,父级):
super()。\uuuu init\uuuu(父级)
self.parent=parent
self.hourstr=tk.StringVar(self,“10”)
self.hour=tk.Spinbox(self,from=0,to=23,wrap=True,name='hourspin',
textvariable=self.hourstr,宽度=4)
vcmd=(父寄存器(自我验证时间),“%P”)
self.hour.configure(validate='key',validatecommand=vcmd)
自我小时绑定('',自我小时)
self.minstr=tk.StringVar(self,'30')
self.minstr.trace(“w”,self.trace_var)
self.last_value=“”
self.min=tk.Spinbox(self,from=0,to=59,wrap=True,name='minspin',
textvariable=self.minstr,宽度=4,)
vcmd=(父寄存器(自我验证分钟),“%P”)
self.min.configure(validate='key',validatecommand=vcmd)
self.min.bind(“”,self.\u焦点\u分钟)
self.hour.grid()
self.min.grid(行=0,列=1)
def trace_变量(自身,*参数):
如果self.last_值==“59”和self.minstr.get()==“0”:
self.hourstr.set(int(self.hourstr.get())+1如果self.hourstr.get()!=“23”否则为0)
self.last_value=self.minstr.get()
定义焦点小时(自我、事件):
结果=self.\u验证\u泛型(self.hourstr.get(),23)
if(不是result或self.hourstr.get()='')\
和self.parent.focus_get()!='。!时间选择器。小时PIN':
messagebox.batherRor('Invalid value',f'{self.hourstr.get()}不是有效的输入')
定义焦点分钟数(自我、事件):
结果=self.\u验证\u泛型(self.minstr.get(),23)
if(不是result或self.minstr.get()='')\
和self.parent.focus_get()!='。!时间选择器。minspin':
messagebox.batherRor('Invalid value',f'{self.minstr.get()}不是有效的输入')
def_验证_小时(自身、新_值):
#返回True允许进行编辑,返回False则阻止编辑。
如果新的_值=='':
返回真值
结果=自我验证通用(新值,23)
返回结果
def_验证_分钟数(自身、新_值):
#返回True允许进行编辑,返回False则阻止编辑。
如果新的_值=='':
返回真值
结果=自我验证通用(新值,59)
返回结果
def\U validate\U通用(自身、新值、最大值):
如果不是新的\u值。isdigit():
返回错误

return len(新值)是
self.min.bind(“”,self.u focus\u minutes)
这是问题所在吗?您将其绑定到
min
对吗?你还想去哪里?我看不到在此触发任何两个回调。self.hour.bind(“”,self.focus\u hour)是另一个回调。这是因为当
self.min
失去焦点时,
self.hour
获得焦点。然后显示
messagebox
,使
self.hour
失去焦点,从而触发回调。例如,如果在
self.min
之后添加按钮,则此类行为将消失。在这种情况下,
self.min
失去焦点后,按钮将获得焦点,而焦点不会触发
self.hour
的'`事件。因此,这里唯一的解决方案是添加一些小部件?实际上调用两个
回调有什么问题?