首页 文章

隐藏Tkinter根窗口,同时显示模态窗口

提问于
浏览
2

我有一个基于TKinter的应用程序,试图管理游戏的设置 . 用户可能安装了此游戏的多个版本,在这种情况下,我需要询问用户他们想要在启动时管理哪个安装 . 那部分单独运作得很好;构建主窗口后弹出选择对话框,并以模态方式运行 .

但是,由于游戏版本之间存在差异,如果我可以针对这些情况稍微调整界面将会很有用 . 然而,这意味着我无法真正构建主窗口,直到我知道我正在处理哪个安装,因此在用户做出选择之前它将是空白的 .

我希望在显示此对话框时隐藏根窗口,但在根窗口上调用 withdraw 只会导致模式对话框也不显示 - Python最终会挂起而没有CPU使用情况,我可以't figure out how to get around the problem without having to resort to a non-modal window (and a significantly different control flow, which I'喜欢避免) .

展示问题和一般代码结构的示例代码(Python 2.7):

from Tkinter import *
from ttk import *

class TkGui(object):
    def __init__(self):
        self.root = root = Tk()
        self.root.withdraw()
        selector = FolderSelection(self.root, ('foo', 'bar'))
        self.root.deiconify()
        print(selector.result)

class ChildWindow(object): #Base class
    def __init__(self, parent, title):
        top = self.top = Toplevel(parent)
        self.parent = parent
        top.title(title)
        f = Frame(top)
        self.create_controls(f)
        f.pack(fill=BOTH, expand=Y)

    def create_controls(self, container):
        pass

    def make_modal(self, on_cancel):
        self.top.transient(self.parent)
        self.top.wait_visibility() # Python will hang here...
        self.top.grab_set()
        self.top.focus_set()
        self.top.protocol("WM_DELETE_WINDOW", on_cancel)
        self.top.wait_window(self.top) # ...or here, if wait_visibility is removed

class FolderSelection(ChildWindow):
    def __init__(self, parent, folders):
        self.parent = parent
        self.listvar = Variable(parent)
        self.folderlist = None
        super(FolderSelection, self).__init__(parent, 'Select folder')
        self.result = ''
        self.listvar.set(folders)
        self.make_modal(self.cancel)

    def create_controls(self, container):
        f = Frame(container)
        Label(
            f, text='Please select the folder '
            'you would like to use.').grid(column=0, row=0)
        self.folderlist = Listbox(
            f, listvariable=self.listvar, activestyle='dotbox')
        self.folderlist.grid(column=0, row=1, sticky="nsew")
        Button(
            f, text='OK', command=self.ok
            ).grid(column=0, row=2, sticky="s")
        self.folderlist.bind("<Double-1>", lambda e: self.ok())
        f.pack(fill=BOTH, expand=Y)

    def ok(self):
        if len(self.folderlist.curselection()) != 0:
            self.result = self.folderlist.get(self.folderlist.curselection()[0])
            self.top.protocol('WM_DELETE_WINDOW', None)
            self.top.destroy()

    def cancel(self):
        self.top.destroy()

TkGui()

1 回答

  • 2

    在您的情况下似乎没有差异,模态窗口与否 . 实际上,您只需要使用 wait_window() 方法 . 根据docs

    在许多情况下,以同步方式处理对话更为实际;创建对话框,显示它,等待用户关闭对话框,然后继续执行您的应用程序 . wait_window方法正是我们所需要的;它进入一个本地事件循环,直到给定的窗口被销毁(通过destroy方法,或通过窗口管理器显式)才返回 .

    考虑使用非模态窗口的示例:

    from Tkinter import *
    
    root = Tk()
    
    def go():
       wdw = Toplevel()
       wdw.geometry('+400+400')
       e = Entry(wdw)
       e.pack()
       e.focus_set()
       #wdw.transient(root)
       #wdw.grab_set()
       root.wait_window(wdw)
       print 'done!'
    
    Button(root, text='Go', command=go).pack()
    Button(root, text='Quit', command=root.destroy).pack()
    
    root.mainloop()
    

    单击 Go 按钮时,将出现非模态对话框,但代码将停止执行,并且只有在关闭对话框窗口后才会显示字符串 done! .

    如果这是你想要的行为,那么这是你修改过的形式的例子(我修改了TkGui中的 __init__make_modal 方法,也添加了 mainloop() ):

    from Tkinter import *
    from ttk import *
    
    class TkGui(object):
        def __init__(self):
            self.root = root = Tk()
            self.root.withdraw()
            selector = FolderSelection(self.root, ('foo', 'bar'))
            self.root.deiconify()
            print(selector.result)
    
    class ChildWindow(object): #Base class
        def __init__(self, parent, title):
            top = self.top = Toplevel(parent)
            self.parent = parent
            top.title(title)
            f = Frame(top)
            self.create_controls(f)
            f.pack(fill=BOTH, expand=Y)
    
        def create_controls(self, container):
            pass
    
        def make_modal(self, on_cancel):
            #self.top.transient(self.parent)
            #self.top.wait_visibility() # Python will hang here...
            #self.top.grab_set()
            self.top.focus_set()
            self.top.protocol("WM_DELETE_WINDOW", on_cancel)
            self.top.wait_window(self.top) # ...or here, if wait_visibility is removed
    
    class FolderSelection(ChildWindow):
        def __init__(self, parent, folders):
            self.parent = parent
            self.listvar = Variable(parent)
            self.folderlist = None
            super(FolderSelection, self).__init__(parent, 'Select folder')
            self.result = ''
            self.listvar.set(folders)
            self.make_modal(self.cancel)
    
        def create_controls(self, container):
            f = Frame(container)
            Label(
                f, text='Please select the folder '
                'you would like to use.').grid(column=0, row=0)
            self.folderlist = Listbox(
                f, listvariable=self.listvar, activestyle='dotbox')
            self.folderlist.grid(column=0, row=1, sticky="nsew")
            Button(
                f, text='OK', command=self.ok
                ).grid(column=0, row=2, sticky="s")
            self.folderlist.bind("<Double-1>", lambda e: self.ok())
            f.pack(fill=BOTH, expand=Y)
    
        def ok(self):
            if len(self.folderlist.curselection()) != 0:
                self.result = self.folderlist.get(self.folderlist.curselection()[0])
                self.top.protocol('WM_DELETE_WINDOW', None)
                self.top.destroy()
    
        def cancel(self):
            self.top.destroy()
    
    TkGui()
    mainloop()
    

    代码在 selector = FolderSelection(self.root, ('foo', 'bar')) 行停止,然后在关闭对话框后继续 .

相关问题