2014-09-24 1012 views
2

我有一个基于TKinter的应用程序,试图管理游戏设置。用户可能安装了该游戏的多个版本,在这种情况下,我需要询问用户启动时要管理的安装。这部分工作足够好,主窗口构建完成后弹出选择对话框,并以模态方式运行。隐藏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() 
+0

请解释:有什么区别 - 如果你隐藏根窗口 - 对话框是否是模态的?结果似乎是一样的? – NorthCat 2014-09-25 18:31:56

+0

@NorthCat通过调用对话框模式,调用类将等待对话框关闭,然后再移动到下一行代码。如果我使用常规窗口,那么它将继续执行该方法,并且一旦完成,对话框将需要在调用类中引发一个事件。这也意味着对话突然需要实际知道关于调用类的东西。我非常想避免这种情况,因为我相信这对代码的整体结构和可读性有所帮助。 – 2014-09-25 19:07:13

回答

2

看来,你的情况是没有区别的,模态窗口或没有。实际上,您只需要使用wait_window()方法。根据docs

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

考虑遵循非模态窗口例如:

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按钮,就会出现非模态对话框,但代码WIL停止执行并串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'))然后在关闭对话框后继续。

+0

你的回答让我意识到问题实际上只是对'transient'的调用;其他两条线都很好。我从来没有想过要删除'transient',因为这是它成为* dialog *的一部分,而不是常规窗口,因为它隐藏了最小化和最大化 - 但由于不会有另一个窗口,所以它们可以在那里。谢谢! – 2014-09-26 11:15:09