【问题标题】:Hiding Tkinter root window while showing modal window在显示模式窗口时隐藏 Tkinter 根窗口
【发布时间】:2014-09-24 23:55:34
【问题描述】:

我有一个基于 TKinter 的应用程序,它尝试管理游戏设置。用户可能安装了这个游戏的多个版本,在这种情况下,我需要询问用户他们想要在启动时管理哪个安装。该部分本身就可以很好地工作。主窗口构建完成后弹出选择对话框,模态运行。

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

我想在显示此对话框时隐藏根窗口,但在根窗口上调用 withdraw 只会导致模式对话框也不显示 - Python 最终挂起,没有 CPU 使用,我在不求助于非模态窗口(以及明显不同的控制流,我想避免)的情况下,无法弄清楚如何解决问题。

展示问题的示例代码和一般代码结构(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()

【问题讨论】:

  • 请解释一下:有什么区别 - 如果你隐藏根窗口 - 对话框是否是模态的?结果好像一样?
  • @NorthCat 通过使对话框模式化,调用类将等待对话框关闭,然后再移动到下一行代码。如果我使用常规窗口,那么它将继续执行该方法,并且一旦完成,对话框将需要在调用类中引发一个事件。这也意味着对话框突然需要真正知道一些关于调用类的信息。我非常想避免这种情况,因为我相信它有助于代码的整体结构和可读性。

标签: python tkinter ttk


【解决方案1】:

在你的情况下,模式窗口与否似乎没有区别。实际上,您只需要使用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 和 make_modal 方法中修改了__init__,还添加了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')) 行停止,然后在您关闭对话框后继续。

【讨论】:

  • 你的回答让我意识到问题实际上只是对transient的调用;其他两条线都很好。我从没想过要删除 transient,因为这是使它成为 dialog 而不是常规窗口的部分原因,因为它隐藏了最小化和最大化 - 但由于不会有另一个窗口,所以没关系让他们在那里。谢谢!
猜你喜欢
  • 2018-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-25
  • 1970-01-01
  • 2018-11-07
  • 2020-06-05
  • 1970-01-01
相关资源
最近更新 更多