[英]How to create a modal dialog in tkinter?
我有一个 MFC 应用程序,它运行一些嵌入式 Python 脚本。 我正在尝试使这个嵌入式脚本创建的对话框之一成为模态,但我没有取得太大的成功。
任何人都可以指出制作模态对话框的方法吗? 我是否需要为此使用 windows 功能,或者仅 Tk 或 Python 功能就足够了?
对于我在谷歌上搜索的内容,看起来像以下功能组合应该会产生魔力,但它们似乎并不像我期望的那样工作:
focus_set()
grab_set()
transient(parent)
grab_set
是使窗口“应用程序模态”的正确机制。 也就是说,它需要来自同一应用程序中所有其他窗口的所有输入(即:同一进程中的其他Tkinter窗口),但它允许您与其他应用程序进行交互。
如果您希望对话框是全局模态的,请使用grab_set_global
。 这将接管整个系统的所有键盘和鼠标输入。 使用它时必须非常小心,因为如果您有一个阻止您的应用程序释放抓取的错误,您可以轻松地将自己锁定在计算机之外。
当我需要这样做时,在开发过程中我将尝试编写一个防弹故障保护措施,例如定时器,它将在一段固定的时间后释放抓取。
下面是在 Mac 10.15.17、Python 3.8.2 上运行的最小工作示例。
运行时,它会创建一个带有单个按钮的主 window。 单击后会出现“模态对话框”,主 window 可见但在模态对话框关闭之前处于禁用状态。
from tkinter import *
class simpledialog(object):
def __init__(self, parent):
# The "return value" of the dialog,
# entered by user in self.entry Entry box.
self.data = None
self.root=Toplevel(parent)
self.entry = Entry(self.root)
self.entry.pack()
self.ok_btn = Button(self.root, text="ok", command=self.ok)
self.ok_btn.pack()
# Modal window.
# Wait for visibility or grab_set doesn't seem to work.
self.root.wait_visibility() # <<< NOTE
self.root.grab_set() # <<< NOTE
self.root.transient(parent) # <<< NOTE
self.parent = parent
def ok(self):
self.data = self.entry.get()
self.root.grab_release() # <<< NOTE
self.root.destroy()
class MainWindow:
def __init__(self, window):
window.geometry('600x400')
Button(window, text='popup', command=self.popup).pack()
Button(window, text='quit', command=self.closeme).pack()
self.window = window
window.bind('<Key>', self.handle_key)
def handle_key(self, event):
k = event.keysym
print(f"got k: {k}")
def popup(self):
d = simpledialog(self.window)
print('opened login window, about to wait')
self.window.wait_window(d.root) # <<< NOTE
print('end wait_window, back in MainWindow code')
print(f'got data: {d.data}')
def closeme(self):
self.window.destroy()
root = Tk()
app = MainWindow(root)
root.mainloop()
print('exit main loop')
标有# <<< NOTE
的行似乎都是必要的。
干杯! jz
编辑 1:将键盘事件处理程序handle_key
添加到 main,以表明在弹出窗口打开前后bind
仍然有效。
编辑 2:您可能还想阻止常规的 window 关闭,否则不会调用grab_release()
...如果是这样,请将self.root.protocol('WM_DELETE_WINDOW', self.ok)
添加到弹出窗口的__init__
. 对于 Mac 用户,这有一些潜在的问题,请参阅如何使用 Tkinter 在 OSX 上拦截 WM_DELETE_WINDOW 。 :-( 唉,软件:一个一切都有些破烂的世界。
在我的一个项目中,我将Tcl窗口管理器属性'-disabled'用于父窗口,该窗口调用了一个(模态)顶层对话窗口。
不知道您使用MFC应用程序显示哪些窗口是使用Tcl创建或使用的,但如果您的父窗口是基于Tk的,则可以执行以下操作:
在Python中,只需在顶层窗口的创建方法内调用父窗口:
MyParentWindow.wm_attributes("-disabled", True)
在你的模态窗口得到你想要的东西后,别忘了在你的模态窗口中使用回调函数,再次启用父窗口的输入! (否则您将无法再与父窗口进行交互!):
MyParentWindow.wm_attributes("-disabled", False)
Tkinter(Tcl版本8.6)Python示例(在Windows 10 64位上测试):
# Python 3+
import tkinter as tk
from tkinter import ttk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.minsize(300, 100)
self.button = ttk.Button(self, text="Call toplevel!", command=self.Create_Toplevel)
self.button.pack(side="top")
def Create_Toplevel(self):
# THE CLUE
self.wm_attributes("-disabled", True)
# Creating the toplevel dialog
self.toplevel_dialog = tk.Toplevel(self)
self.toplevel_dialog.minsize(300, 100)
# Tell the window manager, this is the child widget.
# Interesting, if you want to let the child window
# flash if user clicks onto parent
self.toplevel_dialog.transient(self)
# This is watching the window manager close button
# and uses the same callback function as the other buttons
# (you can use which ever you want, BUT REMEMBER TO ENABLE
# THE PARENT WINDOW AGAIN)
self.toplevel_dialog.protocol("WM_DELETE_WINDOW", self.Close_Toplevel)
self.toplevel_dialog_label = ttk.Label(self.toplevel_dialog, text='Do you want to enable my parent window again?')
self.toplevel_dialog_label.pack(side='top')
self.toplevel_dialog_yes_button = ttk.Button(self.toplevel_dialog, text='Yes', command=self.Close_Toplevel)
self.toplevel_dialog_yes_button.pack(side='left', fill='x', expand=True)
self.toplevel_dialog_no_button = ttk.Button(self.toplevel_dialog, text='No')
self.toplevel_dialog_no_button.pack(side='right', fill='x', expand=True)
def Close_Toplevel(self):
# IMPORTANT!
self.wm_attributes("-disabled", False) # IMPORTANT!
self.toplevel_dialog.destroy()
# Possibly not needed, used to focus parent window again
self.deiconify()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
有关Tcl窗口管理器属性的更多信息,请查看Tcl文档: https : //wiki.tcl.tk/9457
您可以添加它以获得模态 window:
window.wm_attributes("-topmost", True)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.