首页 > 解决方案 > FocusOut 事件触发两个回调而不是 Python/Tkinter 中的预期一个

问题描述

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

预期的行为将是只有来自失去焦点的微调框的回调应该被触发,而不是两者都被触发。

编辑:在 acw1668 发表评论后,现在我知道问题在于消息框对话框从下一个 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()

标签: pythoneventstkinterevent-handlingwidget

解决方案


推荐阅读