#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;
обратных вызовов?