如何让Tkinter的grab_set()在关闭自定义消息框前保持程序控制?
自定义tkinter消息框实现askyesno式阻塞与返回值处理
问题根源
你的代码里grab_set()只是把输入焦点锁定在弹窗上,但不会暂停当前函数的执行流程——所以popup_msg会直接返回初始的False,然后继续执行custom里的打印语句,这就是为什么弹窗还没操作就先输出了结果。另外test_func里的return val完全没用,按钮命令的返回值不会被外部捕获。
解决步骤
要实现和askyesno一样的阻塞效果并正确返回用户选择,需要做两个关键改动:
- 用
wait_window()让当前函数暂停,直到弹窗被销毁 - 用可变对象存储用户选择的结果(因为嵌套函数里修改不可变变量需要额外声明,用列表更简单)
修改后的完整代码
import tkinter as tk from tkinter import ttk from tkinter.messagebox import askyesno class Test(tk.Tk): def builtin(self, wtitle): print('Button One') answer = askyesno(title=wtitle, message='Click something', icon='info') print('User pressed: %s' % answer) def custom(self, wtitle): print('Button Two') answer = popup_msg(self, wtitle, 'Click to close') print('User pressed: %s' % answer) def __init__(self, *args, **kwargs): tk.Tk.__init__(self, *args, **kwargs) self.wm_title('Test') self.geometry('300x100') button_one = ttk.Button(self, text='Built-in', command=lambda: self.builtin('Built-In')) button_two = ttk.Button(self, text='Custom', command=lambda: self.custom('Custom')) button_one.pack(padx=5, pady=5) button_two.pack(padx=5, pady=5) def popup_msg(root, wtitle, msg): # 用列表存储返回值,因为列表是可变对象,嵌套函数可以直接修改 result = [False] def on_choice(val): result[0] = val popup.destroy() popup = tk.Toplevel(root) popup.wm_title(wtitle) popup.geometry("300x100") label = ttk.Label(popup, text=msg, anchor=tk.CENTER) label.pack(side="top", fill="x", pady=10) button1 = ttk.Button(popup, text="Yes", command=lambda: on_choice(True)) button2 = ttk.Button(popup, text="No", command=lambda: on_choice(False)) button1.pack(side="left", padx=20, pady=10) button2.pack(side="right", padx=20, pady=10) popup.grab_set() # 暂停执行,直到popup窗口被销毁 popup.wait_window(popup) # 返回用户选择的结果 return result[0] app = Test() app.mainloop()
关键改动说明
- 阻塞实现:
popup.wait_window(popup)会让popup_msg函数暂停在这里,直到弹窗被destroy(),完美模拟askyesno的阻塞效果。 - 返回值存储:用
result = [False]这个列表来保存用户选择,嵌套函数on_choice可以直接修改列表里的元素,不用额外声明nonlocal。 - 按钮命令优化:直接在按钮命令里把选择值存入result并销毁弹窗,逻辑更清晰。
内容的提问来源于stack exchange,提问作者tsoj
相关产品推荐
相关产品推荐

