views:

1279

answers:

3

My little brother is just getting into programming, and for his Science Fair project, he's doing a simulation of a flock of birds in the sky. He's gotten most of his code written, and it works nicely, but the birds need to move every moment.

Tkinter, however, hogs the time for its own event loop, and so his code won't run. Doing root.mainloop() runs, runs, and keeps running, and the only thing it runs is the event handlers.

Is there a way to have his code run alongside the mainloop (without multithreading, it's confusing and this should be kept simple), and if so, what is it?

Thank you for responding.

NOTE: Right now, he came up with an ugly hack, tying his move() function to <b1-motion>, so that as long as he holds the button down and wiggles the mouse, it works. But there's got to be a better way.

+6  A: 

Use the "after" method on the Tk object:

from Tkinter import *
root=Tk()
def task():
    print "hello"
    root.after(2000,task)  # reschedule event in 2 seconds

root.after(2000,task)
root.mainloop()

Here's the decl and docs for the after method:

def after(self, ms, func=None, *args):
    """Call function once after given time.

    MS specifies the time in milliseconds. FUNC gives the
    function which shall be called. Additional parameters
    are given as parameters to the function call.  Return
    identifier to cancel scheduling with after_cancel."""
Dave Ray
if you specify the timeout to be 0, task will put itself back on the event loop immediately after finishing. this will not block other events, while still running your code as often as possible.
Nathan
A: 

Another option is to let tkinter execute on a separate thread. One way of doing it is like this:

import Tkinter
import threading

class MyTkApp(threading.Thread):
    def __init__(self):
        self.root=Tkinter.Tk()
        self.s = Tkinter.StringVar()
        self.s.set('Foo')
        l = Tkinter.Label(self.root,textvariable=self.s)
        l.pack()
        threading.Thread.__init__(self)

    def run(self):
        self.root.mainloop()


app = MyTkApp()
app.start()

# Now the app should be running and the value shown on the label
# can be changed by changing the member variable s.
# Like this:
# app.s.set('Bar')

Be careful though, multithreaded programming is hard and it is really easy to shoot your self in the foot. For example you have to be careful when you change member variables of the sample class above so you don't interrupt with the event loop of Tkinter.

A: 

2009-12-02--I don't know if this thread is 1 or 10 years old because the datestamps I see on the above comments only include month, day, hour and minute. I thought I'd add my two cents worth anyways.

The solution posted by Bjorn results in a "RuntimeError: Calling Tcl from different appartment" message on my computer (RedHat Enterprise 5, python 2.6.1). Bjorn might not have gotten this message, since, according to one place I checked, mishandling threading with Tkinter is unpredictable and platform-dependent.

The problem seems to be that "app.start()" counts as a reference to Tk, since app contains Tk elements. I fixed this by replacing "app.start()" with a "self.start()" inside init. I also made it so that all Tk references are either inside the function that calls mainloop() OR are inside functions that are CALLED BY the function that calls mainloop() (this is apparently critical to avoid the "different appartment" error). Finally, I added a protocol handler with a callback, since without this the program exits with an error when the Tk window is closed by the user. The revised code is as follows:

import Tkinter
import threading
class MyTkApp(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.start()
    def callback(self):
       self.root.quit()
    def run(self):
        self.root=Tkinter.Tk()
        self.root.protocol("WM_DELETE_WINDOW", self.callback)
        self.s = Tkinter.StringVar()
        self.s.set('Foo')
        l = Tkinter.Label(self.root,textvariable=self.s)
        l.pack()
        self.root.mainloop()
app = MyTkApp()
print 'now can continue running code while mainloop runs'

Thanks to RaphaelSP for the help explaining formatting (the preview looked okay!). I think I've fixed it now.

Kevin
FYI: the year is displayed when an answer is more than twelve months old.
RaphaelSP
And code can be formatted as such by starting each line with four (4) spaces.
RaphaelSP