Python method strangely on pause until tkinter root is closed

I am creating a tkinter application using Python 3.4 that collects posts from an API, filter them and allow the user to review them and make a decision for each one (ignore, delete, share, etc.)

The user is expected to pick a date and some pages and then click on the 'Collect' button. The program then fetch the posts from the pages and stock them in 'wholeList'. When the user clicks on the second button 'Review', the posts must be filtered and passed to the Reviewer.

My problem is that the Reviewer receives no posts at all, and neither does the Filterer. I have added some debugging print() statements at some places, notably to handlerCollect(), and the result baffled me, hence this post.

Instead of finishing the handlerCollect() callback method when I click on 'Collect', the program puts it on hold somewhere between "DEBUG->1" and "DEBUG->2". The main window does not freezes or anything, for I can click on 'Review' and have it print "DEBUG->4" and open up the Reviewer. When I close the main window, "DEBUG->0" "DEBUG->2" and "DEBUG->3" finaly print, along with the rest of the handlerCollect() method executing.

The same behavior happens with handlerChoosePage(), with "DEBUG->0" being delayed until the tkinter root (TK()) is destroyed. My knowledge of structural programming tells me it should be the very first one printed. Instead, it is the very last. My best conclusion is that I must not be ending my Toplevel mainloop()s correctly. I have to admit I have never encountered something like this before. I thought the proper way of ending mainloop()s on Toplevels was with destroy() and I am very confused as to why methods calling mainloop()s get put on hold until the Tk root is destroyed; not really practical.

Relevant part of my interface

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()

The GUICollector module requires no interaction from the user at all. This module seems to work perfectly: doing its job, displaying it is done and then closing after the specified delay. Since the GuiCollector mainloop() seems to be the culprit of the hanging, here is how I end it:

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()

The PageSelector module is destroyed with the same call on the press of a button: self.master.destroy()

Here is the revelant output of the program:

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

Upvotes: 1

Views: 965

Answers (2)

Until I have some time to refactor my code, I solved the problem by adding the following line to my destroy() calls:

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

I understand this doesn't not solve the bad structure of my code, but it answers my specific question. destroy() doesn't end the mainloop of TopLevels, but quit() does. Adding this line makes my code execute in a predictable way.

I will be accepting @pmod 's answer as soon as I have refactored my code and verified his claim that the Tk() mainloop will cover all child TopLevels.

Upvotes: 0

pmod
pmod

Reputation: 10997

The concept of mainloop assumes that you first create and initialize objects (well, at least these that are required at application start, i.e. not used dynamically), set event handlers (implement interface logic) and then go into infinite event handling (what User Interface essentially is), i.e. main loop. So, that is why you see it as it "hangs". This is called event-driven programming

And the important thing is that this event handling is done in one single place, like that:

class GUIApp(tk.Tk):
   ...


app = GUIApp()
app.mainloop()

So, the mainloop returns when the window dies.

Upvotes: 1

Related Questions