tkinter multi-Toplevel windows 释放所有 windows grab_set 当其中只有一个释放抓取状态

tkinter multi-Toplevel windows releases all windows grab_set when only one of them releases the grabbed status

我正在尝试使用具有多个级别的 tkinter 构建 GUI windows。当我打开一个新的 Toplevel window 时,我需要他的 parens 被抓住。为此,我在 Toplevel class.

的开头使用 grab_set()

新的 Toplevel child window 还将具有一些其他功能,允许打开新的 Toplevel grandchild windows。问题是,当任何 Toplevel grandchild window 接近时,处于抓取状态的 windows 的层次结构将获得自由,然后所有这些都可以再次被操纵。

当 grab_settled grandchild window 发布时,如何保持 grab_set() 功能对所有 windows 有效?

这是重现此行为的简单代码示例

from tkinter import *


class GUI(Frame):
    def __init__(self, master, *args, **kwargs):
        Frame.__init__(self, master, *args, **kwargs)

        self.master = master
        self.my_frame = Frame(self.master)
        self.my_frame.pack()

        self.button1 = Button(self.master, text="Open New Window", command=OpenFirstToplevelWindow)
        self.button1.pack()

        self.text = Text(self.master, width=30, height=3)
        self.text.pack()
        self.text.insert(END, "Some text")


class OpenFirstToplevelWindow(Toplevel):
    def __init__(self, *args, **kwargs):
        Toplevel.__init__(self, *args, **kwargs)
        self.grab_set()
        top_button = Button(self, text="Open a second window", command=OpenSecondToplevelWindow)
        top_button.pack()
        app.text.delete(1.0, END)
        app.text.insert(END, "Text edition should be inhibited")
        # Toplevel.wait_window(self)


class OpenSecondToplevelWindow(Toplevel):
    def __init__(self, *args, **kwargs):
        Toplevel.__init__(self, *args, **kwargs)
        self.grab_set()
        top_button = Button(self, text="close second window", command=self.close_window)
        top_button.pack()
        app.text.delete(1.0, END)
        app.text.insert(END, "Text edition still inhibited")

    def close_window(self):
        app.text.delete(1.0, END)
        app.text.insert(END, "Text edition should keep inhibited but it IS NOT")
        self.destroy()


if __name__ == "__main__":
    root = Tk()
    app = GUI(root)
    root.mainloop()

--- 编辑扩展 @acw1668 答案 ---


我一直在处理您的答案,看起来效果很好。问题是我已经意识到,如果我尝试在最后一个 window 中的关闭按钮名称中添加一个 self 以供进一步使用,那么该方法将不起作用:

class OpenSecondToplevelWindow(MyToplevel):
    def __init__(self, *args, **kwargs):
        MyToplevel.__init__(self, *args, **kwargs)
        enable_button = Button(self, text="Enable close button", command=self.enable_click)
        enable_button.pack()
        self.low_button = Button(self, text="Close second window", command=self.close_window, state=DISABLED)
        self.low_button.pack()
        app.text.delete(1.0, END)
        app.text.insert(END, "Text edition still inhibited")

    def enable_click(self):
        self.low_button["state"] = NORMAL

    def close_window(self):
        app.text.delete(1.0, END)
        app.text.insert(END, "Text edition should keep inhibited but it IS NOT")
        self.destroy()

一种方法是在调用 grab_set() 之前保存已设置抓取的 window 的引用(如果有的话)。然后在当前window关闭后恢复抓包到保存的window

以下习俗class(继承自Toplevel)将具有上述特征:

class MyToplevel(Toplevel):
    def __init__(self, master=None, **kw):
        super().__init__(master, **kw)
        # save the reference of current window having the grab set
        self._last_grab_set_win = self.grab_current()
        self.grab_set()

    def __del__(self):
        if self._last_grab_set_win:
            # restore the grab set to saved window
            self._last_grab_set_win.grab_set()

然后在您的代码中使用上面的自定义 MyToplevel 而不是 Toplevel

class OpenFirstToplevelWindow(MyToplevel):
    def __init__(self, *args, **kwargs):
        MyToplevel.__init__(self, *args, **kwargs)
        top_button = Button(self, text="Open a second window", command=OpenSecondToplevelWindow)
        top_button.pack()
        app.text.delete(1.0, END)
        app.text.insert(END, "Text edition should be inhibited")
        # Toplevel.wait_window(self)


class OpenSecondToplevelWindow(MyToplevel):
    def __init__(self, *args, **kwargs):
        MyToplevel.__init__(self, *args, **kwargs)
        top_button = Button(self, text="close second window", command=self.close_window)
        top_button.pack()
        app.text.delete(1.0, END)
        app.text.insert(END, "Text edition still inhibited")

    ...

更新:我不知道如果使用实例小部件而不是本地小部件,为什么不执行__del__()。但是,覆盖 destroy() 似乎可以解决问题:

class MyToplevel(Toplevel):
    def __init__(self, master=None, **kw):
        super().__init__(master, **kw)
        # save the reference of current window having the grab set
        self._last_grab_set_win = self.grab_current()
        self.grab_set()

    # override destroy()
    def destroy(self):
        if self._last_grab_set_win:
            # restore the grab set to saved window
            self._last_grab_set_win.grab_set()
        # call Toplevel.destroy()
        super().destroy()