【问题标题】:Python method strangely on pause until tkinter root is closedPython方法奇怪地暂停直到tkinter root关闭
【发布时间】:2016-06-19 14:07:10
【问题描述】:

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

用户应该选择一个日期和一些页面,然后单击“收集”按钮。然后程序从页面中获取帖子并将它们存储在“wholeList”中。 当用户点击第二个按钮“Review”时,帖子必须被过滤并传递给Reviewer。

我的问题是审稿人根本没有收到任何帖子,Filterer 也没有。我在某些地方添加了一些调试print() 语句,尤其是handlerCollect(),结果让我感到困惑,因此发了这篇文章。

当我单击“收集”时,程序并没有完成handlerCollect() 回调方法,而是将其置于“DEBUG->1”和“DEBUG->2”之间的某个位置。主窗口不会冻结或任何东西,因为我可以单击“查看”并让它打印“DEBUG->4”并打开Reviewer。当我关闭主窗口时,“DEBUG->0”、“DEBUG->2”和“DEBUG->3”最终会打印出来,同时执行handlerCollect() 方法的其余部分。

handlerChoosePage() 也会发生相同的行为,“DEBUG->0”会延迟到 tkinter 根 (TK()) 被销毁。我的结构化编程知识告诉我它应该是第一个打印出来的。相反,它是最后一个。我最好的结论是我不能正确地结束我的Toplevelmainloop()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,不多。如果你启动了多个,你可能会得到嵌套的事件循环,这是一种看到奇怪效果的可靠方法。
  • @schlenk: 不是可能:你肯定会得到嵌套的事件循环。
  • 我看到很多 cmets 说应该只有一个 mainloop (因为如果我没记错的话,每个进程只有一个事件循环),我很乐意听从建议,但我不明白如何在没有在每个辅助窗口上调用 mainloops 的情况下制作具有多个窗口的应用程序...
  • 简单。只是不要这样做。删除当前的 mainloop() 调用。仅致电root=tk.Tk() 一次。所有其他窗口都应该是 Toplevel,通常以root 作为第一个参数(但对于与另一个小部件连接的弹出窗口,请使用小部件)。最后调用 root.mainloop,就像 pmod 的回答一样。

标签: python debugging tkinter destroy


【解决方案1】:

直到我有时间重构我的代码,我通过将以下行添加到我的destroy() 调用来解决问题:

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

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

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

【讨论】:

    【解决方案2】:

    mainloop 的概念假设您首先创建和初始化对象(嗯,至少是在应用程序启动时需要的对象,即不动态使用),设置事件处理程序(实现接口逻辑)和然后进入无限事件处理(用户界面本质上是),即主循环。因此,这就是为什么您将其视为“挂起”的原因。这叫event-driven programming

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

    class GUIApp(tk.Tk):
       ...
    
    
    app = GUIApp()
    app.mainloop()
    

    所以,当窗口死掉时,主循环返回。

    【讨论】:

    • 如果我遗漏了什么,我很抱歉,但这不就是我构建代码的方式吗?我的问题是,事实上,在TopLevel 上调用的mainloop 在其关联的窗口死亡时不会返回,它仅在Tk 窗口死亡时才会返回。你是对的,但我不会为每个窗口调用Tk,对吗?
    • @AntoineEnderEzechiel 不,你没有。 mainloop 不能在事件处理程序中调用,这完全违反了概念。主循环是调用事件处理程序的地方。看看这个stackoverflow.com/questions/16115378/…
    • 请注意,Launcher 的主循环是从主程序调用的。基本上,我们又回到了我问过@schlenk 和@BryanOakley 的同一个问题:如果没有多个mainloops,我应该如何拥有多个窗口?
    • @AntoineEnderEzechiel 您的具体问题在哪里?尝试提出更具体的问题。
    • @AntoineEnderEzechiel 简而言之,拆分布局和动作处理程序的创建,然后你会看到
    猜你喜欢
    • 2022-01-06
    • 1970-01-01
    • 1970-01-01
    • 2017-10-27
    • 1970-01-01
    • 2014-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多