How to run native code along with the Tkinter event loop?

My little brother is just programming, and for his Science Fair project, he is doing a simulation of a flock of birds in the sky. He got most of his code written, and it works beautifully, but the birds must move every moment.

Tkinter, however, starts time for its own event loop, so its code will not work. The execution of root.mainloop() is executed, it starts and continues to work, and the only thing it starts is event handlers.

Is there a way for his code to work along with mainloop (without multithreading, it gets messy, and this should be simple), and if so, what is it?

Right now, he came up with an ugly hack by binding his move() function to <b1-motion> , so that while he holds the button and the mouse vibrates, it works. But there must be a better way.

+95
python events tkinter
Jan 19 '09 at 20:40
source share
5 answers

Use the after method for 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 declaration and documentation 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.""" 
+117
Jan 19 '09 at 20:55
source share

The solution sent by Bjorn leads to the message "RuntimeError: Calling Tcl from different apartments" on my computer (RedHat Enterprise 5, python 2.6.1). Björn may not have received this message, because according to one place I checked , the incorrect use of Threading with Tkinter is unpredictable and platform dependent.

The problem is that app.start() is considered a reference to Tk, since the application contains Tk elements. I fixed this by replacing app.start() with self.start() inside __init__ . I also made sure that all Tk links are either inside a function that calls mainloop() or inside functions called by a function that calls mainloop() (this seems to be important to avoid a “different apartment” error).

Finally, I added a protocol handler with a callback, since without it the program exits with an error when the user closes the Tk window.

The revised code is as follows:

 # Run tkinter code in another thread import tkinter as tk import threading class App(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.start() def callback(self): self.root.quit() def run(self): self.root = tk.Tk() self.root.protocol("WM_DELETE_WINDOW", self.callback) label = tk.Label(self.root, text="Hello World") label.pack() self.root.mainloop() app = App() print('Now we can continue running code while mainloop runs!') for i in range(100000): print(i) 
+41
Dec 02 '09 at 18:55
source share

When writing your own loop, as in the simulation (I assume), you need to call the update function, which does what mainloop does: updates the window with your changes, but you do it in your loop.

 def task(): # do something root.update() while 1: task() 
+17
Jan 29 '11 at 9:35
source share

Another option is to allow tkinter to execute a separate thread. One way to do 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, multi-threaded programming is difficult, and it’s actually easy to shoot yourself. For example, you should be careful when modifying the member variables of the above class so that you do not interrupt the Tkinter event loop.

+4
Feb 11 '09 at 20:14
source share

This is the first working version of what will be a GPS reader and data presenter. tkinter is a very fragile thing with too few error messages. He does not put things and does not say why most of the time. It is very difficult to make a good WYSIWYG form developer. In any case, this is a small procedure 10 times per second and provides information about the form. It took a while for this to happen. When I tried the timer value 0, the form never appeared. My head hurts now! 10 or more times per second is enough for me. Hope this helps someone else. Mike Morrow

 import tkinter as tk import time def GetDateTime(): # Get current date and time in ISO8601 # https://en.wikipedia.org/wiki/ISO_8601 # https://xkcd.com/1179/ return (time.strftime("%Y%m%d", time.gmtime()), time.strftime("%H%M%S", time.gmtime()), time.strftime("%Y%m%d", time.localtime()), time.strftime("%H%M%S", time.localtime())) class Application(tk.Frame): def __init__(self, master): fontsize = 12 textwidth = 9 tk.Frame.__init__(self, master) self.pack() tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth, text='Local Time').grid(row=0, column=0) self.LocalDate = tk.StringVar() self.LocalDate.set('waiting...') tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth, textvariable=self.LocalDate).grid(row=0, column=1) tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth, text='Local Date').grid(row=1, column=0) self.LocalTime = tk.StringVar() self.LocalTime.set('waiting...') tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth, textvariable=self.LocalTime).grid(row=1, column=1) tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth, text='GMT Time').grid(row=2, column=0) self.nowGdate = tk.StringVar() self.nowGdate.set('waiting...') tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth, textvariable=self.nowGdate).grid(row=2, column=1) tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth, text='GMT Date').grid(row=3, column=0) self.nowGtime = tk.StringVar() self.nowGtime.set('waiting...') tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth, textvariable=self.nowGtime).grid(row=3, column=1) tk.Button(self, text='Exit', width = 10, bg = '#FF8080', command=root.destroy).grid(row=4, columnspan=2) self.gettime() pass def gettime(self): gdt, gtm, ldt, ltm = GetDateTime() gdt = gdt[0:4] + '/' + gdt[4:6] + '/' + gdt[6:8] gtm = gtm[0:2] + ':' + gtm[2:4] + ':' + gtm[4:6] + ' Z' ldt = ldt[0:4] + '/' + ldt[4:6] + '/' + ldt[6:8] ltm = ltm[0:2] + ':' + ltm[2:4] + ':' + ltm[4:6] self.nowGtime.set(gdt) self.nowGdate.set(gtm) self.LocalTime.set(ldt) self.LocalDate.set(ltm) self.after(100, self.gettime) #print (ltm) # Prove it is running this and the external code, too. pass root = tk.Tk() root.wm_title('Temp Converter') app = Application(master=root) w = 200 # width for the Tk root h = 125 # height for the Tk root # get display screen width and height ws = root.winfo_screenwidth() # width of the screen hs = root.winfo_screenheight() # height of the screen # calculate x and y coordinates for positioning the Tk root window #centered #x = (ws/2) - (w/2) #y = (hs/2) - (h/2) #right bottom corner (misfires in Win10 putting it too low. OK in Ubuntu) x = ws - w y = hs - h - 35 # -35 fixes it, more or less, for Win10 #set the dimensions of the screen and where it is placed root.geometry('%dx%d+%d+%d' % (w, h, x, y)) root.mainloop() 
+2
Oct 25 '16 at 7:12
source share



All Articles