如何限制Tkinter按钮点击仅同时打开1个Toplevel窗口
问题描述
使用Tkinter开发带日期选择功能的Python应用时,交互逻辑为点击主窗口按钮弹出日期选择子窗口,选完日期后将值回传到主界面。但现有实现存在缺陷:多次点击按钮会生成大量重复的Toplevel子窗口,效果如下图:
现有问题代码如下:
import tkinter as tk class App(tk.Tk): """ Allows user to pick a date """ def __init__(self): super().__init__() self.label_a01 = tk.Label(text = 'Selected date is:') self.var_selected_date = tk.StringVar(value = "Noting selected") self.label_selected_date = tk.Label(textvariable = self.var_selected_date) self.button_a01 = tk.Button(self, text="Select the first day", command = self.select_a_date) self.button_a02 = tk.Button(self, text="Select the last day", command = self.select_a_date) self.label_a01.pack() self.label_selected_date.pack() self.button_a01.pack(pady = 5) self.button_a02.pack(pady = 5) def select_a_date(self): self.window01 = tk.Toplevel(self) self.label_b01 = tk.Label(self.window01, text = 'Please select a date from the calendar') self.button_b01 = tk.Button(self.window01, text="01 Jan 2022", command = lambda: self.update_date_to_mainframe(self.button_a01, "01 Jan 2022")) self.button_b02 = tk.Button(self.window01, text="31 Dec 2022", command = lambda: self.update_date_to_mainframe(self.button_a02, "31 Dec 2022")) self.label_b01.pack(pady = 5) self.button_b01.pack(pady = 5) self.button_b02.pack(pady = 5) def update_date_to_mainframe(self, widget, text_to_update): widget.config(text = text_to_update) def main(): app = App() app.mainloop() if __name__ == '__main__': main()
解决方案
核心逻辑是维护子窗口的引用,新建窗口前先做存在性校验,避免重复创建,具体实现步骤:
- 在主窗口初始化阶段,新增一个实例变量存储日期选择子窗口的引用,初始值设为
None - 触发打开子窗口的方法时,先校验引用对应的窗口是否存在:如果已存在,直接将已打开的窗口提升到最上层、获取焦点,终止后续新建窗口的逻辑
- 为子窗口绑定关闭事件(包括点击右上角关闭按钮、选择完日期主动关闭窗口两种场景),窗口关闭后将引用重置为
None,保证后续可以正常重新唤起窗口
修正后可运行代码
import tkinter as tk class App(tk.Tk): """ Allows user to pick a date """ def __init__(self): super().__init__() # 初始化子窗口引用,初始为None self.date_pick_window = None self.label_a01 = tk.Label(text = 'Selected date is:') self.var_selected_date = tk.StringVar(value = "Nothing selected") self.label_selected_date = tk.Label(textvariable = self.var_selected_date) self.button_a01 = tk.Button(self, text="Select the first day", command = self.select_a_date) self.button_a02 = tk.Button(self, text="Select the last day", command = self.select_a_date) self.label_a01.pack() self.label_selected_date.pack() self.button_a01.pack(pady = 5) self.button_a02.pack(pady = 5) def select_a_date(self): # 先判断子窗口是否已经存在 if self.date_pick_window is not None and self.date_pick_window.winfo_exists(): # 窗口已存在,提至最前并获取焦点,不新建 self.date_pick_window.lift() self.date_pick_window.focus_force() return # 不存在才新建窗口 self.date_pick_window = tk.Toplevel(self) # 绑定窗口右上角关闭事件,关闭时重置引用 self.date_pick_window.protocol("WM_DELETE_WINDOW", self._close_date_pick_window) self.label_b01 = tk.Label(self.date_pick_window, text = 'Please select a date from the calendar') self.button_b01 = tk.Button(self.date_pick_window, text="01 Jan 2022", command = lambda: self.update_date_to_mainframe(self.button_a01, "01 Jan 2022")) self.button_b02 = tk.Button(self.date_pick_window, text="31 Dec 2022", command = lambda: self.update_date_to_mainframe(self.button_a02, "31 Dec 2022")) self.label_b01.pack(pady = 5) self.button_b01.pack(pady = 5) self.button_b02.pack(pady = 5) def _close_date_pick_window(self): """关闭日期选择窗口并重置引用""" if self.date_pick_window is not None and self.date_pick_window.winfo_exists(): self.date_pick_window.destroy() self.date_pick_window = None def update_date_to_mainframe(self, widget, text_to_update): widget.config(text = text_to_update) # 选完日期后自动关闭子窗口 self._close_date_pick_window() # 同步更新选中日期展示 self.var_selected_date.set(text_to_update) def main(): app = App() app.mainloop() if __name__ == '__main__': main()
说明:修正代码同时补全了原代码里选中日期后主界面展示值未更新、初始提示拼写错误(Noting改为Nothing)的小问题,选择完日期后会自动关闭选择窗口,交互更顺畅。
内容的提问来源于stack exchange,提问作者jakelime
相关产品推荐
相关产品推荐

