Python 方法奇怪地暂停直到 tkinter root 关闭

Python method strangely on pause until tkinter root is closed

我正在使用 Python 3.4 创建一个 tkinter 应用程序,它从 API 收集 posts,过滤它们并允许用户查看它们并为每个人做出决定(忽略、删除、分享等)

用户需要选择日期和一些页面,然后单击 'Collect' 按钮。然后程序从页面中获取 posts 并将它们存储在 'wholeList' 中。 当用户点击第二个按钮 'Review' 时,post 必须被过滤并传递给 Reviewer.

我的问题是审稿人根本没有收到 post,Filterer 也没有。我在某些地方添加了一些调试 print() 语句,特别是 handlerCollect(),结果让我感到困惑,因此这个 post.

当我单击 'Collect' 时,程序没有完成 handlerCollect() 回调方法,而是将其暂停在 "DEBUG->1" 和 "DEBUG->2" 之间的某处。主要 window 不会冻结或任何东西,因为我可以单击 'Review' 并让它打印 "DEBUG->4" 并打开 Reviewer。当我关闭主要 window、"DEBUG->0" "DEBUG->2" 和 "DEBUG->3" 最终打印时,以及 handlerCollect() 方法的其余部分正在执行。

相同的行为发生在 handlerChoosePage(),"DEBUG->0" 被延迟,直到 tkinter 根 (TK()) 被破坏。我对结构编程的了解告诉我它应该是第一个印刷出来的。相反,它是最后一个。我最好的结论是我不能正确地结束我的 Toplevel mainloop()s。我不得不承认我以前从未遇到过这样的事情。我认为在 Toplevels 上结束 mainloop()s 的正确方法是使用 destroy(),我很困惑为什么调用 mainloop()s 的方法会被搁置,直到 Tk根被破坏;不太实用。

from GUICollector import GUICollector as Collector

class Launcher(tk.Frame):
    def __init__(self, *args, **kwargs):

        ...
        self.allPagesCB     = Checkbutton(self.dateFrame, text="Collect for all pages",
            variable = self.allPagesVar, command=self.handlerChoosePage)
        self.collectBtn = Button(self, text="Collect", command=self.handlerCollect)
        self.reviewBtn  = Button(self, text="Review", command=self.handlerReview)

    def handlerChoosePage(self):
        if self.allPagesVar.get() == 0:
            child = tk.Toplevel(self)
            selector = PageSelector(self.toCollect, child)
            selector.pack(side="top", fill="both", expand=True)
            selector.mainloop()
            print("DEBUG->0")

    def handlerCollect(self):
        print("DEBUG->1")
        self.collect()
        print("DEBUG->4")
        for post in self.collector.getPosts():
            if post not in self.wholeList:
                print("...")
                self.wholeList.append(post.copy())
        self.collector = None
        print(len(self.wholeList), "posts in wholeList")

    def collect(self):
        window = tk.Toplevel(self)
        self.collector = Collector(self.toCollect, self.sinceEpoch, window)
        self.collector.grid(row=0,column=0)
        self.collector.after(500, self.collector.start)
        print("DEBUG->2")
        self.collector.mainloop() # This is what seems to hang indefinetly
        print("DEBUG->3")

    def handlerReview(self):
        print("DEBUG->5")
        print(len(self.wholeList), "posts in wholeList")
        filterer = Filterer(self.wholeList)
        self.wholeList = filterer.done[:]
        window = tk.Toplevel()
        reviewer = Reviewer(self.wholeList[:], window)
        reviewer.grid(row=0,column=0)
        reviewer.mainloop()

GUICollector 模块根本不需要用户交互。 这个模块似乎工作得很好:完成它的工作,显示它已完成,然后在指定的延迟后关闭。 由于 GuiCollector mainloop() 似乎是挂起的罪魁祸首,所以我是这样结束它的:

class GUICollector(tk.Frame):

    def __init__(self, pagesList, since, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)

    def start(self, event=None):
        if some_logic:
            self.after(250,self.start)
        else:
            self.done() # Does get called.

    def done(self):
        # Some StringVar update to display we are done on screen
        self.after(1250, self.validate)

    def validate(self):
        self.master.destroy()

PageSelector 模块在按下按钮时被相同的调用销毁:self.master.destroy()

这是程序的相关输出:

DEBUG->1
DEBUG->2
=> collected data of page [PageName]
=> Found 3 posts in page 
DEBUG->5
0 posts in wholeList

[The main window (Launcher) is manually closed at this point]
DEBUG->3
DEBUG->4
...
...
...
3 posts in wholeList
DEBUG->0

mainloop 的概念假定您首先创建并初始化对象(好吧,至少是应用程序启动时需要的对象,即不动态使用的对象),设置事件处理程序(实现界面逻辑)和 然后 进入无限事件处理(用户界面本质上是什么),即主循环。所以,这就是为什么你看到它 "hangs"。这叫做event-driven programming

重要的是这个事件处理是在一个地方完成的,就像这样:

class GUIApp(tk.Tk):
   ...


app = GUIApp()
app.mainloop()

因此,主循环 returns 当 window 结束时。

在我有时间重构我的代码之前,我通过将以下行添加到我的 destroy() 调用中解决了这个问题:

self.quit() # Ends mainloop
self.master.destroy() # Destroys master (window)

我知道这并不能解决我代码的错误结构,但它回答了我的具体问题。 destroy() 不会结束 TopLevelmainloop,但 quit() 会。添加此行使我的代码以可预测的方式执行。

一旦我重构了我的代码并验证了他关于 Tk() 主循环将覆盖所有子 TopLevel 的声明,我将立即接受 @pmod 的回答。