views:

56

answers:

2

I have a Python script which uses Tkinter for the GUI. My little script should create a Toplevel widget every X seconds. When I run my code, the first Toplevel widget is created successfully, but when it tries to create a second one the program crashes.

What I am doing is using the after method to call the function startCounting every 5 seconds alongside root's mainloop. Every time this function is called, I append a Toplevel widget object into a list and start a new thread which hopefully will be running the new mainloop.

I would be very grateful if someone could figure this problem out. By the way, this is just a little script that I am currently using to solve my problem, which is preventing me from going on with my real school project. Thanks in advance.

The code:

import threading,thread
from Tkinter import *


def startCounting():
    global root
    global topLevelList
    global classInstance

    topLevelList.append (Toplevel())
    topLevelList[len(topLevelList)-1].title("Child")
    classInstance.append(mainLoopThread(topLevelList[len(topLevelList)-1]))

    root.after(5000,startCounting)


class mainLoopThread(threading.Thread):
    def __init__(self,toplevelW):
        self.toplevelW = toplevelW
        threading.Thread.__init__(self)
        self.start()
    def run(self):
        self.toplevelW.mainloop()



global classInstance
classInstance = []
global topLevelList
topLevelList = []
global root

root = Tk() 
root.title("Main")
startCounting()
root.mainloop()
+4  A: 

Tkinter is designed to run from the main thread, only. See the docs:

Just run all UI code in the main thread, and let the writers write to a Queue object; e.g.

...and a substantial example follows, showing secondary threads writing requests to a queue, and the main loop being exclusively responsible for all direct interactions with Tk.

Many objects and subsystems don't like receiving requests from multiple various threads, and in the case of GUI toolkit it's not rare to need specfically to use the main thread only.

The right Python architecture for this issue is always to devote a thread (the main one, if one must) to serving the finicky object or subsystem; every other thread requiring interaction with said subsystem or object must them obtain it by queueing requests to the dedicated thread (and possibly waiting on a "return queue" for results, if results are required as a consequence of some request). This is also a very sound Python architecture for general-purpose threading (and I expound on it at length in "Python in a Nutshell", but that's another subject;-).

Alex Martelli
Thanks for the quick answer. I'll check the docs.
@user, you're welcome!
Alex Martelli
I've encountered this restriction and it is exactly the tactic I use. I have one method that checks all queues. I then register that method with the Tkinter's main loop by calling root.after(ms, my_method). The last call in my_method is another call to root.after so that my_method continually reregisters itself with the main loop. It's important that exceptions in my_method do not cause you to miss registering with the mainloop. You may want to put your call to root.after in the finally part of try/finally.
Steven Rumbalski
@Steven, excellent points -- +1, thanks.
Alex Martelli
I've been able to solve the problem, thanks.
A: 

Is there a reason you want (or think you need) one event loop per toplevel window? A single event loop is able to handle dozens (if not hundreds or thousands) of toplevel windows. And, as has been pointed out in another answer, you can't run this event loop in a separate thread.

So, to fix your code you need to only use a single event loop, and have that run in the main thread.

Bryan Oakley
Thank you so much for the answers, I've been able to solve the problem thanks to you guys, I had never posted in a place like this and I have been pleasantly surprised with so much help.