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

sqougxex  于 2023-01-01  发布在  Python
关注(0)|答案(4)|浏览(188)

我有一个MFC应用程序,它运行一些嵌入式Python脚本。我试图使这个嵌入式脚本创建的对话框之一模态,但我没有太大的成功。
有谁能告诉我怎么做一个模态对话框吗?我需要使用一个windows函数来做这个吗?或者只用Tk或者Python函数就足够了吗?
对于我在谷歌上搜索的内容,下面的函数组合应该会起到神奇的作用,但它们似乎并不像我预期的那样工作:

focus_set()

grab_set()

transient(parent)
w8ntj3qf

w8ntj3qf1#

grab_set是使窗口成为“应用程序模态”的适当机制。也就是说,它从同一应用程序中的所有其他窗口获取所有输入(即:同一进程中的其他Tkinter窗口),但它允许您与其他应用程序交互。
如果你想让你的对话框是全局模态的,使用grab_set_global。这将接管整个系统的 * 所有 * 键盘和鼠标输入。你必须非常小心使用它,因为如果你有一个阻止你的应用释放抓取的bug,你很容易把自己锁在电脑外面。
当我需要这样做的时候,在开发过程中我会尝试编写一个防弹故障保护,比如一个计时器,它会在固定的时间后释放抓取。

3qpi33ja

3qpi33ja2#

在我的一个项目中,我在父窗口上使用了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

lyr7nygr

lyr7nygr3#

下面是一个运行在Mac 10.15.17和Python 3.8.2上的最小工作示例。
运行时,它会创建一个只有一个按钮的主窗口,当点击按钮时,会出现一个“模态对话框”,主窗口是可见的,但在模态对话框关闭之前是不可用的。

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的行似乎都是必要的。
干杯!
编辑1:将键盘事件处理程序handle_key添加到main中,以显示bind在弹出窗口打开之前和之后仍然正确有效。
编辑2:你可能还想阻止常规的窗口关闭,否则grab_release()就不会被调用......如果是这样的话,在弹出窗口的__init__中添加self.root.protocol('WM_DELETE_WINDOW', self.ok)。对于Mac用户来说,这可能会有一些潜在的问题,请参见How to intercept WM_DELETE_WINDOW on OSX using Tkinter。:-(唉,软件:一个一切都有一点点破碎的世界。

lstz6jyr

lstz6jyr4#

您可以添加以下内容以拥有模态窗口:

window.wm_attributes("-topmost", True)

相关问题