简体   繁体   English

tkinter中如何创建模态对话框?

[英]How to create a modal dialog in tkinter?

I have a MFC application which runs some embedded Python scripts.我有一个 MFC 应用程序,它运行一些嵌入式 Python 脚本。 I am trying to make one of the dialogs this embedded script creates modal, but I am not having much success.我正在尝试使这个嵌入式脚本创建的对话框之一成为模态,但我没有取得太大的成功。

Can anyone point me the way to make a modal dialog?任何人都可以指出制作模态对话框的方法吗? Do I need to use a windows functions for this or only Tk or Python functions are enough?我是否需要为此使用 windows 功能,或者仅 Tk 或 Python 功能就足够了?

For what I googled looks like the following combination of functions should do the magic, but they dont seem to work the way I was expecting:对于我在谷歌上搜索的内容,看起来像以下功能组合应该会产生魔力,但它们似乎并不像我期望的那样工作:

focus_set()

grab_set()

transient(parent)

grab_set is the proper mechanism for making a window "application modal". grab_set是使窗口“应用程序模态”的正确机制。 That is, it takes all input from all other windows in the same application (ie: other Tkinter windows in the same process), but it allows you to interact with other applications. 也就是说,它需要来自同一应用程序中所有其他窗口的所有输入(即:同一进程中的其他Tkinter窗口),但它允许您与其他应用程序进行交互。

If you want your dialog to be globally modal, use grab_set_global . 如果您希望对话框是全局模态的,请使用grab_set_global This will take over all keyboard and mouse input for the entire system. 这将接管整个系统的所有键盘和鼠标输入。 You must be extremely careful when using this because you can easily lock yourself out of your computer if you have have a bug that prevents your app from releasing the grab. 使用它时必须非常小心,因为如果您有一个阻止您的应用程序释放抓取的错误,您可以轻松地将自己锁定在计算机之外。

When I have the need to do this, during development I'll try to write a bulletproof failsafe such as a timer that will release the grab after a fixed amount of time. 当我需要这样做时,在开发过程中我将尝试编写一个防弹故障保护措施,例如定时器,它将在一段固定的时间后释放抓取。

Below is a minimal working example that runs on Mac 10.15.17, Python 3.8.2.下面是在 Mac 10.15.17、Python 3.8.2 上运行的最小工作示例。

When run, it creates a main window with a single button.运行时,它会创建一个带有单个按钮的主 window。 When that's clicked a "modal dialog" appears, and the main window is visible but disabled until the modal dialog is closed.单击后会出现“模态对话框”,主 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')

The lines marked with # <<< NOTE all seemed to be necessary.标有# <<< NOTE的行似乎都是必要的。

Cheers!干杯! jz jz

Edit 1: added the keyboard event handler handle_key to main, to show that the bind is still in effect correctly before and after the popup is opened.编辑 1:将键盘事件处理程序handle_key添加到 main,以表明在弹出窗口打开前后bind仍然有效。

Edit 2: you might also want to block a regular window close, otherwise the grab_release() isn't called... if so, add self.root.protocol('WM_DELETE_WINDOW', self.ok) to the __init__ of the popup.编辑 2:您可能还想阻止常规的 window 关闭,否则不会调用grab_release() ...如果是这样,请将self.root.protocol('WM_DELETE_WINDOW', self.ok)添加到弹出窗口的__init__ . For Mac users, there are some potential issues with that, see How to intercept WM_DELETE_WINDOW on OSX using Tkinter .对于 Mac 用户,这有一些潜在的问题,请参阅如何使用 Tkinter 在 OSX 上拦截 WM_DELETE_WINDOW :-( Alas. Software: a world where everything is just a little bit broken. :-( 唉,软件:一个一切都有些破烂的世界。

In one of my projects I used the Tcl window manager attribute '-disabled' onto the parent window, that called a (modal) toplevel dialog window. 在我的一个项目中,我将Tcl窗口管理器属性'-disabled'用于父窗口,该窗口调用了一个(模态)顶层对话窗口。

Don't know which windows you show with your MFC application are created or used with Tcl stuff, but if your parent window is Tk based you could do this: 不知道您使用MFC应用程序显示哪些窗口是使用Tcl创建或使用的,但如果您的父窗口是基于Tk的,则可以执行以下操作:

In Python simply call onto the parent window inside the creation method of your toplevel window: 在Python中,只需在顶层窗口的创建方法内调用父窗口:

MyParentWindow.wm_attributes("-disabled", True)

After you got what you want with your modal window don't forget to use a callback function inside your modal window, to enable inputs on your parent window again! 在你的模态窗口得到你想要的东西后,别忘了在你的模态窗口中使用回调函数,再次启用父窗口的输入! (otherwise you won't be able to interact with your parent window again!): (否则您将无法再与父窗口进行交互!):

MyParentWindow.wm_attributes("-disabled", False)

A Tkinter (Tcl Version 8.6) Python example (tested on Windows 10 64bit): 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()

For more information about Tcl window manager attributes, just take a look at the Tcl documentation: https://wiki.tcl.tk/9457 有关Tcl窗口管理器属性的更多信息,请查看Tcl文档: https//wiki.tcl.tk/9457

You can add this to have a modal window:您可以添加它以获得模态 window:

 window.wm_attributes("-topmost", True)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM