簡體   English   中英

tkinter中如何創建模態對話框?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM