Событие FocusOut, запускающее два обратных вызова вместо ожидаемого в Python/Tkinter

#python #events #tkinter #event-handling #widget

Вопрос:

Я использую найденный код для создания виджета timepicker для tkinter, состоящего из двух спин-боксов (один для значения часа, а другой для значения минуты), но когда я добавляю поведение для события в обоих спин-боксах, оба запускаются только с одним событием FocusOut только в одном спин-боксе.

Ожидаемое поведение будет заключаться в том, что должен быть запущен только обратный вызов из спин-бокса, который потерял фокус, а не оба.

Отредактировано: После комментария acw1668 теперь я знаю, что проблема в том, что диалоговое окно messagebox отвлекает внимание от следующего спин-бокса, поэтому теперь я ищу правильное решение или хорошую практику для решения этой проблемы.

Вот код:

 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('lt;FocusOutgt;', 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('lt;FocusOutgt;', 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) lt;= 2   and int(new_value) lt;= maxvalue  root = tk.Tk() Timepicker(root).pack() root.mainloop()  

Комментарии:

1. В self.min.bind('lt;FocusOutgt;', self._focus_minutes) этом ли проблема? Ты привязываешь его к min правому? Где еще вы могли бы этого хотеть? Я не вижу, чтобы здесь запускались какие-либо два обратных вызова

2. self.hour.bind(‘lt;Фокусировкаgt;’, self. _focus_hour) — это другой обратный вызов

3. Это потому, что, когда self.min теряет фокус, self.hour получает фокус. Затем messagebox отображается и заставляет self.hour потерять фокус, который запускает обратный вызов. Такое поведение исчезнет, если, например, кнопка будет добавлена после self.min . В этом случае, после self.min потери фокуса, кнопка получит фокус, который не вызовет событие «lt;FocusOutgt; » lt;FocusOutgt; self.hour .

4. Итак, единственное решение здесь-добавить какой-нибудь виджет?

5. На самом деле, в чем проблема двух lt;FocusOutgt; обратных вызовов?